NSSCTF刷题2day(VMP,控制流整平,ponce插件)
[网鼎杯 2020 青龙组]singal
从nssctf上面可以看到这个是一个控制流和VMP的题目,这里我对控制流和VMP都不是很熟悉,所以就先去学习前置知识了。
控制流
文章关键来源:https://blog.csdn.net/WHACKW/article/details/50472186
其实就是一种流程图,再讲的更明白点就是有向图,控制流就是把程序分块,每个块的先后顺序和条件判断,这些就是控制流,而控制流分析就是分析这些条件,而之所以会出现这样的题目,是因为题目中会出现控制流迷惑的操作,从而使得IDA逆向分析软件中的控制流被迷惑成错误的流程,这个时候就不得不进行人工分析了。
迷惑的操作目前我理解的分为:
- 控制流整平操作:让原本先后顺序的几个模块通过改变代码操作使得几个模块的前驱和后驱变得一样,这个时候我们就必须去分析这几个模块的先后关系然后理解真正代码的控制流,从而成功逆向出程序。比如,一般普通的顺序程序我们把其改成switch操作的到达各个部分。如果是if条件的嵌套,也可以改成switch条件的形式,因为对于IDA来说,case的前驱都是swich,通过设置合适的switch的值就可以让其仍然正常的运行程序。
- 循环控制流迷惑:对于循环部分我们也采用条件控制流转化迷惑的方法进行代码迷惑,也就是设定一个永真或者永假让程序恒进入会进入循环块,这样同样也可以起到迷惑IDA中的控制流分块又或者是把循环的逻辑改switch的形式,让其也被整平,也可以有迷惑的作用
- 分支量保护:也就是保护swich器变量的值不会被IDA直接分析到,如果被直接分析到还是有一定可能性被整理成正确的控制流从而导致代码被逆向概率增大,这里有一种方法就是套用一个函数,把swich(x)变成swicth(f(x)),这个f(x)可以是我们自己设定的函数(要求对于IDA自动分析是困难的),不过最从常用而且屡试不爽的就是验证其的hash值。
VMP
学习资料:https://blog.csdn.net/hhd1988/article/details/116268526
https://blog.csdn.net/u014738665/article/details/120715036
VMP技术就是一种虚拟机化的技术,通过编写代码,从而使程序主要再模拟虚拟机,然后通过操作虚拟机的过程来模拟代码的逻辑,从而达到程序进程正常但是逆向难度和代码的可视性加大,逆向出的代码虚拟机的各种代码,并不是直观的代码。所以VMP技术也被成为VMP加壳技术。并且通过阅读上面的文章我们可以知道有快速得知是否有vmp加壳的方法,就是看是否有opcode,因为其相关代码的组成除了它以外都是可选的,换而言之就是必须有opcode
做题(直接分析)
扫壳
先把文件下载下来,然后去看看是否有相关的其它壳。
可以看到文件的相关信息,是32位,无壳
IDA静态分析
一进入IDA就看到主函数的信息,告诉了我们flag的格式是flag{}
然后还看到了vm_operad的函数,可以很直观的猜到这个VMP的操作了。
进入这个函数分析看看。可以看到如下的流程,这里看到了swich函数,结合学习到的知识可以猜测这个是一个控制流分析,不过目前还不确定。
继续进行下一步分析,因为没看到相关的可分析信息,回到主函数。
主函数下面还有一个主函数,不过进入之后发现就是一个典型的初始化的作用。
继续下一步分析,可以看到下面还有一个qmemcpy的函数。
因为到现在都还没找到操作码然后114是一个固定的值,而v1又被作为整型复制了这么多的值,所以可以猜测这里v4的的值就1对应一个个的操作码,因为子啊vmoperad的函数里面用的就是a1的值,所以把v4的值先提取出来(vmp的题目opcode十有八九都是会被用到的)
先把unk_403040的值都复制出来,复制之后发现复制出来有512个,但是没关系,这种提取一般都是只能多,不能少,多的我们设置条件不进行处理就好。
以第一个数值为例子,这里是小端存储,所以一个int类型的四字节,在其原本的模样应该是0x0000A这样子的,所以把提取出来的式子编写脚本还原。
opcode=[0x0A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00,
0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x21, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x51, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0C, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0B, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x08, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00,
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x41, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0C, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x22, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3F, 0x00,
0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x33, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x18, 0x00,
0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xFF, 0xFF,
0x07, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x00, 0xF1, 0xFF, 0xFF, 0xFF, 0x07, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x84, 0xFF,
0xFF, 0xFF, 0x07, 0x00, 0x00, 0x00, 0xC1, 0xFF, 0xFF, 0xFF,
0x07, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x00, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00]
print('opcode[114]={',end='')
for i in range(456):
if opcode[i]!=0x00:
des=str(hex(opcode[i]))
print(f'0x0000{des[2:].upper()}',end=',')
print('}')
#opcode[114]={0x0000A,0x00004,0x000010,0x00008,0x00003,0x00005,0x00001,0x00004,0x000020,0x00008,0x00005,0x00003,0x00001,0x00003,0x00002,0x00008,0x0000B,0x00001,0x0000C,0x00008,0x00004,0x00004,0x00001,0x00005,0x00003,0x00008,0x00003,0x000021,0x00001,0x0000B,0x00008,0x0000B,0x00001,0x00004,0x00009,0x00008,0x00003,0x000020,0x00001,0x00002,0x000051,0x00008,0x00004,0x000024,0x00001,0x0000C,0x00008,0x0000B,0x00001,0x00005,0x00002,0x00008,0x00002,0x000025,0x00001,0x00002,0x000036,0x00008,0x00004,0x000041,0x00001,0x00002,0x000020,0x00008,0x00005,0x00001,0x00001,0x00005,0x00003,0x00008,0x00002,0x000025,0x00001,0x00004,0x00009,0x00008,0x00003,0x000020,0x00001,0x00002,0x000041,0x00008,0x0000C,0x00001,0x00007,0x000022,0x00007,0x00003F,0x00007,0x000034,0x00007,0x000032,0x00007,0x000072,0x00007,0x000033,0x00007,0x000018,0x00007,0x0000A7,0x0000FF,0x0000FF,0x0000FF,0x00007,0x000031,0x00007,0x0000F1,0x0000FF,0x0000FF,0x0000FF,0x00007,0x000028,0x00007,0x000084,0x0000FF,0x0000FF,0x0000FF,0x00007,0x0000C1,0x0000FF,0x0000FF,0x0000FF,0x00007,0x00001E,0x00007,0x00007A,}
提取出操作码的原始值之后,去进行控制流的分析。
分析operad部分的控制流
这里我把a1改名成opcode方便分析。
最后分析结果如下:
然后看看其对应的opcode的数组:
可以看到前面一部分的规律就是,除了第一个之后,每隔六个就开始重复,呈现一种类似周期状态,那么这次就可以猜测这里的循环体控制流迷惑,而且是整平的,然后我们去看看代码块中是否还有关键的地方。
看到代码块的这一部分:
case7的时候只有一个对比的函数,并且v7只会在case7的时候使用,所以这里猜测是把我们输入的字符串转化完了之后再通过case7操作进行对比,我们看看操作码在第几次第一次变成0x000007。
这里通过代码进行检查之后发现0x00007第一次出现位置为84,然后总出现了15次,所以总共对比了15次,所以flag长度也是15。
并且因为出现7的时候比较靠后,然后研究case7的代码逻辑可以发现,当当前的操作码为7的时候,下一个操作码的值就是对应的正确字符串的长度。
这个时候我们编写脚本把字符值提取一下。
enc=[0x0000A,0x00004,0x000010,0x00008,0x00003,0x00005,0x00001,0x00004,0x000020,0x00008,0x00005,0x00003,0x00001,0x00003,0x00002,0x00008,0x0000B,0x00001,0x0000C,0x00008,0x00004,0x00004,0x00001,0x00005,0x00003,0x00008,0x00003,0x000021,0x00001,0x0000B,0x00008,0x0000B,0x00001,0x00004,0x00009,0x00008,0x00003,0x000020,0x00001,0x00002,0x000051,0x00008,0x00004,0x000024,0x00001,0x0000C,0x00008,0x0000B,0x00001,0x00005,0x00002,0x00008,0x00002,0x000025,0x00001,0x00002,0x000036,0x00008,0x00004,0x000041,0x00001,0x00002,0x000020,0x00008,0x00005,0x00001,0x00001,0x00005,0x00003,0x00008,0x00002,0x000025,0x00001,0x00004,0x00009,0x00008,0x00003,0x000020,0x00001,0x00002,0x000041,0x00008,0x0000C,0x00001,0x00007,0x000022,0x00007,0x00003F,0x00007,0x000034,0x00007,0x000032,0x00007,0x000072,0x00007,0x000033,0x00007,0x000018,0x00007,0x0000A7,0x0000FF,0x0000FF,0x0000FF,0x00007,0x000031,0x00007,0x0000F1,0x0000FF,0x0000FF,0x0000FF,0x00007,0x000028,0x00007,0x000084,0x0000FF,0x0000FF,0x0000FF,0x00007,0x0000C1,0x0000FF,0x0000FF,0x0000FF,0x00007,0x00001E,0x00007,0x00007A,]
count=0
for i in range(len(enc)):
if enc[i] == 7 :
print(enc[i+1],end=',')
count+=1
print('||',count)
#34,63,52,50,114,51,24,167,49,241,40,132,193,30,122,|| 15
得到正确的字符串数组,现在只需要确定单个字符被操作的逆向操作就好。
话是这么说的,但其实分析还是要了很长的时间进行分析,最后得到一个如下的python解密方法:
op=[10, 4, 16, 8, 3, 5, 1, 4, 32, 8, 5, 3, 1, 3, 2, 8, 11, 1, 12, 8, 4, 4, 1, 5, 3, 8, 3, 33, 1, 11, 8, 11, 1, 4, 9, 8, 3, 32, 1, 2, 81, 8, 4, 36, 1, 12, 8, 11, 1, 5, 2, 8, 2, 37, 1, 2, 54, 8, 4, 65, 1, 2, 32, 8, 5, 1, 1, 5, 3, 8, 2, 37, 1, 4, 9, 8, 3, 32, 1, 2, 65, 8, 12, 1, 7, 34, 7, 63, 7, 52, 7, 50, 7, 114, 7, 51, 7, 24, 7, 167, 255, 255, 255, 7, 49, 7, 241, 255, 255, 255, 7, 40, 7, 132, 255, 255, 255, 7, 193, 255, 255, 255, 7, 30, 7, 122]
des = [34, 63, 52, 50, 114, 51, 24, 167, 49, 241, 40, 132, 193, 30, 122]
des.reverse()
op.reverse()
v9 = 0
rg = 0
v4 = 0
flag=[]
for i in range(len(op)):
if i == len(op)-1:
flag.append(rg)
elif op[i] == 1 and op[i-1]!=1 :
v4=des[v9] #因为原来操作为存入操作所以这里为存储,因为des和op都逆序了,所以这里的v9对应原来的v9+1
v9+=1
flag.append(rg) #因为存入对比数组的为我们flag的正确顺序,所以这里存入。
elif op[i] == 2:
if (op[i + 1] != 3 and op[i + 1] != 4 and op[i + 1] != 5):
rg=v4-op[i-1]
elif op[i] == 3:
if (op[i+1]!=2 and op[i]!=4 and op[i]!=5):
rg=op[i-1]+v4
elif op[i] == 4:
if (op[i+1]!=2 and op[i+1]!=3 and op[i+1]!=5):
rg=op[i-1]^v4
elif op[i] == 5 :
if(op[i+1]!=2 and op[i+1]!=3 and op[i+1]!=4):
rg=int(v4//op[i-1])
# 舍弃等于6的时候的+法。因为case 6的作用,相当于跳过,是让其指令指向下一个opcode,reverse加上for遍历已经可以达到这样效果了。
#case 7也是类似的,因为其为对比。
elif op[i]==8:
v4=rg
#源代码没有case 9 ,case 10是读取,不作处理
elif op[i]== 11:
rg=v4+1
elif op[i]==12 :
rg=v4-1
flag.reverse()
out=''
for j in flag:
out+=chr(j)
print('flag'+out+'}')
#flag{757515121f3d478 }
得到了flag,然后搜索网上的资料发现还有两种方法可以做:
angr库和ponce插件。我觉得插件的解题思路是可以学的,不过明天再说,今天花了很多时间了。
ponce插件思路
我也第一用这个插件,为了能够明白这个插件的用途所以查了这个插件本质的用途。
其本质的操作干了符号模拟和符号执行的两个操作,看上去其实现的操作只有两个,但其实现的效果却让人很省事的。然后说一下我个人的理解和白话点。
符号模拟:的操作就是在程序动态调试的过程中,对变量进行一种类似标记处理,这个步骤主要目的是方便后续我们进行符号执行的一些操作。
符号执行:符号执行的意思在当前下断点的操作的地方对,对符号化的变量单独开一个虚拟进程用于模拟当前符号化变量在接下来的代码中所具备的逻辑,然后把这个符号化的变量的状态改变(值),然后进入后续的程序,可选的操作一个是求解等式(solve formula to),另一个就是否定条件注入变量值的指令(negate and inject)。
这里因为我的IDA是8.3,而ponce插件最多目前支持到8.1,所以我这个运行多次之后会出现问题,不过可以展示一下使用的方法:
安装ponce
在github直接搜索就可以搜到
放入IDA进行分析
首先扫壳和分析就不再进行展示了,IDA32进行分析,ponce插件配置如下:
进入程序之后进入vmp操作的函数里面去操作:
在case 7上设置断点,然后再read函数上设置断点。
设置完之后我们进行动态调试。
随便输入15个字符之后,找到15个字符存入的地址然后进行符号模拟的操作,然后进入f9动调到下一个断点(因为case 7是一个对比条件,可以利用ponce的negate操作进行符号执行,所以直接f9动调到下一个断点)
到case 7如下:
进行nageta操作。
看到第一个字符的变量值已经被修改为正确的输入了。这个时候f9,就会通过if条件的状态进入下一次验证。
然后进行同样的negeta操作,第二个字符也被修改为正确的值了。,然后循环往复就可以获得字符串的正确值了。
757515121f3d478
然后得到了正确的flag,可以看到如歌ponce插件用的好的话,可以大大省去分析时间。