171217 逆向-HomuraVM
来源:互联网 发布:淘宝卖家怎么设置定制 编辑:程序博客网 时间:2024/05/21 16:55
1625-5 王子昂 总结《2017年12月17日》 【连续第443天总结】
A. CG-CTF(HomuraVM)
B.
南邮的Re师傅们真是热爱VM啊……OTZ
这一题比WxyVM的两个要难一点,却又比asm汇编级别的解释器简单许多,感谢南邮师傅们(吼姆拉酱?ww)恰到好处的难度控制
拖入IDA,结构很清晰
将输入拷贝到一个数组中,然后初始化机器码
变换后进行check
很明显解释器就是sub_8DC,而sub_8AA则是反调试
对sub_8DC进行反编译,发现不完整
这里的JUMPOUT就是用来跳转至对应解释函数的语句
不过对机器码进行解析的部分都反编译出来了,就是每个字符-67后去dword_1048中寻找偏移值,然后以dword_1048为基准进行跳转
jmp base[a[i]-67] + base
回到汇编状态进行分析各个解释函数
可以看到有这么多,不过还好它们的核心语句基本上就只有一两行汇编,逐个记录即可
主要变量有两个计数器a和i,分别表示读取input和机器码的下标
两个寄存器qword_202080和qword_202088,下将其称为n1和n2
简单的函数如下
93c: a += 1962: a -= 1988: input[a] += 19aa: input[a] -= 19cc: n1 += 19ee: n1 -= 1a10: n2 = n1a36: n2 = n1 + input[a]a67: n2 -= (n1&input[a])*2aa6: n2 -= 1ac8: n2 += 1
比较复杂的4个函数:
aea: if(input[a]!=0){b1d}else{af8} af8: i++;if(k[i]==0x5d){c23}else{af8}b1d: i++;b31: if(input[a]!=0){b79}else{b3f} b3f: i--;if(k[i]==0x5b){i++;c23}:else{b3f}b79: i++;b8d: if(n2!=0){bbd}else{b9b} b9b: i++;if(k[i]==0x7d){c23}else{b9b}bbd: i++;bce: if(n2!=0){c13}else{bdc} bdc: i--;if(k[i]==0x7b){i++;c23}else{bdc} c13: i++;
这4个函数是循环结构,通过input[a]和n2来进行判断,通过i来控制执行的语句
其中0x5d 0x5b 0x7d 0x7b这4个终止值让人比较在意,char分别是”[]{}”
看起来可能有点复杂,没关系,等会儿再继续解析
表示出现的机器码和解释函数对应关系:
# dword_1048处dump出来的偏移数组n = [0xa67, 0xc23, 0xc23, 0xc23, 0xaa6, 0xc23, 0xc23, 0xc23, 0xc23, 0xc23, 0xa36, 0xc23, 0xc23, 0xc23, 0xc23, 0xc23, 0xc23, 0xac8, 0xc23, 0xc23, 0xc23, 0xc23, 0xc23, 0xc23, 0xaea, 0xc23, 0xb31, 0xc23, 0xc23, 0xc23, 0x9ee, 0xc23, 0xc23, 0xc23, 0xc23, 0xc23, 0xc23, 0x93c, 0xc23, 0xc23, 0xc23, 0xc23, 0x988, 0xc23, 0x962, 0xc23, 0xc23, 0x9cc, 0xc23, 0xc23, 0x9aa, 0xa10, 0xc23, 0xc23, 0xc23, 0xc23, 0xb8d, 0xc23, 0xbce]s = "ahmoruvCMG[]{}"for i in s: print(i, hex(n[ord(i)-67])[2:])
可以生成一份表(第三列代码部分是另加的对应字典)
这里可以看到,[
和]
对应构成一个循环,{
和}
对应构成一个循环
以’[‘和’]’为例
当遇到’[‘时,判断input[a]是否==0,为0则直接跳到’]’后,否则运行[]内的代码
当遇到’]’时,判断input[a]是否==0,为0则继续运行后面的内容,否则返回到’[‘前
实际上就是一个while循环
这个思想挺像以前看到的BrainF@ck语言来着
观察机器码,发现只有三种循环:{aG}{mG}[ur]
这样可以针对性地翻译出它们的对应代码来降低理解难度:
(为了书写方便将input[a]简写为s[a],下同)
{mG}: s[a] += n2; n2=0{aG}: n1 -= n2; n2=0[ur]: n1 += s[a]; s[a]=0
(其实上一步可以省略)
分析机器码,发现大量的重复部分,区别只是中间的a和r
这样翻译起来就简单很多了
(感谢出题师傅手下留情)
尝试解析重复的部分,发现n2可以被消去
解析完以后发现h代表input下标+1,因此以它作为分界线更为合适
也就是说,将不同的a和r放在代码块的中间
通过正则表达式分组出如下代码:
虽然第一句长的不太一样,但是解析以后发现效果其实相同
每一行翻译出的伪代码为:
a += 1n1 = 0n2 = 0n1 = s[a]# n1根据a和r改变...# 计算s[a] = n1+s[a-1] - (n1&s[a-1])*2
注意这里的s[a-1]是计算后的内容了
也就是说正向算法是
# x为不同行中的改变内容f(计算前的s[a] + x,计算后的s[a-1]) = 计算后的s[a]
这样就得到了递推公式
虽然计算方法有点点复杂,无法逆运算
不过数据范围都在一个比特内,因此只有256种可能,穷举即可
# 有可能会出现多解情况,已验证皆为一解,因此可以直接returndef find(x, y): for z in range(256): if(y == x + z - (x & z) * 2): return z
于是根据递推公式可以逆求出所有的计算前的input
for i in range(1, 34): # 逆求值 p = find(n[i-1], n[i]) # 修正对n1的改变 offset = (o[i].count('a')-1) - (o[i].count('r')-1) print(chr(p + offset), end='')
得到flag
开头少了一位,是因为在赋值的时候
*(_DWORD *)input_q = input[strlen(input) - 1];
将最后一位赋给了第一位(之后的操作全部都是从第二位开始的)
通过最后一位和第一位的值同样可以逆推出原值,直接根据格式猜也行233
另外check的地方有一个bug:
for ( j = 0; j <= 33; ++j ) { if ( *(&v5 + j) != v42[j + 1LL] ) { puts("GG!"); exit(0); } } puts("correct!");
意味着只要满足前34位值相同即可
而解释器的结束条件是机器码读取完毕,也就是说仅会对前34位进行变换
开始输入的地方也没有对长度进行校验
因此满足check的条件就是
正确flag + 任意位字符 (最后一位为’}’)
不过flag显而易见所以无伤大雅啦~
PS:
静态分析被指针、内存等等搞乱了的时候,需要动态调试来观察内存中的变化
因此动态调试还是挺有必要滴:
这个反调试函数是通过比较是否Sessionid和ppid来判断的:
v1 = getsid(v0); result = getppid(); if ( v1 != result ) exit(0); return result;
如果是调试器开启的程序则会导致ppid和sid不等,进而退出程序
最简单的方法就是直接patch,将0x8AA的首字节改为0x3c(retn)
不清楚gdb有没有附加调试功能,以及附加能不能过这个反调方法呢~
下次挑战hctf的level2的解释器=A=
C. 明日计划
加密与解密
- 171217 逆向-HomuraVM
- 逆向
- 逆向
- 逆向
- 逆向-360逆向writeup
- 逆向工程
- 逆向思考
- 逆向ring0msg
- 逆向思维
- 逆向工程
- 逆向COM
- 逆向思维
- 逆向分词
- 逆向IopAttachDeviceToDeviceStackSafe
- 逆向学习
- 逆向工程
- 逆向法则
- 逆向学习
- 面试之BroadcastReceiver
- Day017
- Day 017
- 异或运算是什么,看看大白话怎么说
- 快排
- 171217 逆向-HomuraVM
- OkHttp封装,以及拦截器,拦截公共参数
- java Console 控制台为null问题 Console con = System.console()
- Day018
- 线段树--带 * + 符号
- Kotlin从入门到放弃(三)——协程
- Day 018
- Android LocalBroadcastReceiver本地广播
- .Net mvc 登录认证成功后无法跳转到指定的页面,求指教