본문 바로가기
# write-up/- ctf

[seccon 2021] Reversing - flag

by ddddh 2021. 12. 17.

이번 seccon은 연합팀 'KOREAN'으로 나가게 되어 최종 4위를 하였습니다. 하지만 명예 한국인 한 분도 계셨다는ㅋㅋ.

사실 조금 늦게 참여하였는데 운이 좋게 리버싱 한 문제를 풀 수 있었습니다. 

 

 

다음은 리버싱 문제였던 'flag' 풀이입니다.

문제 클릭시 웹 사이트로 리다이렉트되며 알맞는 플레그를 웹에서 체크하는 문제였습니다. 리버싱 태그로 나왔기 때문에 살펴보면 wasm 웹 어셈블리어 바이너리가 존재하는걸 볼 수 있습니다. 즉, 해당 문제에서 플래그 문자열을 입력하고 체크 버튼을 클릭시 내부적으로 웹 어셈블리어 바이너리에서 검증을 하게 되는겁니다.

 

웹 어셈블리어를 분석할 때는 유명한 팁이 존재합니다. 바로 wasm 바이너리를 디컴파일 후 다시 elf로 빌드해서 보는 것입니다. 사실 디컴파일 소스로 봐도 무방은 하나 IDA Pro 에서 만들어주는 의사코드에 익숙해져 있기 때문에...

 

이렇게 만들어진 웹 어셈블리어 바이너리를 IDA Pro로 로딩했을 때 특징들이 존재합니다.

 

 첫 번째로는 init이 존재한다는 점입니다. 여기서 전역 변수 설정 등 여러가지 초기화를 거치게 됩니다.

 

두 번째로는 다음과 같이 store, load 등이 존재합니다. 이렇게 생긴 함수들을 살펴보면 i32는 int32를 나타내는 것이며 'store' 저장하겠다. 인자 3개는 메모레, 위치, 값 이라고 생각하면됩니다.

 

즉, i32_store(memory, offset, value)는 offset에 value를 저장, i32_load(memory, offest, value)는 offset 값을 가져온다는 뜻으로 생각하시면 됩니다.

 

 

다시 문제 페이지의 자바 스크립트를 확인하면 다음과 같은 인자와 호출되는 함수를 확인할 수 있습니다.

flag.wasm 안의 check 함수를 실행하며 매번 똑같은 key값과 enc 값을 넘겨주는 것을 볼 수 있습니다. 그렇다면 이제부터 check 함수를 분석해보면 됩니다.

 

 

check 함수 초반 부분부터 enum 을 사용해서 분석하면 다음과 같이 인자로 받은 3개의 값을 저장 후 어떠한 루틴을 통해 결과를 내는 것을 볼 수 있습니다.

 

이것들을 분석한 뒤 Python으로 포팅한 결과입니다.

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
import sys
def w2c_f23(key, idx1, idx2, idx3, idx4):
    key[idx2] =  ((( ((key[idx4] + key[idx1]) & 0xff>> 7| (2 * ((key[idx4] + key[idx1]) & 0xff))) ^ key[idx2]) & 0xff
    key[idx3] = ((( ((key[idx1] + key[idx2]) & 0xff>> 6| (4 * ((key[idx1] + key[idx2]) & 0xff))) ^ key[idx3]) & 0xff
    key[idx4] = ((( ((key[idx2] + key[idx3]) & 0xff>> 5| (8 * ((key[idx2] + key[idx3]) & 0xff))) ^ key[idx4]) & 0xff
    key[idx1] = ((( ((key[idx3] + key[idx4]) & 0xff>> 4| (16 * ((key[idx3] + key[idx4]) & 0xff))) ^ key[idx1]) & 0xff
 
def reverse_w2c_f23(key, idx1, idx2, idx3, idx4):
    key[idx1] = ((( ((key[idx3] + key[idx4]) & 0xff>> 4| (16 * ((key[idx3] + key[idx4]) & 0xff))) ^ key[idx1]) & 0xff
    key[idx4] = ((( ((key[idx2] + key[idx3]) & 0xff>> 5| (8 * ((key[idx2] + key[idx3]) & 0xff))) ^ key[idx4]) & 0xff
    key[idx3] = ((( ((key[idx1] + key[idx2]) & 0xff>> 6| (4 * ((key[idx1] + key[idx2]) & 0xff))) ^ key[idx3]) & 0xff
    key[idx2] =  ((( ((key[idx4] + key[idx1]) & 0xff>> 7| (2 * ((key[idx4] + key[idx1]) & 0xff))) ^ key[idx2]) & 0xff
 
 
myinput = b"a"* 55 + b"}"
enc = b"6dbf84f73cf6a112268b09525ea550a665e21cb2e3e13af7e3ea0ecb52f5b9cda5b6522b1e978734553f1d7956d4af94bfc3f4d68c8fba9eeecf4035550b9106f70d57d1a6cdaf3211eaaa78d71a9038b71be621241e8b608a43b107f8860f543ab0189aa063800de4bae7d0b11045b8"
key = b"NekoPunch"
key = key[:8]
 
data_segment_data_1 = b"0123456789abcdef"
 
if len(key) >= 8 and bytes([myinput[-1]]) == b"}" and len(myinput) * 4 == len(enc):
    
    dup_key = bytearray(key[:8+ (0x10 - len(key)) * b"\x00")
 
    result = 0
    for loop in range(0len(myinput), 8):
 
        for idx1 in range(8):
            char = myinput[idx1+loop]
            dup_key[8+idx1] = char
 
        for idx2 in range(0x80):
            w2c_f23(dup_key, 04812)
            w2c_f23(dup_key, 59131);
            w2c_f23(dup_key, 101426);
            w2c_f23(dup_key, 153711);
            w2c_f23(dup_key, 0123);
            w2c_f23(dup_key, 5674);
            w2c_f23(dup_key, 101189);
            w2c_f23(dup_key, 15121314);
 
        print()
 
 
 
        for idx3 in range(16):
            tmp1 = enc[loop + idx3]
            tmp2 = dup_key[idx3] // 16
            value = (tmp1 != data_segment_data_1[tmp2]) | result
            result = value
 
            tmp1 = enc[loop + idx3 + 1]
            tmp2 = dup_key[idx3] % 16
            value = (tmp1 != data_segment_data_1[tmp2]) | result
            result = value
 
        print(result)
 
 
cs

 

여기서 알 수 있는 사실은 인자로 받은 enc는 역산이 가능한 인코딩된 플래그 값입니다.

 

비트연산을 역으로 짜서 다시 돌리게 되면 플래그를 도출해낼 수 있습니다.

 

 

 

 

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
import sys
def w2c_f23(key, idx1, idx2, idx3, idx4):
    key[idx2] =  ((( ((key[idx4] + key[idx1]) & 0xff>> 7| (2 * ((key[idx4] + key[idx1]) & 0xff))) ^ key[idx2]) & 0xff
    key[idx3] = ((( ((key[idx1] + key[idx2]) & 0xff>> 6| (4 * ((key[idx1] + key[idx2]) & 0xff))) ^ key[idx3]) & 0xff
    key[idx4] = ((( ((key[idx2] + key[idx3]) & 0xff>> 5| (8 * ((key[idx2] + key[idx3]) & 0xff))) ^ key[idx4]) & 0xff
    key[idx1] = ((( ((key[idx3] + key[idx4]) & 0xff>> 4| (16 * ((key[idx3] + key[idx4]) & 0xff))) ^ key[idx1]) & 0xff
 
def reverse_w2c_f23(key, idx1, idx2, idx3, idx4):
    key[idx1] = ((( ((key[idx3] + key[idx4]) & 0xff>> 4| (16 * ((key[idx3] + key[idx4]) & 0xff))) ^ key[idx1]) & 0xff
    key[idx4] = ((( ((key[idx2] + key[idx3]) & 0xff>> 5| (8 * ((key[idx2] + key[idx3]) & 0xff))) ^ key[idx4]) & 0xff
    key[idx3] = ((( ((key[idx1] + key[idx2]) & 0xff>> 6| (4 * ((key[idx1] + key[idx2]) & 0xff))) ^ key[idx3]) & 0xff
    key[idx2] =  ((( ((key[idx4] + key[idx1]) & 0xff>> 7| (2 * ((key[idx4] + key[idx1]) & 0xff))) ^ key[idx2]) & 0xff
 
enc = b"\x6D\xBF\x84\xF7\x3C\xF6\xA1\x12\x26\x8B\x09\x52\x5E\xA5\x50\xA6\x65\xE2\x1C\xB2\xE3\xE1\x3A\xF7\xE3\xEA\x0E\xCB\x52\xF5\xB9\xCD\xA5\xB6\x52\x2B\x1E\x97\x87\x34\x55\x3F\x1D\x79\x56\xD4\xAF\x94\xBF\xC3\xF4\xD6\x8C\x8F\xBA\x9E\xEE\xCF\x40\x35\x55\x0B\x91\x06\xF7\x0D\x57\xD1\xA6\xCD\xAF\x32\x11\xEA\xAA\x78\xD7\x1A\x90\x38\xB7\x1B\xE6\x21\x24\x1E\x8B\x60\x8A\x43\xB1\x07\xF8\x86\x0F\x54\x3A\xB0\x18\x9A\xA0\x63\x80\x0D\xE4\xBA\xE7\xD0\xB1\x10\x45\xB8"    
myinput = enc
 
key = b"NekoPunch"
key = key[:8]
 
 
data_segment_data_1 = b"0123456789abcdef"
flag = b""
dup_key = bytearray(key[:8+ (0x10 - len(key)) * b"\x00")
result = 0
for loop in range(0len(myinput), 16):
 
    for idx1 in range(16):
        char = myinput[idx1+loop]
        dup_key[idx1] = char
 
 
    for idx2 in range(0x80):
        reverse_w2c_f23(dup_key, 15121314);
        reverse_w2c_f23(dup_key, 101189);
        reverse_w2c_f23(dup_key, 5674);
        reverse_w2c_f23(dup_key, 0123);
        reverse_w2c_f23(dup_key, 153711);
        reverse_w2c_f23(dup_key, 101426);
        reverse_w2c_f23(dup_key, 59131);
        reverse_w2c_f23(dup_key, 04812)
    
    flag += dup_key[8:]
    print(flag)
 
cs

 

최종적으로 다음과 같은 결과를 얻을 수 있습니다.

 

 

b'SECCON{w'
b'SECCON{wh4ts_Ur_'
b'SECCON{wh4ts_Ur_r3c0mm3n'
b'SECCON{wh4ts_Ur_r3c0mm3nd3d_w4y_'
b'SECCON{wh4ts_Ur_r3c0mm3nd3d_w4y_2_d3c0mp'
b'SECCON{wh4ts_Ur_r3c0mm3nd3d_w4y_2_d3c0mp1l3_WASM'
b'SECCON{wh4ts_Ur_r3c0mm3nd3d_w4y_2_d3c0mp1l3_WASM?}\x00\x00\x00\x00\x00\x00'

'# write-up > - ctf' 카테고리의 다른 글

Codegate 2019 - The Matirx  (5) 2019.01.30
GoogleCTF 2017 - food  (1) 2017.06.19
HUST 2017 - Mystic Crypt  (0) 2017.06.06
[DEFCON 2017] - pegem  (2) 2017.05.10
[Plaid CTF 2017] zamboni  (0) 2017.05.02