sandoth 发表于 2020-5-22 12:54

记一次逆向工程作业——SMC代码自修改程序的逆向分析

本帖最后由 sandoth 于 2020-5-22 15:34 编辑

第一次发帖,由于刚开始学习逆向工程,所以文中可能有些错误或者描述不完善的地方,欢迎大家在评论中批评指正或者提出问题进行讨论。

## 环境

主机:Windows10 64位 专业版
虚拟机软件:VMWare 15.1.0<br>虚拟机环境:Windows XP
软件:IDA VSCode Notepad++
编程语言:Python3 C/C++
文件:7-3.exe SMC.idc



## 概览

使用IDA打开7-3.exe,查看程序逻辑,并对部份意义明确的变量重命名,以便分析。

![](https://ckwteg.dm.files.1drv.com/y4mjgmAcc6Jbuj03ohNO4oCZxv5LlIiVi5Vuncx8PW8uk5V2ehoz6Fev7sowSxE0lP9q9pnZiEQqLSK1RsPrPDJm_v__qqxVI9w_qmRuLLnyYVTtb7Vn9PoVe0LSQKKOsYH4ir4hmpBBXZOvS2ZF-GOc7wBfppxqCrBY864knyAn0B-23_KV0nmaGIJZ3gMpgg6X5NXRC40F8y-NWSBX5VbUg)

## 0x01

首先分析开头的汇编程序可知,程序在一开始要求用户输入字符串,即flag,对输入的flag求取长度,当flag长度`length=28(0x1C)`时,程序跳转至`loc_40107F`

```assembly
mov   edx,
movsx   eax,
cmp   eax, '7Dh'//'}'
jnz   short loc_4010DD
```

由上述可知,flag长度为28,并且最后一个字符为:`}`

![](https://cqwteg.dm.files.1drv.com/y4m-ijxT5N8JxZSDImR512c_7AJ6vuH4ldK4NIEV_WHBfM1HXggH2DFGyDPC5hiaASeS4X4RqLTLUo-wmoepYGMMoLi2ofE47ArZccEdSuAqZvRLf2pjZOx4Q5lD3DzQyZOXTWYMFlpb92W2S8IDUGUjD7puW9MEC04TN8xJRR1F6Mh5fzC1-vWRheFxfqtOHT9dOV7z3fbSTo7exgFi84CxA)

## 0x02

在`loc_40109E`中

```assembly
cmp   , 43h
jge   short loc_4010BC
```

显然,右侧框图为循环,并且`var_70`充当了计数的作用,对`byte_414C3C`进行了处理,通过相应的解密函数,我们可以得出解密的结果,可以看出,结果为一个函数,通过IDA的函数构建功能,我们可以得到`sub_414C3C`。

上述循环结束后,程序将`byte_414C3C`转化为`sub_414C3C`。在`loc_4010BC`中

```assembly
mov   dword ptr , offset sub_414C3C//转化后汇编代码,截图为转化前
mov   esi, esp
push    offset unk_414BE0
lea   edx,
push    edx
call    dword ptr
```

程序调用了解密出的函数`sub_414C3C`,传入了`flag`和`unk_414BE0`两个参数。

![](https://ekvihw.dm.files.1drv.com/y4mBjlWGBTVMgjbDTNUB1hC6r6_GGqSY9DH80ReiWyTXcSf_b1oLLr1JLWzf1fehOhVI2DX2GG7cibwHpmgXY-MaxZJ6AT76-hdDcFI-R5j5qaUptatHzVrliNYIsnKOMBXdkqT0bpfnnflARwJaYegMCAVPU3xVXM6sWdpwKZIX9B-tYa7ohFUXfig3hn7rDncfgxkGoWFYtzuL9gZEyc84w)

我们进入函数`sub_414C3C`。

![](https://eavihw.dm.files.1drv.com/y4m0d4O_d0KlvBv8rFQ7ePkOkWoqddcCiquo4Sekv7IR6eODwVw9SnStKsd9OJKcyUlJj9RRGQpkuejvBmoZ3IZKWdri9iDradjM_O2SMNuQZ9SsdkjfzG3qMO-LqKj1a2wt5LWoGAEsiAdT3TK7qZJzFwKkkpFlRYqwHDk5oru2Si8nF6kq62miSAOlW7ad0VdMbNPhKIc-ZZM3rcSgBa8qA)

由汇编代码可知`arg_0`,即`flag`前五位分别需要满足的条件,使用IDA自带的ASCII转字符功能可以得到对应的字符,结果为`flag{`。

![](https://dqvihw.dm.files.1drv.com/y4mjpEI5pRBo-TfD7-R8Kb_foWMCJZPm42XIXBkdzhb1I8t9EHl8cO7p_zileNerITbGPucSFmB4z3e1iltiE3Sk-iajalHNVt1APuB1CCqlKpLS4GHMDw34wjVlKNWSK3nwMJe7VJvF5aThleg4No67tMyQpod5n5FKlnIUEiOmgL5p_CC4mJ2DBh9_sxqleLTGvwP2VqldfBysuivbxEbaw)

剩余部分,即为对传入的第二个参数`unk_414BE0`的解密和重新调用。通过阅读解密的汇编代码,我们可以通过`SMC.idc`中的解密函数`xor_setp2()`进行解密,将`unk_414BE0`转化为函数`sub_414BE0`,由`sub_414C3C`的逻辑知,有两个参数传入了`sub_414BE0`,分别为`flag`和`unk_414A84`,其中`flag`已不包括前五个字符。

## 0x03

进入`sub_414BE0`进行分析,其中`arg_0`即为`flag`。

![](https://d6vihw.dm.files.1drv.com/y4mgOvVlEg7vHfRi34rzxFivr24n6z95HIMDCQK2V48IWH_esL_k2c2KPXzKoGS8f1jLD2ogv0sqDsbx1GPVi0rFCFXKgW3rd1aYXfBn3LmSHi2gw212dliuG6YXonLtCK9T4YhRnDBfosyTmmCg4f5zGc0fanVOoJ-ypiE959Wfdvrsm3nEq39wNWxQFrxDx8IEHAOpoJEHsgcW5Vk9AiXDg)
图中存在一个循环,内容为对`flag`的前四位的判断,由此我们可以再得到`flag`的四位字符。

判断情况为,将的flag的每8位与`0xCC`异或,得出的结果分别与`var_8`中的对应8位,即两个十六进制数相比,只有四次比较均通过,才能够正常执行函数的剩余部分。需要考虑数据的端存储方式,下面给出解密程序的C语言实现

```c
int decode0x03(){
    int ch = 0;
    int arr = {0x93,0xA9,0xA4,0x98};
    for (int i = 3; i >= 0; i--)
    {
      ch = (arr^0xCC);
      printf("%c",ch);
    }
    return 0;
}
```

输出结果为

```c
The_
```

下面分析函数`sub_414BE0`对`unk_414A84`的处理情况

![](https://dkvihw.dm.files.1drv.com/y4m5ydW8kDtl_D8vIazbc2xNE3mzpV08QSZyQI6yMvmu8fxv6MjdK5UWtZe6WDSFzYlhP4ad3VsgweGAHgKjO8mABxj-gAd7FexjtIOT1VouS-tIB8oNPF6qmqKPe0U9egDD-h48SGbQJ0nfXe1RH2VRbwGgsa6PzuvUOBedW0Ik-aSvMxZbjnZvBUIf_iWxS2CFW2W5D9Kij4RsOK4mDHyPQ)
和之前分析过得情况相似,均是先将其解密,形成一个可使用的函数,然后再进行调用。同样,我们根据解密的过程使用`SMC.idc`中的函数`xor_setp3()`进行解密,得到函数`sub_414A84`,传入参数为`flag`和`unk_414A30`,这里的`flag`已经不包括前九位了。

## 0x04

进入函数`sub_414A84`,由于数据有些复杂,这里使用IDA的伪代码查看功能进行分析。这里截取部分标志性的代码片段进行分析。

```c
//将字母、数字、部分字符的ASCII码存入数组v20中
v2 = 65;
v3 = 0;
do
*((_BYTE *)&v20 + v3++) = v2++;
while ( v2 <= 90 );
v4 = 97;
do
*((_BYTE *)&v20 + v3++) = v4++;
while ( v4 <= 122 );
v5 = 48;
do
*((_BYTE *)&v20 + v3++) = v5++;
while ( v5 <= 57 );
*(__int16 *)((char *)&v20 + v3) = 0x2F2B;
```

在此代码块中,我们可以看到,程序初始化了一个数组`v20`,并将所有的数字和字母以及一部分字符的ASCII码值存在了`v20`中。

```c
//rename "a1" as "flag"
//对每三个字符进行一组处理,共处理flag中的六个字符
do
{
v8 = *(_BYTE *)(flag + v7 + 1);
v9 = *(_BYTE *)(flag + v7 + 2);
v10 = *(_BYTE *)(flag + v7) & 3;
v11 = (unsigned int)*(_BYTE *)(flag + v7) >> 2;
v7 += 3;
*v6 = *((_BYTE *)&v20 + v11);
v6 = *((_BYTE *)&v20 + (((unsigned int)v8 >> 4) | 16 * v10));
v6 = *((_BYTE *)&v20 + (((unsigned int)v9 >> 6) | 4 * (v8 & 0xF)));
v6 = *((_BYTE *)&v20 + (v9 & 0x3F));
v6 += 4;
}
while ( v7 < 6 );
//对flag中的下两个字符进行处理
if ( v7 < 8 )
{
v12 = *(_BYTE *)(v7 + flag);
*v6 = *((_BYTE *)&v20 + ((unsigned int)*(_BYTE *)(v7 + flag) >> 2));
v13 = v12 & 3;
if ( v7 == 7 )
{
    v6 = *((_BYTE *)&v20 + 16 * v13);
    v6 = 61;
}
else
{
    v14 = *(_BYTE *)(v7 + flag + 1);
    v6 = *((_BYTE *)&v20 + (16 * v13 | ((unsigned int)*(_BYTE *)(v7 + flag + 1) >> 4)));
    v6 = *((_BYTE *)&v20 + 4 * (v14 & 0xF));
}
v15 = v6 + 3;
*v15 = 61;
v6 = v15 + 1;
}
```

此代码块为对`flag`的处理过程,对于前六个字符,程序将其均分为两份,即每三个字符为一个部分。首先,将第一个字符存入`v10`,第二个字符存入`v8`,第三个字符存入`v9`。

`v11`代表将`flag`的第一个字符的二进制表示右移两位,即取左侧六位存入`v11`,而后将其在`v20`数组中对应的字符存在了数组`v6`中;

同理分析得,`v6`代表`flag`第一个字符的后两位与第二个字符的前四位二进制数拼接后在`v20`中对应的字符;

`v6`代表`flag`第二个字符的后四位与第三个字符的前两位二进制数拼接后在`v20`中对应的字符;

`v6`代表`flag`第三个字符的后六位二进制数拼接后在`v20`中对应的字符。

第一轮的处理结束,第二轮将会处理接下来的三位字符,处理方式与上述过程类似,不再重复描述。

当完成前六位字符的处理过后,数组`v6`中已经根据数组`v20`的映射关系存入了八位字符。开始处理第七和第八位字符。

`if`中,同样根据`v20`的映射关系,将第七位字符的前六位二进制表示所代表的字符存入`v6`中;将第七位字符的后两位与第八位字符的前四位拼接而成的六位二进制数根据数组`v20`所对应的字符存入`v6`中;最后剩余第八个字符的后四位经过乘4运算在后面补两个零,同样构成一个六位二进制数,再根据数组`v20`的对应方式,将对应字符存入`v6`;最后再将`=`存入`v6`

通过上面的分析,此函数的加密过程即为Base64的加密过程,通过加密`flag`后生成的字符串与所给字符串相比较,只有符合时才能够正常运行后面的程序。

开头有:

```assembly
mov   , 68566D63h
mov   , 304E4562h
mov   , 3D386C52h
```

在对`flag`的判断部分

![](https://davihw.dm.files.1drv.com/y4mOPbXAiJ-DFAA5StkFtSsxRKGJDdRsEsLr1P1pBERZ2hVBmF96YQj2sD72vHZWpSb2VwESRpHnaWoZX5RpwVHOrLY-j5PL_5DXdUkBPsao9SfSjD0BH4lqm0BbYR12HMdwiefxxJZ-tgQT78Fk01qGOs2gap01t_sBoKOxgqQMHJiA8CS-A8QT3smUImOq0l4f21cBjnZws0JCaVOpajbaA)

可以看到,由于地址的连续性,开头的三个变量`var_10` `var_C` `var_8`与经Base64编码过的`flag`进行对比,当所有的对比均通过后才可以正常执行接下来的操作。通过IDA的ASCII转字符功能,我们可以得到

```assembly
mov   , 'hVmc'
mov   , '0NEb'
mov   , '=8lR'
```

考虑数据的端存储方式,正确的Base64编码后的字符串应为`cmVhbEN0Rl8=`,通过解码,即可得到`flag`的一部分,下面给出Python3的解密实现

```python
import base64
undecoded_base64 = b'cmVhbEN0Rl8='
des2 = base64.b64decode(undecoded_base64)
print(str(des2).strip('b').strip('\''))
```

输出为

```python
realCtF_
```

![](https://cqvihw.dm.files.1drv.com/y4mDiotWKuj7_2HHPlujCraUOzb7iU8kWC5cVxZ3wTlOYtalbMJjY3_YfRivgGxFM38lg0Bi2A1Rz3S7wzD3OrriH3FwSyTAe0zhrW7cYE4sB_CgvTh73MRsnk7Sro2PBzUfmau0IVh5Z7dIe4m4FtNrg2PkGXFJt4m0syWzcSuHDJZIgq6pGlnrhnh1sfehxiLH4KAAoGpNqFATXKFqSU9pw)
同样,在最后也存在对传入参数`unk_414A30`的处理,同样,我们根据解密的过程使用`SMC.idc`中的函数`xor_setp4()`进行解密,得到函数`sub_414A30`,传入参数只剩下`flag`,这里的`flag`已经不包括前十七位了。

## 0x05

进入函数`sub_414A30`进行分析,下为开头的数据定义。

```assembly
var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= word ptr -4
var_2= byte ptr -2
arg_0= dword ptr8
------------部分省略------------
mov   , 7574766Bh
push    esi
push    edi
mov   , 68344360h
or      edx, 0FFFFFFFFh
mov   , 6F22h
```

可知`var_C` `var_8` `var_C`地址是连续的。

![](https://c6vihw.dm.files.1drv.com/y4mizlTzxvpqBRXGVvyTs5uWPCUO5cGbkDacf7uQ2UGFvenMnca7CxD1awjj6DhM7-3QFz-1cnVkTwFnHsm8wGUycpovsJltjhhyQvd6MGdw4iNGlYKHxJ4bdHRvvwOMRdTe57hMBUoSgYCLFx0HWrIx6OltfgZA4wdE750X7W_C-rQzj1wEz2VXaKYiwN-X4I696SP3gbfxIMCEsHHXe_a5A)

`loc_414A68`部分即为`flag`与`var_C`的对比过程,可知`flag`的每一个字符需要与`var_C`存在一个关系

```c
flag = var_C - 1;
```

根据比较过程和已知数据并考虑过数据的端处理情况后,这里给出解密函数的C语言实现

```c
int decode0x05(){
    int str = {0x6B,0x76,0x74,0x75,
                  0x60,0x43,0x34,0x68,
                  0x22,0x6F};
    for (int i = 0; i < 10; i++)
    {
      str1 = str1-1;
      printf("%c",str1);
    }
    return 0;
}
```

输出为

```c
just_B3g!n
```

## 测试

综合以上的分析,我们可以给出程序的flag

```
flag{The_realCtF_just_B3g!n}
```


## 总结

在逆向的过程中,要注意数据的端存储方式,以保证正确识别`flag`内容,避免由字符顺序带来的不必要的干扰。

其次,不能过分依赖伪代码,有些情况下伪代码可能会因为表示方式而对解决问题产生一定的误导作用。譬如说,我在解决0x03部分的问题时就是被伪代码迷惑,由于表达与理解的差异而无法正确解决,最后还是通过查看汇编代码正确厘清了逻辑关系,并能够完成解密;0x05部分同样也是如此,相对于伪代码,汇编代码的表述更加准确,而不容易出现理解的错误。同时,在分析汇编代码时,能够更好地理解数据在内存中的存储方式,帮助我们理解伪代码中的变量使用,其作用也是很大的,例如在分析0x04时,就是通过伪代码得出`Base64`这一重要突破点的。因此要灵活地使用。

sandoth 发表于 2020-5-22 15:44

idc文件可以在FIle->Script File加载,使用此文件解密时注意修改文件内调用的函数,以保证解密的是当前需要解密的内容

真诚真诚 发表于 2020-5-22 15:14

学习到了

pizazzboy 发表于 2020-5-22 15:16

厉害了。

fightingman 发表于 2020-5-26 13:11

楼主在哪读书,还教逆向。佩服佩服{:1_893:}

sandoth 发表于 2020-5-26 17:02

fightingman 发表于 2020-5-26 13:11
楼主在哪读书,还教逆向。佩服佩服

我们老师也说很少有学校开逆向工程这门课,说实话我也挺吃惊的。

pantherTL 发表于 2020-6-5 08:39

感谢分享,每次都有新的收获。
页: [1]
查看完整版本: 记一次逆向工程作业——SMC代码自修改程序的逆向分析