ShellCode之寻找Debug下真实函数地址
一丶简介与原理
在Debug模式下,函数地址与真实函数地址不一致. 这导致我们在写类似于ShellCode的代码的时候会产生问题.比如远程线程代码注入.
产生这一原因是因为在Debug模式下,我们的函数地址是一层跳转表.是编译器维护的.名字叫做ILT,所以对函数名的直接访问都被映射了.映射为了修饰后的函数名.而真实函数地址在其跳转表之后.
如: JMP 偏移 JMP之后才是真正的函数地址.
在ILT表中的一般都是我们自定义的函数. 我们自定义的函数才会在其表中.
下面看一下代码与调试.
int fun1()
{
int a = 10 * 10;
scanf("%d", &a);
return a;
}
int fun2()
{
int b = 10 * 10;
int c = b * 2;
scanf("%d", &b);
return c;
}
int main()
{
fun1();
fun2();
printf("fun1 = %x
",(DWORD)fun1);
printf("fun2 = %x
",(DWORD)fun2);
/*printf("va fun1 %x
", (DWORD)get_DebugVaAddress(fun1));
printf("va fun2 %x
", (DWORD)get_DebugVaAddress(fun2));*/
system("pause");
return 0;
}
可以看到定义了两个函数,分别是 fun1 以及 fun2 并且分别进行了调用. 下面我们就其调试看一下ILT表.
真实的函数地址
在调用fun1 函数之前我们进入了反汇编进行查看. 可以看到 现在的fun1地址其实是 0x00A3109B 而真实的fun1函数地址就是 0XA31750
这就会导致我们一个问题.当我们写ShellCode的时候.想要将fun1写入到内存中 一般都会进行如下几个步骤:
- 1.定义fun1 并且在fun1中写好地址无关代码
- 2.在fun1下面定义一个fun2函数.fun2可以什么都不做.或者加点标记
- 3.利用 fun2 - fun1 得出 fun1函数的字节大小(ShellCode)大小
- 4.利用写内存函数将fun1 写入到内存.
伪代码如下:
void fun1()
{
//你的ShellCode代码
}
void fun2()
{
//做结束指令的fun2
__asm{
nop
nop
}
}
DWORD dwShellCodeSize = (DWORD)fun2 - (DWORD)fun1;
WriteProcessMemory(Process,BaseAdddr,fun1,dwShellCodeSize...);
如果程序运行在Release下是没有问题的.如果是Debug就会有问题了. 在Debug下函数都是ILT表. 两者相减就会出错.
而真实函数我们也知道是 ILT表中之后记录的函数地址. 也就是跳转之后的地址.
二丶原理与代码解决方式
在ILT表中记录的是 JMP + 偏移的方式 来进行跳转的. 而我们学过HOOK的伙伴们应该知道偏移是怎么的出来的.
偏移 = 目的地址 - 源地址 - 指令长度 而反过来说 目的地址 = 源地址 + 偏移 + 指令长度
比如上面的 伪函数fun1地址(0x00A3109B 记录的偏移为: 0x6B0) 真实fun1 0XA31750
代入指令:
-
偏移 = 目的地址 - 源地址 - 指令长度
0XA31750 - 0x00A3109B - 5(jmp占5个字节指令长度) = 0x6B0
-
目的地址 = 源地址 + 偏移 + 指令长度
0x00A3109B + 0x6B0 + 5 = 0XA31750
代码实现方式就很简单了.首先取出 伪函数地址. 对其按照 unsigned char * 类型解析. 判断首字节是否是E9(JMP)
如果是则取出后面的偏移,让当前地址 + 偏移 + 指令长度来得出真实地址.
注意因为你要判断16进制的0xE9 那么一定要按照UCHAR类型解析 否则高位会认为是符号位.
代码如下:
void* get_DebugVaAddress(void* procFunction)
{
unsigned int VaAddr = 0;
unsigned int VaOffset = 0;
unsigned char* DebugCalcProc = NULL;
if (procFunction == NULL)
{
return procFunction;
}
/*
1.得出当前带有jmp跳转表的函数
E9 XX XX XX XX
2.判断是否是E9 如果是继续进行计算,不是返回自己函数本身
3.是的情况下 从当前函数地址位置取出其偏移
偏移在HOOK中是 目的地址 - 源地址 - 指令长度填写上的
反过来讲 当前函数地址 + 指令长度 + 偏移则得到真实的目的地址
计算真实地址即可。
*/
unsigned int DebugVaAddr = (unsigned int)procFunction;
DebugCalcProc = (unsigned char*)procFunction; //一定要uchar类型 要判断数值的
if (DebugCalcProc[0] == 0xE9) //jmp
{
VaOffset = *(unsigned int*)(DebugCalcProc + 1);
//当前地址 + 指令长度 + 偏移 = 目的地址
VaAddr = DebugVaAddr + 5 + VaOffset;
return (void*)VaAddr;
}
else
{
return (void*)procFunction;
}
return (void*)procFunction;
}
下面看下实战:
可以看出在Debug下我们自己进行转化可以得到真实函数地址. 这样在调试ShellCode的时候也更加方便.
当然如果你也可以加条件宏进行编译.这样Release下就不试用GetDebugVaAddress