2021年11月深育杯REVERSE的press
下载附件,是两个文件:
.
猜想是题目类型是与本地文件相关的操作类型,照例扔入exeinfope
中查看信息:
.
然后两个文件可以简单猜想一下press
程序应该是输出了一些信息到out
文件中,在linux
下照例运行一下press
程序看一下回显:
.
.
那么这个程序没有输入
,根据经验这种类型的题目通常直接利用程序外部的一些东西来作为条件继续执行的,如本地环境
,文件读取
等。所以照例扔入IDA
查看伪代码,有main
函数看main
函数:
.
.
知道大致逻辑之后我们找一下s数组
在哪里,前面打开文件的关键自定义函数还没看,现在看一下。因为是环境准备函数
,所以不用过意深究:
.
.
继续回到关键自定义函数sub_40094B
中,因为比较绕,所以我们慢慢分析:
.
.
好了,第一个阶段++++++++++[->++++++++++++++++<]
总算是分析完了,涉及一个负数循环
的系统特殊操作数和一个修改6020A0
而变形形成的一个总共加了160
的循环。(那个负数循环的系统特殊操作数我之所以手算不出来是因为前面环境准备函数中有对 dword_602360[i]
的操作,这里就干扰了系统特殊操作数,难以手动分析)
那我们继续分析后半部分:
.
.
到这里第二阶段,[->-<]
也分析完了,我们继续往下走:
.
.
那么第三阶段的>>[-]+++++<*++.<
也分析完了。总得来说改自定义函数应该就是读入一个flag
,生成(160-flag)*5+2
的值,现在再看看外层函数是怎么操作的:
.
.
总的流程梳理完了,out
中的内容就是每个flag
字符进行(x+160-single_flag)*5+2
而来的,注意这里第一个x
是前一个flag
字符。因为前面最后的分析中加密的160-single_flag)*5+2
的值是放在了6021A0[1]
中的,而且是一直存在的。
6021A0[0]
和6021A0[2]
在遍历S数组中已经被置0
了,只有这个6021A0[1]
保留了值,外面的while
循环重置的只是6020A0
的S
数组下标
的值。
.
.
.
首先我们写一个爆破的脚本,因为原本的结果一看就是base64
加密的,所以直接内嵌
base64解密代码。
还有需要注意的就是((b-j)*5+2)&0xff
这里之所以要&0xff
是因为字符的ASCII
码都是255
内的,但是python
中没有变量大小概念,所以必须截取一个byte
大小,不然就有大量超出255
的数出现,结果就显示不了了。所以这里涉及了我笔记中的>> 、%、&运算符算法积累
out=[0x60, 0xE1, 0x2F, 0x05, 0x79, 0x80, 0x5E, 0xE1, 0xC5, 0x57,
0x8B, 0xCC, 0x5C, 0x9A, 0x67, 0x26, 0x1E, 0x19, 0xAF, 0x93,
0x3F, 0x09, 0xE2, 0x97, 0x99, 0x7B, 0x86, 0xC1, 0x25, 0x87,
0xD6, 0x0C, 0xDD, 0xCF, 0x2A, 0xF5, 0x65, 0x0E, 0x73, 0x59,
0x1D, 0x5F, 0xA4, 0xF4, 0x65, 0x68, 0xD1, 0x3D, 0xD2, 0x98,
0x5D, 0xFE, 0x5B, 0xEF, 0x5B, 0xCC]
flag=""
b=0
for i in range(len(out)):
b+=160 #这里就是说的(x+160-single_flag)
for j in range(127):
if (((b-j)*5+2)&0xff==out[i]): #这里之所以要&0xff是因为字符的ASCII码都是255内的,但是python中没有变量大小概念,所以必须截取一个byte大小,不然就有大量超出255的数出现,结果就显示不了了
flag+=chr(j)
b=out[i]
break
import base64
print(base64.b64decode(flag))
结果:
.
.
当然我们也可以不用爆破的方法,直接写出逆向逻辑脚本,只是因为*5
的逆向/5
会有余数
,所以要不断加256
到没有
余数为止,就比较麻烦:
out=[0x60, 0xE1, 0x2F, 0x05, 0x79, 0x80, 0x5E, 0xE1, 0xC5, 0x57,
0x8B, 0xCC, 0x5C, 0x9A, 0x67, 0x26, 0x1E, 0x19, 0xAF, 0x93,
0x3F, 0x09, 0xE2, 0x97, 0x99, 0x7B, 0x86, 0xC1, 0x25, 0x87,
0xD6, 0x0C, 0xDD, 0xCF, 0x2A, 0xF5, 0x65, 0x0E, 0x73, 0x59,
0x1D, 0x5F, 0xA4, 0xF4, 0x65, 0x68, 0xD1, 0x3D, 0xD2, 0x98,
0x5D, 0xFE, 0x5B, 0xEF, 0x5B, 0xCC]
flag=""
for i in range(len(out)):
c = out[i] - 2
while c != c // 5 * 5:
c += 256
c = c // 5
if i == 0:
flag+= chr(160 - c)
else:
flag+= chr((out[i-1] + 160 - c)&0xff)
import base64
print(base64.b64decode(flag))
结果:
.
.
然后后来还在水番正文
的博客中发现了z3库
破解的方法,也不失为一个好方法,修改后如下:
博客地址:https://blog.csdn.net/weixin_50166464/article/details/121318120?spm=1001.2014.3001.5501
from z3 import *
enflag = [0x60, 0xE1, 0x2F, 0x05, 0x79, 0x80, 0x5E, 0xE1, 0xC5, 0x57, 0x8B, 0xCC, 0x5C, 0x9A, 0x67, 0x26,
0x1E, 0x19, 0xAF, 0x93, 0x3F, 0x09, 0xE2, 0x97, 0x99, 0x7B, 0x86, 0xC1, 0x25, 0x87, 0xD6, 0x0C,
0xDD, 0xCF, 0x2A, 0xF5, 0x65, 0x0E, 0x73, 0x59, 0x1D, 0x5F, 0xA4, 0xF4, 0x65, 0x68, 0xD1, 0x3D,
0xD2, 0x98, 0x5D, 0xFE, 0x5B, 0xEF, 0x5B, 0xCC]
parameter = [BitVec(i, 8) for i in range(56)] #这里定义的是56个char类型的参数准备求解
s = Solver()
s.add( ((160 - parameter[0]) * 5 + 2) == enflag[0] ) #第一台液压机
for i in range(1, 56): #之后的液压机
s.add( (((160 + enflag[i - 1]) - parameter[i]) * 5 + 2) == enflag[i] )
if sat == s.check():
ans = s.model() #model方法返回结果数组,索引是前面定义的参数名。
flag = ""
for i in range(56):
flag += chr(ans[parameter[i]].as_long()) #as_long()转成整数后把结果集附在在flag上
import base64
print(base64.b64decode(flag))
.
.
附录,后面从别人团队的博客和水番正文
博客中发现数组S
是类似于第五空间的brainfuck
,把brainfuck
对应的操作流程翻译过来,就可以了,这样反而更好理解一点:(多重水印是比较难看哈~)
.
.
.
解毕!敬礼!