windows sdk编程系列文章 ---- 谈病毒技术利用之代码可移植性

本文深入探讨了可移植性问题,通过去除输入表实现代码移植性提升,并详细解析了指针操作、PE文件结构、内存寻址及程序加载过程。同时,文章介绍了解决函数重定位的方法,提供了基本的字符串比较函数实现。最终,通过预编译指令调整程序入口点,实现了代码的可移植性和重定位问题的解决。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://hi.baidu.com/combojiang/item/d2ef960fb5ad878e02ce1b36

理论:

为了写一段可以移植的程序,需要解决两个问题,一个是重定位,一个是去掉输入表。这两个问题解决了,你的代码就具有了可移植性。谈到可移植性,最典型的就是病毒程序,它们的可移植性做得很好。他们可以注入到其他进程的空间,正常地运行。

说到这里,我们先看看我们一个普通的程序,如果我们在一个程序中调用了sdk函数或者调用了其他dll中的函数,那么程序的输入表中就会包含了你调用的函数所在的dll,如下图:我们用view dependency看看。
我们看到Game.exe这个可执行程序,关联了这么多的dll. 如果缺少一个dll,Game.exe都不能正确地运行。
也就是说在Game.exe的输入表中包含这些dll. 如果要移植Game中的代码到其他的进程,还必要让目标进程
加载所有Game.exe需要的dll,移植才能成功。由此可见,这样的代码,可移植性太差了。

本篇我们要创建一个程序,没有关联任何的dll,这样它的可移植性就非常好了。如下图所示。
那么怎样才能做到这么好的可移植性能,两种办法:一种是不调用任何的dll中的函数。另一种办法是调用dll中的函数,但是需要作一些处理。

代码:见光盘NoImport_winmain,编译环境vc6, release版本。

typedef void *HMODULE;
typedef const char* LPCTSTR;
typedef long BOOL;
typedef unsigned int UINT;
typedef void * HWND;
typedef unsigned char BYTE;
typedef void * HINSTANCE;
typedef char * LPSTR;
typedef unsigned short WORD;
typedef unsigned long DWORD;

typedef HMODULE (__stdcall *PLoadLibrary)(
LPCTSTR lpFileName
);

typedef BOOL (__stdcall *PMessageBeep)(
UINT uType
);

typedef int (__stdcall *PMessageBox)(          HWND hWnd,
    LPCTSTR lpText,
    LPCTSTR lpCaption,
    UINT uType
);

typedef void (__stdcall *PExitProcess)(
UINT uExitCode
);


typedef struct _tagAPIINFORMATION
{
    BYTE len;         //api name len
    char ApiName[20]; //api name
    int ApiPointer; //api function pointer
}APIINFORMATION;

void Get_Kernel(int _kernelFun ,int *ImageBase);
void Get_Api(int _dllbase,APIINFORMATION *ApiEntry);
void Get_Apis(int _dllbase,int numEntries,APIINFORMATION *ApiEntry);
int MyStrCmp(char *src,char *dest,int num);

int __stdcall WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{

    int _kernelFun;
    int _Kernel = 0;
    int _User32 = 0;
    char szUser32[] = {'u','s','e','r','3','2','.','d','l','l',0};
  
    APIINFORMATION Kernel32Api[3] = {
        {14,{'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0},0},
        {11,{'L','o','a','d','L','i','b','r','a','r','y',0},0},
        {11,{'E','x','i','t','P','r','o','c','e','s','s',0},0}
    };
    APIINFORMATION User32Api[2] = {
        {11,{'M','e','s','s','a','g','e','B','e','e','p',0},0},
        {10,{'M','e','s','s','a','g','e','B','o','x',0},0}
    };

    char msgtitle[] = {'l','i','t','t','l','e',' ','t','e','s','t',0};
    char msgCaption[] = {'n','o',' ','i','m','p','o','r','t','s',',',' ','f','u','n','n','y',' ','e','h','?',0};
    _asm
    {
        mov eax, [ebp]
        mov eax,[eax + 4]
        mov _kernelFun,eax
    }

    Get_Kernel(_kernelFun,&_Kernel);
    Get_Apis(_Kernel,3,Kernel32Api);
  
    _User32 = (int)((PLoadLibrary)Kernel32Api[1].ApiPointer)(szUser32); //获取user32模块基地址
    Get_Apis(_User32,2,User32Api);

    ((PMessageBeep)User32Api[0].ApiPointer)(-1);

    ((PMessageBox)User32Api[1].ApiPointer)(0,msgtitle,msgCaption,0);

    return 0;
}

void Get_Kernel(int _kernelFun ,int *ImageBase)
{
     BYTE * pAddr = (BYTE *)_kernelFun;
    WORD * pwAddr;
    DWORD *pdAddr;
    while(1)
    {
        pAddr --;
        pwAddr = (WORD *)(pAddr + 0x3c);
        if(!(*pwAddr & 0xF800))
        {
             pdAddr = (DWORD *)(pAddr + *pwAddr + 0x34);
            if(*pdAddr == (DWORD)pAddr )
            {
                 *ImageBase = (DWORD)pAddr;
                break;
            }
        }
       
    }
}

void Get_Api(int _dllbase,APIINFORMATION *ApiEntry)
{
    DWORD *pdAddr;
    WORD *pwAddr;
    BYTE *pAddr;
    DWORD importTable;
    DWORD AddrOfNames;
    DWORD NumberOfNames;
    DWORD AddrNameOrd;
    DWORD AddrOfFunctions;
    char *ApiName;
    int index = 0;

    pAddr = (BYTE *)_dllbase;
    pdAddr = (DWORD *)*(DWORD *)(pAddr + 0x3c);
    pdAddr = (DWORD *)((DWORD)pdAddr + _dllbase);
  

    pdAddr = (DWORD *)*(DWORD *)((BYTE *)pdAddr + 0x78);
    importTable = ((DWORD)pdAddr + _dllbase); //保存dll导出表地址

    AddrOfNames = (DWORD)*(DWORD *)(importTable + 32) + _dllbase;//保存AddrOfNames
    AddrNameOrd = (DWORD)*(DWORD *)(importTable + 36) + _dllbase; //指向函数序号数组地址
    AddrOfFunctions = (DWORD)*(DWORD *)(importTable + 28) + _dllbase; //指向函数地址数组地址
    NumberOfNames = (DWORD)*(DWORD *)(importTable + 24) ; //取出函数名数组中的元素数

    pdAddr = (DWORD *)AddrOfNames;
       ApiName = (char *)(*pdAddr + _dllbase);//取出函数名数组中的第一项

    for(int i = NumberOfNames; i >0; i--)
    {       
        index ++;
        if(MyStrCmp(ApiEntry->ApiName,(char *)ApiName,ApiEntry->len))
        {
             while(*ApiName != 0)
                ApiName ++;

            ApiName++; //指向下一个函数名
        }
        else
        {
            pwAddr = (WORD *)AddrNameOrd;
            index--;

            pwAddr += index;
            pdAddr = (DWORD *)AddrOfFunctions;
            ApiEntry->ApiPointer = (DWORD)(*(pdAddr + *pwAddr)) + _dllbase;

            break;
        }
    }

}

void Get_Apis(int _dllbase,int numEntries,APIINFORMATION *ApiEntry)
{
    for(int i = 0; i<numEntries;i ++)
        Get_Api(_dllbase,&ApiEntry[i]);
}

//比较函数,相等返回0,不等返回1
int MyStrCmp(char *src,char *dest,int num)
{
    int dwret ;
    __asm
    {
        mov eax,1
        push esi
        push edi
        push ecx
        mov ecx,num
        mov esi,src
        mov edi,dest
        repz cmpsb
        jnz NOT_FOUND
        xor eax,eax
NOT_FOUND:
        pop ecx
        pop edi
        pop esi
        mov dwret,eax
    }
    return dwret;
}
分析:
上面我们给出的程序代码,编译执行后,我们看到还是存在输入表,如下图所示.
我们看到,程序依然关联了ntdll.dll和kernel32.dll. 我们从代码看,已经比较干净了。为什么还有输入表呢?
答案是:这是由于我们的winmain入口函数造成的。原因就是,winmain不是最原始的函数入口。

下面我们看看windows程序的加载过程:

在WinMain函数被执行之前,有一系列复杂的加载动作,还要执行一大段启动代码。运行程序NoImport.exe时,操作系统的加载程序首先为进程分配一 个4GB的虚拟地址空间,然后把程序NoImport.exe所占用的磁盘空间作为虚拟内存映射到这个4GB的虚拟地址空间中。一般情况下,会映射到虚拟地址空 间中0X00400000的位置。加载一个应用程序的时间比一般人所设想的要少,因为加载一个PE文件并不是把这个文件整个一次性的从磁盘读到内存中,而 是简单的做一个内存映射,映射一个大文件和映射一个小文件所花费的时间相差无几。当然,真正执行文件中的代码时,操作系统还是要把存在于磁盘上的虚拟内存 中的代码交换到物理内存(RAM)中。但是,这种交换也不是把整个文件所占用的虚拟地址空间一次性的全部从磁盘交换到物理内存中,操作系统会根据需要和内 存占用情况交换一页或多页。当然,这种交换是双向的,即存在于物理内存中的一部分当前没有被使用的页也可能被交换到磁盘中。
接着,系统在内核中创建进程对象和主线程对象以及其它内容。
然后操作系统的加载程序搜索PE文件中的引入表,加载所有应用程序所使用的动态链接库。对动态链接库的加载与对应用程序的加载完全类似。
再接着,操作系统执行PE文件首部所指定地址处的代码,开始应用程序主线程的执行。首先被执行的代码并不是NoImport.exe中的WinMain函数,而是被称为C Runtime startup code的WinMainCRTStartup函数,该函数是连接时由连接程序附加到文件NoImport.exe中的。该函数得到新进程的全部命令行指针和环境变量的指针,完成一些C运行时全局变量以及C运行时内存分配函数的初始化工作。如果使用C++编程,还要执行全局类对象的构造函数。最后,WinMainCRTStartup函数调用WinMain函数。
WinMainCRTStartup函数传给WinMain函数的4个参数分别为:hInstance、hPrevInstance、lpCmdline、nCmdShow。
hInstance:该进程所对应的应用程序当前实例的句柄。WinMainCRTStartup函数通过调用GetStartupInfo函数获得该参数的值。该参数实际上是应用程序被加载到进程虚拟地址空间的地址,通常情况下,对于大多数进程,该参数总是0X00400000。
hPrevInstance:应用程序前一实例的句柄。由于Win32应用程序的每一个实例总是运行在自己的独立的进程地址空间中,因此,对于Win32应用程序,WinMainCRTStartup函数传给该参数的值总是NULL。如果应用程序希望知道是否有另一个实例在运行,可以通过线程同步技术,创建一个具有唯一名称的互斥量,通过检测这个互斥量是否存在可以知道是否有另一个实例在运行。
lpCmdline:命令行参数的指针。该指针指向一个以0结尾的字符串,该字符串不包括应用程序名。
nCmdShow:指定如何显示应用程序窗口。如果该程序通过在资源管理器中双击图标运行,WinMainCRTStartup函数传给该参数的值为SW_SHOWNORMAL。如果通过在另一个应用程序中调用CreatProcess函数运行,该参数由CreatProcess函数的参数lpStartupInfo(STARTUPINFO.wShowWindow)指定。

而在WinMainCRTStartup的执行中,调用了kernel32.dll中的api函数,因此,造成了NoImport.exe程序依然关联了ntdll.dll和kernel32.dll,即NoImport.exe存在输入表。

了解了这个原因后,我们把程序再修改下,代码如下:(见光盘NoImport)

#pragma   comment(linker,   "/ENTRY:combojiang")
typedef void *HMODULE;
typedef const char* LPCTSTR;
typedef long BOOL;
typedef unsigned int UINT;
typedef void * HWND;
typedef unsigned char BYTE;
typedef void * HINSTANCE;
typedef char * LPSTR;
typedef unsigned short WORD;
typedef unsigned long DWORD;

typedef HMODULE (__stdcall *PLoadLibrary)(
LPCTSTR lpFileName
);

typedef BOOL (__stdcall *PMessageBeep)(
UINT uType
);

typedef int (__stdcall *PMessageBox)(          HWND hWnd,
    LPCTSTR lpText,
    LPCTSTR lpCaption,
    UINT uType
);

typedef void (__stdcall *PExitProcess)(
UINT uExitCode
);

typedef struct _tagAPIINFORMATION
{
    BYTE len;         //api name len
    char ApiName[20]; //api name
    int ApiPointer; //api function pointer
}APIINFORMATION;

void Get_Kernel(int _kernelFun ,int *ImageBase);
void Get_Api(int _dllbase,APIINFORMATION *ApiEntry);
void Get_Apis(int _dllbase,int numEntries,APIINFORMATION *ApiEntry);
int MyStrCmp(char *src,char *dest,int num);

void combojiang()
{

    int _kernelFun;
    int _Kernel = 0;
    int _User32 = 0;
    char szUser32[] = {'u','s','e','r','3','2','.','d','l','l',0};
   
    APIINFORMATION Kernel32Api[3] = {
        {14,{'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0},0},
        {11,{'L','o','a','d','L','i','b','r','a','r','y',0},0},
        {11,{'E','x','i','t','P','r','o','c','e','s','s',0},0}
    };
    APIINFORMATION User32Api[2] = {
        {11,{'M','e','s','s','a','g','e','B','e','e','p',0},0},
        {10,{'M','e','s','s','a','g','e','B','o','x',0},0}
    };

    char msgtitle[] = {'l','i','t','t','l','e',' ','t','e','s','t',0};
    char msgCaption[] = {'n','o',' ','i','m','p','o','r','t','s',',',' ','f','u','n','n','y',' ','e','h','?',0};
    _asm
    {
        mov eax, [ebp + 4]
        mov _kernelFun,eax
    }

    Get_Kernel(_kernelFun,&_Kernel);
    Get_Apis(_Kernel,3,Kernel32Api);
   
    _User32 = (int)((PLoadLibrary)Kernel32Api[1].ApiPointer)(szUser32); //获取user32模块基地址
    Get_Apis(_User32,2,User32Api);

    ((PMessageBeep)User32Api[0].ApiPointer)(-1);

    ((PMessageBox)User32Api[1].ApiPointer)(0,msgtitle,msgCaption,0);

    ((PExitProcess)Kernel32Api[2].ApiPointer)(0);
}

void Get_Kernel(int _kernelFun ,int *ImageBase)
{
     BYTE * pAddr = (BYTE *)_kernelFun;
    WORD * pwAddr;
    DWORD *pdAddr;
    while(1)
    {
        pAddr --;
        pwAddr = (WORD *)(pAddr + 0x3c);
        if(!(*pwAddr & 0xF800))
        {
             pdAddr = (DWORD *)(pAddr + *pwAddr + 0x34);
            if(*pdAddr == (DWORD)pAddr )
            {
                 *ImageBase = (DWORD)pAddr;
                break;
            }
        }
        
    }
}

void Get_Api(int _dllbase,APIINFORMATION *ApiEntry)
{
    DWORD *pdAddr;
    WORD *pwAddr;
    BYTE *pAddr;
    DWORD importTable;
    DWORD AddrOfNames;
    DWORD NumberOfNames;
    DWORD AddrNameOrd;
    DWORD AddrOfFunctions;
    char *ApiName;
    int index = 0;

    pAddr = (BYTE *)_dllbase;
    pdAddr = (DWORD *)*(DWORD *)(pAddr + 0x3c);
    pdAddr = (DWORD *)((DWORD)pdAddr + _dllbase);
   

    pdAddr = (DWORD *)*(DWORD *)((BYTE *)pdAddr + 0x78);
    importTable = ((DWORD)pdAddr + _dllbase); //保存dll导出表地址

    AddrOfNames = (DWORD)*(DWORD *)(importTable + 32) + _dllbase;//保存AddrOfNames
    AddrNameOrd = (DWORD)*(DWORD *)(importTable + 36) + _dllbase; //指向函数序号数组地址
    AddrOfFunctions = (DWORD)*(DWORD *)(importTable + 28) + _dllbase; //指向函数地址数组地址
    NumberOfNames = (DWORD)*(DWORD *)(importTable + 24) ; //取出函数名数组中的元素数

    pdAddr = (DWORD *)AddrOfNames;
       ApiName = (char *)(*pdAddr + _dllbase);//取出函数名数组中的第一项

    for(int i = NumberOfNames; i >0; i--)
    {        
        index ++;
        if(MyStrCmp(ApiEntry->ApiName,(char *)ApiName,ApiEntry->len))
        {
             while(*ApiName != 0)
                ApiName ++;

            ApiName++; //指向下一个函数名
        }
        else
        {
            pwAddr = (WORD *)AddrNameOrd;
            index--;

            pwAddr += index;
            pdAddr = (DWORD *)AddrOfFunctions;
            ApiEntry->ApiPointer = (DWORD)(*(pdAddr + *pwAddr)) + _dllbase;

            break;
        }
    }

}

void Get_Apis(int _dllbase,int numEntries,APIINFORMATION *ApiEntry)
{
    for(int i = 0; i<numEntries;i ++)
        Get_Api(_dllbase,&ApiEntry[i]);
}

//比较函数,相等返回0,不等返回1
int MyStrCmp(char *src,char *dest,int num)
{
    int dwret ;
    __asm
    {
        mov eax,1
        push esi
        push edi
        push ecx
        mov ecx,num
        mov esi,src
        mov edi,dest
        repz cmpsb
        jnz NOT_FOUND
        xor eax,eax
NOT_FOUND:
        pop ecx
        pop edi
        pop esi
        mov dwret,eax
    }
    return dwret;
}

这个代码中,我们修改了程序入口函数为combojiang. 程序编译运行,我们发现,现在达到我们的最初要求了。呵呵,可以看出,我们的程序代码没有包含任何的头文件和库文件。

分析下这个程序中用到的技术.

1) 了解指针。
   在win32系统下,所有的指针都是4个字节,比较下面3个指针:
   char * pAddr;
   WORD * pwAddr;
   DWORD * pdAddr;
这三个指针,数据类型不同,指针的数据类型代表了他们指向的数据的类型。我们再看下面:
如果这三个指针都指向同一个地址,例如:0x401000;
pAddr ++;
pwAddr++;
pdAddr++;
第一个pAddr++,     实际地址增加了一个字节。此时,pAddr 的值为0x401001;
第二个pwAddr++,实际地址增加了两个字节。此时,pAddr 的值为0x401002;
第三个pdAddr++,实际地址增加了4个字节。 此时,pAddr 的值为0x401004;

指针的数据类型,体现了指针加减时的步长。这样代码中指针加减的操作,大家就理解了。

2) 理解pe文件格式
通常dll只要有输出的函数,都有导出表,我们的exe如果调用了dll中导出的函数,那么在exe中就会添加导入表。关于pe部分的知识,在看雪出版的书《加密与解密》中讲的很详细。这里就不过多介绍了。下面的代码,就是从dll的导出表中,根据函数名,找出函数的地址。导出表如下图:

void Get_Api(int _dllbase,APIINFORMATION *ApiEntry)
{
    DWORD *pdAddr;
    WORD *pwAddr;
    BYTE *pAddr;
    DWORD importTable;
    DWORD AddrOfNames;
    DWORD NumberOfNames;
    DWORD AddrNameOrd;
    DWORD AddrOfFunctions;
    char *ApiName;
    int index = 0;

    pAddr = (BYTE *)_dllbase;
    pdAddr = (DWORD *)*(DWORD *)(pAddr + 0x3c);
    pdAddr = (DWORD *)((DWORD)pdAddr + _dllbase);
   

    pdAddr = (DWORD *)*(DWORD *)((BYTE *)pdAddr + 0x78);
    importTable = ((DWORD)pdAddr + _dllbase); //保存dll导出表地址

    AddrOfNames = (DWORD)*(DWORD *)(importTable + 32) + _dllbase;//保存AddrOfNames
    AddrNameOrd = (DWORD)*(DWORD *)(importTable + 36) + _dllbase; //指向函数序号数组地址
    AddrOfFunctions = (DWORD)*(DWORD *)(importTable + 28) + _dllbase; //指向函数地址数组地址
    NumberOfNames = (DWORD)*(DWORD *)(importTable + 24) ; //取出函数名数组中的元素数

    pdAddr = (DWORD *)AddrOfNames;
       ApiName = (char *)(*pdAddr + _dllbase);//取出函数名数组中的第一项

    for(int i = NumberOfNames; i >0; i--)
    {        
        index ++;
        if(MyStrCmp(ApiEntry->ApiName,(char *)ApiName,ApiEntry->len))
        {
             while(*ApiName != 0)
                ApiName ++;

            ApiName++; //指向下一个函数名
        }
        else
        {
            pwAddr = (WORD *)AddrNameOrd;
            index--;

            pwAddr += index;
            pdAddr = (DWORD *)AddrOfFunctions;
            ApiEntry->ApiPointer = (DWORD)(*(pdAddr + *pwAddr)) + _dllbase;

            break;
        }
    }

}

这里面需要注意的一点就是,如上图所示,AddrOfNames指向的RVA数组所指向的函数名,从上图结构看,这些函数名可以是不连续存储的,但是实际上,他们是连续存储的。因此,我们找出第一个函数名后,让指针指向下一个函数名的方法就是越过第一个函数名最后的字符串结束符'\0‘,就指向了下一个函数名。
代码如下:
           while(*ApiName != 0)
                ApiName ++;

            ApiName++; //指向下一个函数名

3)理解内存里找出kernel模块基地址的方法。
我们采用的是程序加载时在栈0x12ffc4保存着一个地址,如下图:
这个地址保存了0x7c816fd7, 对应的指令如下图:
0x7c816fd7是在kernel32的领空内的,如下图:

这样,我们就可以通过0x7c816fd7来找到kernel32的基地址。
程序中的下面代码,就是得到0x12ffc4中的0x7c816fd7,然后保存到_kernelFun。
    _asm
    {
        mov eax, [ebp + 4]
        mov _kernelFun,eax
    }

接下来,通过_kernelFun获取kernel32基地址的函输入下:
void Get_Kernel(int _kernelFun ,int *ImageBase)
{
     BYTE * pAddr = (BYTE *)_kernelFun;
    WORD * pwAddr;
    DWORD *pdAddr;
    while(1)
    {
        pAddr --;
        pwAddr = (WORD *)(pAddr + 0x3c);
        if(!(*pwAddr & 0xF800))
        {
             pdAddr = (DWORD *)(pAddr + *pwAddr + 0x34);
            if(*pdAddr == (DWORD)pAddr )
            {
                 *ImageBase = (DWORD)pAddr;
                break;
            }
        }
        
    }
}
这个代码比较好理解,他是完全根据PE文件的格式来找的。至于为什么在栈0x12FFC4中存储了这样一个返回地址。我们可以看看http://bbs.pediy.com/showthread.php?t=59352  这篇文章,为了便于大家阅读,我贴出这篇文章的内容:

ESP定律背后的原理之一:0x12FFC4
ESP定律:如果压缩壳执行前后的堆栈会维持平衡,在12FFC0上下读写断点,会中断在OEP的第一条指令push ebp上。那么各位有没有探究过ESP的尾数值为什么不是0xFFFC,而是0x0FFC4?这是由windows源代码的实现所确定的。

Windows创建进程的函数链为kernel32.dll!_CreateProcessInternalW -> … -> kernel32.dll!_BaseProcessStartThunk() -> kernel32.dll!_BaseProcessStart() -> EP

在进入_BaseProcessStartThunk()时,堆栈的状态确实为
+ 0012FFFC    00000000                 <- ESP

---------------------------------------------------------<code>
    _BaseProcessStartThunk@8 proc near
xor      ebp, ebp
push     eax
push     0
jmp      _BaseProcessStart@4 ; BaseProcessStart(x)
_BaseProcessStartThunk@8 endp
----------------------------------------------------------</code>

在执行完_BaseProcessStartThunk时,堆栈的状态为
+ 0012FFFC    00000000                
+ 0012FFF8    EAX
+ 0012FFF4    0x00000000              <- ESP
    
---------------------------------------------------------<code>
     _BaseProcessStart proc
push     0Ch
push     offset dword_7C816FE0
call     __SEH_prolog
and      [ebp+ms_exc.disabled], 0
push     4
lea      eax, [ebp+8]
push     eax
push     9
push     0FFFFFFFEh
call     ds:__imp__NtSetInformationThread@16 ; NtSetInformationThread(x,x,x,x)
call     dword ptr [ebp+8]
push     eax              ; dwExitCode
call     _ExitThread@4
nop
nop
nop
---------------------------------------------------------</code>

在执行完BaseProcessStart()后,堆栈的状态为

+ 0012FFFC    0x00000000
+ 0012FFF8    OldEAX -2-> EntryPoint By NtSetInformationThread()
+ 0012FFF4    reserved, inited to 0x00000000
+ 0012FFF0    OldEBP(0x00000000)     <-p- NowEBP
+ 0012FFEC    TryLevel, inited to 0xFFFFFFFF
+ 0012FFE8    ScopeTable
+ 0012FFE4    __except_handler3 in kernel32.dll
+ 0012FFE0    0xFFFFFFFF             <-p- fs:00h
+ 0012FFDC    ExceptionInfo, Parameter of UnhandledExceptionFilter()
+ 0012FFD8    OldESP(0x0012FFC8)
+ 0012FFD4    ExceptionCode, Parameter of kernel32.dll!ExitProcess
+ 0012FFD0    EBX(pPEB)
+ 0012FFCC    ESI
+ 0012FFC8    EDI                    <-p- OldESP
+ 0012FFC4    retaddr in kernel32.dll!_BaseProcessStartThunk
                                                <-p- NowESP


附:
     DWORD    SizeOfStackReserve;                          // 0x48-4b
     DWORD    SizeOfStackCommit;                           // 0x4c-4f
     DWORD    SizeOfHeapReserve;                           // 0x50-53
     DWORD    SizeOfHeapCommit;                            // 0x54-57

这是IOH中的四个域。
Reserved保留的, Committed提交的是两种类型的虚拟内存。
Reserved指这段内存已有用途,不能再被分配;Committed提交的内存指的是已经与物理内存有映射的虚拟内存段(当然也可以被移到硬盘上的缓 存文件pagefile.sys中)。因为Stack, Heap这两种类型的内存段都有随着程序的运行尺寸不断增大的特性,所以一开始不必占用太多宝贵 的物理内存,当它们用完了,会引发内存异常,windows内存管理器就又为程序分配一部分。

SizeOfStackReserve   程序开始时保留用作堆栈空间的内存段的大小
SizeOfStackCommit    程序开始时在保留的堆栈内存段中与物理内存映射的提交的内存段的大小
SizeOfHeapReserve   程序开始时保留用作堆空间的内存段的大小
SizeOfHeapCommit    程序开始时在保留的堆内存段中与物理内存映射的提交的内存段的大小.

4)了解更改程序入口点的预编译指令
    #pragma   comment(linker,   "/ENTRY:combojiang")

最后,我们自己写了一个基本的字符串比较函数,也贴出来吧。
//比较函数,相等返回0,不等返回1
int MyStrCmp(char *src,char *dest,int num)
{
    int dwret ;
    __asm
    {
        mov eax,1
        push esi
        push edi
        push ecx
        mov ecx,num
        mov esi,src
        mov edi,dest
        repz cmpsb
        jnz NOT_FOUND
        xor eax,eax
NOT_FOUND:
        pop ecx
        pop edi
        pop esi
        mov dwret,eax
    }
    return dwret;
}

注: 本篇我们只是讨论了可移植性中的一个问题,就是取消导入表. 还有另一个问题,就是代码移植中涉及的重定位问题. 重定位包括数据重定位和函数重定位,本篇数据定义的方式,已经解决了数据重定位,剩下函数重定位的问题. 关于重定位,有两种方法,一种是修改目标进程的重定位表,一种是采用下面的做法。第一种太麻烦了,第二种,只能借助于汇编了,C还没法做到这点。

下面摘自老Y的一段话来说明如何重定位,结束本篇吧。

重定位问题
在汇编里可以很简单的使用下面这种方式来重定位代码或全局数据:
Start:
     call lbl_Next
lbl_Next:
     pop ebx
     sub ebx, 5
     sub ebx, offset Start

要访问全局数据就这样:Mov eax, dword ptr[ebx + GlobalData]
那么用C语言里怎么重定位呢,呵呵,有人说过在C里不能嵌汇编吗?没有,嘿,那就用汇编,如:
/**
*@brief 取得全局变量或函数重定位后的地址
*
*@param[in]     pVar 全局变量或函数的地址
*@return 返回全局变量或函数的实际地址
*/
PVOID KGetGlobalVarAddr(PVOID pVar)
{
   PVOID pCurAddr = NULL;
   __asm
   {
Start:
     call lbl_Next
lbl_Next:
     pop eax
     sub eax, 5
     sub eax, offset Start
     add eax, pVar
     mov pCurAddr, eax
   }
   return pCurAddr;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值