二进制组周练题解汇总
2019-9-11 ollvm混淆
关于ollvm的混淆所参考的博客,感觉其中的使用部分非常有趣。
该博客中指出原本的ollvm工具提供了三种混淆方式:控制流扁平化、指令替换、虚假控制流程。而本次周练接触到的就是控制流扁平化。控制流扁平化的实现实际上就是将一些if-else语句,嵌套成do-while形式。
首先打开IDA查看代码。
很显然这个程序是用fencode与encode两个函数对输入字符串进行加密后再与s2串进行比较。然后先进入fencode看一下是怎么加密的。
不过fencode里面的整个汇编代码太长了。秉持着太长不看的原则我们F5看一下伪C代码。伪C代码长什么样这里就不贴图了。总的来说也正如上文所说,伪C代码中有大量的嵌套的do-while语句。
而这个代码稍加分析即可得知,它利用v8的值作为跳转的信号,在各个while语句间进行跳转。所以我们实际上只需要关注v8的值的改变过程,还原出来程序的原本逻辑,就能得出结果。我所采用的方法也是最原始的方法,直接以v8的初始值为开始,一边追踪v8的改变一边做记录,这里简单贴一下自己的记录:
def fencode:
s=input()
v15=len(s)
flag=-1#v14
mark=0
a2=[]
if(v15!=24):
#-1090034712
flag=0
return 0#1267770526
else:
#-1770272074
v12=0
#789829600
if v12<6:
#867656585
v11=0
#288393639
if v11<4:
#1085938007
v10=0
for v9 in range(4) :
#117331278
v10+=s[4*v12+v9]*m[4*v11+v9]
#-1647015695
#-2141009135
a2[mark++]=v10%127
else :
++v12#总之就是这里的循环
#1667780839
else:
#-2078944754
return 1#126770526
还原程序的逻辑过程是很繁琐的过程,这里直接给出两个程序的逻辑还原后结果:
def fendcode2():
m=[[2,2,4,-5],[1,1,3,-3],[-1,-2,-3,4],[-1,0,-2,2]]
mark=0
s=input()
if len(s)!=24:
return 0
for i in range(6):
for j in range(4):
ch=0
for k in range(4):
ch+=s[4*i+k]*m[j][k]
a2[mark]=ch%127
mark+=1
return 1
def encode2(ss,l,news):
a='FeVYKw6a0lDIOsnZQ5EAf2MvjS1GUiLWPTtH4JqRgu3dbC8hrcNo9/mxzpXBky7+'
mark=0
i=0
while i<l:
x1=ss[i]#v21
x2=ss[i+1]#v20
x3=ss[i+2]#v17整个数组开大一点保证后面是0
i+=3
news[mark]=a[(x1>>2)&0x3f]
news[mark+1]=a[(x2>>4)|(x1<<4)&0x3f]
news[mark+2]=a[(x3>>6)|(x2<<2)&0x3f]
news[mark+3]=a[x3&0x3f]
mark+=4
if mark%3:
news[mark-1]='='
还原了加密逻辑之后整个程序就很显然了,fencode函数做了一个矩阵乘法,将输入的24个字符的字符串视为4*6的矩阵,与矩阵m相乘得到新的矩阵,然后进入encode2按照题目的规则进行一个base64编码,再与题目自身的base64编码作比较。
所以我们解题的思路也很清晰了。先进行一个base64解码,再将解码过后的结果乘以m的逆矩阵,就能得到结果。不过注意到fencode的加密过程不是普通矩阵乘法的行与列相乘,而更像是行与行相乘,一开始我还考虑过会不会对结果产生影响。不过事实证明虽然调换了运算顺序,但逆矩阵依然能够发挥作用。毕竟矩阵的转置的逆矩阵=矩阵的逆矩阵的转置嘛。不理解的话可以自己写一下试试。 另外至于如何求矩阵的逆可以参考另一篇博客。所以这里只给出前两个的解密算法。
def decode1(st):
arr1=[]
alpha = 'FeVYKw6a0lDIOsnZQ5EAf2MvjS1GUiLWPTtH4JqRgu3dbC8hrcNo9/mxzpXBky7+'
for ch in st:
for i in range(len(alpha)):
if ch==alpha[i]:
arr1.append(i)
break
print(arr1,len(arr1))
mark=0
arr2=[]
while mark<len(arr1):
arr2.append(((arr1[mark]<<2)|(arr1[mark+1]>>4))&0xff)
arr2.append(((arr1[mark+1]<<4)|(arr1[mark+2]>>2))&0xff)
arr2.append(((arr1[mark+2]<<6)|arr1[mark+3])&0xff)
mark+=4
for i in range(len(arr2)):#mark1
if arr2[i]>127:
arr2[i]=-256+arr2[i]
print(arr2,len(arr2))
return arr2
def decode2(arr,rev_m):
res=[]
for i in range(6):
for j in range(4):
ch=0
for k in range(4):
ch+=arr[4*i+k]*rev_m[j][k]
res.append(ch)
return res
另外正所谓真正的高手是不用F5的,我这次也算是吸取了教训。在decode1函数中原本是没有mark1的处理的,但当我运行解密后却发现有的值是超过了127的,明明伪C代码里面有一个%127的操作,理论上不会超过127才对。并且如果我将decode1得到的数组丢进decode2中时,会发现有一部分的解密结果是对的,比如结尾是‘}’,开头为f。而当我重新回头看fencode的汇编代码时我发现,fencode在运算的时候是使用的int型,然后除以127之后截断成了byte型,所以实际上大于127的值均为负数。所以考虑到有正有负这一点,加上mark1部分的处理后就能得到正确的flag了。最后的flag是flag{dO_y0U_KNoW_0IlVm?}