zoukankan      html  css  js  c++  java
  • ShellCode的几种调用方法

    ShellCode是一种漏洞代码,中文名也叫填充数据,一般是用C语言或者汇编编写。在研究的过程中,自己也学到了一些东西,发现其中也有许多坑,所以贴出来,如果大家有碰到的,可以参考一下。

    以启动电脑上的计算器为例,编写ShellCode其实就是两部分,一是获取ShellCode字节码,二是调用它。

    获取方式一般是观察代码反汇编和内存相结合:

    VOID Test()
    {
        HMODULE v1 =  LoadLibraryA("kernel32.dll");//0X7778ff70
        //WinExec("calc.exe", SW_SHOW);
    
        /*
            00E31E5E  mov         esi,esp
            00E31E60  push        0E36B30h
            00E31E65  call        dword ptr ds:[0E3A060h]
            00E31E6B  cmp         esi,esp
            00E31E6D  call        __RTC_CheckEsp (0E3111Dh)
            00E31E72  mov         dword ptr [v1],ea,0x
                WinExec("calc.e,0xe", SW_SHOW);
    
    
    
            00E31E75  mov         esi,esp
            00E31E77  push        5
            00E31E79  push        0E36B40h
            00E31E7E  call        dword ptr ds:[0E3A064h]
            00E31E84  cmp         esi,esp
            00E31E86  call        __RTC_CheckEsp (0E3111Dh)
    
    
    
        return 0;
        */
        __asm
        {
    
            push    ebp;
            mov        ebp, esp;
            xor eax, eax;
            push eax;
            sub esp, 08h;
            mov byte ptr[ebp - 0Ch], 63h; //c
            mov byte ptr[ebp - 0Bh], 61h; //a
            mov byte ptr[ebp - 0Ah], 6Ch; //l
            mov byte ptr[ebp - 09h], 63h; //c
            mov byte ptr[ebp - 08h], 2Eh; //.
            mov byte ptr[ebp - 07h], 65h; //e
            mov byte ptr[ebp - 06h], 78h; //x
            mov byte ptr[ebp - 05h], 65h; //e
    
            lea eax, [ebp - 0ch];
            push eax;                    //将calc.exe压入栈内
    
            mov        eax, 0x7778ff70;
            call    eax;                    //调用WinExec
    
            mov esp, ebp;
            pop    ebp;
        }
    
    }

    然后就是所谓的苦力活,将反汇编中的字节码一个一个抄出来,整合成为一个ShellCode

    CHAR ShellCode[] = {
        0x55,0x8B,0xEC,0x51,0x51,0x83,0x65,0xFC,0x00,0x56,0x57,0xC7,0x45,0xF8,0x63,0x61,
        0x6C,0x63,0x64,0xA1,0x18,0x00,0x00,0x00,0x33,0xC9,0x8B,0x40,0x30,0x8B,0x40,0x0C,
        0x8B,0x78,0x1C,0x8B,0x3F,0x8B,0x47,0x20,0x66,0x83,0x78,0x10,0x2E,0x74,0x06,0x41,
        0x83,0xF9,0x02,0x7C,0xEE,0x8B,0x4F,0x08,0xBA,0xB9,0x6B,0xFF,0xCB,0xE8,0x23,0x00,
        0x00,0x00,0x8B,0x4F,0x08,0xBA,0x13,0xB9,0xE6,0x25,0x8B,0xF0,0xE8,0x14,0x00,0x00,
        0x00,0x6A,0x01,0x8D,0x4D,0xF8,0x51,0xFF,0xD0,0x6A,0x00,0x6A,0x00,0xFF,0xD6,0x5F,
        0x5E,0x8B,0xE5,0x5D,0xC3,0x55,0x8B,0xEC,0x83,0xEC,0x10,0x53,0x56,0x8B,0xF1,0x89,
        0x55,0xF0,0x33,0xD2,0x57,0x8B,0x46,0x3C,0x8B,0x5C,0x30,0x78,0x03,0xDE,0x89,0x5D,
        0xF4,0x8B,0x4B,0x20,0x03,0xCE,0x39,0x53,0x18,0x76,0x39,0x8B,0x39,0x33,0xC0,0x03,
        0xFE,0x8A,0x1F,0x84,0xDB,0x8B,0x5D,0xF4,0x74,0x1C,0x8B,0xD8,0x8A,0x07,0x6B,0xDB,
        0x21,0x0F,0xBE,0xC0,0x03,0xD8,0x47,0x8A,0x07,0x84,0xC0,0x75,0xF1,0x89,0x5D,0xF8,
        0x8B,0x5D,0xF4,0x8B,0x45,0xF8,0x3B,0x45,0xF0,0x74,0x12,0x83,0xC1,0x04,0x42,0x3B,
        0x53,0x18,0x72,0xC7,0x33,0xC0,0x5F,0x5E,0x5B,0x8B,0xE5,0x5D,0xC3,0x8B,0x43,0x24,
        0x8D,0x04,0x50,0x0F,0xB7,0x0C,0x30,0x8B,0x43,0x1C,0x8D,0x04,0x88,0x8B,0x04,0x30,
        0x03,0xC6,0xEB,0xE2
    };

    对于ShellCode能不能在各个电脑上都适用,我还不敢保证,因为我之前也尝试将别人写的拿过来运行,但是程序崩溃。所以最好自己试着写一遍。

    ShellCode字符也可以写成这种形式:

    char shellcode[] =
    "x55x8bxecx51x51x83x31xc0x88x46x07x89x46x0cxb0x0bx89"
    "xf3x8dx4ex08x31xd2xcdx80xe8xe4xffxffxffx2fx62x69x6e"
    "x2fx73x68x58";   //...

    它利用的是x的转义字符特性,其实是一样的。

    写好ShellCode后就该调用它了

    方法1:利用动态申请内存,一定是可执行属性

    typedef void (_stdcall *CODE)();
    
    
    VOID Sub_1()
    {
        PVOID p = NULL;
        p = VirtualAlloc(NULL, sizeof(ShellCode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        if (p == NULL)
        {
            return;
        }
        memcpy(p, ShellCode, sizeof(ShellCode));
        
        CODE code = (CODE)p;
        code();
    }

    方法2,:强制类型转换成函数指针

    VOID Sub_2()
    {
        ((void(WINAPI*)(void))&ShellCode)();
    }

    虽然看上去有点复杂,但是拆开分析一下还是很简单的,首先取了ShellCode的地址,将它强制类型转换成函数指针,第一个void表示函数返回值,第二个void可以不要,它是说该函数没有参数,最后在后面加上小括号,注意WINAPI的调用约定一定不能少,我用的VS2015编译器,写的控制台程序的默认调用约定是_cdecl。

    方法3:嵌入式汇编呼叫ShellCode


    #pragma comment(linker, "/section:.data,RWE")
    VOID Sub_3() { __asm { mov eax, offset ShellCode jmp eax } }

    这种方法写法也比较灵活,其中第一句 mov eax, offset ShellCode 可以用 lea eax, ShellCode 代替,因为它们是等价的

    第二句的 jmp 也可以用 call 代替。所以就是四种组合了。

    最上面的一句 #pragma comment(linker, "/section:.data,RWE") 是很重要的,我曾经因为没有它,而导致多次错误,却一直在ShellCode上找原因。

    它也是说将这段代码放入可执行区域。

    方法4:伪指令

    
    
    #pragma comment(linker, "/section:.data,RWE"
    VOID Sub_4()
    {
        __asm
        {
            
            mov eax, offset ShellCode
           ;_emit 伪指令在当前文本段落的当前位置定义一个字节。 _emit 伪命令类似于 MASM 的 DB 指令。
            _emit 0xFF  
            _emit 0xE0
    
        }
    }

    这种方法虽然可以成功执行,但是我也不知道0XFF,0XE0 起到了什么作用。

     
  • 相关阅读:
    读书笔记,《我还是喜欢东京——带你感受城市细节》
    学习笔记:Maven的ArcheType的学习笔记
    如何从中企动力(新网)转移域名到阿里云(万网)
    Maven自定义Archetype(zz)
    读书笔记,《Java 8实战》第五章,使用流
    读书笔记,《Java 8实战》,第四章,引入流
    读书笔记,《Java 8实战》,第三章,Lambda表达式
    读书笔记,《Java8实战》第一章,为什么要关心 Java8
    读书笔记,《深入理解java虚拟机》,第三章 垃圾收集器与内存分配策略
    行业知识:关于发电量与碳排放和等效植树的换算关系
  • 原文地址:https://www.cnblogs.com/kekoukele987/p/7456261.html
Copyright © 2011-2022 走看看