请注意,本文编写于 1519 天前,最后修改于 1491 天前,其中某些信息可能已经过时。
比赛有回顾,地址点这里
静态分析加一些反调试措施和加壳的保护
比赛的时候只做到了Part1,完成了Key1=Caucasus@s_ability的求解,完全没有发现part2的存在。最后神经病的去看假面骑士了,研究了这几个主人公之间的关系,属实蠢驴。
首先查壳,拖入IDA,查看字符串,查找引用,进入Part1主体函数,菜鸡五连。这个就不给具体过程了,上几张图(毕竟作为一个re菜鸡也只会这样了)。逻辑很简单,上面那一片的数字转为字符,是一个倒叙的小端序。正过来代入加密程序,解除我们的Key1=Caucasus@s_ability
这是我们队伍大佬给出来的题解中的python关与Part1的脚本,当时比赛的时候我用的C++,没有保存代码,就用这个代替一下吧,之后还是要慢慢习惯用python解决问题了。
在这里尝试一下,发现不是flag,联想到之前的出题规则,要求出题人要在五分钟之内给到选手正确的线索,所以这个Key1一定是一个有用的东西,x键,查看str的交叉引用,果然,在这段下方还有两个地方用到的这个字符串,跳转过去。
跳转过去,找到一个函数,和一大堆字节数据,我一直都觉得这个是一个字节流数据,但是这是一段没有被IDA反汇编的汇编代码。这里就到了我纠结了很久的地方,我不太明白这个函数与这一大串代码的关系,在这位大佬博客里我找到了答案,这是一段被加密后的程序段,而Key1作为这个加密的要是,第二个函数作为加密时候使用的方法。所以我们要做的就是把这段数据流进行解密,然后F5,分析里面的程序。
第二部分,这个就是我的知识盲区了,对于IDA自带的Python脚本我基本上没怎么用过,整个过程我也是参考大佬博客才完成的,但是这个方法我还是要好好学习一下。
import idc
from binascii import *
s = "Caucasus@s_ability"
def main():
count = 1045 //设置读取大小
start = 0x10040164D //设置开始读取位置,byte_10040164D
addr = start
fp = open('origin','wb')
for i in range(0,1045):
c = s[i%18]
addr = start + i;
res = idc.Byte(addr)
res ^= ord(c)
fp.write(a2b_hex(hex(res)[2:].rjust(2,'0')))
fp.close()
if __name__ == '__main__':
main()
地址0x164d是被加壳的内容,大小为1045。sub_1506函数在解壳,之后在第5行即可直接调用unk_164d()函数。其中第18行就是解码过程,其中(lpAddress+i)就是unk_164d[i],然后(v8+i%18)就是str[i%18],可以先把解码之后的内容dump出来。生成一个origin的二进制文件,用IDA打开,看见这段代码虽然没办法反汇编,但是已经里答案很近了,我们需要用这个代码去替换掉之前程序中的数据。
再将这些数据写回,定义一个rewrite函数,然后在IDA下面python处调用函数。
def rewrite():
fp = open('tjh.exe','rb')
fp2 = open('origin','rb')
data2 = fp2.read()
data=fp.read()
idx = data.find('\x16\x29\xf4\x8f') #是地址0x164d的内容,找到index,可以准确替换
out_data = data[:idx]
for i in range(0,1045):
out_data+=data2[i]
out_data += data[idx+1045:]
fp3 = open('out.exe','wb')
fp3.write(out_data)
fp3.close()
函数结束后,会在根目录下生成一个out.exe,拖入IDA,发现这个exe就是之前两个二进制文件的组合。
这里要注意一个问题,就是IDA中会对中文目录报错,所以我们先修改exe的名称,再进行连接。
之后拖入IDA进行反编译,F5,出现最后的加密逻辑。
这个逻辑很简单,与Part1类似。上脚本
s=r"""
v9 = 2007666;
v10 = 2125764;
v11 = 1909251;
v12 = 2027349;
v13 = 2421009;
v14 = 1653372;
v15 = 2047032;
v16 = 2184813;
v17 = 2302911;
v18 = 2263545;
v19 = 1909251;
v20 = 2165130;
v21 = 1968300;
v22 = 2243862;
v23 = 2066715;
v24 = 2322594;
v25 = 1987983;
v26 = 2243862;
v27 = 1869885;
v28 = 2066715;
v29 = 2263545;
v30 = 1869885;
v31 = 964467;
v32 = 944784;
v33 = 944784;
v34 = 944784;
v35 = 728271;
v36 = 1869885;
v37 = 2263545;
v38 = 2283228;
v39 = 2243862;
v40 = 2184813;
v41 = 2165130;
v42 = 2027349;
v43 = 1987983;
v44 = 2243862;
v45 = 1869885;
v46 = 2283228;
v47 = 2047032;
v48 = 1909251;
v49 = 2165130;
v50 = 1869885;
v51 = 2401326;
v52 = 1987983;
v53 = 2243862;
v54 = 2184813;
v55 = 885735;
v56 = 2184813;
v57 = 2165130;
v58 = 1987983;
v59 = 2460375;
"""
def solve():
res = s.split('\n')
n_list = []
for item in res[1:-1]:
number = item.split('=')[1].strip(';')
n_list.append(int(number))
v61 = 19683
v62 = 0x8000000b
flag = ""
for i in range(0,51):
for c in range(0x20,0x7f):
t = v61 * (c%v62)
if t == n_list[i]:
flag += chr(c)
print(flag)
最后得出flag为为flag{Thousandriver_is_1000%_stronger_than_zero-one}
整道题考察了两个点,一个是静态代码的分析,一道是部分代码的壳加密。值得注意的的是,在题目的函数中,有一个反调试的函数,禁止运行一切调试程序。在我没有想明白这个题目的最终解法之前,我看见了一篇wp,进入了IDA的那个函数,强行修改了反调试函数,将其汇编指令改为了nop,然后进行了动态调试。当调试道了那个加密函数的时候,自动完成了加密与解密,打上断点,就可以直接分析到我们最后手动解密的函数。附上这位大佬的wp,但是在我实际操作的过程中,发现我使用的IDA对汇编进行修补后,保存的exe,X64debug不识别,提示损坏的PE文件,没有办法进行动态调试。这里先存疑,应该是我那里操作不当,但是这的确是一种更加nb的方法。总的来说,在反汇编函数中发现反调试代码时,一定要留个神,因为对这个代码动态分析很有可能是一种捷径,换句话说可能在内部自己自制了一种可对称的加密算法。还有一个是,在一道逆向题目没有想法的时候,尝试对关键字符X获取交叉引用,比去看假面骑士有用的多。
全部评论 (暂无评论)
info 还没有任何评论,你来说两句呐!