본문 바로가기

CTFs

[HackingCamp 15] 제 15회 해킹캠프 출제한 문제 '불'친절한 풀이

제 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
 
= remote('localhost'6667)
 
for i in xrange(116):
    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
 
= remote('localhost'6666)
 
= 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 - 닥터후 팬이라면 이해할 수 있는 플래그 ㅋㅋ