有点啰嗦,先说声抱歉。问题的更新在最后。
我正在尝试在程序中使用挂钩函数并根据 IDA 的反编译伪代码修改它们。为此,我编写了一个简单的、无目的的 32 位程序(这并不重要,无需尝试分析):
// main.cpp
#include <iostream>
class C {
public:
int a = 0;
int b = 0;
int c[255];
int *d = nullptr;
int stuff = 0;
C(){
std::cout << d << std::endl;
d = new int[300];
std::cout << d << std::endl;
++stuff;
for(int i = 1; i < 255; ++i){
c[i] = i;
if( i == 254){
c[i] = 0;
}
}
for(int i = 1; i < 300; ++i){
d[i] = i;
if( i == 299){
d[i] = 0;
}
}
}
~C(){
std::cout << d << std::endl;
delete[] d;
std::cout << d << std::endl;
++stuff;
}
void play(){
bool c_stop, d_stop;
c_stop = false;
d_stop = false;
for(int i = 0;;++i){
if(c[i] == 0 && !c_stop){
std::cout << "C had " << i + 1<< std::endl;
c_stop = true;
}
if(d[i] == 0 && !d_stop){
std::cout << "D had " << i + 1 << std::endl;
d_stop = true;
}
if(d_stop && c_stop){
break;
}
}
}
};
void func(){
C clae;
std::cout << "One missisipi " << clae.stuff << std::endl;
}
int main(){
func();
C clae;
std::cout << clae.stuff << std::endl;
clae.play();
}
虽然无需尝试了解它的作用,但它通常会产生如下输出:
00000000
0007D958
One missisipi 1
0007D958
0007D958
00000000
0007D958
1
C had 255
D had 300
0007D958
0007D958
我说通常是因为地址会随着执行而改变。
现在,使用 IDA,我找到了对应的子程序void func():
void *__thiscall sub_1161000(void *this)
{
int v1; // eax@1
int v2; // edx@1
int v4; // [sp+0h] [bp-43Ch]@1
int v5; // [sp+8h] [bp-434h]@1
int v6; // [sp+Ch] [bp-430h]@1
int v7; // [sp+10h] [bp-42Ch]@1
char v8; // [sp+14h] [bp-428h]@1
int v9; // [sp+41Ch] [bp-20h]@1
int *v10; // [sp+420h] [bp-1Ch]@1
void *v11; // [sp+424h] [bp-18h]@1
int (__cdecl *v12)(int, int, int, int); // [sp+428h] [bp-14h]@1
int v13; // [sp+42Ch] [bp-10h]@1
v10 = &v4;
v13 = -1;
v12 = sub_1161200;
v11 = this;
v1 = sub_1161280(&v8);
v13 = 0;
v7 = v1;
v6 = sub_11613F0(&unk_119B300, "One missisipi ");
v5 = sub_1161810(v9);
sub_1161AB0(sub_1161AD0);
sub_1161B30((int)&v8, v2);
return v11;
}
我确定这是手头的功能,因为跳过对该地址的调用会打印:
00000000
0007D958
One missisipi 1
0007D958
0007D958
这是预期的func()输出。此外,进入该子例程将打印上述内容,但分阶段对应于构造函数、打印函数和析构函数。
所以,现在我已经将伪代码修补到有效的 C 中,如下所示:
void *__thiscall func(void *_this) {
int v1; // eax@1
int v2; // edx@1
int v4; // [sp+0h] [bp-43Ch]@1
int v5; // [sp+8h] [bp-434h]@1
int v6; // [sp+Ch] [bp-430h]@1
int v7; // [sp+10h] [bp-42Ch]@1
char v8; // [sp+14h] [bp-428h]@1
int v9; // [sp+41Ch] [bp-20h]@1
int *v10; // [sp+420h] [bp-1Ch]@1
void *v11; // [sp+424h] [bp-18h]@1
int(__cdecl * v12)(int, int, int, int); // [sp+428h] [bp-14h]@1
int v13; // [sp+42Ch] [bp-10h]@1
int *base_addr = (int *)GetModuleHandle(NULL); // Windows function to get the process base address.
v10 = &v4;
v13 = -1;
v12 = INT_FUNC(0x1161200);
v11 = _this;
v1 = INT_FUNC(0x1161280)(&v8);
v13 = 0;
v7 = v1;
v6 = INT_FUNC(0x11613F0)(0x119B300, "One missisipi ");
v5 = INT_FUNC(0x1161810)(v9);
PTR_FUNC(0x1161AB0)(PTR_FUNC(0x1161AD0));
PTR_FUNC(0x1161B30)((int)&v8, v2);
return v11;
}
其中INT_FUNC定义为:
#define PE_BASE 0x1160000
#define OFFSET(x) (int*)(base_addr + ((int*)x - (int*)PE_BASE))
#define PTR_FUNC(x) (((void *(*)())OFFSET(x)))
#define INT_FUNC(x) (((int (*)())OFFSET(x)))
我所做的是一些指针运算,将 IDA 中的地址转换为运行时对应正确地址的地址,并将它们转换为函数。PE_BASE是 IDA 中进程的基地址(我知道您可以将它们重新设置为零)。请注意,在此阶段,我根本没有修改该函数,而是仅从伪代码中重新创建了它。
最后,为了挂钩我的函数,我使用调试器启动程序,加载包含我的自定义 DLL 的 DLL func,然后在目标子例程的开头,我将那里的指令替换为:
push dword my_dll.func
ret
每当执行目标子例程时,它就会愉快地跳转到我的函数。问题是,它只是不起作用。如果我现在运行我的程序,它只会打印:
00000000
01393FE8
我在函数被hook的时候调试了程序,罪魁祸首是这行,导致段错误:
这是以这种方式调用的第一个函数地址(未调用之前的函数,但仅将其地址传递给v12)。我的第一个想法是我的指针算法中的一个错误导致它跳转到一个错误的地址,但检查反汇编确认情况并非如此:
哪个是正确的函数(基于上图中的值edx):
似乎一切都在它应该在的地方。
所以,我的问题是:发生了什么?它为什么这样做?我尝试使用 IDA 的伪代码是失败的,因为它不可靠吗?或者,我有什么错误吗?
更新:
感谢bart1e的回答,我现在修复了代码以反映 IDA 评论的变量的实际大小(代码比原始版本更简化,但问题仍然相同):
int func() {
char v1[0x408]; // [sp+18h] [bp-414h]@1
int v2; // [sp+420h] [bp-Ch]@1
int* base_addr = (int*)GetModuleHandle(NULL);
INT_FUNC(0x331F04)(v1);
INT_FUNC(0x3B7E90)(OFFSET(0x3C57C0), "One missisipi ");
INT_FUNC(0x37E7C0)(v2); // <<<<<<<< SEGMENTATION FAULT
INT_FUNC(0x37E5C0)(INT_FUNC(0x3B5FD0));
return INT_FUNC(0x332044)(v1);
}
/* int func()
{
char v1; // [sp+18h] [bp-414h]@1
int v2; // [sp+420h] [bp-Ch]@1
sub_331F04((int)&v1);
sub_3B7E90((int)&dword_3C57C0, "One missisipi ");
sub_37E7C0(v2);
sub_37E5C0(sub_3B5FD0);
return sub_332044(&v1);
} */
但是现在,当我尝试调试 DLL 时,它仍然会导致分段错误,并打印:
00000000
015CFFE8
One missisipi



