zoukankan      html  css  js  c++  java
  • Shellcode编程小技巧

    工作需要,需要注入其他程序监控一些东西,检测到的数据通过WM_COPY 消息发送给显示窗体。(大体是这样的还没定稿)

    ##1 选择一个框架 ##

    tombkeeper/Shellcode_Template_in_C

    mattifestation/PIC_Bindshell

    框架选择上,我选择了第一个,妇科圣手tomkeeper提供的框架,对比发现这个比较简单。

    ##2 搭建框架 ##

    我使用的是vs10,(vs15我试了下各种错误 ,可能是自己笨吧,用管了vs10了)

    - 新建个解决方案(名字看自己喜好)

    - 解决方案上新添个空的 win32console 工程

    - 再在工程上新建个 xxxx.c的文件,把tomkeeper工程中的shellcode.c的内容复制过来

    - 编译试试吧

    - 其他的2个工具文件代码,分别是生成字符串用的工具(str2intarr.c) 和 生成函数hash宏定义的 gethash.c (方法同上)

    - 我自己定义了个字符串生成的增强版(如下,我这个是cpp文件,和上面的有所不同),根据 第2个参数 <a/w> 将字符串转为多字节字符串和宽字节字符串。

    #include <Windows.h>
    
    int _tmain(int argc, char* argv[])
    {
        if (argc < 2)
        {
            printf("Use %s <a/w> <string> 
    ",argv[0]);
        }else
        {
            printf("DWORD str[] = {
    	");
    
            if (argv[1][0] == 'a')
            {
                int strLen = strlen(argv[2]);
                strLen = (strLen /4 +1)*4;
                int n = strLen /4 ;
                DWORD* pArry = (DWORD*)malloc(strLen);
                ZeroMemory(pArry,strLen);
                memcpy(pArry,argv[2],strlen(argv[2]));
                
                for (int i=0;i<n;i++ )
                {
                    
                    printf("0x%08x",*(pArry+i));
                    if (i != n-1)
                    {
                        printf(", ");
                    }
                    if (i && i%9 ==8)
                    {
                        printf("
    	");
                    }
                }
            
            }else
            {
                int strLen = strlen(argv[2]);
                int nAlloc = strLen*2+4;
                DWORD* pWchar = (DWORD*)malloc(nAlloc);
                ZeroMemory(pWchar,nAlloc);
                MultiByteToWideChar(GetACP(),NULL,argv[2],strLen,(WCHAR*)pWchar,strLen);
                int n = (strLen/2+1);
                for (int i=0;i< n;i++)
                {
                    printf("0x%08x",*(pWchar+i));
                    if (i != n-1)
                    {
                        printf(", ");
                    }
                    if (i && i%9 ==8)
                    {
                        printf("
    	");
                    }
                }
    
            }
            printf("
    };");
            //getchar();
        }
        return 0;
    }

    ##3 编译选项的设置  ##

    编译选项的设置直接关系到shellcode大小的准确计算,代码的排列顺序,以及代码的精简程度,还有 GS。。。 是否被添加。

    - 设置为release模式,我就不多说了,大家都会

    - 去掉Gs 选项,如果不去掉shellcode()中会有gs检测函数的插入,这是我们不想要的。

    - opt, 调试时我选择 disable, 发布时我使用 minsize (体积小些)。disable代码顺序是一样的就是多了不少int3 (cc)。

    - linker 下的debuging :generate Debug info 我设置为YES。便于调试,对shellcode无影响

    ##4 测试shellcode的正确性 ##

    tomkeeper的工程能将shellcode以c数组的形式输出出来,我增加了个函数可以将 shellcode dump到2进制文件中。

    - 利用c数组, char shellcode[] = {0xb8,....}; __asm{ call shellcode} ,这样的话要注意将 工程的NX关闭(数据执行保护DEP) ,不会的自行补脑

    - 第2种将 dump出的文件读入到申请的可读可写可执行的内存中,然后 __asm { call lpMem} .(我用的这种办法,不用每次进行复制黏贴,编译,执行)

    FILE* file = 0;
        int iRead = 0;
    
    
        HANDLE hFile =CreateFile("c:\temp\c.sc",GENERIC_WRITE|GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    
        fprintf(stdout,"test shellcode ........... 
    ");
        if (hFile != INVALID_HANDLE_VALUE)
        {
            DWORD dwFileSizeHigh = 0;
            DWORD dwFileSizeLow = GetFileSize(hFile,&dwFileSizeHigh);
            fprintf(stdout,"file size is 0x%x 
    ",dwFileSizeLow);
            BYTE* lpMem = (BYTE*)VirtualAlloc(NULL,dwFileSizeLow+4,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
            ZeroMemory(lpMem,dwFileSizeLow+4);
            ReadFile(hFile,lpMem,dwFileSizeLow,&dwFileSizeHigh,NULL);
            printf("should read %d ,readed %d byte 
    ",dwFileSizeLow,dwFileSizeHigh);
            CloseHandle(hFile);
            //getchar();
            _asm{
                call lpMem
            }
            
        }else
        {
            fprintf(stdout,"open file error [0x%x] 
    ",GetLastError());
        }

    我先把shellcode dump到 c:\temp\c.sc 中,在用这个代码去测试它。

    ##5 小技巧 (排错、编程 经验) ##

    - 不要用 *1.02 这种浮点算法,会死在这种命令上,那个2B2040绝对会出错,所以尽量别用了。

    000300B3 DF6D D8 FILD QWORD PTR SS:[EBP-28]
    000300B6 DC0D 40202B00 FMUL QWORD PTR DS:[2B2040]
    000300BC D97D D6 FSTCW WORD PTR SS:[EBP-2A]

    - 如何在shellcode后面追加上 配置信息便于测试呢

    shellcode会带着些配置信息很长见的。 一般这些都是后期用工具合并上去的。

    对于我们调试每次都去追加修改太麻烦了。所以要用到 #define NAKED __declspec(naked) 和 __asm{ __emit 0xbb }

    __emit 可以使数据以代码的形式写入到代码段,也就是shellcode中。记得和 DWORD str[] = {0xabcdef,0xbaddef..}这种技术分开。这样就实现了任意数据的存入

    NAKED 进制优化:当我们使用minsize选项的时候,这些代码可能就被优化掉,我试过了,所以才用到了NAKED,注意函数必须有 参数,声明不用实现用。

    最后:在永远不会调用到的地方调用下,要不编译器一定会给你省略掉

    void  StoreConfigData(DWORD dwVoid);
    
    NAKED void  StoreConfigData(DWORD dwVoid)
    { 
    
    __asm{
    __emit 0xff 
    __emit 0xfe 
    __emit 0xff
    
    .}

     我这里专门写了个小工具将bin文件转为上面的形式

    // bin2emit.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include <Windows.h>
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        if (argc < 2)
        {
            printf("Use %s <path of bin> 
    ",argv[0]);
        }else
        {
            printf("path of bin: %s 
    ",argv[1]);
            HANDLE hFile = CreateFile(argv[1],GENERIC_WRITE|GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
            if (hFile == INVALID_HANDLE_VALUE)
            {
                printf("open '%s' error 
    ",argv[1]);
                return 0;
            }
            DWORD dwFileSizeLow,dwFileSizeHigh;
            dwFileSizeLow = GetFileSize(hFile,&dwFileSizeHigh);
    
            LPVOID lpBuffer = HeapAlloc(GetProcessHeap(),0x08,dwFileSizeLow);
    
            if (!ReadFile(hFile,lpBuffer,dwFileSizeLow,&dwFileSizeHigh,0))
            {
                CloseHandle(hFile);
                printf("read file '%s' error 
    ",argv[1]);
                HeapFree(GetProcessHeap(),HEAP_NO_SERIALIZE,lpBuffer);
                return 0;
            }
            CloseHandle(hFile);
            printf("
    
    
     __asm{
    	");
            for ( int i=0;i < dwFileSizeLow;i++)
            {
                printf("__emit 0x%02x 
    	", *(((LPBYTE)lpBuffer)+i) );
            }
            printf("
    }");
            
        }
        return 0;
    }

    - 函数重定位

    我们的shellcode都是经过 call联系出来的,在调用的时候编译器已经算好了偏移,所以不存在重定位的问题,但是,向createthread这种将传入函数地址作为参数的函数就会出先这种问题;

    解决的办法就是要动态计算地址。

    //记得-8 
    DWORD GetEip()
    {
        __asm{
            call get_eip
    get_eip:
            pop eax
        }
    }
    void WINAPI AdjustFunction(struct ADJUSTFUNCTIONS* pFuns)
    {
        DWORD dwGetEip;
        
        dwGetEip = GetEip(); 
        dwGetEip -= 8;    //计算得到 GetEip()函数在内存中的地址
        
        pFuns->lpThreadProc = (DWORD)dwGetEip+( (DWORD)ThreadProc- (DWORD)GetEip ); // 以GetEip()函数做参考动态计算出其他 函数的地址
    }
    // 调用的时候这样写
    hThread = modules.kernel32.CreateThread(NULL,0,adjustFuns.
    lpThreadProc,&param,0,NULL);

    - 函数动态获取

    开发中发现通过函数hash获取时会出现和GetProcAddress返回地址不同的问题。出现以上问题有3种可能:

    1.hash碰撞了,没办法那只能找替换函数了,例如CreateFile 函数,可以看看ntdll重的zw 、nt、rtl、有没有名称相似的函数,我们知道很多函数都是层层封装的,不怕找不到的。再说tk教主的hash计算时带着i也就是长度进行了计算,大大减小碰撞几率。

    2.函数名搞错了,注意 xxxA xxxB xxxx 有些函数有A和W版本,你可要看清楚自己要的是哪个,但有些函数没有A和W区分。还有很多函数是通过lib链接的,根本导不出函数如 —beginthreadex,所以还是用 createthread代替吧 。

    3. 还有些函数导出地址不是最终真正的函数地址,而是像ntdll.xxxxx的这种形式的内存地址,这样就还要再次去计算才能得到,而GetProcAddress这个函数应该进行了2次转换。例如:HeapAlloc是kernel32导出的,通过hash找到的是ntdll!RtlAllocateHeap字符串的地址(大约是这个样子,反正是个ntdll开通的),所以还是直接去ntdll下找 RtlAllocateHeap 它吧,所以 找函数尽量去ntdll中招吧。

    http://www.onlinedown.net/soft/59825.htm

    http://www.pc6.com/softview/SoftView_16910.html  dllviewer 可以看看dll导出函数表

     - 初始化局部变量 

    struct MODULES modules = {0x00};

    这种初始化还是不要有的好,我跟踪发现这样会让执行流程变乱。

    还是用自己写的memset吧。

    ps:shellcode真省地方,写了这么多代码还没2K呢。 

    # 自我实现一些函数 #

    有些函数没必要动态获取,太麻烦了。很多函数很简单,用代码就能实现

    - GetProcessHeap  (测试环境 win7 x64 win8 x64 win10 x64)

    HANDLE WINAPI GetProcessHeap()
    {
        __asm{
    
            mov eax,fs:[0x30]
            mov eax,[eax+0x18]
        }
    }

    - memcpy

    void WINAPI zMemcpy(LPVOID lpDest,LPVOID lpSrc,DWORD dwSize)
    {
        if (!lpDest || !lpSrc ||!dwSize)
        {
            _asm int 3
        }
        __asm{
            mov ecx,dwSize
            mov edi,lpDest
            mov esi,lpSrc
            rep movsb
        }
    }

     为了兼容x64shellcode开发,这里将内嵌的汇编都去掉了。

    最近发现在大数据除法和取余时,vs会嵌入alldiv、allrem等函数,这将直接造成,shellcode跑飞

    签名档: 从事网络安全和编程的我,很希望能找到志同道合的朋友交流。 欢迎cn博客的好友拍砖,留言。
  • 相关阅读:
    C# 装箱原型
    C# 反射浅谈(一)
    javascript介绍(二)
    javascript介绍(一)
    C#中 托管资源和非托管资源
    varchar && nvarchar 闲谈
    高内聚&&低耦合
    【android】移植IOS视图响应陀螺仪交互行为
    【android】如何实现猿题库题目的排版
    开心工作标准的硬件环境
  • 原文地址:https://www.cnblogs.com/M4ster/p/shellcode_write.html
Copyright © 2011-2022 走看看