제 15회 해킹캠프 문제 풀이
음.. 올해 2월 18일 부터 19일 까지 진행되었던 제 15회 해킹캠프 내부에서 진행된 미니 CTF 문제 출제를 이번에 맡게 되었습니다! 출제한 분야는 리버싱 문제(동현이형하고 같이 출제) 하고 크립토 + 팡호하고 웹버싱 문제 하나(쿨쿨베드) + SwimBro 하고 포버싱 문제 하나(메모리 뭐시기...)
개인적으로 평을 하자면
크립토는 재밌는 문제는 못냈고 그냥 개념? + 노가다 문제로 냈고,
리버싱 문제도 괜춘하고 재밌었다? 라는 평은 있었던 거 같지만 또 해캠이다보니 아이디어적인거나 하드한거 내기도 뭐시기 해서 ChickenMe 한문제 빼고 간단하게 풀리는 쏘쏘 한 것들로 출제를 했었다.
그런데 요즘 해캠이 변질되서 잘하시는 분들이 와서 퍄퍞캬퍜퍄 하시고 가셔서 이젠 문제 난이도를 어렵읍읍..
이번 플래그들이나 문제 컨셉을 치킨으로 잡아봤는데 괜춘했던거 같다 ㅋㅋㅋ
Reversing
1. Message
- 이 문제 의도는 딱 하나 Python 실행파일 타입인 pyc 파일 구조를 알고 있는지.
- 사실 byte code obfuscating 하려 했지만 아닌거 같아서 pyc 파일 구조 중 marshal, PE로 치면 .text 를 알고 최종적으로 디컴파일 해 플래그를 얻을 수 있는지를 물어보는 문제였다.
- 검색해 보니 이미 이 문제 풀이를 올려둔 블로그가 있어서 더 자세한건 생략
2. ChickenMon
- 스트립된 ELF32 인가 ELF64 파일 인데. 내부를 보면 그냥 쏘쏘한 xor 하고 뭐시기 하고 하는 부분이 있다.
- 그런데 하나 조심해야 할 점은 사람들이 리버싱 할 때 .init 을 잘 안본다는 점? 을 이용해서 .init 에 (예전 이름으로 .ctors) ptrace 로 연산에 쓰이는 chksum 을 만약 디버깅 중이면 그 값을 이상하게 바꾸는 식으로 해서 이상한 값이 나오도록 해 뒀다.
- 실제로 그것때문에 고민좀 했다라는 팀이 있었던거 같다.
3. ChickenMe
- 음 이 문제는 PE32 문젠데 올클 방지용으로 낸 문제다
- 사실 키를 비교하는 루틴은 함수 하나에서 다 이뤄지고 연산도 그렇게 어렵게 해 놓지 않아서 잘만하면 정적으로 가능한 문제였다.
- 일단 많은 사람들이 못풀었던 이유는 Anti-Debugging + Anti-Attach + Anti-VM 기법인거 같다.
- 사용된 걸 좀 나열해 보면 NT 계열 안티 디버깅들 + 예외 기반들.. + 안티 어태치 + self 디버깅 + Fake Signatures + 등등..
- 키를 비교하는 루틴은 z3 로 연립방정식을 풀고 crc64 1자리 bruteforce 등으로 해서 풀 수 있습니다
- 만약 안티 디버깅 들을 우회하려면 따라가면서 손수 패칭하는 방법도 있습네다
- 테스트 해 보니 플러그인이나 치트엔진 + 등등 디버거에서 디버깅 + attach 는 다 안되는거 같더라고요
- 여기에 사용된 모든 기법을 설명하긴 길어지기도 하고 귀찮읍읍.. 하니 제가 쓴 잉여 문서로 대체대체합니당
- 링크 : http://zer0day.tistory.com/335
- 추가로 사용된 재밌는? 기법도 있으니 궁금하시면 댓글이나 메일 주셔도 되용
- 해캠 끝나고 문제들 오픈했을 때 태양님이 풀어주셨네요 ㅠㅠ (감사해용..)
4. Warming-Up
- 해당 문제는 대회 시작 1시간 전에 멍때리다 갑자기 아이디어가 생각나서 얼렁 만들어 출제하게 된 문제다 ㅋㅋ
- 문제 소스코드는 엄청 간단하지만 아키텍쳐를 사람들이 거의 모를만한? 걸로 컴파일 했다. s360x 인가 s390x 이였나.. 쨋든..
- 아래는 문제 소스코드 첨부
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | #include <stdio.h> #include <stdlib.h> // Flag{str1pped_b1nary_yeahhhh} int main(int argc, char *argv[], char **envp) { if(argc == 1) { printf("F"); sleep(1); printf("l"); sleep(2); printf("a"); sleep(4); printf("g"); sleep(8); printf("{"); sleep(16); printf("s"); sleep(32); printf("t"); sleep(64); printf("r"); sleep(128); printf("1"); sleep(256); printf("p"); sleep(512); printf("p"); sleep(1024); printf("e"); sleep(512); printf("d"); sleep(256); printf("_"); sleep(128); printf("b"); sleep(64); printf("1"); sleep(32); printf("n"); sleep(16); printf("a"); sleep(8); printf("r"); sleep(4); printf("y"); sleep(2); printf("_"); sleep(1); printf("y"); sleep(2); printf("e"); sleep(4); printf("a"); sleep(8); printf("h"); sleep(16); printf("h"); sleep(32); printf("h"); sleep(64); printf("h"); sleep(128); printf("}"); sleep(256); } return 1; } | cs |
- 그런데 이걸 sleep 을 덜 해서 그런지 '바이너리좀봐'? 팀의 희근이형께서 걍 플래그 뜰 때 까지 실행해서 푸셔버렸다 ㅋㅋㅋㅋㅋ
- 이태양님께선 원래 의도 중 2번 째 의도였던 objdump 같은 거로 푸셨고 ㅇㅅㅇ
- 원래 의도는 sleep 에 들어가 있는 시간을 바이너리 패칭하거나 LD_PRELOAD 로 sleep 함수를 후킹해서 푸는 방법이였따..
5. ChickenGo (문제 업로드 문제 등으로 중간에 내림 ㅠㅠ)
Crypto
1. Caesar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | # 서버 파일은 안주고 클라이언트 파일만 주었는데 # 접속을 해 보면 4글자-8글자 이런식으로 보내준다 # 힌트로 앞에 4글자는 flag 라는 힌트를 주었는데 이걸로 시저 키를 찾고 나머지도 복호화 해서 보내면 끗 # 또 클라이언트를 보면 타이머가 있는데 이건 그냥 스니핑해서 적어도 보고 적을 시간 없게 하려고 ㅋㅋㅋ... # 그래서 플래그는 Flag{claordkslausclzhf} # 치맥아니면치콜 from pwn import * def caesar(plain, key): res = "" for ch in plain: cs = '' if ch.isalpha(): c = ord(ch) + key if c < ord('a'): c += 26 if c > ord('z'): c -= 26 cs = chr(c) elif ch == '-': res += ch continue res += cs return res s = remote('localhost', 6667) for i in xrange(1, 16): print s.recvuntil('/15\n') s.recvuntil('[*] Encrypted = ') Encrypted = s.recvuntil('\n').replace('\n', '') key = ord(Encrypted[0]) - ord('f') # 첫번째 글자에서 'f' 를 빼 키 찾기 plain = caesar(Encrypted, -key).strip() print "Plain Text is %s" % plain s.sendline(plain) print s.recv(1024).replace('\n', '') print s.recv(1024) print s.recv(1024) ''' zero@ubuntu:~/Dropbox/Wargame/HackingCamp15/Crypto-Coding/Caesar2$ python sol.py [+] Opening connection to localhost on port 6667: Done --------------------------------------------- - Hacking Camp secure CAESAR system - --------------------------------------------- - Decrypt Message [+] Round 1/15 Plain Text is flag-ziwivuha [*] Input your answer : [+] Correct answer... :) ... [+] Round 14/15 Plain Text is flag-uwzfdozp [*] Input your answer : [+] Correct answer... :) [+] Round 15/15 Plain Text is flag-ywujrxch [*] Input your answer : [+] Correct answer... :) [+] Congrat! [+] Flag{claordkslausclzhf} [*] Closed connection to localhost port 6667 ''' | cs |
2. DES
- 이 문제는 DES 같은 block 기반의 암호화 알고리즘의 Weak-Key 라는 것을 아느냐 란 개념 문제였다.
- 구글에 Weak Key 라 치고 제일 앞에 뜨는 위키에 들어가보면 Weak Key 리스트가 쭉 있는데 거기서 하나 때려 넣어보다보면 복호화 된당.
3. Santa
- 고전 암호화 기법 중 문자열 치환 암호화인데 그러니 간단히 설명하면 a -> b, b -> c, ... 이렇게 다 치환을 하는 방법이다.
- 푸는 방법은 사전 기반이나 많이 쓰이는 단어들 ( ex) the, is, 등) 이미 일부를 알고 있는걸 추측해서 하나씩 복호화 해서 맞추는 방법이다.
- 그런데 이걸 원큐에 해 주는 사이트가 있다 ㅋㅋ (http://quipqiup.com/) 예전 버전 이름은 decrypto 8.5
4. RSA
- 매 번 랜덤한 p, q, c 를 주고 stage 가 100 까지 있다. 그냥 p, q 로 n, phi, d 를 구해서 private key 를 생성해 주고 그걸로 c 를 복호화 해 준 걸 보내면 끗.
- 플래그는 wnrsmsiclzlsdlsirmrjtdlanswpfhek(죽느냐치킨이냐그것이문제로다)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | from pwn import * from Crypto.Util.number import * import Crypto.PublicKey.RSA as RSA s = remote('localhost', 6666) e = 0x10001 stage = 100 for i in xrange(1, stage + 1): print s.recvuntil('/100\n') s.recvuntil('[*] p = ') p = int(s.recvuntil('\n').replace('\n', '')) s.recvuntil('[*] q = ') q = int(s.recvuntil('\n').replace('\n', '')) s.recvuntil('[*] c = ') c = s.recvuntil('\n').replace('\n', '') n = p * q d = inverse(e, (p - 1) * (q - 1)) key = RSA.construct((n, long(e), d)) d = key.decrypt(c.decode('hex')) print "Decrypted Data is %s" % d s.sendline(d) print s.recv(1024).replace('\n', '') print s.recv(1024) print s.recv(1024) ''' zero@ubuntu:~/Desktop$ python sol.py [+] Opening connection to 100.100.100.20 on port 207: Done --------------------------------------------- - Hacking Camp secure RSA system - --------------------------------------------- - Decrypt Message with given values [+] Round 1/100 Decrypted Data is SSC1O3VSZYDUKTMV [*] Input your answer : [+] Correct answer... :) [+] Round 2/100 Decrypted Data is GPRUDKLMNFHO9RTC [*] Input your answer : [+] Correct answer... :) .. [+] Round 98/100 Decrypted Data is 70RPGEI7HIOU8VBH [*] Input your answer : [+] Correct answer... :) [+] Round 99/100 Decrypted Data is 5NZT5XLO9SZ9SXYF [*] Input your answer : [+] Correct answer... :) [+] Round 100/100 Decrypted Data is E3ZRI76N2WNC99RX [*] Input your answer : [+] Correct answer... :) [+] Congrat! [+] Flag{wnrsmsiclzlsdlsirmrjtdlanswpfhek} [*] Closed connection to 100.100.100.20 port 207 ''' | cs |
5. Vigenere
- 기존 Vigenere 와 다르게 table 에 몇개가 더 추가가 되어 있다.
- 키 길이는 특정 범위 내에서 암호화 문 길이로 나눴을 때 나머지가 0이 되도록 되어 있으니 5,6,10 인걸 추측할 수 있고 문자열에 Flag{, } 가 포함되거나 등의 이유로 보면 키가 5,6 은 절대 안된다는걸 알 수 있다.
- 키가 반복 사용 되니까 키 10자리 중 6개는 처음부터 알고 시작을 한다. 나머지 4자리를 brute force 하거나 말이 되도록 키를 조금씩 조정 해서 답을 맞출 수 있다. 스크립트도 별거 없고 귀찮읍읍.. 니 스킵...
6. OTP
- 음 이것도 결국엔 복호화한 일부를 게싱? 해야 할 수도 있는데... 쨋든..
- CT1 = PT1 XOR KEY, CT2 = PT2 XOR KEY 라 하면 CT1 XOR CT2 = (PT1 XOR KEY) XOR (PT2 XOR KEY) = PT1 XOR PT2 가 된다. 즉, 암호화된 문장 2개를 XOR하면 실제 평문 2개를 XOR한 거와 같은 결과가 나온다.
- 평문에는 분명 식별가능하고 의미있는 단어가 쓰였을 것이고 이를 추측해서 키를 만들어서 결론적으로 키를 거의 완성시킬 수 있다.
- 일명 many-time-pad-attack 을 통해서 이걸 + 게싱으로 풀 수 있는데 귀찮으니 링크로 걸 테니 알아서 보도록!
- 링크 : https://github.com/Jwomers/many-time-pad-attack/blob/master/attack.py
- 플래그는 Totally_And_Radic4lly_Driving_In_Sp4c3 - 닥터후 팬이라면 이해할 수 있는 플래그 ㅋㅋ