本题考察内容比较基础,主要是让初次接触逆向的人了解逆向是什么,逆向工具的基础操作等。以下均为我自己的理解,可能会有些片面,想深入了解的话建议多去百度和谷歌查阅相关资料。
1.逆向是什么
最常见的比如游戏外挂或破解软件等都有逆向参与。比如说你用C语言写了一个游戏,并且设定最终关卡只有在通过前面的关卡之后才开启,但我们可以将这个程序逆向,从而找到限制了我们进入最终关卡的逻辑代码(比如一个if语句),并通过工具修改掉if语句中检验的变量的值,使条件成立,这样我们就能直接进入。当然实际做起来并没有这么简单,如何破解开发者对程序设置的保护(比如脱壳),如何在逆向后分析出被破解程序的大致逻辑和框架,如何根据了解到的程序逻辑写出一个脚本达到我们的目标,这些都不是简单的事,需要我们有足够的C语言和汇编的基础,并进行过许多的逆向,积累足够的经验与见识。上面说的这些游戏外挂,破解软件等并不能用来当做正当行业,国家也只允许我们自己研究,而不能将研究结果用作商业途径,但逆向的发展前景也很广的,比如说游戏公司需要逆向外挂来反外挂,保证自己的游戏环境;网络安全公司需要逆向病毒来掌握防护这些病毒的方法;开发方面也有逆向开发工程师,工作就是逆向分析别人的成品,然后进行改良,模仿制造出类似但属于公司自己的产品。
2.逆向工具的基础操作(以解本题为例)
题目是一个exe文件,也就是windows上的可执行文件,是我们初次接触逆向时常见的类型;除此之外还有一种常见的类型是elf文件,一般没有后缀名,是linux上的可执行文件,要在linux虚拟机上用终端执行。
首先我们先用查壳工具exeinfope来了解程序的信息(壳就是开发者为了保护自己程序而给程序套上的一层护盾,没有进行脱壳就直接逆向的话逆向出的结果是错误的),使用方法是直接把程序拖入框中:
可以得知程序是32位的,没有说程序是否有壳,那就先用反编译软件打开试试。
常用的反编译软件有两个,一个是静态分析的软件ida,一个是动态分析的软件od。一般的反编译只能编译出程序的汇编代码,但ida可以将汇编代码转化为伪代码,这样只要有C语言的基础就能尝试看懂;而od虽然不能生成伪代码,但他可以在程序运行时进行调试,修改程序数据,因此在逆向一些程序时用od可能不需要写脚本,只需要修改程序中的指定数据就能破解该程序。对于刚接触逆向的初学者来说可以使用ida来逆向程序,根据伪代码分析出程序的主要逻辑,以及flag的加密方法,然后写出脚本得到flag。但ida中的伪代码对于一些复杂的程序有时候并不准确,需要结合汇编来一起分析。对于逆向来说最好的方法就是将两个软件结合使用,先用ida分析出程序的运行逻辑,然后用ob动态调试程序,从而进行破解。
这里我们用ida来打开,ida分为32位和64位两种,我们按之前软件分析的用32位的ida打开:
有些程序用查壳工具无法得知有没有加壳,我们可以先用ida打开,如果程序加壳的话左侧的函数框中函数会很少;有些程序无法得知是32位还是64位的,我们可以先用64位的ida打开,如果是32位的话是无法进行反编译的。
打开后下一步是要找到他的主函数,可以从左侧找,也可以搜索字符串,从而找到主函数。从左侧找的话可以找找有没有main函数,有的话直接双击然后按F5就可以反编译出它主函数的伪代码;但有的程序不会显示main函数,这时候可以试着搜索字符串。按shift+F12搜索字符串:
我们找到了一个可疑字符串please input flag:,双击后跳转到存放该字符串的位置。这里只是定义了该字符串,我们要找主函数的话下一步应该找引用了该字符串的地方,所以下一步我们按Ctrl+X查找引用该字符串的位置:
这个程序引用该字符串的地方只有一个,跳转之后按下F5就可以将引用该字符串的函数转化为伪代码:
然后就是分析这个函数的逻辑,先是让我们输入flag,然后存放到变量Str中,然后对Str的长度使用一个if判断:
if ( strlen(Str) == dword_407064 )
这里应该是在判断flag的长度,我们双击dword_407064,跳转到它的地址:
可以知道这个地址存放了一个16进制的数:1Bh(结尾加h的是16进制数,具体可以百度一下”进制数后缀“),转换成10进制就是27,说明falg的长度是27。继续向下分析,下面是一个while循环,条件是
v3 < dword_407064 - 1
可以得知是在遍历一遍字符串flag,然后看下面的这个if循环:
if ( byte_407000[v3] != (Str[(v3 + 1) % dword_407064] ^ Str[v3]) )
出现了一个新的变量:byte_407000,我们双击它查看它存放的内容:
数一下从该地址往后一共有27个数,正好跟flag的长度相同,而它又在while循环中与flag逐位比较,所以我们暂时先把它当成一个数组。
然后是这个if循环的右边,dword_407064 我们已经知道存放的值是27,而在这个while循环中,循环条件是 v3 < dword_407064 - 1,可知v3 + 1<27,所以可以看成:
byte_407000[v3] = Str[v3 + 1] ^ Str[v3])
也就是说,flag的每一位与这一位的下一位进行异或后等于byte_407000中对应位数的值。这里的异或操作具体计算就不讲了,大家可以去百度自己查一下,这里只要知道一个数异或另一个数两次的话结果不变,也就是(A^B)^B=A。
再看下一个if语句:
if ( byte_406FFF[dword_407064] != (byte_407000[0] ^ *(&v4 + dword_407064)) )
byte_406FFF与byte_407000的地址相邻,因此byte_406FFF[dword_407064]指向的地址就是byte_407000的最后一位;v4双击后发现地址与Str相邻:
因此&v4 + dword_407064指向的地址就是Str的最后一位。
结合一下就可以写出解题的代码:
Python:
list1 = [0x17,0x17,0x1B,0x16,0x17,0x12,0x1D,0x0C,0x12,9,0x0F,0x0C,2,0x5E,0x6C,0x2B,0x44,0x6F,0x2D,0x17,0x50,0x50,0x17,4,0x13,0x18,0x6A]
for i in range(len(list1)-1,-1,-1):
list1[i] = list1[i] ^ list1[ (i+1)%len(list1) ]
list1 = list(map(chr,list1))
print("".join(list1))
C++:
#include<iostream>
#include<stdlib.h>
#include<cstring>
using namespace std;
int main(){
int array1[] = {0x17,0x17,0x1B,0x16,0x17,0x12,0x1D,0x0C,0x12,9,0x0F,0x0C,2,0x5E,0x6C,0x2B,0x44,0x6F,0x2D,0x17,0x50,0x50,0x17,4,0x13,0x18,0x6A};
for(int i=26;i>-1;i--){ // 解密
array1[i] = array1[i] ^ array1[(i+1) % 27];
}
for(int i=0;i<27;i++){ // 输出
char a = array1[i];
cout<<a;
}
cout<<endl;
system("pause");
return 0;
}
3.总结
如果想要学习逆向的话,入门需要有一定的c基础;咱们学校的c++课程是在大一一年都有的,汇编语言在大二第一个学期,想要学习逆向的话可以提前接触一下。