[原创]通用shellcode开发原理与实践
2023-4-6 09:25:28 Author: bbs.pediy.com(查看原文) 阅读量:14 收藏

学习来源:0day安全一书

概述

shellcode即一段植入进程的代码。

定位shellcode

在实际漏洞利用过程中,由于动态链接库的装入和卸载等原因,Windows进程的函数栈帧很有可能会产生”位移“,即shellcode在内存中的地址是动态变化的。因此我们需要找到一个跳板,使得程序的执行流程总是能找到我们的shellcode。

jmp esp

在上一节中verify_password函数返回后栈中的情况如下所示:

  1. 实线体现了代码植入的流程:将返回地址淹没为我们手工查出的shellcode起始地址 0x0012FAF0,函数返回时,这个地址被弹入EIP寄存器中,处理器按照EIP寄存器中的地址取指令,最后栈中的数据被处理器当成指令执行。
  2. 虚线则点出了这样一个细节:在函数返回时候,ESP恰好只想栈帧中返回地址的后一个位置。

图一 栈帧位移示意图
图片描述

图二 溢出发生时栈、寄存器与代码之间的关系

图片描述

一般情况下,ESP寄存器中的地址总是指向系统栈中且不会被溢出的数据破坏。函数返回时,ESP所指的位置恰好是我们所淹没的返回地址的下一个位置,如图三。

图三 使用"跳板"的溢出利用流程

图片描述

由于ESP寄存器在函数返回后不被溢出数据干扰,且始终指向返回地址之后的位置,我们就可以使用图三所示的这种定位shellcode的方法来进行动态定位。

  1. 用内存中的任意一个jmp esp指令的地址覆盖函数的返回地址,而不是用原来的手工查询出的shellcode起始地址直接覆盖。
  2. 函数返回地址被重定向去执行内存中的这条jmp esp指令,而不是直接开始执行shellcode。
  3. 由于ESP在函数返回时仍指向栈区(函数返回地址之后),jmp esp指令被执行后,处理器会到栈区函数返回地址之后的地方取指令执行。
  4. 重新布置shellcode。在淹没函数返回地址后,继续淹没一片栈空间。将缓冲区前边一段地方用任意数据填充,把shellcode恰好摆放在函数返回地址之后。这样,jmp esp指令执行过后会恰好跳进shellcode。

这种定位shellcode的方法使用进程空间里的一条 jmp esp指令作为"跳板",无论栈帧怎么"位移",都能精确地跳回栈区,从而适应程序运行中shellcode内存地址的动态变化。

当然这只是一种定位shellcode的方式,还有其他许多种定位shellcode的方式

开发通用shellcode

环境

1

2

操作系统: Windows 10 x64

编译器:   vs 2019

定位shellcode使用API的原理

通过手工查出来的API地址会在其他计算机上失效,在shellcode中使用静态函数地址来调用API会使exploit的额通用性收到很大的限制,所以,实际中使用的shellcode必须还要能动态地获得自身所需的API的函数地址。

Windows的API是通过动态链接库中的导出函数来实现的,例如,内存操作等函数在kernel32.dll中实现;大量的图形界面相关的API则在user32.dll中实现。Win32平台下的shellcode使用最广泛的方法,就是通过从进程环境块中找到动态链接库的导出表,并搜索出所需的API地址,然后逐一调用。

几乎所有Win32程序都会加载ntdll.dllkernel32.dll这两个基础的动态链接库。如果想要在win_32平台下定位kernel32.dll中的API地址,可以采取如下办法。

64位系统

  1. 首先通过选择字GS在内存中找到当前存放着指向当前线程环境块TEB。在GS中存储的是TEBGDT(Global Descriptor Table)中的序号,通过GDT获取TEB的基址。
  2. 线程环境块偏移位置为0x60的地方存放着指向进程环境块PEB的指针(即GS[0x30])。
  3. 进程环境块中偏移位置为0x18的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息。
  4. PEB_LDR_DATA结构体偏移位置为0x20 的地方存放着指向模块初始化链表的头指针 InInitializationOrderModuleList
  5. 模块初始化链表 InInitializationOrderModuleList中按顺序存放着 PE 装入运行时初始化模块的信息,第一个链表结点是 ntdll.dll,第二个链表结点就是 kernelbase.dll,第三个节点才是kernel32.dll
  6. 找到属于kernel32.dll的结点后,在其基础上再偏移 0x20 就是 kernel32.dll在内存中的加载基地址。
  7. kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头。
  8. PE 头偏移 0x88 的地方存放着指向函数导出表的指针。

32位系统

  1. 首先通过选择字FS在内存中找到当前存放着指向当前线程环境块TEB。在FS中存储的是TEBGDT(Global Descriptor Table)中的序号,通过GDT获取TEB的基址。
  2. 线程环境块偏移位置为0x30的地方存放着指向进程环境块PEB的指针(即FS[0x30])。
  3. 进程环境块中偏移位置为0x0C的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息。
  4. PEB_LDR_DATA结构体偏移位置为0x1C 的地方存放着指向模块初始化链表的头指针 InInitializationOrderModuleList
  5. 模块初始化链表 InInitializationOrderModuleList中按顺序存放着 PE 装入运行时初始化模块的信息,第一个链表结点是 ntdll.dll,第二个链表结点就是 kernel32.dll
  6. 找到属于kernel32.dll的结点后,在其基础上再偏移 0x08 就是 kernel32.dll在内存中的加载基地址。
  7. kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头。
  8. PE 头偏移 0x78 的地方存放着指向函数导出表的指针。
  9. 至此,我们可以按如下方式在函数导出表中算出所需函数的入口地址,如图四所示。

图四 在shellcode中动态定位API的原理
图片描述

代码实现

使用汇编生成shellcode

x64 弹出计算器shellcode

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

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

/*

; Get kernel32.dll base address

xor rdi, rdi            ; RDI = 0x0

mul rdi                 ; RAX&RDX =0x0

mov rbx, gs:[rax+0x60]  ; RBX = Address_of_PEB

mov rbx, [rbx+0x18]     ; RBX = Address_of_LDR

mov rbx, [rbx+0x20]     ; RBX = 1st entry in InitOrderModuleList / ntdll.dll

mov rbx, [rbx]          ; RBX = 2nd entry in InitOrderModuleList / kernelbase.dll

mov rbx, [rbx]          ; RBX = 3rd entry in InitOrderModuleList / kernel32.dll

mov rbx, [rbx+0x20]     ; RBX = &kernel32.dll ( Base Address of kernel32.dll)

mov r8, rbx             ; RBX & R8 = &kernel32.dll

; Get kernel32.dll ExportTable Address

mov ebx, [rbx+0x3C]     ; RBX = Offset NewEXEHeader

add rbx, r8             ; RBX = &kernel32.dll + Offset NewEXEHeader = &NewEXEHeader

xor rcx, rcx            ; Avoid null bytes from mov edx,[rbx+0x88] by using rcx register to add

add cx, 0x88ff

shr rcx, 0x8            ; RCX = 0x88ff --> 0x88

mov edx, [rbx+rcx]      ; EDX = [&NewEXEHeader + Offset RVA ExportTable] = RVA ExportTable

add rdx, r8             ; RDX = &kernel32.dll + RVA ExportTable = &ExportTable

; Get &AddressTable from Kernel32.dll ExportTable

xor r10, r10

mov r10d, [rdx+0x1C]    ; RDI = RVA AddressTable

add r10, r8             ; R10 = &AddressTable

; Get &NamePointerTable from Kernel32.dll ExportTable

xor r11, r11

mov r11d, [rdx+0x20]    ; R11 = [&ExportTable + Offset RVA Name PointerTable] = RVA NamePointerTable

add r11, r8             ; R11 = &NamePointerTable (Memory Address of Kernel32.dll Export NamePointerTable)

; Get &OrdinalTable from Kernel32.dll ExportTable

xor r12, r12

mov r12d, [rdx+0x24]    ; R12 = RVA  OrdinalTable

add r12, r8             ; R12 = &OrdinalTable

jmp short apis

; Get the address of the API from the Kernel32.dll ExportTable

getapiaddr:

pop rbx                 ; save the return address for ret 2 caller after API address is found

pop rcx                 ; Get the string length counter from stack

xor rax, rax            ; Setup Counter for resolving the API Address after finding the name string

mov rdx, rsp            ; RDX = Address of API Name String to match on the Stack

push rcx                ; push the string length counter to stack

loop:

mov rcx, [rsp]          ; reset the string length counter from the stack

xor rdi,rdi             ; Clear RDI for setting up string name retrieval

mov edi, [r11+rax*4]    ; EDI = RVA NameString = [&NamePointerTable + (Counter * 4)]

add rdi, r8             ; RDI = &NameString    = RVA NameString + &kernel32.dll

mov rsi, rdx            ; RSI = Address of API Name String to match on the Stack  (reset to start of string)

repe cmpsb              ; Compare strings at RDI & RSI

je resolveaddr          ; If match then we found the API string. Now we need to find the Address of the API

incloop:

inc rax

jmp short loop

; Find the address of GetProcAddress by using the last value of the Counter

resolveaddr:

pop rcx                 ; remove string length counter from top of stack

mov ax, [r12+rax*2]     ; RAX = [&OrdinalTable + (Counter*2)] = ordinalNumber of kernel32.<API>

mov eax, [r10+rax*4]    ; RAX = RVA API = [&AddressTable + API OrdinalNumber]

add rax, r8             ; RAX = Kernel32.<API> = RVA kernel32.<API> + kernel32.dll BaseAddress

push rbx                ; place the return address from the api string call back on the top of the stack

ret                     ; return to API caller

apis:                   ; API Names to resolve addresses

; WinExec | String length : 7

xor rcx, rcx

add cl, 0x7                 ; String length for compare string

mov rax, 0x9C9A87BA9196A80F ; not 0x9C9A87BA9196A80F = 0xF0,WinExec

not rax ;mov rax, 0x636578456e6957F0 ; cexEniW,0xF0 : 636578456e6957F0 - Did Not to avoid WinExec returning from strings static analysis

shr rax, 0x8                ; xEcoll,0xFFFF --> 0x0000,xEcoll

push rax

push rcx                    ; push the string length counter to stack

call getapiaddr             ; Get the address of the API from Kernel32.dll ExportTable

mov r14, rax                ; R14 = Kernel32.WinExec Address

; UINT WinExec(

;   LPCSTR lpCmdLine,    => RCX = "calc.exe",0x0

;   UINT   uCmdShow      => RDX = 0x1 = SW_SHOWNORMAL

; );

xor rcx, rcx

mul rcx                     ; RAX & RDX & RCX = 0x0

; calc.exe | String length : 8

push rax                    ; Null terminate string on stack

mov rax, 0x9A879AD19C939E9C ; not 0x9A879AD19C939E9C = "calc.exe"

not rax

;mov rax, 0x6578652e636c6163 ; exe.clac : 6578652e636c6163

push rax                    ; RSP = "calc.exe",0x0

mov rcx, rsp                ; RCX = "calc.exe",0x0

inc rdx                     ; RDX = 0x1 = SW_SHOWNORMAL

sub rsp, 0x20               ; WinExec clobbers first 0x20 bytes of stack (Overwrites our command string when proxied to CreatProcessA)

call r14                    ; Call WinExec("calc.exe", SW_HIDE)

*/

void main() {

  void* exec;

  BOOL rv;

  HANDLE th;

  DWORD oldprotect = 0;

  // Shellcode

  unsigned char payload[] =

    "\x48\x31\xff\x48\xf7\xe7\x65\x48\x8b\x58\x60\x48\x8b\x5b\x18\x48\x8b\x5b\x20\x48\x8b\x1b\x48\x8b\x1b\x48\x8b\x5b\x20\x49\x89\xd8\x8b"

    "\x5b\x3c\x4c\x01\xc3\x48\x31\xc9\x66\x81\xc1\xff\x88\x48\xc1\xe9\x08\x8b\x14\x0b\x4c\x01\xc2\x4d\x31\xd2\x44\x8b\x52\x1c\x4d\x01\xc2"

    "\x4d\x31\xdb\x44\x8b\x5a\x20\x4d\x01\xc3\x4d\x31\xe4\x44\x8b\x62\x24\x4d\x01\xc4\xeb\x32\x5b\x59\x48\x31\xc0\x48\x89\xe2\x51\x48\x8b"

    "\x0c\x24\x48\x31\xff\x41\x8b\x3c\x83\x4c\x01\xc7\x48\x89\xd6\xf3\xa6\x74\x05\x48\xff\xc0\xeb\xe6\x59\x66\x41\x8b\x04\x44\x41\x8b\x04"

    "\x82\x4c\x01\xc0\x53\xc3\x48\x31\xc9\x80\xc1\x07\x48\xb8\x0f\xa8\x96\x91\xba\x87\x9a\x9c\x48\xf7\xd0\x48\xc1\xe8\x08\x50\x51\xe8\xb0"

    "\xff\xff\xff\x49\x89\xc6\x48\x31\xc9\x48\xf7\xe1\x50\x48\xb8\x9c\x9e\x93\x9c\xd1\x9a\x87\x9a\x48\xf7\xd0\x50\x48\x89\xe1\x48\xff\xc2"

    "\x48\x83\xec\x20\x41\xff\xd6";

  unsigned int payload_len = 205;

  exec = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

  RtlMoveMemory(exec, payload, payload_len);

  rv = VirtualProtect(exec, payload_len, PAGE_EXECUTE_READ, &oldprotect);

  th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec, 0, 0, 0);

  WaitForSingleObject(th, -1);

}

x86 弹出计算器shellcode

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

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

/*

start:                                   

   mov   ebp, esp                  ;     prologue

   add   esp, 0xfffff9f0           ;     Add space int ESP to avoid clobbering

 find_kernel32:                      

   xor   ecx, ecx                  ;     ECX = 0

   mov   esi,fs:[ecx+0x30]         ;     ESI = &(PEB) ([FS:0x30])

   mov   esi,[esi+0x0C]            ;     ESI = PEB->Ldr

   mov   esi,[esi+0x1C]            ;     ESI = PEB->Ldr.InInitOrder

 next_module:                        

   mov   ebx, [esi+0x08]           ;     EBX = InInitOrder[X].base_address

   mov   edi, [esi+0x20]           ;     EDI = InInitOrder[X].module_name

   mov   esi, [esi]                ;     ESI = InInitOrder[X].flink (next)

   cmp   [edi+12*2], cx            ;    (unicode) modulename[12] == 0x00 ?

   jne   next_module               ;     No: try next module

 find_function_shorten:              

   jmp find_function_shorten_bnc   ;     Short jump

 find_function_ret:                  

   pop esi                         ;     POP the return address from the stack

   mov   [ebp+0x04], esi           ;     Save find_function address for later usage

   jmp resolve_symbols_kernel32    ; 

 find_function_shorten_bnc:             

   call find_function_ret          ;     Relative CALL with negative offset

 find_function:                      

   pushad                          ;     Save all registers

   mov   eax, [ebx+0x3c]           ;     Offset to PE Signature

   mov   edi, [ebx+eax+0x78]       ;     Export Table Directory RVA

   add   edi, ebx                  ;     Export Table Directory VMA

   mov   ecx, [edi+0x18]           ;     NumberOfNames

   mov   eax, [edi+0x20]           ;     AddressOfNames RVA

   add   eax, ebx                  ;     AddressOfNames VMA

   mov   [ebp-4], eax              ;     Save AddressOfNames VMA for later

 find_function_loop:                 

   jecxz find_function_finished    ;     Jump to the end if ECX is 0

   dec   ecx                       ;     Decrement our names counter

   mov   eax, [ebp-4]              ;     Restore AddressOfNames VMA

   mov   esi, [eax+ecx*4]          ;     Get the RVA of the symbol name

   add   esi, ebx                  ;     Set ESI to the VMA of the current symbol name

 compute_hash:                       

   xor   eax, eax                  ;     NULL EAX

   cdq                             ;     NULL EDX

   cld                             ;     Clear direction

 compute_hash_again:                 

   lodsb                           ;     Load the next byte from esi into al

   test  al, al                    ;     Check for NULL terminator

   jz    compute_hash_finished     ;     If the ZF is set, we've hit the NULL term

   ror   edx, 0x0d                 ;     Rotate edx 13 bits to the right

   add   edx, eax                  ;     Add the new byte to the accumulator

   jmp   compute_hash_again        ;     Next iteration

 compute_hash_finished:             

 find_function_compare:             

   cmp   edx, [esp+0x24]           ;     Compare the computed hash with the requested hash

   jnz   find_function_loop        ;     If it doesn't match go back to find_function_loop

   mov   edx, [edi+0x24]           ;     AddressOfNameOrdinals RVA

   add   edx, ebx                  ;     AddressOfNameOrdinals VMA

   mov   cx,  [edx+2*ecx]          ;     Extrapolate the function's ordinal

   mov   edx, [edi+0x1c]           ;     AddressOfFunctions RVA

   add   edx, ebx                  ;     AddressOfFunctions VMA

   mov   eax, [edx+4*ecx]          ;     Get the function RVA

   add   eax, ebx                  ;     Get the function VMA

   mov   [esp+0x1c], eax           ;     Overwrite stack version of eax from pushad

 find_function_finished:             

   popad                           ;     Restore registers

   ret                             ; 

 resolve_symbols_kernel32:         

  push 0xe8afe98                  ;     WinExec hash

  call dword ptr [ebp+0x04]       ;     Call find_function

  mov   [ebp+0x10], eax           ;     Save WinExec address for later usage

  push 0x78b5b983                 ;     TerminateProcess hash

  call dword ptr [ebp+0x04]       ;     Call find_function

  mov   [ebp+0x14], eax           ;     Save TerminateProcess address for later usage

 create_calc_string:                 

  xor eax, eax                   ;      EAX = null

  push eax                       ;      Push null-terminated string

  push dword 0x6578652e               ;         

  push dword 0x636c6163          ;    

  push esp                       ;      ESP = &(lpCmdLine)

  pop  ebx                       ;      EBX save pointer to string

 ; UINT WinExec(

 ; LPCSTR lpCmdLine, -> EBX

 ; UINT   uCmdShow      -> EAX

 ; );

 call_winexec:                       

    xor eax, eax                   ;    EAX = null

    push eax                       ;    uCmdShow

    push ebx                       ;    lpCmdLine

    call dword ptr [ebp+0x10]      ;    Call WinExec

 ; BOOL TerminateProcess(

 ; HANDLE hProcess,     -> 0xffffffff

 ; UINT   uExitCode     -> EAX

 ; );

 terminate_process:                  

    xor eax, eax                   ;    EAX = null

    push eax                       ;    uExitCode

    push 0xffffffff                ;    hProcess

    call dword ptr [ebp+0x14]      ;    Call TerminateProcess

*/

// Our WinExec PopCalc shellcode

unsigned char payload[] =

"\x89\xe5\x81\xc4\xf0\xf9\xff\xff\x31\xc9\x64\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x5e\x08\x8b\x7e"

"\x20\x8b\x36\x66\x39\x4f\x18\x75\xf2\xeb\x06\x5e\x89\x75\x04\xeb\x54\xe8\xf5\xff\xff\xff\x60\x8b\x43"

"\x3c\x8b\x7c\x03\x78\x01\xdf\x8b\x4f\x18\x8b\x47\x20\x01\xd8\x89\x45\xfc\xe3\x36\x49\x8b\x45\xfc\x8b"

"\x34\x88\x01\xde\x31\xc0\x99\xfc\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\x24\x24\x75"

"\xdf\x8b\x57\x24\x01\xda\x66\x8b\x0c\x4a\x8b\x57\x1c\x01\xda\x8b\x04\x8a\x01\xd8\x89\x44\x24\x1c\x61"

"\xc3\x68\x98\xfe\x8a\x0e\xff\x55\x04\x89\x45\x10\x68\x83\xb9\xb5\x78\xff\x55\x04\x89\x45\x14\x31\xc0"

"\x50\x68\x2e\x65\x78\x65\x68\x63\x61\x6c\x63\x54\x5b\x31\xc0\x50\x53\xff\x55\x10\x31\xc0\x50\x6a\xff"

"\xff\x55\x14";

unsigned int payload_len = 178;

int main(void) {

    void * exec_mem;

    BOOL rv;

    HANDLE th;

  DWORD oldprotect = 0;

    // Allocate a memory buffer for payload

    exec_mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

    // Copy payload to new buffer

    RtlMoveMemory(exec_mem, payload, payload_len);

    // Make new buffer as executable

    rv = VirtualProtect(exec_mem, payload_len, PAGE_EXECUTE_READ, &oldprotect);

    printf("\nHit me!\n");

  printf("Shellcode Length:  %d\n", strlen(payload));

    getchar();

    // If all good, run the payload

    if ( rv != 0 ) {

            th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);

            WaitForSingleObject(th, -1);

    }

    return 0;

}

使用C生成shellcode

  1. 使用vs创建一个空项目,这里以vs2019 x86 平台为例。
  2. 右键项目属性。我们需要修改release版本中的一些信息。如下
  • 属性 -> C/C++ -> 代码生成 -> 安全检查->禁用安全检查 (/GS-)
  • 属性 -> 链接器 -> 清单文件-> 生成清单-> 否 (/MANIFEST:NO)
  • 属性 -> 链接器 -> 调试-> 生成调试信息->

如下所示:
图片描述
图片描述
图片描述

获取函数hash

通常情况下,我们会对所需的API函数名进行hash运算,在搜索导出表时对当前遇到的函数名也进行同样的hash,这样只要比较hash所得的摘要(digest)就能判定是不是我们所需的API了。虽然这种搜索方法需要引入额外的hash算法,但是可以节省出存储函数名字符串的代码。

注意:

1

这里所说的 hash 指的是 hash 算法,是一个运算过程。经过 hash 后得到的值将被称做摘要,即 digest,请注意。

这里提供一段简单的 "hash" 算法

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

DWORD GetHash(const char* fun_name)

{

    DWORD digest = 0;

    while (*fun_name)

    {

        digest = ((digest << 25) | (digest >> 7)); //循环右移 7

        digest += *fun_name; //累加

        fun_name++;

    }

    return digest;

}

void main()

{

    DWORD hash;

    hash = GetHash("GetProcAddress");

    printf("result of hash is 0x%.8x\n", hash);

}

提取shellcode

源码

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

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

void main()

{

    //the pointer of kernel32.dll base address

    DWORD dwKernel32Addr = 0;

    _asm {

        push eax

        mov eax, dword ptr fs:[0x30]

        mov eax, [eax + 0x0C]

        mov eax,[eax + 0x1C]

        mov eax, [eax]

        mov eax, [eax + 0x08]

        mov dwKernel32Addr, eax

        pop eax

    }

    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwKernel32Addr;

    PIMAGE_NT_HEADERS32 pNtHeader = (PIMAGE_NT_HEADERS32)(dwKernel32Addr + pDosHeader->e_lfanew);

    PIMAGE_DATA_DIRECTORY pDataDirectory = pNtHeader->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT;

    PIMAGE_EXPORT_DIRECTORY pExportFuncTable = (PIMAGE_EXPORT_DIRECTORY)(dwKernel32Addr + pDataDirectory->VirtualAddress);

    PDWORD pAddrOfFunc = (PDWORD)(pExportFuncTable->AddressOfFunctions + dwKernel32Addr);

    PDWORD pAddrOfFuncNames = (PDWORD)(pExportFuncTable->AddressOfNames + dwKernel32Addr);

    PWORD  pAddrOfOrdinals = (PWORD)(pExportFuncTable->AddressOfNameOrdinals + dwKernel32Addr);

    DWORD dwFuncGetProcAddress = 0;

    for (size_t i = 0; i < pExportFuncTable->NumberOfNames; i++)

    {

        PCHAR lpFuncName = (PCHAR)(pAddrOfFuncNames[i] + dwKernel32Addr);

        DWORD digest = 0;

        while (*lpFuncName)

        {

            digest = ((digest << 25) | (digest >> 7));

            digest += *lpFuncName;

            lpFuncName++;

        }

        if (digest == 0xbbafdf85)//0xbbafdf85是经过自定义hash算法得到GetProcAddress函数的摘要

        {

            dwFuncGetProcAddress = pAddrOfFunc[pAddrOfOrdinals[i]] + dwKernel32Addr;

            break;

        }

    }

    /*

    如果是弹窗弹窗,这里我们需要 : LoadLibraryExA、MessageBoxA、ExitProcess、user32.dll

    */

    /*

    定义函数指针GetProcAddress

    */

    typedef    FARPROC (WINAPI *funcGetProcAddress)(

                HMODULE hModule,

                LPCSTR lpProcName

            );

    funcGetProcAddress pfuncGetProcAddress = (funcGetProcAddress)dwFuncGetProcAddress;

    /*

    LoadLibraryExA 函数指针获取

    */

    typedef HMODULE (WINAPI *funcLoadLibraryExA)(

             LPCSTR lpLibFileName,

             HANDLE hFile,

             DWORD dwFlags

        );

    //如果采用字符串模式,其字符串会被放入数据段,使用的每次加载地址都不一样,

    char szLoadLibraryExA[] = { 'L','o','a','d','L','i','b','r','a','r','y','E','x','A','\0' };

    char szUser32[] = { 'u','s','e','r','3','2','.','d','l','l','\0' };

    char szMessageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A','\0' };

    char szExitProcess[] = { 'E','x','i','t','P','r','o','c','e','s','s','\0' };

    funcLoadLibraryExA pfuncLoadLibraryExA = (funcLoadLibraryExA)(pfuncGetProcAddress((HMODULE)dwKernel32Addr,szLoadLibraryExA));

    /*

    ExitProcess函数指针

    */

    typedef    VOID

    (WINAPI *funcExitProcess)(

            _In_ UINT uExitCode

            );

    funcExitProcess pfuncExitProcess = (funcExitProcess)(pfuncGetProcAddress((HMODULE)dwKernel32Addr, szExitProcess));

    /*

    * 加载user32.dll 和messagebox

    */

    typedef int    (WINAPI    *funcMessageBoxA)(

        _In_opt_ HWND hWnd,

        _In_opt_ LPCSTR lpText,

        _In_opt_ LPCSTR lpCaption,

        _In_ UINT uType);

    funcMessageBoxA pfuncMessageBoxA = (funcMessageBoxA)(pfuncGetProcAddress((HMODULE)(pfuncLoadLibraryExA(szUser32, NULL, NULL)), szMessageBoxA));

    char szContext[] = {'t','h','i','s',' ','i','s',' ','a',' ','t','e','s','t','\0' };

    char szTitle[] = { 't','e','s','t','\0' };

    pfuncMessageBoxA(NULL, szContext, szTitle, MB_OK);

    pfuncExitProcess(0);

}

编译成功之后,程序正常运行
图片描述

将程序拖入OD,按F9进入程序模块,如图所示:

图片描述

然后从第一行开始,下拉到空白区选中 右键 复制 二进制复制。然后再在010editor中粘贴自 从十六进制文本粘贴。如下图:

图片描述

选中然后复制为c代码

图片描述

这样就得到了我们的shellcode

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

42

43

44

45

46

unsigned char hexData[351] = {

    0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x5C, 0x53, 0x56,

    0x57, 0xC7, 0x45, 0xFC, 0x00, 0x00, 0x00, 0x00,

    0x50, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B,

    0x40, 0x0C, 0x8B, 0x40, 0x1C, 0x8B, 0x00, 0x8B,

    0x40, 0x08, 0x89, 0x45, 0xFC, 0x58, 0x8B, 0x7D,

    0xFC, 0x33, 0xF6, 0x8B, 0x47, 0x3C, 0x8B, 0x44,

    0x38, 0x78, 0x03, 0xC7, 0x8B, 0x48, 0x1C, 0x8B,

    0x50, 0x24, 0x03, 0xCF, 0x8B, 0x58, 0x18, 0x03,

    0xD7, 0x89, 0x4D, 0xF0, 0x8B, 0x48, 0x20, 0x03,

    0xCF, 0x89, 0x55, 0xF4, 0x89, 0x4D, 0xF8, 0x85,

    0xDB, 0x74, 0x41, 0x8B, 0x14, 0xB1, 0x33, 0xC0,

    0x8A, 0x0C, 0x3A, 0x03, 0xD7, 0x84, 0xC9, 0x74,

    0x18, 0xC1, 0xC8, 0x07, 0x8D, 0x52, 0x01, 0x0F,

    0xBE, 0xC9, 0x03, 0xC1, 0x8A, 0x0A, 0x84, 0xC9,

    0x75, 0xEF, 0x3D, 0x85, 0xDF, 0xAF, 0xBB, 0x74,

    0x0A, 0x46, 0x3B, 0xF3, 0x73, 0x16, 0x8B, 0x4D,

    0xF8, 0xEB, 0xD0, 0x8B, 0x45, 0xF4, 0x8B, 0x5D,

    0xF0, 0x0F, 0xB7, 0x04, 0x70, 0x8B, 0x1C, 0x83,

    0x03, 0xDF, 0xEB, 0x02, 0x33, 0xDB, 0x8D, 0x45,

    0xB4, 0xC7, 0x45, 0xB4, 0x4C, 0x6F, 0x61, 0x64,

    0x50, 0x57, 0xC7, 0x45, 0xB8, 0x4C, 0x69, 0x62,

    0x72, 0xC7, 0x45, 0xBC, 0x61, 0x72, 0x79, 0x45,

    0x66, 0xC7, 0x45, 0xC0, 0x78, 0x41, 0xC6, 0x45,

    0xC2, 0x00, 0xC7, 0x45, 0xDC, 0x75, 0x73, 0x65,

    0x72, 0xC7, 0x45, 0xE0, 0x33, 0x32, 0x2E, 0x64,

    0x66, 0xC7, 0x45, 0xE4, 0x6C, 0x6C, 0xC6, 0x45,

    0xE6, 0x00, 0xC7, 0x45, 0xC4, 0x4D, 0x65, 0x73,

    0x73, 0xC7, 0x45, 0xC8, 0x61, 0x67, 0x65, 0x42,

    0xC7, 0x45, 0xCC, 0x6F, 0x78, 0x41, 0x00, 0xC7,

    0x45, 0xD0, 0x45, 0x78, 0x69, 0x74, 0xC7, 0x45,

    0xD4, 0x50, 0x72, 0x6F, 0x63, 0xC7, 0x45, 0xD8,

    0x65, 0x73, 0x73, 0x00, 0xFF, 0xD3, 0x8B, 0xF0,

    0x8D, 0x45, 0xD0, 0x50, 0xFF, 0x75, 0xFC, 0xFF,

    0xD3, 0x8B, 0xF8, 0x8D, 0x45, 0xC4, 0x50, 0x6A,

    0x00, 0x6A, 0x00, 0x8D, 0x45, 0xDC, 0x50, 0xFF,

    0xD6, 0x50, 0xFF, 0xD3, 0x6A, 0x00, 0x8D, 0x4D,

    0xE8, 0xC7, 0x45, 0xA4, 0x74, 0x68, 0x69, 0x73,

    0x51, 0x8D, 0x4D, 0xA4, 0xC7, 0x45, 0xA8, 0x20,

    0x69, 0x73, 0x20, 0x51, 0x6A, 0x00, 0xC7, 0x45,

    0xAC, 0x61, 0x20, 0x74, 0x65, 0x66, 0xC7, 0x45,

    0xB0, 0x73, 0x74, 0xC6, 0x45, 0xB2, 0x00, 0xC7,

    0x45, 0xE8, 0x74, 0x65, 0x73, 0x74, 0xC6, 0x45,

    0xEC, 0x00, 0xFF, 0xD0, 0x6A, 0x00, 0xFF, 0xD7,

    0x5F, 0x5E, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3

};

在程序中运行我们的shellcode。

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

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

unsigned char hexData[351] = {

    0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x5C, 0x53, 0x56,

    0x57, 0xC7, 0x45, 0xFC, 0x00, 0x00, 0x00, 0x00,

    0x50, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B,

    0x40, 0x0C, 0x8B, 0x40, 0x1C, 0x8B, 0x00, 0x8B,

    0x40, 0x08, 0x89, 0x45, 0xFC, 0x58, 0x8B, 0x7D,

    0xFC, 0x33, 0xF6, 0x8B, 0x47, 0x3C, 0x8B, 0x44,

    0x38, 0x78, 0x03, 0xC7, 0x8B, 0x48, 0x1C, 0x8B,

    0x50, 0x24, 0x03, 0xCF, 0x8B, 0x58, 0x18, 0x03,

    0xD7, 0x89, 0x4D, 0xF0, 0x8B, 0x48, 0x20, 0x03,

    0xCF, 0x89, 0x55, 0xF4, 0x89, 0x4D, 0xF8, 0x85,

    0xDB, 0x74, 0x41, 0x8B, 0x14, 0xB1, 0x33, 0xC0,

    0x8A, 0x0C, 0x3A, 0x03, 0xD7, 0x84, 0xC9, 0x74,

    0x18, 0xC1, 0xC8, 0x07, 0x8D, 0x52, 0x01, 0x0F,

    0xBE, 0xC9, 0x03, 0xC1, 0x8A, 0x0A, 0x84, 0xC9,

    0x75, 0xEF, 0x3D, 0x85, 0xDF, 0xAF, 0xBB, 0x74,

    0x0A, 0x46, 0x3B, 0xF3, 0x73, 0x16, 0x8B, 0x4D,

    0xF8, 0xEB, 0xD0, 0x8B, 0x45, 0xF4, 0x8B, 0x5D,

    0xF0, 0x0F, 0xB7, 0x04, 0x70, 0x8B, 0x1C, 0x83,

    0x03, 0xDF, 0xEB, 0x02, 0x33, 0xDB, 0x8D, 0x45,

    0xB4, 0xC7, 0x45, 0xB4, 0x4C, 0x6F, 0x61, 0x64,

    0x50, 0x57, 0xC7, 0x45, 0xB8, 0x4C, 0x69, 0x62,

    0x72, 0xC7, 0x45, 0xBC, 0x61, 0x72, 0x79, 0x45,

    0x66, 0xC7, 0x45, 0xC0, 0x78, 0x41, 0xC6, 0x45,

    0xC2, 0x00, 0xC7, 0x45, 0xDC, 0x75, 0x73, 0x65,

    0x72, 0xC7, 0x45, 0xE0, 0x33, 0x32, 0x2E, 0x64,

    0x66, 0xC7, 0x45, 0xE4, 0x6C, 0x6C, 0xC6, 0x45,

    0xE6, 0x00, 0xC7, 0x45, 0xC4, 0x4D, 0x65, 0x73,

    0x73, 0xC7, 0x45, 0xC8, 0x61, 0x67, 0x65, 0x42,

    0xC7, 0x45, 0xCC, 0x6F, 0x78, 0x41, 0x00, 0xC7,

    0x45, 0xD0, 0x45, 0x78, 0x69, 0x74, 0xC7, 0x45,

    0xD4, 0x50, 0x72, 0x6F, 0x63, 0xC7, 0x45, 0xD8,

    0x65, 0x73, 0x73, 0x00, 0xFF, 0xD3, 0x8B, 0xF0,

    0x8D, 0x45, 0xD0, 0x50, 0xFF, 0x75, 0xFC, 0xFF,

    0xD3, 0x8B, 0xF8, 0x8D, 0x45, 0xC4, 0x50, 0x6A,

    0x00, 0x6A, 0x00, 0x8D, 0x45, 0xDC, 0x50, 0xFF,

    0xD6, 0x50, 0xFF, 0xD3, 0x6A, 0x00, 0x8D, 0x4D,

    0xE8, 0xC7, 0x45, 0xA4, 0x74, 0x68, 0x69, 0x73,

    0x51, 0x8D, 0x4D, 0xA4, 0xC7, 0x45, 0xA8, 0x20,

    0x69, 0x73, 0x20, 0x51, 0x6A, 0x00, 0xC7, 0x45,

    0xAC, 0x61, 0x20, 0x74, 0x65, 0x66, 0xC7, 0x45,

    0xB0, 0x73, 0x74, 0xC6, 0x45, 0xB2, 0x00, 0xC7,

    0x45, 0xE8, 0x74, 0x65, 0x73, 0x74, 0xC6, 0x45,

    0xEC, 0x00, 0xFF, 0xD0, 0x6A, 0x00, 0xFF, 0xD7,

    0x5F, 0x5E, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3

};

void main()

{

    void* exec;

    BOOL rv;

    HANDLE th;

    DWORD oldprotect = 0;

    unsigned int payload_len =351;

    exec = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

    RtlMoveMemory(exec, hexData, payload_len);

    rv = VirtualProtect(exec, payload_len, PAGE_EXECUTE_READ, &oldprotect);

    th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec, 0, 0, 0);

    WaitForSingleObject(th, -1);

}

成功运行,如下所示:

图片描述

后话

漏洞研究小白,目前正在跟着书学习。希望自己能持之以恒。大家一起共勉

实战CVE漏洞分析与防范(第1季)


文章来源: https://bbs.pediy.com/thread-276757.htm
如有侵权请联系:admin#unsafe.sh