zoukankan      html  css  js  c++  java
  • shellcode编写

    1.最简单的不考虑平台性等等问题

    HANDLE libHandle;
    char dllbuf[22] = "user32.dll";
    libHandle = LoadLibraryA(dllbuf);
    
    __asm
    {
    sub esp, 0x440
    xor ebx, ebx
    
    //下面3条代码是为了构建字符串, ebx=0是为了作为字符串的NULL结尾字节, 此时通过esp作为字符串参数传递目标函数即可使用freesec这个字符串
    push ebx
    push 0x636573 // sec
    push 0x65657266 //free
    
    mov eax,esp
    push ebx  //0
    push eax   //字符串freesec
    push eax
    push ebx  //0
    mov eax ,0x77d804ea //MessageBoxA的地址
    call eax  //MessageBoxA 调用
    push ebp
    mov eax,0x7c81cdda  //exit()的地址
    call eax   //退出程序
    }
    
    return 0;

    这里主要学会了如何在shellcode中构建字符串和使用字符串

    2.在xp及2003 server 动态定位API

     不同的操作系统版本,打补丁的版本会使得各个系统dll中的代码,数据等信息. 所以函数地址直接硬编码肯定不行.需要动态获得API地址

    获取kernel32.dll的API地址原理:

    (1) fs段寄存器(段选择子)可以找到当前线程环境块 TEB

    (2) 线程环境块偏移0x30的地方存放着指向进程环境块PEB的指针

    (3) 进程环境块偏移0xc地方存放指向PEB_LDR_DATA结构体指针,其中存放着已经被进程装载的动态链接库的信息

    (4) PEB_LDR_DATA结构偏移0x1c存放着指向模块初始化链表头指针InInitializationOrderModuleList

    (5) InInitializationOrderModuleList中按顺序存放着pe装入时模块信息. 第一个是ntdll.dll , 第2个是kernel32.dll. 然后在偏移0x8字节使其加载基址

    (6) 然后通过pe文件基址找到导出表即可

    通过导出表获取API地址需要对API名称字符串进行对比,但是直接使用字符串对比会使得shellcode过于长. 因此考虑使用计算hash值进行比较.如下代码

    unsigned int hash(char* funcName)
    {
    unsigned int val = 0;
    while (*funcName)
    {
    val = ((val << 16) | (val >> 16)) + (unsigned int)*funcName; //对函数名每个字符的ascii值循环右移16bit,注意设计的hash函数计算结果

    不能存在某个字节是0, 因为 00会截断字符串
    funcName++;
    }
    return val;
    }

    移位指令和累加都比较简单,只要先计算好想要加载的api的hash值,然后通过对导入表中的api计算其hash值,最后对2者相比即可

    经计算得:

    MessageBoxA 's hash is 01F90236

    LoadLibraryA 's hash is 02350261

    ExitProcess 's hash is 02340245

    然后汇编代码:

        __asm
        {
            cld; clear DF flag
            //压栈时注意将同一个dll中的API放在一起进行压栈,这样可以方便切换dll基址,节约代码量
            //LoadLibraryA必须先获取到并保存,后面加载模块时还会用到
            push 0x01F90236; MessageBoxA's hash
            push 0x02340245; ExitProcess's hash
            push 0x02350261; LoadLibraryA's hash
            mov esi, esp;获取hash值地址到esi
            lea edi, [esi - 0xc];
            //现有的重要寄存器:esi指向3个API的hash的地址,edi指向hash值的地址下方0xc处目的是为了保存3个
            //API的真正地址
    
            xor ebx, ebx; ebx = 0
            mov bh,0x04
            sub esp, ebx; 开辟栈空间
    
    
            mov bx, 0x3233
            push ebx
    
            push 0x72657375  //"user32"
            push esp
            xor edx, edx//edx=0
    
    
            mov ebx, fs:[edx + 0x30];ebx指向peb
            mov ecx,[ebx+0x0c]
            mov ecx,[ecx+0x1c]//ecx为模块链表头指针,即ecx值为ntdll所属的链表节点的地址
            mov ecx,[ecx]        //链表节点前4字节存储下一个节点地址,赋值给了ecx
            mov ebp,[ecx+0x08]//偏移8处为该模块的基址即kernel32.dll基址
            //此时就esp,ebp,esi,edi有用,其他寄存器可以修改.edx=0,可以直接用
    
    
    find_lib_functions:
            lodsd //将dword大小的数据从esi写到eax中,此时df是置位的,并增加esi指向下一个dword.实际上就是一个一个地获取APIhash
            cmp eax, 0x01F90236 //以之前对APIhash压栈的模块顺序的第一个API进行比较,方便模块切换
            jnz find_functions
    
            //下面2个xchg指令是交换2个寄存器值,当执行到该处时eax是当前要获取的API,保存到ebp中防止被loadlibrary
            //函数修改,因为执行到这里说明要切换dll了,所以ebp刚好又通过返回值更新了
            xchg eax,ebp
            call [edi-0x8]
            xchg eax,ebp
            //此时,esp,ebp,eax,edi,esi是重要寄存器
    find_functions:
            //此时各寄存器作用:eax=目标函数名,ebx无,ecx无,edx=0,ebp=dll基址,esi=hash,edi=获取的API地址
            pushad
            mov eax,[ebp+0x3c]//eax=peHead=nthead
            mov ecx,[ebp+eax+0x78]//nthead+0x78=导出表rva
            add ecx,ebp    //这里在内存中,可以直接用基址加上rva得到导出表绝对地址
            mov ebx, [ecx+0x20]    //得到存储名字的数组的rva,数组存的是名字字符串地址rva
            add ebx,ebp        //得到绝对地址
            xor edi,edi        
            //edi作为该数组索引,ebx作为数组基址,esi存储API字符串,eax作为字符串的单个字符串,edx作为临时的hash
    
    next_function_loop:
            inc edi
            mov esi,[ebx+edi*4]    //将地址rva存放到esi
            add esi,ebp        //获取绝对地址
            cdq            //因为eax在这里不会>f0000000 所以edx的每一bit都是0.其实就是将edx赋值为0,因为
                        //后续代码会将edx作为hash的临时保存, 所以在计算hash前需要清0
    
    hash_loop:
            movsx eax,byte ptr [esi]    
            cmp al,ah    //当到字符串结尾时al=0,ah此时是0,就进行比较,否则继续计算hash
            jz compare_hash//edx保存着hash值
            ror edx ,16
            add edx,eax
            inc esi
            jmp hash_loop
    
    compare_hash:
            cmp edx,[esp+0x1c]
            jnz next_function_loop
    
            //找到该函数后进行的操作
            mov ebx,[ecx+0x24] //将ebx指向导出索引表即orginal table
            add ebx, ebp
    
            mov di, [ebx + 2 * edi]//找到对应序号
            mov ebx, [ecx + 0x1c] //将ebx指向导出地址表,寻找真正的函数地址
            add ebx, ebp
            add ebp, [ebx + 4 * edi]//将函数地址存放到ebp
            xchg eax, ebp    //又存放到eax
            pop edi//弹出原来的edi,即获取到的api地址缓存. 因为pushad将edi最后压栈的
            stosd//将eax赋值给edi并且edi增加一个dword大小
            push edi//重新保存edi
            popad
            cmp eax, 0x01F90236 //判断是否是最后一个要获取的函数,是的话就进行函数调用了
            jne find_lib_functions
    
    function_call:
            xor ebx, ebx
            push ebx
            push 0x636573 // sec
            push 0x65657266  //free
    
            mov eax, esp
            push ebx
            push eax
            push eax
            push ebx
    
            call [edi-0x4]
            push ebx
            call [edi-0x8]
            nop
            nop
        }

    二进制数据为:

    "xFCx68x36x02xF9x01x68x45x02x34x02x68x61x02x35x02"
    "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53"
    "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B"
    "x49x1Cx8Bx09x8Bx69x08xADx3Dx36x02xF9x01x75x05x95"
    "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59"
    "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x33xD2x0FxBEx06"
    "x3AxC4x74x08xC1xCAx10x03xD0x46xEBxF1x3Bx54x24x1C"
    "x75xE3x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDD"
    "x03x2CxBBx95x5FxABx57x61x3Dx36x02xF9x01x75xA8x33"
    "xDBx53x68x73x65x63x00x68x66x72x65x65x8BxC4x53x50"
    "x50x53xFFx57xFCx53xFFx57xF8"

    测试程序:

    // ts.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    
    char shellcode[]=
    "xFCx68x36x02xF9x01x68x45x02x34x02x68x61x02x35x02"
    "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53"
    "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B"
    "x49x1Cx8Bx09x8Bx69x08xADx3Dx36x02xF9x01x75x05x95"
    "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59"
    "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x33xD2x0FxBEx06"
    "x3AxC4x74x08xC1xCAx10x03xD0x46xEBxF1x3Bx54x24x1C"
    "x75xE3x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDD"
    "x03x2CxBBx95x5FxABx57x61x3Dx36x02xF9x01x75xA8x33"
    "xDBx53x68x73x65x63x00x68x66x72x65x65x8BxC4x53x50"
    "x50x53xFFx57xFCx53xFFx57xF8";
    
    

    int _tmain(int argc, _TCHAR* argv[])
    {
      __asm
      {
        xor eax,eax
        lea eax,[eax+shellcode]
        push eax
        ret
      }
      return 0;
    }

    如图:

    解决shellcode在高版本windows平台系统兼容性

    将上述shellcode在win8.1 64机器上无法运行

    经过调试发现: (在cdq处下条件记录断点)

    00401079 COND: func name= = LCIDToLocaleName
    00401079 COND: func name= = LCMapStringA
    00401079 COND: func name= = LCMapStringEx
    00401079 COND: func name= = LCMapStringW
    00401079 COND: func name= = LeaveCriticalPolicySectionInternal
    00401079 COND: func name= = LeaveCriticalSection
    00401079 COND: func name= = LeaveCriticalSectionWhenCallbackReturns
    00401079 COND: func name= = LoadAppInitDlls
    00401079 COND: func name= = LoadLibraryExA
    00401079 COND: func name= = LoadLibraryExW
    00401079 COND: func name= = LoadResource
    00401079 COND: func name= = LoadStringA
    00401079 COND: func name= = LoadStringBaseExW
    00401079 COND: func name= = LoadStringByReference
    00401079 COND: func name= = LoadStringW
    00401079 COND: func name= = LocalAlloc

    有这个LoadLibraryExA 而没有了LoadLibraryA

    因此将代码中的LoadLibraryA 的hash改为LoadLibraryExA的hash 并修改传递的参数

    修改后代码如下: 

        __asm
        {
            cld; clear DF flag
            //压栈时注意将同一个dll中的API放在一起进行压栈,这样可以方便切换dll基址,节约代码量
            //LoadLibraryA必须先获取到并保存,后面加载模块时还会用到
            push 0x01F90236; MessageBoxA's hash
            push 0x02340245; ExitProcess's hash
            //push 0x02350261; LoadLibraryA's hash
            push 0x02AD02A6; LoadLibraryExA's hash
            mov esi, esp; 获取hash值地址到esi
            lea edi, [esi - 0xc];
            //现有的重要寄存器:esi指向3个API的hash的地址,edi指向hash值的地址下方0xc处目的是为了保存3个
            //API的真正地址
    
            xor ebx, ebx; ebx = 0
                mov bh, 0x04
                sub esp, ebx; 开辟栈空间
    
                xor edx, edx//edx=0
                mov bx, 0x3233
                push ebx
                push 0x72657375  //"user32"
                mov ebx,esp
                push edx        //LoadLibraryExA 第3个参数, 设为0
                push edx        //LoadLibraryExA 第2个参数, 设为0
                push ebx        //LoadLibraryExA 第1个参数, 即为user32.dll
    
    
    
    
                mov ebx, fs:[edx + 0x30]; ebx指向peb
                mov ecx, [ebx + 0x0c]
                mov ecx, [ecx + 0x1c]//ecx为模块链表头指针,即ecx值为ntdll所属的链表节点的地址
                mov ecx, [ecx]        //链表节点前4字节存储下一个节点地址,赋值给了ecx
                mov ebp, [ecx + 0x08]//偏移8处为该模块的基址即kernel32.dll基址
                                        //此时就esp,ebp,esi,edi有用,其他寄存器可以修改.edx=0,可以直接用
    
    
                find_lib_functions:
            lodsd //将dword大小的数据从esi写到eax中,此时df是置位的,并增加esi指向下一个dword.实际上就是一个一个地获取APIhash
                cmp eax, 0x01F90236 //以之前对APIhash压栈的模块顺序的第一个API进行比较,方便模块切换
                jnz find_functions
    
                //下面2个xchg指令是交换2个寄存器值,当执行到该处时eax是当前要获取的API,保存到ebp中防止被loadlibrary
                //函数修改,因为执行到这里说明要切换dll了,所以ebp刚好又通过返回值更新了
                xchg eax, ebp
                call[edi - 0x8]
                xchg eax, ebp
                //此时,esp,ebp,eax,edi,esi是重要寄存器
                find_functions :
            //此时各寄存器作用:eax=目标函数名,ebx无,ecx无,edx=0,ebp=dll基址,esi=hash,edi=获取的API地址
            pushad
                mov eax, [ebp + 0x3c]//eax=peHead=nthead
                mov ecx, [ebp + eax + 0x78]//nthead+0x78=导出表rva
                add ecx, ebp    //这里在内存中,可以直接用基址加上rva得到导出表绝对地址
                mov ebx, [ecx + 0x20]    //得到存储名字的数组的rva,数组存的是名字字符串地址rva
                add ebx, ebp        //得到绝对地址
                xor edi, edi
                //edi作为该数组索引,ebx作为数组基址,esi存储API字符串,eax作为字符串的单个字符串,edx作为临时的hash
    
                next_function_loop :
            inc edi
                mov esi, [ebx + edi * 4]    //将地址rva存放到esi
                add esi, ebp        //获取绝对地址
                cdq            //因为eax在这里不会>f0000000 所以edx的每一bit都是0.其实就是将edx赋值为0,因为
                                //后续代码会将edx作为hash的临时保存, 所以在计算hash前需要清0
                hash_loop :
            movsx eax, byte ptr[esi]
                cmp al, ah    //当到字符串结尾时al=0,ah此时是0,就进行比较,否则继续计算hash
                jz compare_hash//edx保存着hash值
                ror edx, 16
                add edx, eax
                inc esi
                jmp hash_loop
    
                compare_hash :
            cmp edx, [esp + 0x1c]
                jnz next_function_loop
    
                //找到该函数后进行的操作
                mov ebx, [ecx + 0x24] //将ebx指向导出索引表即orginal table
                add ebx, ebp
    
                mov di, [ebx + 2 * edi]//找到对应序号
                mov ebx, [ecx + 0x1c] //将ebx指向导出地址表,寻找真正的函数地址
                add ebx, ebp
                add ebp, [ebx + 4 * edi]//将函数地址存放到ebp
                xchg eax, ebp    //又存放到eax
                pop edi//弹出原来的edi,即获取到的api地址缓存. 因为pushad将edi最后压栈的
                stosd//将eax赋值给edi并且edi增加一个dword大小
                push edi//重新保存edi
                popad
                cmp eax, 0x01F90236 //判断是否是最后一个要获取的函数,是的话就进行函数调用了
                jne find_lib_functions
    
                function_call :
            xor ebx, ebx
                push ebx
                push 0x636573 // sec
                push 0x65657266  //free
    
                mov eax, esp
                push ebx
                push eax
                push eax
                push ebx
    
                call[edi - 0x4]
                push ebx
                call[edi - 0x8]
                nop
                nop
        }

    win8.1 64位测试:

    win7 64位机测试:

  • 相关阅读:
    Xamarin.Forms之Resx
    Xamarin.Forms之OnElementPropertyChanged那些事
    Xamarin.Android之Application的使用
    Xamarin.Forms之FluentValidation(Model验证)
    Xamarin.Forms如何为View添加背景色
    odoo server命令行以及配置文件
    ODOO的命令行调用以及config默认值
    发掘odoo.cli.server.Server的秘密,OpenERP的第三根线头儿
    odoo.cli.main()做了什么?
    odoo.cli.main()指的是哪里?OpenERP的第二根线头儿
  • 原文地址:https://www.cnblogs.com/freesec/p/6481380.html
Copyright © 2011-2022 走看看