在re逆向做题的时候我们会发现部分经过反编译的代码在经过一定的编译之后也是不能直接拿到经过反编译之后的伪C代码,就跟你要拿出手机扫把还要把手机壳换一个一样多此一举的操作,这一类的部分的指令被称为花指令(也就是属于无效指令的内容),为了方便识别,我们要进行一定的操作。
本次我会引入一个示例来进行一次例子的分析。
例题链接:[MoeCTF 2022]chicken_soup
[MoeCTF 2022]chicken_soup | NSSCTFhttps://www.nssctf.cn/problem/3323题目图片:
先将其进行查壳,发现无壳:
同时是32位的,我们将其拖拽到ida32位上进行反编译分析
得到以下的界面:
先按F5,得到以下的界面:
我们发现,在第十行和十一行是两个加密算法,
我们点进去看看
花指令的相关操作
发现有两个类似跳转的语句,我们发现这个和后续的没有多少关联,这个就是我在之前提到的花指令,我们要想使得它在某些时候有用,就要对其进行一定的操作,也就是对其进行nop操作。
nop的操作,cttl+n
但是在之前要将其打开,因为我们只对这一行进行nop操作
错误示范
先对其进行打开,按u
再将其nop掉,最后将其重新变为一个新的,按C,最后按P
我们会发现现在的前面的部分的地址还是红的,如果现在就对其进行识别操作按F5
我们会得到如下的界面
这样的界面我们会发现逻辑会很难懂,这就是我们会了解到的栈的不平衡
正确示范
我们在上方的部分按P,再按F5
会得到如下的界面:
现在就正常多了,也更容易理解
加密算法分析及其解密算法代码逻辑解释
代码如下:
unsigned int __cdecl sub_401000(const char *a1)
{
unsigned int result; // eax
unsigned int i; // [esp+18h] [ebp-8h]
for ( i = 0; ; ++i )
{
result = strlen(a1) - 1;
if ( i >= result )
break;
a1[i] += a1[i + 1];
}
return result;
}
下一个代码如下:
unsigned int __cdecl sub_401080(const char *a1)
{
unsigned int result; // eax
unsigned int i; // [esp+18h] [ebp-8h]
for ( i = 0; ; ++i )
{
result = i;
if ( i >= strlen(a1) )
break;
a1[i] = (16 * a1[i]) | ((int)(unsigned __int8)a1[i] >> 4);
}
return result;
}
我们对第一个进行分析,主要部分是在for循环的部分
首先它在for循环的判断条件下进行判断,没有定义循环停止的判断条件,但是在循环内我们看到但凡i大于了导入的字符串的个数-1就会停止循环(和数组一样 ,字符穿数组的下标也是从0开始的,同时for循环的跳出语句也是break语句)
我们看到这里的加密过程为:
a1[i] += a1[i + 1];
但是我们连字符串都不清楚,因此第一个的循环分析我们对其逻辑只能将其逻辑倒推进行
a[i] = a[i] + a[i+1]
因为在这里的加密是从左到右依次进行加密,因此我们可以知道最后一个加密是没有进行的(也就是说是到倒数第二个就结束了)
我们就得到了如下的解密代码
for(i = strlen(a1)-2;i>=0;i--)
{
a[i]= a[i] - a[i+1]
}
我们来分析以下第二个加密算法
核心的部分在这里:
a1[i] = (16 * a1[i]) | ((int)(unsigned __int8)a1[i] >> 4);
现在我们的问题就是分析以下这段语句的含义是什么
这行代码的目的是对字符串中的每个字符进行一种位操作。具体地说,它将每个字符的低 4 位(即0~15)移到了高 4 位的位置,然后将原始字符的高 4 位和新的低 4 位进行逻辑或运算。这个操作实际上将每个字符拆分成两个 4 位的半字节,并交换它们的位置。
我们进行逆向分析来看看:
我们经过发现能发现以下几点:
- 在编码过程中,我们将高 4 位移到了低 4 位,然后左移了 4 位,再与原始字符的高 4 位进行了逻辑或运算。
- 在解码过程中,我们首先恢复了原始字符的高 4 位,然后将移动后的低 4 位移回到了高 4 位的位置,最终得到了原始字符。
- 这两个过程中的操作是互补的,彼此抵消,因此该算法是对称的。
也就是说这个算法时对称的。
因此我们得到了它的解密代码
但是得到了解密代码依旧还不完全成功
还差字符串没拿到手
回到main函数的部分我们看下面这一段代码
if ( sub_401110(v4, &unk_403000) )
puts("\nTTTTTTTTTTQQQQQQQQQQQQQLLLLLLLLL!!!!");
else
puts("\nQwQ, please try again.");
return 0;
我们可以看到这一段时对&unk_403000这一个地址内的内容进行分析
我们点开可以看到这一块的部分:
由于篇幅有限,这里展开的不完整
我们要将其的十六进制的内容提取出来,也就是标蓝的部分进行解析
选中,进行提取
shift + e
再选择hex
得到如下内容
unsigned char ida_chars[] =
{
0xCD, 0x4D, 0x8C, 0x7D, 0xAD, 0x1E, 0xBE, 0x4A, 0x8A, 0x7D,
0xBC, 0x7C, 0xFC, 0x2E, 0x2A, 0x79, 0x9D, 0x6A, 0x1A, 0xCC,
0x3D, 0x4A, 0xF8, 0x3C, 0x79, 0x69, 0x39, 0xD9, 0xDD, 0x9D,
0xA9, 0x69, 0x4C, 0x8C, 0xDD, 0x59, 0xE9, 0xD7
};
我们将代码整合一下,得到如下内容
#include <stdio.h>
#include <string.h>
int main() {
unsigned char a1[] = {
0xCD, 0x4D, 0x8C, 0x7D, 0xAD, 0x1E, 0xBE, 0x4A, 0x8A, 0x7D,
0xBC, 0x7C, 0xFC, 0x2E, 0x2A, 0x79, 0x9D, 0x6A, 0x1A, 0xCC,
0x3D, 0x4A, 0xF8, 0x3C, 0x79, 0x69, 0x39, 0xD9, 0xDD, 0x9D,
0xA9, 0x69, 0x4C, 0x8C, 0xDD, 0x59, 0xE9, 0xD7
};
int i;
// 第二个逆向操作
for (i = 0; ; ++i) {
if (i >= strlen(a1)) // 遍历数组直到结束
break;
a1[i] = (16 * a1[i]) | ((int)(unsigned char)a1[i] >> 4); // 恢复高4位到低4位的操作
}
// 第一个逆向操作
for (i = strlen(a1) - 2; i >= 0; i--) {
a1[i] = a1[i] - a1[i + 1]; // 恢复数组的每个元素值
}
printf("%s", a1); // 打印逆向操作后的结果
return 0;
}
得到的结果如下:
moectf{p4tch_pr0gr4m_t0_d3c0mpi1e_it!}
ida的python插件的应用
我在看了很多大佬的文献之后得到了以下的方法
得到如下的窗口
你就可以先写一个较为简单的来试试看
结果在下面的窗口进行输出