ShellCode之寻找Debug下真实函数地址

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 \r\n",(DWORD)fun1);
	printf("fun2 = %x \r\n",(DWORD)fun2);
	/*printf("va fun1 %x \r\n", (DWORD)get_DebugVaAddress(fun1));
	printf("va fun2 %x \r\n", (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)
{
    LPVOID VaAddr = 0;
    unsigned int VaOffset = 0;
    LPVOID DebugCalcProc = NULL;
    if (procFunction == NULL)
    {
        return procFunction;
    }
    /*
    1.得出当前带有jmp跳转表的函数
        E9 XX XX XX XX
    2.判断是否是E9 如果是继续进行计算,不是返回自己函数本身
    3.是的情况下 从当前函数地址位置取出其偏移
      偏移在HOOK中是 目的地址 - 源地址 - 指令长度填写上的
      反过来讲 当前函数地址 + 指令长度 + 偏移则得到真实的目的地址
      计算真实地址即可。
    */
    LPVOID DebugVaAddr = procFunction;
    DebugCalcProc = procFunction; //一定要uchar类型 要判断数值的
    if (((UCHAR*)DebugCalcProc)[0] == 0xE9) //jmp
    {
        VaOffset = (*(unsigned int*)((CHAR*)DebugCalcProc + 1));
        //当前地址 + 指令长度 + 偏移  = 目的地址
        VaAddr = (char*)DebugVaAddr + 5 + VaOffset;
        return (void*)VaAddr;
    }
    else
    {
        return (void*)procFunction;
    }
    return (void*)procFunction;
}

下面看下实战:

可以看出在Debug下我们自己进行转化可以得到真实函数地址. 这样在调试ShellCode的时候也更加方便.

当然如果你也可以加条件宏进行编译.这样Release下就不试用GetDebugVaAddress

posted @ 2020-12-17 10:18  iBinary  阅读(536)  评论(1编辑  收藏  举报