分享一个编写传奇封外挂(反外挂)系统的完成过程 - 游戏客户端编写加壳免误报篇[原创]

分享一个编写传奇封外挂(反外挂)系统的完成过程[原创]

正文

       什么是免杀、免误报。现在的杀毒软件因为鉴别能力的原因和偷懒干脆一棍子打死等因素造成我们大多正常程序都被误认为是病毒木马,一下载到用户电脑就直接报毒甚至直接删除。现在的杀毒软件普遍存在这种偷懒行为,以某六零程序最为严重,只要你的程序调用了内存、进程、网络和文件相关方面的API,达到一定数量就会报毒,就算没有调用,只要在导入表里面存在也会报毒。当然也包括一些代码撞衫病毒库的,代码量越大撞衫的几率就越大。有时候VS默认生成的hellow word都会报毒。至于加了知名加密壳的程序也是必报的。

     报毒会对我们的产品造成极大的困扰,你不可能告诉每一个用户说这是误报需要手动加入白名单,用户也不一定完全都信任我们。虽然对于误报程序,杀毒软件方面也提供了途径上传给他们分析后好加入他们的白名单系统,但是毕竟很麻烦。我早些年也申请过,没通过,记得是有公司规模资质要求,还需要有什么电信增值许可(具体名字忘了)之类的,类似于文网文之类的证书,总之很麻烦。更何况上传也不止一家,每次更新都得来一遍,有多少家杀软你就得上传多少家去,有的可能会根据你的软件情况收费,某六零好像就有收费的,价格还不便宜。

   免杀,免误报现在网络上已经形成一套知识体系了,有兴趣的朋友可以搜索了解。免误杀基本原理是通过修改代码的特征,使撞衫病毒木马的代码改变特征。对于“危险API”的调用可以改成隐式调用方式,使API不出现在导入表中。隐式调用就是 LoadLibray + GetProcAddress ,要注意这里的调用最好直勾勾地调用、也不要有初始化的字符串,否则同样报毒。

   本文介绍的免杀免误报方式是通过编写自定义壳的方式来实现的。本壳是自己设计的,关于壳的知识可以购买《windows pe权威指南》 和看雪学院的 《加密和解密》书籍深入学习。

   本项目的壳是将整个欲处理的游戏程序全部加密的方式处理的、并非普通壳只加密代码段,下面是介绍本项目壳的开发过程:

第一步、跳转  : 产生一个异常使程序跳转到壳代码。这里不能使用 jmp跳转、入口点使用jmp 某六零必报毒。所以不能使用jmp 。

00B8F0DC | 68 09D51C01              | push 游戏.11CD509                               |
00B8F0E1 | 33C0                     | xor eax,eax                                        |
00B8F0E3 | 64:FF30                  | push dword ptr fs:[eax]                            |
00B8F0E6 | 64:8920                  | mov dword ptr fs:[eax],esp                         |
00B8F0E9 | 3E:8900                  | mov dword ptr ds:[eax],eax                         |    |

第二步、反沙箱:因为杀毒软件沙箱一般硬件配置比较低、所以运用这个特点、限制运行系统硬件的CPU,内存,以及鼠标键盘外设等硬件条件来阻止程序在沙箱运行。因为正常用户都是鼠标双击或者键盘回车来启动程序的,所以几秒内一定有鼠标键盘事件。还可以设计进程数量限制、SetErrorMode模式等反沙箱方案,下面只贴出部分演示代码,一旦代码全贴出来很快就会变得无效。想了解更多关于反沙箱知识请百度搜索学些。这些知识也是动态变化的,一旦被贴到网上,很快就会失效。最好自己多揣摩、猜测去测试、发现。其中值得一提的是:多运用多线程技术对反沙箱方面比较好使、因为动沙箱检测多线程方面本身难度就比较大。

   //通过PPEB 获取CPU数量 ,限制最少2CPU
    DWORD* pdword = &ppack_info->anti_cpu_count;
    _asm
    {
        mov eax,0x12
        mov ebx,0x20
        mov ecx,0x60
        mov eax,dword ptr fs:[eax + 0x06]
        add eax,0x10
        mov eax,dword ptr ds:[eax + ebx ]
        add eax,0x04
        mov eax,dword ptr ds:[eax + ecx ]
        push eax
        mov eax,dword ptr [pdword]
        pop dword ptr[eax]
    }
    if (*pdword < 0x02)
    {
        ((void(*)(PACK_INFO*))(ppack_info->dwFN_doexit_normal - ppack_info->dwFN_offset))(ppack_info);
        return;
    }

//物理内存容量 ,最少1G内存
    MEMORYSTATUSEX memInfo;
    memInfo.dwLength = sizeof(MEMORYSTATUSEX);
    ((type_GlobalMemoryStatusEx)((DWORD)ppack_info->fnGlobalMemoryStatusEx - ppack_info->dwFN_offset))(&memInfo);
    //*pdword = memInfo.ullTotalPhys / 1024 / 1024;
    DWORD dwHi = *((DWORD*)&memInfo.ullTotalPhys + 1);//取高32位
    DWORD dwLo = *((DWORD*)&memInfo.ullTotalPhys );   //低32位

    if (dwHi == 0 && dwLo < 999 * 1024*1024) //小于 999M
    {
        ((void(*)(PACK_INFO*))(ppack_info->dwFN_doexit_normal - ppack_info->dwFN_offset))(ppack_info);
        return;
    }


//开机时间 > 1 分钟
    ppack_info->fnGetTickCount = (type_GetTickCount)((DWORD(*)(PACK_INFO*, EComFunType, DWORD, DWORD, DWORD, DWORD))(ppack_info->dwFN_commfunction - ppack_info->dwFN_offset))(ppack_info, EComFunType::GetProcAddress, ppack_info->hmodKernel32, (DWORD)ppack_info->name_GetTickCount_str1, ppack_info->name_GetTickCount_fixidx, (DWORD)ppack_info->name_GetTickCount_str2);
    if (((type_GetTickCount)((DWORD)ppack_info->fnGetTickCount - ppack_info->dwFN_offset))() < 1 * 60 * 1000)
    {
        ((void(*)(PACK_INFO*))(ppack_info->dwFN_doexit_normal - ppack_info->dwFN_offset))(ppack_info);
        return;
    }


 //校验:必须存在鼠标
    type_GetSystemMetrics fnGetSystemMetrics = (type_GetSystemMetrics)((DWORD(*)(PACK_INFO*, EComFunType, DWORD, DWORD, DWORD, DWORD))(ppack_info->dwFN_commfunction - ppack_info->dwFN_offset))(ppack_info, EComFunType::GetProcAddress, ppack_info->hmodUser32, (DWORD)ppack_info->name_GetSystemMetrics_str1, ppack_info->name_GetSystemMetrics_fixidx, (DWORD)ppack_info->name_GetSystemMetrics_str2);
    if (!((type_GetSystemMetrics)((DWORD)fnGetSystemMetrics - ppack_info->dwFN_offset))(SM_MOUSEPRESENT))
    {
        ((void(*)(PACK_INFO*))(ppack_info->dwFN_doexit_normal - ppack_info->dwFN_offset))(ppack_info);
        return;
    }

//校验:GetLastInputInfo 上次输入时间间隔 < 8s
    type_GetLastInputInfo  fnGetLastInputInfo = (type_GetLastInputInfo)((DWORD(*)(PACK_INFO*, EComFunType, DWORD, DWORD, DWORD, DWORD))(ppack_info->dwFN_commfunction - ppack_info->dwFN_offset))(ppack_info, EComFunType::GetProcAddress, ppack_info->hmodUser32, (DWORD)ppack_info->name_GetLastInputInfo_str1, ppack_info->name_GetLastInputInfo_fixidx, (DWORD)ppack_info->name_GetLastInputInfo_str2);
    LASTINPUTINFO* ppi = (LASTINPUTINFO*)ppack_info->mov_ToAddr;
    ppi->cbSize = sizeof(LASTINPUTINFO);
    ((type_GetLastInputInfo)((DWORD)fnGetLastInputInfo - ppack_info->dwFN_offset))(ppi);
    if (((type_GetTickCount)((DWORD)ppack_info->fnGetTickCount - ppack_info->dwFN_offset))() - ppi->dwTime > 8 * 1000)
    {
        ((void(*)(PACK_INFO*))(ppack_info->dwFN_doexit_normal - ppack_info->dwFN_offset))(ppack_info);
        return;
    }

第三步、动态获取API,第二步中部分API也是本步骤获取的。本壳的导入表里面已经存在LoadLibraryA和GetProcAddress两个函数,其它函数全靠这里动态加载导入。要注意导入函数时不能直勾勾地调用、也不能直接写DLL名称和导入函数,得做一些加密混淆处理。

ppack_info->fnLoadlibraryA = (type_LoadLibraryA)(*(DWORD*)ppack_info->fnLoadlibraryA + ppack_info->dwFN_offset);
    ppack_info->fnGetProcAddress = (type_GetProcAddress)(*(DWORD*)ppack_info->fnGetProcAddress + ppack_info->dwFN_offset);
    ppack_info->hmodKernel32 = ((DWORD(*)(PACK_INFO*, EComFunType, DWORD, DWORD, DWORD, DWORD))(ppack_info->dwFN_commfunction - ppack_info->dwFN_offset))(ppack_info, EComFunType::LoadLibraryA, (DWORD)ppack_info->name_Kernel32_str1, ppack_info->name_Kernel32_fixidx, (DWORD)ppack_info->name_Kernel32_str2, NULL);
    ppack_info->fnSleep = (type_Sleep)((DWORD(*)(PACK_INFO*, EComFunType, DWORD, DWORD, DWORD, DWORD))(ppack_info->dwFN_commfunction - ppack_info->dwFN_offset))(ppack_info, EComFunType::GetProcAddress, ppack_info->hmodKernel32, (DWORD)ppack_info->name_Sleep_str1, ppack_info->name_Sleep_fixidx, (DWORD)ppack_info->name_Sleep_str2);
    ppack_info->fnGlobalMemoryStatusEx = (type_GlobalMemoryStatusEx)((DWORD(*)(PACK_INFO*, EComFunType, DWORD, DWORD, DWORD, DWORD))(ppack_info->dwFN_commfunction - ppack_info->dwFN_offset))(ppack_info, EComFunType::GetProcAddress, ppack_info->hmodKernel32, (DWORD)ppack_info->name_GlobalMemoryStatusEx_str1, ppack_info->name_GlobalMemoryStatusEx_fixidx, (DWORD)ppack_info->name_GlobalMemoryStatusEx_str2);
    ppack_info->fnExitProcess = (type_ExitProcess)((DWORD(*)(PACK_INFO*, EComFunType, DWORD, DWORD, DWORD, DWORD))(ppack_info->dwFN_commfunction - ppack_info->dwFN_offset))(ppack_info, EComFunType::GetProcAddress, ppack_info->hmodKernel32, (DWORD)ppack_info->name_ExitProcess_str1, ppack_info->name_ExitProcess_fixidx, (DWORD)ppack_info->name_ExitProcess_str2);
    ppack_info->hmodUser32 = ((DWORD(*)(PACK_INFO*, EComFunType, DWORD, DWORD, DWORD, DWORD))(ppack_info->dwFN_commfunction - ppack_info->dwFN_offset))(ppack_info, EComFunType::LoadLibraryA, (DWORD)ppack_info->name_user32_str1, ppack_info->name_user32_fixidx, (DWORD)ppack_info->name_user32_str2, NULL);
    

第四步、解密:解密原始的PE文件数据,解密算法可以自行设计即可,一般就是亦或、加减、位移这些简单的、运算速度快的算法组合起来就可以了。

第五步、修复IAT: 关于修复IAT的知识网上比较多,可以具体搜索来看,加壳基本都会涉及到修复IAT ,下面贴出实例代码,这些关于PE文件格式的代码如果正常编写一般都会匹配成病毒,木马,所以加了干扰变形调用。

void fixiat(PACK_INFO* ppack_info)
{
    int i;
    DWORD dwoffset = ppack_info->unuse_word1 ;
    IMAGE_DOS_HEADER* pdoshdr = (IMAGE_DOS_HEADER*)(ppack_info->dwShell_BaseAddress + dwoffset);
    ppack_info->unuse_word1++;
    IMAGE_NT_HEADERS* pnthdr = (IMAGE_NT_HEADERS*)(((char*)pdoshdr - dwoffset) + ((IMAGE_DOS_HEADER*)((char*)pdoshdr - dwoffset))->e_lfanew + dwoffset);
    ppack_info->unuse_byte1 -= ppack_info->unuse_word1 % 0xff;
    IMAGE_IMPORT_DESCRIPTOR* pImportDes = (IMAGE_IMPORT_DESCRIPTOR*)(ppack_info->dwShell_BaseAddress + ((IMAGE_NT_HEADERS*)((char*)pnthdr - dwoffset))->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + dwoffset);
    ppack_info->unuse_word1 ^= ppack_info->unuse_word1;
    while ( ((IMAGE_IMPORT_DESCRIPTOR*)((char*)pImportDes - dwoffset))->FirstThunk)
    {
        char* pDllName = (char*)(ppack_info->dwShell_BaseAddress + ((IMAGE_IMPORT_DESCRIPTOR*)((char*)pImportDes - dwoffset))->Name);
        DWORD hMod =  ((DWORD(*)(PACK_INFO*, EComFunType, DWORD, DWORD, DWORD, DWORD))(ppack_info->dwFN_commfunction - ppack_info->dwFN_offset))(ppack_info
            , EComFunType::LoadLibraryA
            , (DWORD)pDllName
            , 0xff
            , NULL
            , NULL) - ppack_info->dwFN_offset;
        //抹去dll名称
        for (i = 0; i < 0x64 && pDllName[i] != 0; i++)
        {
            pDllName[i] = 0xcc;
        }

        IMAGE_THUNK_DATA* pThunkINT = ((IMAGE_IMPORT_DESCRIPTOR*)((char*)pImportDes - dwoffset))->OriginalFirstThunk == NULL ? 
            NULL : (IMAGE_THUNK_DATA*)(ppack_info->dwShell_BaseAddress + ((IMAGE_IMPORT_DESCRIPTOR*)((char*)pImportDes - dwoffset))->OriginalFirstThunk);
        ppack_info->unuse_byte1 += 0x02;
        IMAGE_THUNK_DATA* pThunkIAT = (IMAGE_THUNK_DATA*)(ppack_info->dwShell_BaseAddress + ((IMAGE_IMPORT_DESCRIPTOR*)((char*)pImportDes - dwoffset))->FirstThunk);
        ppack_info->unuse_dword1 += ppack_info->unuse_word1;
        if (pThunkINT == NULL)
        {
            while (pThunkIAT->u1.Function)
            {
                if (pThunkIAT->u1.Ordinal & IMAGE_ORDINAL_FLAG32)
                {
                    DWORD dwHit = pThunkIAT->u1.Ordinal & 0xFFFF;
                    ppack_info->unuse_byte1++;
                    pThunkIAT->u1.Function = ((DWORD(*)(PACK_INFO*, EComFunType, DWORD, DWORD, DWORD, DWORD))(ppack_info->dwFN_commfunction - ppack_info->dwFN_offset))(ppack_info
                        , EComFunType::GetProcAddress
                        , hMod + ppack_info->dwFN_offset
                        , (DWORD)(char*)(pThunkIAT->u1.Ordinal & 0xFFFF)
                        , 0xff
                        , NULL) - ppack_info->dwFN_offset;
                }
                else
                {
                    IMAGE_IMPORT_BY_NAME* ImportName = (IMAGE_IMPORT_BY_NAME*)(ppack_info->dwShell_BaseAddress + pThunkIAT->u1.AddressOfData);
                    ppack_info->unuse_dword1 ^= ppack_info->unuse_byte1 + 0xcc;
                    pThunkIAT->u1.Function = ((DWORD(*)(PACK_INFO*, EComFunType, DWORD, DWORD, DWORD, DWORD))(ppack_info->dwFN_commfunction - ppack_info->dwFN_offset))(ppack_info
                        , EComFunType::GetProcAddress
                        , hMod + ppack_info->dwFN_offset
                        , (DWORD)(char*)ImportName->Name
                        , 0xff
                        , NULL) - ppack_info->dwFN_offset;
                    //抹去函数名
                    for (i = 0; i < 0x64 && ImportName->Name[i] != 0; i++)
                    {
                        ImportName->Name[i] = 0xcc;
                    }
                }
                pThunkIAT++;
            }
        }
        else
        {
            while (pThunkINT->u1.Function)
            {
                if (pThunkINT->u1.Ordinal & IMAGE_ORDINAL_FLAG32)
                {
                    DWORD dwHit = pThunkINT->u1.Ordinal & 0xFFFF;
                    ppack_info->unuse_byte1++;
                    pThunkIAT->u1.Function = ((DWORD(*)(PACK_INFO*, EComFunType, DWORD, DWORD, DWORD, DWORD))(ppack_info->dwFN_commfunction - ppack_info->dwFN_offset))(ppack_info
                        , EComFunType::GetProcAddress
                        , hMod + ppack_info->dwFN_offset
                        , (DWORD)(char*)(pThunkINT->u1.Ordinal & 0xFFFF)
                        , 0xff
                        , NULL) - ppack_info->dwFN_offset;
                }
                else
                {
                    IMAGE_IMPORT_BY_NAME* ImportName = (IMAGE_IMPORT_BY_NAME*)(ppack_info->dwShell_BaseAddress + pThunkINT->u1.AddressOfData);
                    ppack_info->unuse_dword1 ^= ppack_info->unuse_byte1 + 0xcc;
                    pThunkIAT->u1.Function = ((DWORD(*)(PACK_INFO*, EComFunType, DWORD, DWORD, DWORD, DWORD))(ppack_info->dwFN_commfunction - ppack_info->dwFN_offset))(ppack_info
                        , EComFunType::GetProcAddress
                        , hMod + ppack_info->dwFN_offset
                        , (DWORD)(char*)ImportName->Name
                        , 0xff
                        , NULL) - ppack_info->dwFN_offset;
                    //抹去函数名
                    for (i = 0; i < 0x64 && ImportName->Name[i] != 0; i++)
                    {
                        ImportName->Name[i] = 0xcc;
                    }
                }
                pThunkIAT++;
                pThunkINT++;
            }
        }
        pImportDes++;
    }

    ((void(*)(DWORD, PACK_INFO*))(ppack_info->dwFN_call_fnAddr - ppack_info->dwFN_offset))(ppack_info->dwFN_fixreloc, ppack_info);
}

第六步、修复重定向: EXE程序一般不存在重定向数据,并且本项目的壳取消了EXE的随机基址,不需要修复。

第七步、修复tls : 严格来说这个不是第七步,而是在生成加壳后程序的时候生成的,有些程序有tls,而本项目的壳是属于完全加密壳,需要手动生成tls节和相关数据,以及调用 tls回调函数。tls相关知识点在网上也很多,请自行搜索学习。

第八步、跳转回解密以后的 oep

  

完成

如果你也有这方面兴趣爱好交流欢迎加扣58085250与我直接沟通交流指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值