linux程序逆向ida,IDAPro-使⽤IDAPro逆向C++程序
简介
在假期期间,我花了很多时间学习和逆向⽤C++写的程序。这是我第⼀次学习C++逆向,并且只使⽤IDA进⾏分析,感觉难度还是⽐较⼤的。
这是你⽤Hex-ways分析⼀个有意思的函数时看到的东西
v81 = 9;
v63 = *(_DWORD *)(v62 + 88);
if ( v63 )
{
v64 = *(int (__cdecl **)(_DWORD, _DWORD, _DWORD,
_DWORD, _DWORD))(v63 + 24);
if ( v64 )
v62 = v64(v62, v1, *(_DWORD *)(v3 + 16), *(_DWORD
*)(v3 + 40), bstrString);
}
我们的任务是添加⼀些符号名称、分辨出类等,让hex-rays能够有⾜够的信息给出我们⼀个可靠、易于理解的输出
padding = *Dst;
if ( padding < 4 )
return -1;
buffer_skip_bytes(this2->decrypted_input_buffer, 5u);
buffer_skip_end(this2->decrypted_input_buffer, padding);
if ( this2->encrypt_in != null )
{
if ( this2->compression_in != null )
{
buffer_reinit(this2->compression_buffer_in);
packet_decompress(this2,
this2->decrypted_input_buffer,
this2->compression_buffer_in);
buffer_reinit(this2->decrypted_input_buffer);
avail_len = buffer_avail_bytes(this2->compression_buffer_in);
ptr = buffer_get_data_ptr(this2->compression_buffer_in);
buffer_add_data_and_alloc(this2->decrypted_input_buffer, ptr, avail_len);
}
}
packet_type = buffer_get_u8(this2->decrypted_input_buffer);
*len = buffer_avail_bytes(this2->decrypted_input_buffer);
this2->packet_len = 0;
return packet_type;
当然hex-rays不会⾃⼰命名这些变量名,你需要理解这些代码,⾄少给这些类⼀个合适的名字能帮你分析代码。
这⾥我的所有例⼦都是⽤visual studio或者Gnu C++编译的,这两个编译器的结果是相似,即使他们在某些语法上并不兼容。如果⾃⼰的编译器遇到问题,⾃⼰改下代码吧。
C++程序的结构
这⾥我就不介绍OOP编程的知识了,你也应该已经知道了。我们只从整体看下OOP是如何⼯作的和实现的。
Class = data structure + code (methods).
类的数据结构只能在源码⾥看到,函数则会显⽰在你的反汇编器⾥。
Object = memory allocation + data + virtual functions.
对象是⼀个类的⼀个实例,你可以在IDA⾥看到它。⼀个对象需要内存,所以你会看到调⽤new()或者栈分配内存,调⽤构造函数或者析构函数。你也会看到访问成员变量(成员对象),调⽤虚函数。
虚函数很蠢,如果不下断点运⾏程序,你很难知道哪些代码会被执⾏。
成员函数简单点,他们就像C语⾔⾥的结构。并且IDA有⾮常顺⼿的⼯具声明结构,hex-rays能在反汇编过程中很好的⽤到这些结构信息。
接下来我们将回到具体的问题上来。
对象的创建
int __cdecl sub_80486E4()
{
void *v0; // ebx@1
v0 = (void *)operator new(8);
sub_8048846(v0);
(**(void (__cdecl ***)(void *))v0)(v0);
if ( v0 )
(*(void (__cdecl **)(void *))(*(_DWORD *)v0 + 8))(v0);
return 0;
}
这是⼀个我⽤G++编译的⼩程序的反汇编结果,我们能看到new(8),意思是这个对象⼤⼩为8bytes,⽽不是我们有⼀个8bytes⼤⼩的变量。
函数sub_8048846在调⽤new()之后⽴刻被调⽤,并把new()产⽣的指针作为参数,这肯定就是构造函数了。
下⼀个函数就有点让⼈头⼤了,它在调⽤v0之前对v0做了两次解引⽤。这是⼀个虚函数调⽤。
所有的多态对象在他们变量中都有⼀个特殊的指针,被称作vtable。这个表包含了所有虚函数的地址,所以C++程序在需要的时候能够调⽤他们。在多种编译器中,我测试出vtable总是⼀个对象的第⼀个元素,总是待在相同的位置,即使是在⼦类中。(这也许对多继承不合适,我没有测试过)。
让我们开始⽤IDA进⾏分析:
重命名符号名称
点击⼀个名字,然后按n,就会弹出修改名字的窗⼝,你可以把它改成⼀个有意义的名字。⽬前我们还不知道这个类在做什么,所以我建议把这个类命名成“class1”,直到我们理解了这个类在做些什么。在我们完成分析class1之前我们很可能会遇到其他类,所以我建议遇到他们的时候只改下这些类的名字。
int __cdecl main()
{
void *v0; // ebx@1
v0 = (void *)operator new(8);
class1::ctor(v0);
(**(void (__cdecl ***)(void *))v0)(v0);
if ( v0 )
(*(void (__cdecl **)(void *))(*(_DWORD *)v0 + 8))(v0);
return 0;
}
创建结构
IDA的结构(structures)窗⼝⾮常有⽤。按shitf + f9能够调出来。我建议你把它拖出来放到IDA窗⼝的右边(IDA的QT版能这么做),然后你就能同时看到反汇编窗⼝和结构窗⼝。
按Inrt键并创建⼀个新的结构“class1”。我们已经知道这个结构是8bytes长,按d键增加变量,直到我们有两个dd变量。重命名第⼀个变量为“vtable”,然后就变成下⾯的样⼦了。
接下⾥我们添加函数的类型信息,右键v0,选择Convert to struct * ,选择class1。此外,按y,然后输⼊“ class1 * ”也能得到⼀样的结果。
创建⼀个新的长度为12bytes的结构并把它命名成“class1_vtable”。现在我们并不知道vtable有多⼤,
但改结构的⼤⼩很容易。点击class1结构⾥的vtable,按y,把它的类型改成“class1_vtable *”。按F5刷新下伪代码的窗⼝,结果如下:
我们可以把⽅法命名成"method1"到“method3”。method3当然就是析构函数。根据编程约定和所使⽤的编译器,第⼀个函数经常是析构函数,但这⾥有⼀个反例。现在我们分析下构造函数。
分析构造函数
int __cdecl class1::ctor(void *a1)
{
sub_80487B8(a1);
*(_DWORD *)a1 = &off_8048A38;
return puts("B::B()");
}
你可以先把a1的类型改⼀下。puts()调⽤证实了这个是构造函数,我们甚⾄能了解到这个类叫“B”。
sub_80487B8() 在构造函数⾥被直接调⽤,这个函数也许是class1的经函数,但也可能是⽗类的构造函数。
off_8048A38是class1的vtable,到这⾥你已经能知道vtable的⼤⼩了(只需要看vtable附近有Xref的数据的数量)和⼀个class1虚函数的列表。你可以把他们命名成“ class1_mXX”,但需要注意的是其中的⼀些函数可能与其他类共享。
更改这个vtable的类型信息也是没有问题的。但我不推荐这么做,因为你会丢掉IDA的经典窗⼝,并且这样做也提供不了任何你在经典窗⼝⾥看不到的东西。
构造函数⾥的奇怪调⽤:
int __cdecl sub_80487B8(int a1)
{
int result; // eax@1
*(_DWORD *)a1 = &off_8048A50;
puts("A::A()");
result = a1;
*(_DWORD *)(a1 + 4) = 42;
return result;
}
构造函数⾥的sub_80487b8() 函数是同样类型的函数:⼀个虚函数表 指针放到了vtable成员⾥,puts()调⽤告诉我们我们在另外⼀个构造函数⾥。
不要把参数a1的类型改成class1,因为我我们已经不在class1⾥了。我们找到了⼀个新的类,把它命名成class2。这个类class1的⽗类。我们做下和class1⼀样的⼯作。他们之间的区别仅仅是我们不知道class2成员的具体⼤⼩。这⾥有两种⽅法找到它:
看对class2 ::ctor的xref,如果我们能找到⼀个对它的直接调⽤,例如⼀个对class2的实例化,我们就能知道class2成员函数的⼤⼩。
看vtable⾥的函数,尝试找出被访问过的最⾼的成员。
在我们这种情况下,class2 ::ctor访问了最开始的4个字节之后的4个字节。因为class2的⼦类class1是8个字节长,所以class2的⼤⼩也是8个字节。