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函数可能是字符串加密函数。下图所示的代码段是比较重要的了。下图所示。

  这里有一个值得注意的地方,即是代码中出现的alblcldl。在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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.data:0040A030 byte_40A030     db 61h                  ; DATA XREF: sub_401000:loc_401051↑r
.data:0040A031 db 6Ah ; j
.data:0040A032 db 79h ; y
.data:0040A033 db 67h ; g
.data:0040A034 db 6Bh ; k
.data:0040A035 db 46h ; F
.data:0040A036 db 6Dh ; m
.data:0040A037 db 2Eh ; .
.data:0040A038 db 7Fh ; 
.data:0040A039 db 5Fh ; _
.data:0040A03A db 7Eh ; ~
.data:0040A03B db 2Dh ; -
.data:0040A03C db 53h ; S
.data:0040A03D db 56h ; V
.data:0040A03E db 7Bh ; {
.data:0040A03F db 38h ; 8
.data:0040A040 db 6Dh ; m
.data:0040A041 db 4Ch ; L
.data:0040A042 db 6Eh ; n
.data:0040A043 db 0

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寄存器

  显然这里的eax寄存器表明是否匹配,为1则成功,否则失败。因此可以比较这两个函数“sub_401000”和“sub_401240”在返回时是否对eax寄存器进行赋值。

sub_401240函数的返回代码段

sub_401000函数返回代码段

  所以呀,由以上2张图片就能知道,函数sub_401240并没有在eax寄存器写入任何值,因此它可能是干扰项。函数sub_401000的返回代码段则有对eax寄存器进行写入、修改操作。如果有字符不匹配,sub_401000返回0(xor eax, eax,自我异或值为0);全部匹配则返回1(mov eax, 1eax寄存器赋值为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
#include <stdio.h>
#include <stdlib.h>

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哟。