EASYHOOK, 异或运算,函数地址
1.题目描述
描述:暂无
来源:XCTF 4th-WHCTF-2017
场景:暂无
附件:名为“1c40a4a45618413d83d4724b7f1a5d2f.exe”的可执行文件

2.文件分析
拖动到Exeinfo PE下分析,仍然是未加壳的文件,应该由MIcrosoft Visual C++ 6编写成的。所以可以直接放到IDA中分析。

程序打开以后随便输入一段错误的字符串,提示“Wrong!”,然后按任意键退出。

3.逆向分析
在IDA中找到main函数,看到以下代码块。虽然没有调用strlen函数,但是在调试的过程中发现程序依然对输入字符串长度有要求,即需要13h = 19个字符,否则失败退出。下图所示。

为了能够进入成功的代码段,笔者在这里输入“1234567890aaabbbccc”的字符串。再次调试发现有一段混乱的字符串“307234?6i.k]nXm\sCp”,长度刚好也为19位。在指令lea edx, [esp+2Ch+Buffer]中正是把这段字符串的首地址赋值给edx寄存器(地址可能是19FF14)。后续分析得知sub_401220是字符串加密函数。
此外,程序还会在自己所处的目录下创建一个名为“Your_input”的文本文件,打开后发现里面的文本正是加密处理后的字符串。

因此需要跳转进sub_401220函数分析,大概一看好像也没什么,但是下图中红色方框的指令“lea eax, sub_401080”,把函数sub_401080的地址赋值给eax寄存器了,实际上这个函数还被调用了。

笔者一开始也不太敢相信这个函数会被调用,其实可以选中sub_401080函数字段,鼠标右键打开菜单选中“Xrefs graph to...”,就会看到函数的调用关系。下图所示。

调用sub_401080函数虽然没有看见call指令,但是确实被调用了。
而这个函数又调用了2个函数,分别为“sub_401000”,“sub_401140”。以下咱们来逐个分析。

3.1 sub_401000
sub_401000函数可能是字符串加密函数。下图所示的代码段是比较重要的了。下图所示。

这里有一个值得注意的地方,即是代码中出现的al,bl,cl,dl。在Intel x86_64汇编体系下。通用寄存器rax是64位(64位二进制数,或16位十六进制数),eax是32位(32位二进制数,或8位十六进制数),ax是16位(16位二进制数,或4位十六进制数),所以al是低8位二进制数(或低2位十六进制数)。其它的寄存器也是一样。
上图中的and ebx, 80000001h是为了判断下标为奇数还是偶数。一般数字转换为二进制后,奇数最后一位是1,偶数最后一位是0,所以这段指令可以判断数字的奇偶。
字符串下标为奇数则对应字符减去下标后并与下标进行异或计算,如果为偶数则向后读取2位的字符,然后再做异或计算。最后一位字符与13h(即dl寄存器值)进行异或计算。
因此按照以上步骤,由“1234567890aaabbbccc”加密的字符串是“307234?6i.k]nXm\sCp”。
而在sub_401000函数的后面部分则需要将加密后的字符串与数组byte_40A030比对。下图所示。

笔者在此处把数组里的内容列举出来。
1 | .data:0040A030 byte_40A030 db 61h ; DATA XREF: sub_401000:loc_401051↑r |
3.2 sub_401240
之前提到的sub_401140(被sub_401080调用)函数其实并没有重要的代码,此处分析sub_401240函数。在main函数这一部分还有sub_401240函数,可能也会影响最终结果,下图所示。

可能它会把字符串是否匹配的真值(true, false)放到eax寄存器中。所以还是要看看。如下图所示。

在调试过程中发现,加密后的字符串仿佛在与“This_is_not_the_flag”比较。笔者当时在这里犯起了嘀咕。到底是与“This_is_not_the_flag”比较还是在与上述byte_40A030比较呢?
再次返回到main函数的后半部分,下图所示。
显然这里的eax寄存器表明是否匹配,为1则成功,否则失败。因此可以比较这两个函数“sub_401000”和“sub_401240”在返回时是否对eax寄存器进行赋值。


所以呀,由以上2张图片就能知道,函数sub_401240并没有在eax寄存器写入任何值,因此它可能是干扰项。函数sub_401000的返回代码段则有对eax寄存器进行写入、修改操作。如果有字符不匹配,sub_401000返回0(xor eax, eax,自我异或值为0);全部匹配则返回1(mov eax, 1,eax寄存器赋值为1)
4.最终结果
根据以上几个小节的分析,笔者将还原算法代码列举在这里。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int main()
{
unsigned char arr[] =
{
0x61, 0x6A, 0x79, 0x67, 0x6B, 0x46, 0x6D, 0x2E, 0x7F, 0x5F,
0x7E, 0x2D, 0x53, 0x56, 0x7B, 0x38, 0x6D, 0x4C, 0x6E, 0x00
};
int i = 0;
arr[18] = arr[18]^(0x13);
for(i=17;i>=0;i--)
{
//1.下标是偶数
if(i % 2 == 0)
{
arr[i+2] = arr[i]^i;
}
else
{
arr[i] = arr[i]^i;
arr[i] += i;
}
}
printf("%s\n",arr);
system("pause");
return 0;
}
所以以上程序运行后就能得到结果了,即“alag{Ho0k_w1th_Fun}”。但是有一点比较坑的是,笔者在把这段flag提交到攻防世界平台时,居然提示错误,后来又输入“flag{alag{Ho0k_w1th_Fun}}”还是不对,最后试了试,正确答案是“flag{Ho0k_w1th_Fun}”,要把首字母改成“f”。难怪很少有人提交正确的flag哟。



