shellcode编写要点:

因为shellcode作为一段能独立运行的代码,对于一些使用的函数、变量等有一定的要求。
1、不能使用全局变量
2、不能使用常量字符串
3、编译时使用release编译
4、需要指定出程序入口点
5、对于一些需要接收外部输入或是创建某些进程或线程时,需要手动创建内存进行保存

简单例子,实现弹窗:

动态加载api,通过实现getprocaddress对指定dll的函数进行加载,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
FARPROC getProcAddress(HMODULE hModuleBase)
{
    PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
    PIMAGE_NT_HEADERS32 lpNtHeader = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + lpDosHeader->e_lfanew);
    if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size) {
        return NULL;
    }
    if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) {
        return NULL;
    }
    PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    PDWORD lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);
    PWORD lpword = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);
    PDWORD lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions);
 
    DWORD dwLoop = 0;
    FARPROC pRet = NULL;
    for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++) {
        char* pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase);
 
        if (pFunName[0] == 'G' &&
            pFunName[1] == 'e' &&
            pFunName[2] == 't' &&
            pFunName[3] == 'P' &&
            pFunName[4] == 'r' &&
            pFunName[5] == 'o' &&
            pFunName[6] == 'c' &&
            pFunName[7] == 'A' &&
            pFunName[8] == 'd' &&
            pFunName[9] == 'd' &&
            pFunName[10] == 'r' &&
            pFunName[11] == 'e' &&
            pFunName[12] == 's' &&
            pFunName[13] == 's')
        {
            pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hModuleBase);
            break;
        }
    }
    return pRet;
}
查找kernel32基地址:

图片描述
图片描述
图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 根据反编译几次来查找kernel32地址
__declspec(naked) DWORD getKernel32()
{
    __asm
    {
        mov eax, fs: [30h]
        mov eax, [eax + 0ch]
        mov eax, [eax + 14h]
        mov eax, [eax]
        mov eax, [eax]
        mov eax, [eax + 10h]
        ret
    }
}
去掉全局变量
1
2
3
typedef HMODULE(WINAPI* FN_LoadLibraryW)(LPCWSTR lpLibFileName);
    char xyLoadLibraryW[] = { 'L','o','a','d','L','i','b','r','a','r','y','W',0 };
    FN_LoadLibraryW fn_LoadLibraryW = (FN_LoadLibraryW)fn_GetProcAddress((HMODULE)getKernel32(), xyLoadLibraryW);
指定入口点(如果不指定编译器会加一些别的代码进来,导致shellcode非常大)

图片描述

生成shellcode:

release模式编译好exe后,以16进制打开,找到.text节数据
图片描述
.text节从0x00000400开始,那么现在就找一段连续的数据,不一定要到最大地址。
图片描述
选中后以C格式化方式复制下载,放到加载器中。

1
2
3
4
5
6
7
8
9
10
11
12
#include <windows.h>
 
unsigned char a[] = { 0x55,  0x8B,  0xEC,  0x83,  0xEC,  0x5C,  0x53,  0x56,  0x57,  0xE8,  0x72,  0x01,  0x00,  0x00,  0x8B,  0xD0,  0x33,  0xDB,  0x8B,  0x42,  0x3C,  0x39,  0x5C,  0x10,  0x7C,  0x0F,  0x84,  0x9F,  0x00,  0x00,  0x00,  0x8B,  0x74,  0x10,  0x78,  0x85,  0xF6,  0x0F,  0x84,  0x93,  0x00,  0x00,  0x00,  0x8B,  0x44,  0x16,  0x24,  0x33,  0xC9,  0x8B,  0x7C,  0x16,  0x20,  0x03,  0xC2,  0x89,  0x45,  0xFC,  0x03,  0xFA,  0x8B,  0x44,  0x16,  0x1C,  0x8B,  0x74,  0x16,  0x18,  0x03,  0xC2,  0x89,  0x45,  0xF8,  0x4E,  0x66,  0x0F,  0x1F,  0x44,  0x00,  0x00,  0x8B,  0x04,  0x8F,  0x03,  0xC2,  0x80,  0x38,  0x47,  0x75,  0x4E,  0x80,  0x78,  0x01,  0x65,  0x75,  0x48,  0x80,  0x78,  0x02,  0x74,  0x75,  0x42,  0x80,  0x78,  0x03,  0x50,  0x75,  0x3C,  0x80,  0x78,  0x04,  0x72,  0x75,  0x36,  0x80,  0x78,  0x05,  0x6F,  0x75,  0x30,  0x80,  0x78,  0x06,  0x63,  0x75,  0x2A,  0x80,  0x78,  0x07,  0x41,  0x75,  0x24,  0x80,  0x78,  0x08,  0x64,  0x75,  0x1E,  0x80,  0x78,  0x09,  0x64,  0x75,  0x18,  0x80,  0x78,  0x0A,  0x72,  0x75,  0x12,  0x80,  0x78,  0x0B,  0x65,  0x75,  0x0C,  0x80,  0x78,  0x0C,  0x73,  0x75,  0x06,  0x80,  0x78,  0x0D,  0x73,  0x74,  0x07,  0x41,  0x3B,  0xCE,  0x76,  0xA3,  0xEB,  0x0F,  0x8B,  0x45,  0xFC,  0x8B,  0x5D,  0xF8,  0x0F,  0xB7,  0x04,  0x48,  0x8B,  0x1C,  0x83,  0x03,  0xDA,  0x8D,  0x45,  0xD0,  0xC7,  0x45,  0xD0,  0x4C,  0x6F,  0x61,  0x64,  0x50,  0xC7,  0x45,  0xD4,  0x4C,  0x69,  0x62,  0x72,  0xC7,  0x45,  0xD8,  0x61,  0x72,  0x79,  0x57,  0xC6,  0x45,  0xDC,  0x00,  0xE8,  0xA0,  0x00,  0x00,  0x00,  0x50,  0xFF,  0xD3,  0x33,  0xC9,  0xC7,  0x45,  0xA4,  0x75,  0x00,  0x73,  0x00,  0x66,  0x89,  0x4D,  0xB8,  0x8D,  0x4D,  0xE0,  0x51,  0x8D,  0x4D,  0xA4,  0xC7,  0x45,  0xA8,  0x65,  0x00,  0x72,  0x00,  0x51,  0xC7,  0x45,  0xAC,  0x33,  0x00,  0x32,  0x00,  0xC7,  0x45,  0xB0,  0x2E,  0x00,  0x64,  0x00,  0xC7,  0x45,  0xB4,  0x6C,  0x00,  0x6C,  0x00,  0xC7,  0x45,  0xE0,  0x4D,  0x65,  0x73,  0x73,  0xC7,  0x45,  0xE4,  0x61,  0x67,  0x65,  0x42,  0xC7,  0x45,  0xE8,  0x6F,  0x78,  0x57,  0x00,  0xFF,  0xD0,  0x50,  0xFF,  0xD3,  0x33,  0xC9,  0xC7,  0x45,  0xBC,  0x53,  0x00,  0x68,  0x00,  0x51,  0x66,  0x89,  0x4D,  0xCC,  0x66,  0x89,  0x4D,  0xF4,  0x8D,  0x4D,  0xEC,  0x51,  0x8D,  0x4D,  0xBC,  0xC7,  0x45,  0xC0,  0x65,  0x00,  0x6C,  0x00,  0x51,  0x6A,  0x00,  0xC7,  0x45,  0xC4,  0x6C,  0x00,  0x63,  0x00,  0xC7,  0x45,  0xC8,  0x6F,  0x00,  0x64,  0x00,  0xC7,  0x45,  0xEC,  0x54,  0x00,  0x45,  0x00,  0xC7,  0x45,  0xF0,  0x53,  0x00,  0x54,  0x00,  0xFF,  0xD0,  0x5F,  0x5E,  0x33,  0xC0,  0x5B,  0x8B,  0xE5,  0x5D,  0xC3,  0xCC,  0xCC,  0xCC,  0xCC,  0xCC,  0xCC,  0xCC,  0xCC,  0x64,  0xA1,  0x30,  0x00,  0x00,  0x00,  0x8B,  0x40,  0x0C,  0x8B,  0x40,  0x14,  0x8B,  0x00,  0x8B,  0x00,  0x8B,  0x40,  0x10,  0xC3,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00 };
 
int EntryMain() {
    DWORD temp;
 
    //创建内存
    VirtualProtect(a, sizeof(a), PAGE_EXECUTE, &temp);
    //执行shellcode
    ((void(*)())a)();
}
特别注意:

如果你需要实现的是例如一个通信shellcode,那么你应该对接收数据手动的创建一块内存进行存储,如果只是简单的动态加载了api,但是你现在是以shellcode的方式进行执行,会引发内存异常,这是因为程序在执行这块数据的时候没有找到。

1
2
3
4
5
6
7
8
//部分代码
 
//接收数据
if (Myrecv(sock, (char*)&payloadLen, sizeof(payloadLen), 0) != sizeof(payloadLen)){
        goto Fail;
    }
//划分内存,用于存储接收数据
char* orig_buffer = (char*)MyVirtualAlloc(NULL, payloadLen, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

最新回复:
wbqsohucom 2024-01-16 14:40:29
学习了,谢谢楼主分享
AsuraCoder 2024-03-03 15:15:32
感觉不如直接用asmjit生成汇编啊
bestbird 2024-03-03 22:32:16
老实说,用高级语言写ShellCode还是Delphi最方便。。。。。。

1、字符串随便用。例如:
    @fnLocalAlloc := fnGetProcAddress(hKernel32, FixPAnsiChar('LocalAlloc'));
        if @fnLocalAlloc = nil then Exit;

        @fnLocalReAlloc := fnGetProcAddress(hKernel32, FixPAnsiChar('LocalReAlloc'));
        if @fnLocalReAlloc = nil then Exit;

        @fnLocalFree := fnGetProcAddress(hKernel32, FixPAnsiChar('LocalFree'));
        if @fnLocalFree = nil then Exit;

        @fnIsBadReadPtr := fnGetProcAddress(hKernel32, FixPAnsiChar('IsBadReadPtr'));
        if @fnIsBadReadPtr = nil then Exit;

原理:Delphi的字符串编译后是跟函数放在一起的。

所以,写的时候可以一边写一边调试,写完就已经是ShellCode了,不用二次加工。

function EnumWindowsProc(Wnd: hwnd; Param: Pointer): BOOL; stdcall;
var
  szClassName: array[0..MAX_PATH] of AnsiChar;
begin
  WindowsAPI^.fnGetClassNameA(Wnd, szClassName, MAX_PATH);

  if WindowsAPI.fnlstrcmpiA(szClassName, FixPAnsiChar('MyWINDOW')) = 0 then
  begin
    if WindowsAPI.pPath.bIsEnabled then 
      WindowsAPI.fnPostMessageW(Wnd, WM_LOCAL_SETCONTEXT, 1, 0)
    else
      WindowsAPI.fnPostMessageW(Wnd, WM_LOCAL_SETCONTEXT, 0, 0);
  end;
  Result := True;
end;

procedure UpdateState(bEnable: Boolean);
begin
  WindowsAPI.pPath.bIsImeEnabled := bEnable;
  WindowsAPI.fnEnumThreadWindows(0, TFNWndEnumProc(FixPAnsiChar(@EnumWindowsProc)), 0);
end;

2、64位支持内镶汇编。

3、调试完毕后:

  nCodeSize := PAnsiChar(@CodeEnd) - PAnsiChar(@Path);
  SetLength(Bytes, nCodeSize);
  CopyMemory(@Bytes[0], @Path, nCodeSize);
  save to file,Done.

bestbird 2024-03-04 01:12:47

补充一点:上面类似 WindowsAPI.pPath这些,其实这里WindowsAPI相当于全局变量,也是Delphi才可以轻易实现的,VS要实现非常困难。

我平时写ShellCode就跟正常程序一样编写,直接新建一个工程然后在上面放一个按钮,写完、直接IDE里面调试完,在按钮事件调用最后那段保存代码即可。如果不用存为ShellCode,就是普通的程序了。

很强强 2024-03-07 17:56:14
bestbird 补充一点:上面类似&nbsp;WindowsAPI.pPath这些,其实这里WindowsAPI相当于全局变量,也是Delphi才可以轻易实现的,VS要实现非常困难。我平时写ShellCode ...
感谢,之前没了解到,就直接用C写
很强强 2024-03-07 17:58:03
AsuraCoder 感觉不如直接用asmjit生成汇编啊
感谢分享,之前没了解到
chengqiyan 2024-03-08 12:09:41
keystone+capstone就能写 而且代码量少90%
很强强 2024-03-23 14:56:32
chengqiyan keystone+capstone就能写 而且代码量少90%
还是比较习惯写C语言,其他的语言看着有点不舒服