cobf
PE imports obfuscator
shellcode.cpp
Go to the documentation of this file.
1 
8 #ifndef SHELLCODE_CPP
9 #define SHELLCODE_CPP
10 
11  // The includes.
12 #include <shellcode.hpp>
13 #include <winternl.h>
14 
15 // Needed variables.
16 PVOID shellcode::shellcode_start = exit;
17 DWORD shellcode::shellcode_entry = (DWORD)((PBYTE)load_syms - (PBYTE)shellcode_start);
18 DWORD shellcode::shellcode_size = (DWORD)((PBYTE)funs_end - (PBYTE)shellcode_start);
19 
20 // Table holding the pointers and the sizes of the shellcode functions.
22 {
23  (DWORD)((PBYTE)exit - (PBYTE)shellcode_start),
24  (DWORD)((PBYTE)wstr_len - (PBYTE)shellcode_start),
25  (DWORD)((PBYTE)hash_string - (PBYTE)shellcode_start),
26  (DWORD)((PBYTE)str_cpy - (PBYTE)shellcode_start),
27  (DWORD)((PBYTE)str_toi - (PBYTE)shellcode_start),
28  (DWORD)((PBYTE)str_chr - (PBYTE)shellcode_start),
29  (DWORD)((PBYTE)ansi_to_wide - (PBYTE)shellcode_start),
30  (DWORD)((PBYTE)wstr_cpy - (PBYTE)shellcode_start),
31  (DWORD)((PBYTE)wstr_i_cmp - (PBYTE)shellcode_start),
32  (DWORD)((PBYTE)get_dll_handle - (PBYTE)shellcode_start),
33  (DWORD)((PBYTE)load_dll - (PBYTE)shellcode_start),
34  (DWORD)((PBYTE)resolve_api_set - (PBYTE)shellcode_start),
35  (DWORD)((PBYTE)get_symbol_ptr - (PBYTE)shellcode_start),
36  (DWORD)((PBYTE)load_syms - (PBYTE)shellcode_start)
37 };
38 
39 // Disable the runtime checks.
40 #pragma runtime_checks("", off)
41 
42 // Stop the execution of the shellcode.
43 VOID shellcode::exit()
44 {
45  // Just break.
46  __debugbreak();
47 };
48 
49 // Length of wide string
50 size_t shellcode::wstr_len(PWSTR str)
51 {
52  PWCHAR s = (PWCHAR)str;
53  for (; *s; ++s);
54  return(s - str);
55 };
56 
57 // Compile time string hashing.
58 DWORD shellcode::hash_string(PCHAR str)
59 {
60  // Init value.
61  DWORD hash = 0;
62 
63  // Loop on the chars.
64  while (*str)
65  {
66  // Build it.
67  hash += *str++;
68  hash += hash << 10;
69  hash ^= hash >> 6;
70  };
71 
72  // Finish.
73  hash += hash << 3;
74  hash ^= hash >> 11;
75  hash += hash << 15;
76  return hash;
77 };
78 
79 // Copy string to a buffer.
80 VOID shellcode::str_cpy(PCHAR out, PSTR in)
81 {
82  while (*in) *out++ = *in++;
83  *out = 0;
84 };
85 
86 // Convert a string into integer.
87 DWORD shellcode::str_toi(PSTR str)
88 {
89  // Init.
90  DWORD result = 0;
91  BYTE digit;
92 
93  // Convert.
94  for (; ; str += 1) {
95  digit = *str - '0';
96  if (digit > 9)
97  break;
98  result = (10 * result) + digit;
99  };
100 
101  // Return.
102  return result;
103 };
104 
105 // Search for a char inside a string.
106 PCHAR shellcode::str_chr(PSTR str, CHAR chr)
107 {
108  // Search for it.
109  do {
110  if (*str == chr) { return str; };
111  } while (*str++);
112  return (0);
113 };
114 
115 // Convert ansi to wide chars.
116 VOID shellcode::ansi_to_wide(PCHAR str, PWCHAR out, size_t size)
117 {
118  // Test if the buffer is zero-length.
119  size /= sizeof(WCHAR);
120  if (!size) return;
121 
122  // Copy the chars.
123  while (--size && *str) {
124  *out++ = (WCHAR)*str++;
125  };
126 
127  // Append NULL.
128  *out = 0;
129 };
130 
131 // Copy string between to buffers.
132 VOID shellcode::wstr_cpy(PWSTR str1, PCWSTR str2, size_t length)
133 {
134  // Char by char.
135  while (length--) *str1++ = *str2++;
136  *str1 = 0;
137 };
138 
139 
140 // Compare two wide strings.
141 INT shellcode::wstr_i_cmp(PWSTR str1, PCWSTR str2, size_t length)
142 {
143  // Init.
144  WCHAR c1, c2;
145  do
146  {
147  // Make the check.
148  c1 = *str1++;
149  c2 = *str2++;
150  if (c1 == L'\0') {
151  return c2 - c1;
152  };
153 
154  // Move to the next iteration.
155  } while (((c1 == c2) || (c1 - c2 == 32) ||
156  (c2 - c1 == 32)) && --length);
157 
158  // Final compare.
159  return c2 - c1;
160 };
161 
162 // Get the dll base by name.
163 HANDLE shellcode::get_dll_handle(HANDLE pe_base, PDWORD sh_funs, PWSTR dll_name)
164 {
165  // Resolving the needed functions.
166  auto fun_wstr_i_cmp = sh_resolve(pe_base, sh_funs, wstr_i_cmp);
167  auto fun_resolve_api_set = sh_resolve(pe_base, sh_funs, resolve_api_set);
168 
169  // Needed local variables.
170  PPEB pPeb = NtCurrentTeb()->ProcessEnvironmentBlock;
171  PLIST_ENTRY head = &pPeb->Ldr->InMemoryOrderModuleList;
172 
173 QUERY_LDR:
174 
175  // Looping on the LDR modules (ordered as in memory).
176  PLIST_ENTRY current = head;
177  while ((current = current->Flink) != head)
178  {
179  // Get and test the current module.
180  PLDR_DATA_TABLE_ENTRY data_table = CONTAINING_RECORD(current, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
181 
182  // If the current module is the needed one.
183  if (!fun_wstr_i_cmp(((PUNICODE_STRING)(&data_table->Reserved4))->Buffer,
184  dll_name, 0))
185  {
186  // Return the module base.
187  return data_table->DllBase;
188  };
189  };
190 
191  // Check if api set schema.
192  WCHAR api_schema[] = { L'a', L'p', L'i', L'-', L'\0' };
193  WCHAR ext_schema[] = { L'e', L'x', L't', L'-', L'\0' };
194  if (!fun_wstr_i_cmp(dll_name, api_schema, sizeof("api")) ||
195  !fun_wstr_i_cmp(dll_name, ext_schema, sizeof("ext")))
196  {
197  // The length of the dll name is more than the api set dll name.
198  if (fun_resolve_api_set(pe_base, sh_funs, pPeb->Reserved9[0], dll_name, dll_name)) {
199  goto QUERY_LDR;
200  };
201  };
202 
203  // Not found.
204  return NULL;
205 };
206 
207 // Load the dll base by name.
208 HANDLE shellcode::load_dll(HANDLE pe_base, PDWORD sh_funs, PCHAR dll_name)
209 {
210  // Resolving the needed functions.
211  auto fun_ansi_to_wide = sh_resolve(pe_base, sh_funs, ansi_to_wide);
212  auto fun_get_dll_handle = sh_resolve(pe_base, sh_funs, get_dll_handle);
213  auto fun_get_symbol_ptr = sh_resolve(pe_base, sh_funs, get_symbol_ptr);
214  auto fun_wstr_len = sh_resolve(pe_base, sh_funs, wstr_len);
215  auto fun_hash_string = sh_resolve(pe_base, sh_funs, hash_string);
216 
217  // Converting the dll name.
218  WCHAR dll_buffer[MAX_PATH];
219  fun_ansi_to_wide(dll_name, dll_buffer, sizeof(dll_buffer));
220 
221  // Quering the ldr first.
222  HANDLE h_dll = fun_get_dll_handle(pe_base, sh_funs, dll_buffer);
223  if (h_dll) return h_dll;
224 
225  // Getting the base of the ntdll.
226  WCHAR ntdll_name[] = { L'n', L't', L'd', L'l', L'l', L'.', L'd', L'l', L'l', L'\0' };
227  HANDLE h_ntdll = fun_get_dll_handle(pe_base, sh_funs, ntdll_name);
228  if (!h_ntdll) return NULL;
229 
230  // Resolving `LdrLoadDll` to load the new dll.
231  u_sym_info ldr_load_dll_info;
232  CHAR ldrloaddll_name[] = { 'L', 'd', 'r', 'L', 'o', 'a', 'd', 'D', 'l', 'l', '\0' };
233  ldr_load_dll_info.sym_hash = fun_hash_string(ldrloaddll_name);
234  pLdrLoadDll fLdrLoadDll = (pLdrLoadDll)fun_get_symbol_ptr(pe_base, sh_funs, h_ntdll, ldr_load_dll_info, TRUE);
235  if (fLdrLoadDll == NULL) {
236  return NULL;
237  };
238 
239  // Loading the required dll using `LdrLoadDll`.
240  UNICODE_STRING u_module = {
241  (USHORT)(fun_wstr_len(dll_buffer) * 2),
242  (USHORT)(fun_wstr_len(dll_buffer) * 2 + 2),
243  (PWCHAR)dll_buffer
244  };
245 
246  // Loading the dll.
247  if (!NT_SUCCESS(fLdrLoadDll(
248  NULL, 0,
249  &u_module,
250  &h_dll
251  ))) return NULL;
252 
253  // Return the loaded module base.
254  return h_dll;
255 };
256 
257 // Resolve the api set schema for the dll name.
258 BOOL shellcode::resolve_api_set(HANDLE pe_base, PDWORD sh_funs, PVOID schema_map, PCWSTR virtual_dll, PWCHAR real_dll)
259 {
260  // Resolving the needed functions.
261  auto fun_wstr_cpy = sh_resolve(pe_base, sh_funs, wstr_cpy);
262  auto fun_wstr_i_cmp = sh_resolve(pe_base, sh_funs, wstr_i_cmp);
263 
264  // Reading the version.
265  DWORD Version = *(DWORD*)schema_map;
266 
267  // Two versions with two methods.
268  if (Version >= 3) {
269 
270  // Use the proper structures.
271  PApiSetHeader63 pHeader = (PApiSetHeader63)schema_map;
272  PApisetNameEntry pApiSets = (PApisetNameEntry)((PBYTE)schema_map + pHeader->NamesOffset);
273 
274  // Use binary search to enumerate the dlls.
275  INT Start, End, Next, CmpResult;
276  Start = Next = 0;
277  End = (DWORD)pHeader->NumberOfApisets - 1;
278 
279  // Binary search loop.
280  while (End >= Start) {
281 
282  Next = (Start + End) >> 1;
283  PWCHAR VirtualDll = (PWCHAR)((PBYTE)schema_map + pApiSets[Next].Offset);
284 
285  // Compare and conditions.
286  CmpResult = fun_wstr_i_cmp(VirtualDll, virtual_dll, pApiSets[Next].Size / 2);
287  if (CmpResult < 0)
288  End = Next - 1;
289  else if (CmpResult > 0)
290  Start = Next + 1;
291  else break;
292  };
293 
294  // We've found the right entry.
295  if (End >= Start && pApiSets[Next].NumberOfHosts) {
296 
297  // Get the value.
298  PApisetValueEntry pValue = (PApisetValueEntry)((PBYTE)schema_map + pApiSets[Next].HostOffset);
299  pValue += pApiSets[Next].NumberOfHosts - 1;
300 
301  // Fill the buffer address and return true.
302  fun_wstr_cpy(real_dll, (PWCHAR)((PBYTE)schema_map + pValue->ValueOffset),
303  pValue->ValueLength / 2);
304  return TRUE;
305  };
306  }
307  else {
308 
309  // Use the proper structures.
310  PApiSetHeader6 pHeader = (PApiSetHeader6)schema_map;
311  PApisetNameEntry2 pApiSets = (PApisetNameEntry2)((PBYTE)schema_map + sizeof(ApiSetHeader6));
312 
313  // Use binary search to enumerate the dlls.
314  int Start, End, Next, CmpResult;
315  Start = Next = 0;
316  End = (DWORD)pHeader->Count - 1;
317 
318  // Binary search loop.
319  while (End >= Start) {
320 
321  Next = (Start + End) >> 1;
322  PWCHAR VirtualDll = (PWCHAR)((PBYTE)schema_map + pApiSets[Next].NameOffset);
323 
324  // Compare and conditions.
325  CmpResult = fun_wstr_i_cmp(VirtualDll, virtual_dll, pApiSets[Next].NameLength / 2);
326  if (CmpResult < 0)
327  End = Next - 1;
328  else if (CmpResult > 0)
329  Start = Next + 1;
330  else break;
331  };
332 
333  // We've found the right entry and if there are any availible values.
334  DWORD ValuesCount;
335  if (End >= Start && (ValuesCount = *(DWORD*)((PBYTE)schema_map + pApiSets[Next].DataOffset))) {
336 
337  // Get the value.
338  PValuesEntry2 pValue = (PValuesEntry2)((PBYTE)schema_map + pApiSets[Next].DataOffset);
339  pValue += ValuesCount - 1;
340 
341  // Fill the buffer address and return True.
342  fun_wstr_cpy(real_dll, (PWCHAR)((PBYTE)schema_map + pValue->ValueOffset),
343  pValue->ValueLength / 2);
344  return TRUE;
345  };
346  };
347 
348  // Not found.
349  return FALSE;
350 };
351 
352 // Get the dll base by name.
353 PVOID shellcode::get_symbol_ptr(HANDLE pe_base, PDWORD sh_funs, HANDLE dll_handle, u_sym_info sym_info, BOOL by_name)
354 {
355  // Resolving the needed functions.
356  auto fun_hash_string = sh_resolve(pe_base, sh_funs, hash_string);
357  auto fun_str_cpy = sh_resolve(pe_base, sh_funs, str_cpy);
358  auto fun_str_chr = sh_resolve(pe_base, sh_funs, str_chr);
359  auto fun_load_dll = sh_resolve(pe_base, sh_funs, load_dll);
360  auto fun_get_symbol_ptr = sh_resolve(pe_base, sh_funs, get_symbol_ptr);
361  auto fun_str_toi = sh_resolve(pe_base, sh_funs, str_toi);
362 
363  // Dereferencing the headers.
364  PIMAGE_DOS_HEADER dos_hdr = (PIMAGE_DOS_HEADER)dll_handle;
365  PIMAGE_NT_HEADERS nt_hdrs = (PIMAGE_NT_HEADERS)((PBYTE)dll_handle + dos_hdr->e_lfanew);
366  PIMAGE_OPTIONAL_HEADER opt_hdr = &nt_hdrs->OptionalHeader;
367 
368  // Dereferencing the export table data directory.
369  PIMAGE_DATA_DIRECTORY data_dir = &opt_hdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
370 
371  // Check if exists.
372  if (!data_dir->VirtualAddress || !data_dir->Size) {
373  return NULL;
374  };
375 
376  // Get the export table pointer.
377  PIMAGE_EXPORT_DIRECTORY export_table = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)dll_handle +
378  data_dir->VirtualAddress);
379 
380  // In case an ordinal is resolved.
381  DWORD f_address;
382  if (!by_name)
383  {
384  // Invalid ordinal.
385  if (sym_info.sym_ord < export_table->Base ||
386  sym_info.sym_ord >= export_table->Base + export_table->NumberOfFunctions)
387  return NULL;
388 
389  // Getting the address directly.
390  f_address = ((DWORD*)((PBYTE)dll_handle + export_table->AddressOfFunctions))
391  [sym_info.sym_ord - export_table->Base];
392  }
393  else
394  {
395  // My be no names exported.
396  if (!export_table->AddressOfNames) {
397  return NULL;
398  };
399 
400  // Resolve by name.
401  size_t idx = 0;
402  DWORD* p_names = (DWORD*)((PBYTE)dll_handle +
403  export_table->AddressOfNames);
404  for (; idx < export_table->NumberOfNames; idx++)
405  {
406  // Get the name.
407  PCHAR sym_name = (PCHAR)dll_handle + p_names[idx];
408  if (fun_hash_string(sym_name) != sym_info.sym_hash)
409  continue;
410 
411  // Found.
412  f_address = ((DWORD*)((PBYTE)dll_handle + export_table->AddressOfFunctions))[
413  ((PWORD)((PBYTE)dll_handle + export_table->AddressOfNameOrdinals))[idx]
414  ];
415  break;
416  };
417 
418  // Test if not found.
419  if (idx == export_table->NumberOfNames)
420  {
421  // Not found.
422  return NULL;
423  };
424  };
425 
426  // Test if the symbol is forwarded.
427  PVOID p_address = (PBYTE)dll_handle + f_address;
428  if (f_address >= data_dir->VirtualAddress &&
429  f_address < data_dir->VirtualAddress + data_dir->Size)
430  {
431  // Reading the dll name and the function name.
432  CHAR dll_name[MAX_PATH], sym_name[MAX_PATH];
433  fun_str_cpy(dll_name, (PCHAR)p_address);
434 
435  // Appending ".dll".
436  PCHAR dot_loc = fun_str_chr(dll_name, '.');
437  fun_str_cpy(sym_name, ++dot_loc);
438  *dot_loc++ = 'd';
439  *dot_loc++ = 'l';
440  *dot_loc++ = 'l';
441  *dot_loc++ = '\0';
442 
443  // Load the dll.
444  HANDLE h_forward = fun_load_dll(pe_base, sh_funs, dll_name);
445  if (!h_forward) return NULL;
446 
447  // Re make the resolve.
448  u_sym_info sym_data;
449  if (*sym_name == '#') {
450  // By ordinal.
451  sym_data.sym_ord = (WORD)fun_str_toi(&sym_name[1]);
452  p_address = fun_get_symbol_ptr(pe_base, sh_funs, h_forward, sym_data, FALSE);
453  }
454  else
455  {
456  // By name.
457  sym_data.sym_hash = fun_hash_string(sym_name);
458  p_address = fun_get_symbol_ptr(pe_base, sh_funs, h_forward, sym_data, TRUE);
459  };
460  };
461 
462  // Resolved.
463  return p_address;
464 };
465 
466 // Load the obfuscated symbols at runtime.
467 VOID shellcode::load_syms(HANDLE pe_base, DWORD sh_funs_rva, DWORD syms_rva)
468 {
469  // Calculate the pointers.
470  PDWORD sh_funs = (PDWORD)((PBYTE)pe_base + sh_funs_rva);
471  p_obfuscated_sym syms = (p_obfuscated_sym)((PBYTE)pe_base + syms_rva);
472 
473  // Resolving the needed functions.
474  auto fun_load_dll = sh_resolve(pe_base, sh_funs, load_dll);
475  auto fun_get_symbol_ptr = sh_resolve(pe_base, sh_funs, get_symbol_ptr);
476  auto fun_load_halt = sh_resolve(pe_base, sh_funs, exit);
477 
478  // Looping on the entries.
479  while (syms->dll_name)
480  {
481  // Calculate the pointers.
482  p_obfuscated_sym p_sym = syms++;
483  PCHAR c_dll_name = (PCHAR)pe_base + p_sym->dll_name;
484  PVOID* p_thunk = (PVOID*)((PBYTE)pe_base + p_sym->sym_thnk);
485 
486  // Get the loaded dll handle.
487  HANDLE dll_handle = fun_load_dll(pe_base, sh_funs, c_dll_name);
488  if (!dll_handle)
489  {
490  // The dll is not loaded.
491  return fun_load_halt();
492  };
493 
494  // Get the symbol address.
495  PVOID sym_ptr = fun_get_symbol_ptr(pe_base, sh_funs, dll_handle, p_sym->sym_info, p_sym->by_name);
496  if (!dll_handle)
497  {
498  // The symbol is not found.
499  return fun_load_halt();
500  };
501 
502  // Replace the symbol thunk.
503  *p_thunk = sym_ptr;
504  };
505 };
506 
507 // Used to flag the end of the functions.
508 VOID shellcode::funs_end() {};
509 
510 // Renable the runtime checks.
511 #pragma runtime_checks("", restore)
512 
513 #endif // !SHELLCODE_CPP.
shellcode::shellcode_size
static DWORD shellcode_size
Definition: shellcode.hpp:250
shellcode::shellcode_start
static PVOID shellcode_start
Definition: shellcode.hpp:248
shellcode::shellcode_entry
static DWORD shellcode_entry
Definition: shellcode.hpp:249
sh_resolve
#define sh_resolve(base, funs, fun)
Definition: shellcode.hpp:25
shellcode.hpp
shellcode::shellcodes_funs
static DWORD shellcodes_funs[shellcode_number_of_functions]
Definition: shellcode.hpp:253