MozhuCY's blog.

WcyVM

字数统计: 1.5k阅读时长: 5 min
1970/01/01 Share

加急出的一个垃圾vm

  • 给校赛出的题目,虽然很早写出来解释器部分,但是因为加密部分没有想好,所以一直没有什么进展,比赛结束前10个小时,因为逆向题目不太够,就草草写了个算法(单表替换),放了出来,结果半个小时就被秒了,很明显不是预期解,比赛结束后也听到了而且想到了各种解法,虽然归根结底是算法的问题,不过姑且把这个题当做一个例题来整理复习一下逆向的基本姿势和神奇操作
  • 首先明确一个方向,因为是单表替换,所以加密后的flag只有有限次数的组合,所以可以利用各种爆破姿势来进行解题

//vm这个东西都要被出题人玩烂了,打log什么的基本做法就不写了

黑盒测试

  • 首先要确定是个单表加密,那么这里可以采用黑盒测试,方式还算简单,比如输入一大串符合长度的A,可以在内存中看到加密后的每一位还是一样的,也就是说还是一段连续相同的数据
  • 听说校内某队伍发现这点以后直接手动打的替换表,然后将对应关系抄到纸上找的flag2333333,不过后来想了一下虽然做法不怎么优雅但是做出来的速度应该挺快的.
  • 下面介绍三种比较优雅的做题方式,hook内存,angr符号执行,pin侧信道攻击

符号执行(如何一分钟秒掉这个vm)

  • 这是已知最快的解法了,先来说下符号执行,很明显,路径很短而且相对固定,算法模式很简单,没有字符之间的操作,按照符号执行的思想到最后应该只有一组70元1次方程组,内部的约束求解器计算的应该是非常快的,可以说是秒解
  • 甚至可以直接套最简单的那种angr的模板来做
1
2
3
4
5
6
from angr import *
p = Project("./WcyVM",auto_load_libs=False)
s = p.factory.entry_state()
sm = p.factory.simulation_manager(s)
r = sm.explore(find = 0x401366,avoid = 0x401372)
print r.found[0].posix.dumps(0)
  • 没错,就是这么简洁,直接弹flag了(-1s)

pin侧信道

  • 这个相对angr来说就相对复杂了一些,这里攻击的地方主要是程序结尾的check函数部分,因为在验证失败的时候程序是直接ret 0的,而验证成功的时候则会继续后向遍历,可以想象下两次运行的过程中对于flag的验证运行的指令数是不一样的,而且运算过程没有flag位于位之间的操作,那么利用pintool就可以块的解出这道题目,而且即使这个题目不是单表加密,掺杂了和index和位之间操作,手动找替换就变得复杂了(当然最稳的还是传统的打log),有的时候angr也会因为方程太多速度变得很慢.
  • 但是在验证的过程中,没有达到预期的效果,无论输入的flag长度有多长或者改错几位,只要不是全部正确,指令数就是一个相对固定的数字,不知道出现了什么玄学问题(=w=)

hook

  • 当时在suctf的时候的Enigma,看到了奈沙师傅利用frida爆破flag的操作,相比之下,我就是复现程序之后利用手动写脚本爆出来的….相比之下第一种方式的速度肯定是更快,话说前几天的湖湘杯的逆向也是,我的思路还是复现hash函数的算法然后手动爆,但是师傅却将exe作为了dll加载然后利用RVA调用的hash函数进行爆破Orz.
  • 这里就不利用现成的框架来hook了,尝试一下自己写一个hook程序,我们要做的就是监视部分地址的内存,linux中的ptrace系统调用很好的支持了这一个功能,frida框架的原理其实也是利用ptrace对程序进行hook
  • ptrace系统调用可以使一个进程控制另一个进程,对另一个进程进行内存读写,寄存器查看,或者断点调试等操作
  • 这里整理几个常见的request参数和相对应的功能

PTRACE_TRACEME

  • 该函数是由子线程进行调用的,可以使该子进程被父进程跟踪,进而父进程可以对此进程进行内存读写,寄存器的控制,参数一般都为0,ptrace(PTRACE_TRACEME,0,NULL,NULL).在执行之后,父进程传递给该函数的所有信号,都会使子线程停止(SIGKILL除外).一般的调试器开始调试一个程序的时候,实际上是分了两步来操作,一步是利用fork()函数生成一个子进程,在子进程中,先执行一个TRACEME然后再利用exec相关函数运行待调试程序,不过这个时候父进程就可以对fork分支出的子进程进行调试操作了.(fork函数会有两个返回值,分支出的两个线程的pid是不一样的,返回值为0的是子进程,返回值非零的是父进程,但是在父进程中可以发现pid其实是子进程的pid)

PTRACE_ATTACH

  • 该函数一般在父进程执行调用,一般的调用方式是ptrace(PTRACE_ATTACH,pid,NULL,NULL),在父进程中执行后,pid对应进程的程序的父进程就会变成执行ATTACH的程序,其实和在子进程中执行TRACEME是等价的,这也就是调试器attach到一个进程的原理

PTRACE_KILL

  • 主线程调用,执行后可以kill掉子进程.在进行attach的过程中,有的时候并不能成功kill掉子进程,这个时候就要用自带的errno来查看报错id
CATALOG
  1. 1. 加急出的一个垃圾vm
  2. 2. 黑盒测试
  3. 3. 符号执行(如何一分钟秒掉这个vm)
  4. 4. pin侧信道
  5. 5. hook
    1. 5.0.1. PTRACE_TRACEME
    2. 5.0.2. PTRACE_ATTACH
    3. 5.0.3. PTRACE_KILL