├── .appveyor.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── demos ├── README.md ├── knock.cpp ├── knock_test.py ├── peb_lookup.h ├── pop_calc.cpp ├── popup.cpp └── popup_arr.cpp ├── docs └── FromaCprojectthroughassemblytoshellcode.pdf ├── masm_shc ├── CMakeLists.txt ├── main.cpp ├── string_util.cpp └── string_util.h └── runshc ├── CMakeLists.txt ├── LICENSE ├── main.cpp ├── util.cpp └── util.h /.appveyor.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - Visual Studio 2019 3 | 4 | platform: x64 5 | 6 | branches: 7 | only: 8 | - master 9 | 10 | install: 11 | - git submodule update --init --recursive 12 | - set PATH=C:\Program Files\CMake\bin;%PATH% 13 | 14 | build: 15 | verbosity: detailed 16 | 17 | configuration: 18 | - Release 19 | 20 | environment: 21 | artifactName: $(APPVEYOR_PROJECT_NAME)-$(APPVEYOR_REPO_COMMIT)-$(CONFIGURATION) 22 | matrix: 23 | - env_arch: "x64" 24 | - env_arch: "x86" 25 | 26 | before_build: 27 | - mkdir build 28 | - cd build 29 | - if [%env_arch%]==[x64] ( 30 | cmake .. -A x64 ) 31 | - if [%env_arch%]==[x86] ( 32 | cmake .. -A Win32 ) 33 | - cmake -DCMAKE_INSTALL_PREFIX:PATH=%APPVEYOR_BUILD_FOLDER%/%APPVEYOR_REPO_COMMIT% .. 34 | 35 | build_script: 36 | - cmake --build . --config %CONFIGURATION% --target install 37 | 38 | after_build: 39 | - mkdir %artifactName% 40 | - cp %APPVEYOR_BUILD_FOLDER%/%APPVEYOR_REPO_COMMIT%/* %artifactName% 41 | 42 | artifacts: 43 | - path: build\%artifactName% 44 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required ( VERSION 3.0 ) 2 | 3 | project ( masm_shc ) 4 | 5 | # Add sub-directories 6 | # 7 | add_subdirectory ( masm_shc ) 8 | add_subdirectory ( runshc ) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 hasherezade 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # masm_shc 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/y50mphwb8rtg9vh9?svg=true)](https://ci.appveyor.com/project/hasherezade/masm-shc) 4 | [![Github All Releases](https://img.shields.io/github/downloads/hasherezade/masm_shc/total.svg)](https://github.com/hasherezade/masm_shc/releases) 5 | 6 | A helper utility for creating shellcodes. Cleans MASM file generated by MSVC, gives refactoring hints. 7 | 8 | + [Demos](demos) 9 | + [Paper 📰 (VXUG)](https://vxug.fakedoma.in/papers/VXUG/Exclusive/FromaCprojectthroughassemblytoshellcodeHasherezade.pdf) ; ([mirror](https://github.com/hasherezade/masm_shc/blob/6e7eaba4a6e2d170c5c75a2c28b22e1c71b9087e/docs/FromaCprojectthroughassemblytoshellcode.pdf)) 10 | 11 | -------------------------------------------------------------------------------- /demos/README.md: -------------------------------------------------------------------------------- 1 | # masm_shc 2 | ## demos 3 | 4 | Examples of the code to be refactored to shellcode: 5 | 6 | 1. popup.cpp - a simple "Hello World" Message Box 7 | 2. knock.cpp - a simple server, opening ports, waiting for a defined input, giving response 8 | 9 | ### Howto 10 | 11 | 1. Compile the example from C to MASM, using MSVC from a commadline 12 | 13 | ``` 14 | cl /c /GS- /FA .cpp 15 | ``` 16 | 17 | 2. Use masm_shc to clean the obtained MASM, inline the strings etc. 18 | 19 | ``` 20 | masm_shc.exe .asm .asm 21 | ``` 22 | 23 | It should automatically resolve most of the issues. The remaining issues should be resolved manually following the diplayed hints. It will also inform if the Entry Point was changed 24 | 25 | 3. Compile the resulting file by MASM into a PE (use `ml` for 32-bit files and `ml64` for 64 bit files analogicaly) 26 | 27 | ``` 28 | ml .asm /link /entry: 29 | ``` 30 | 31 | 4. Use PE-bear to dump the .text section from the resulting PE. This will be our shellcode. 32 | 33 | 5. Use runshc to test the shellcode (remember that the bitness of runshc must be the same as the bitness of the shellcode) 34 | 35 | -------------------------------------------------------------------------------- /demos/knock.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | WARNING: the code section will be modified, and must be set writeable! 3 | Example: 4 | ml64 knock.asm /link /entry:main /section:.text,ERW 5 | */ 6 | 7 | #include 8 | #include "peb_lookup.h" 9 | 10 | #define LOCALHOST_ROT13 ">?D;=;=;>" 11 | 12 | typedef struct 13 | { 14 | decltype(&LoadLibraryA) _LoadLibraryA; 15 | decltype(&GetProcAddress) _GetProcAddress; 16 | } t_mini_iat; 17 | 18 | typedef struct 19 | { 20 | decltype(&WSAStartup) _WSAStartup; 21 | decltype(&socket) _socket; 22 | decltype(&inet_addr) _inet_addr; 23 | decltype(&bind) _bind; 24 | decltype(&listen) _listen; 25 | decltype(&accept) _accept; 26 | decltype(&recv) _recv; 27 | decltype(&send) _send; 28 | decltype(&closesocket) _closesocket; 29 | decltype(&htons) _htons; 30 | decltype(&WSACleanup) _WSACleanup; 31 | } t_socket_iat; 32 | 33 | 34 | bool init_iat(t_mini_iat &iat) 35 | { 36 | LPVOID base = get_module_by_name((const LPWSTR)L"kernel32.dll"); 37 | if (!base) { 38 | return false; 39 | } 40 | 41 | LPVOID load_lib = get_func_by_name((HMODULE)base, (LPSTR)"LoadLibraryA"); 42 | if (!load_lib) { 43 | return false; 44 | } 45 | LPVOID get_proc = get_func_by_name((HMODULE)base, (LPSTR)"GetProcAddress"); 46 | if (!get_proc) { 47 | return false; 48 | } 49 | 50 | iat._LoadLibraryA = reinterpret_cast(load_lib); 51 | iat._GetProcAddress = reinterpret_cast(get_proc); 52 | return true; 53 | } 54 | 55 | bool init_socket_iat(t_mini_iat &iat, t_socket_iat &sIAT) 56 | { 57 | LPVOID WS232_dll = iat._LoadLibraryA("WS2_32.dll"); 58 | 59 | sIAT._WSAStartup = reinterpret_cast(iat._GetProcAddress((HMODULE)WS232_dll, "WSAStartup")); 60 | sIAT._socket = reinterpret_cast(iat._GetProcAddress((HMODULE)WS232_dll, "socket")); 61 | sIAT._inet_addr = reinterpret_cast(iat._GetProcAddress((HMODULE)WS232_dll, "inet_addr")); 62 | sIAT._bind = reinterpret_cast(iat._GetProcAddress((HMODULE)WS232_dll, "bind")); 63 | sIAT._listen = reinterpret_cast(iat._GetProcAddress((HMODULE)WS232_dll, "listen")); 64 | sIAT._accept = reinterpret_cast(iat._GetProcAddress((HMODULE)WS232_dll, "accept")); 65 | sIAT._recv = reinterpret_cast(iat._GetProcAddress((HMODULE)WS232_dll, "recv")); 66 | sIAT._send = reinterpret_cast(iat._GetProcAddress((HMODULE)WS232_dll, "send")); 67 | sIAT._closesocket = reinterpret_cast(iat._GetProcAddress((HMODULE)WS232_dll, "closesocket")); 68 | sIAT._htons = reinterpret_cast(iat._GetProcAddress((HMODULE)WS232_dll, "htons")); 69 | sIAT._WSACleanup = reinterpret_cast(iat._GetProcAddress((HMODULE)WS232_dll, "WSACleanup")); 70 | 71 | return true; 72 | } 73 | 74 | ///--- 75 | bool switch_state(char *buf, char *resp) 76 | { 77 | switch (resp[0]) { 78 | case 0: 79 | if (buf[0] != '9') break; 80 | resp[0] = 'Y'; 81 | return true; 82 | case 'Y': 83 | if (buf[0] != '3') break; 84 | resp[0] = 'E'; 85 | return true; 86 | case 'E': 87 | if (buf[0] != '5') break; 88 | resp[0] = 'S'; 89 | return true; 90 | default: 91 | resp[0] = 0; break; 92 | } 93 | return false; 94 | } 95 | 96 | inline char* rot13(char *str, size_t str_size, bool decode) 97 | { 98 | for (size_t i = 0; i < str_size; i++) { 99 | if (decode) { 100 | str[i] -= 13; 101 | } 102 | else { 103 | str[i] += 13; 104 | } 105 | } 106 | return str; 107 | } 108 | 109 | bool listen_for_connect(t_mini_iat &iat, int port, char resp[4]) 110 | { 111 | t_socket_iat sIAT; 112 | if (!init_socket_iat(iat, sIAT)) { 113 | return false; 114 | } 115 | const size_t buf_size = 4; 116 | char buf[buf_size]; 117 | 118 | LPVOID u32_dll = iat._LoadLibraryA("user32.dll"); 119 | 120 | 121 | auto _MessageBoxW = reinterpret_cast(iat._GetProcAddress((HMODULE)u32_dll, "MessageBoxW")); 122 | 123 | bool got_resp = false; 124 | WSADATA wsaData; 125 | SecureZeroMemory(&wsaData, sizeof(wsaData)); 126 | /// code: 127 | if (sIAT._WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { 128 | return false; 129 | } 130 | struct sockaddr_in sock_config; 131 | SecureZeroMemory(&sock_config, sizeof(sock_config)); 132 | SOCKET listen_socket = sIAT._socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 133 | if (listen_socket == INVALID_SOCKET) { 134 | _MessageBoxW(NULL, L"Creating the socket failed", L"Stage 2", MB_ICONEXCLAMATION); 135 | sIAT._WSACleanup(); 136 | return false; 137 | } 138 | char *host_str = rot13(LOCALHOST_ROT13, _countof(LOCALHOST_ROT13) - 1, true); 139 | sock_config.sin_addr.s_addr = sIAT._inet_addr(host_str); 140 | sock_config.sin_family = AF_INET; 141 | sock_config.sin_port = sIAT._htons(port); 142 | 143 | rot13(host_str, _countof(LOCALHOST_ROT13) - 1, false); //encode it back 144 | 145 | bool is_ok = true; 146 | if (sIAT._bind(listen_socket, (SOCKADDR*)&sock_config, sizeof(sock_config)) == SOCKET_ERROR) { 147 | is_ok = false; 148 | _MessageBoxW(NULL, L"Binding the socket failed", L"Stage 2", MB_ICONEXCLAMATION); 149 | 150 | } 151 | if (sIAT._listen(listen_socket, SOMAXCONN) == SOCKET_ERROR) { 152 | is_ok = false; 153 | _MessageBoxW(NULL, L"Listening the socket failed", L"Stage 2", MB_ICONEXCLAMATION); 154 | } 155 | 156 | SOCKET conn_sock = SOCKET_ERROR; 157 | while (is_ok && (conn_sock = sIAT._accept(listen_socket, 0, 0)) != SOCKET_ERROR) { 158 | if (sIAT._recv(conn_sock, buf, buf_size, 0) > 0) { 159 | got_resp = true; 160 | if (switch_state(buf, resp)) { 161 | sIAT._send(conn_sock, resp, buf_size, 0); 162 | sIAT._closesocket(conn_sock); 163 | break; 164 | } 165 | } 166 | sIAT._closesocket(conn_sock); 167 | } 168 | 169 | sIAT._closesocket(listen_socket); 170 | sIAT._WSACleanup(); 171 | return got_resp; 172 | } 173 | 174 | int main() 175 | { 176 | t_mini_iat iat; 177 | if (!init_iat(iat)) { 178 | return 1; 179 | } 180 | char resp[4]; 181 | SecureZeroMemory(resp, sizeof(resp)); 182 | listen_for_connect(iat, 1337, resp); 183 | listen_for_connect(iat, 1338, resp); 184 | listen_for_connect(iat, 1339, resp); 185 | return 0; 186 | } 187 | -------------------------------------------------------------------------------- /demos/knock_test.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | import argparse 4 | 5 | 6 | def main(): 7 | parser = argparse.ArgumentParser(description="Send to the Crackme") 8 | parser.add_argument('--port', dest="port", default="1337", help="Port to connect") 9 | parser.add_argument('--buf', dest="buf", default="9", help="Buffer to send") 10 | args = parser.parse_args() 11 | my_port = int(args.port) 12 | print('[+] Connecting to port: ' + str(my_port)) 13 | key = args.buf.encode() 14 | try: 15 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 16 | s.connect(('127.0.0.1', my_port)) 17 | s.send(key) 18 | result = s.recv(512) 19 | if result is not None: 20 | print("[+] Response: " + str(result)) 21 | s.close() 22 | except socket.error: 23 | print("Could not connect to the socket. Is the crackme running?") 24 | 25 | 26 | if __name__ == "__main__": 27 | try: 28 | main() 29 | except KeyboardInterrupt: 30 | sys.exit(0) 31 | -------------------------------------------------------------------------------- /demos/peb_lookup.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #ifndef TO_LOWERCASE 7 | #define TO_LOWERCASE(out, c1) (out = (c1 <= 'Z' && c1 >= 'A') ? c1 = (c1 - 'A') + 'a': c1) 8 | #endif 9 | 10 | // enhanced version of LDR_DATA_TABLE_ENTRY 11 | typedef struct _LDR_DATA_TABLE_ENTRY1 { 12 | LIST_ENTRY InLoadOrderLinks; 13 | LIST_ENTRY InMemoryOrderLinks; 14 | LIST_ENTRY InInitializationOrderLinks; 15 | void* DllBase; 16 | void* EntryPoint; 17 | ULONG SizeOfImage; 18 | UNICODE_STRING FullDllName; 19 | UNICODE_STRING BaseDllName; 20 | ULONG Flags; 21 | SHORT LoadCount; 22 | SHORT TlsIndex; 23 | HANDLE SectionHandle; 24 | ULONG CheckSum; 25 | ULONG TimeDateStamp; 26 | } LDR_DATA_TABLE_ENTRY1, * PLDR_DATA_TABLE_ENTRY1; 27 | 28 | inline LPVOID get_module_by_name(WCHAR* module_name) 29 | { 30 | PEB *peb; 31 | #if defined(_WIN64) 32 | peb = (PPEB)__readgsqword(0x60); 33 | #else 34 | peb = (PPEB)__readfsdword(0x30); 35 | #endif 36 | PEB_LDR_DATA *ldr = peb->Ldr; 37 | 38 | LIST_ENTRY *head = &ldr->InMemoryOrderModuleList; 39 | for(LIST_ENTRY *current = head->Flink; current != head; current = current->Flink){ 40 | LDR_DATA_TABLE_ENTRY1* entry = CONTAINING_RECORD(current, LDR_DATA_TABLE_ENTRY1, InMemoryOrderLinks); 41 | if (!entry || !entry->DllBase) break; 42 | 43 | WCHAR* curr_name = entry->BaseDllName.Buffer; 44 | if (!curr_name) continue; 45 | 46 | size_t i; 47 | for (i = 0; i < entry->BaseDllName.Length; i++) { 48 | // if any of the strings finished: 49 | if (module_name[i] == 0 || curr_name[i] == 0) { 50 | break; 51 | } 52 | WCHAR c1, c2; 53 | TO_LOWERCASE(c1, module_name[i]); 54 | TO_LOWERCASE(c2, curr_name[i]); 55 | if (c1 != c2) break; 56 | } 57 | // both of the strings finished, and so far they were identical: 58 | if (module_name[i] == 0 && curr_name[i] == 0) { 59 | return entry->DllBase; 60 | } 61 | } 62 | 63 | return nullptr; 64 | } 65 | 66 | inline LPVOID get_func_by_name(LPVOID module, char* func_name) 67 | { 68 | IMAGE_DOS_HEADER* idh = (IMAGE_DOS_HEADER*)module; 69 | if (idh->e_magic != IMAGE_DOS_SIGNATURE) { 70 | return nullptr; 71 | } 72 | IMAGE_NT_HEADERS* nt_headers = (IMAGE_NT_HEADERS*)((BYTE*)module + idh->e_lfanew); 73 | IMAGE_DATA_DIRECTORY* exportsDir = &(nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]); 74 | if (!exportsDir->VirtualAddress) { 75 | return nullptr; 76 | } 77 | 78 | DWORD expAddr = exportsDir->VirtualAddress; 79 | IMAGE_EXPORT_DIRECTORY* exp = (IMAGE_EXPORT_DIRECTORY*)(expAddr + (ULONG_PTR)module); 80 | SIZE_T namesCount = exp->NumberOfNames; 81 | 82 | DWORD funcsListRVA = exp->AddressOfFunctions; 83 | DWORD funcNamesListRVA = exp->AddressOfNames; 84 | DWORD namesOrdsListRVA = exp->AddressOfNameOrdinals; 85 | 86 | //go through names: 87 | for (SIZE_T i = 0; i < namesCount; i++) { 88 | DWORD* nameRVA = (DWORD*)(funcNamesListRVA + (BYTE*)module + i * sizeof(DWORD)); 89 | WORD* nameIndex = (WORD*)(namesOrdsListRVA + (BYTE*)module + i * sizeof(WORD)); 90 | DWORD* funcRVA = (DWORD*)(funcsListRVA + (BYTE*)module + (*nameIndex) * sizeof(DWORD)); 91 | 92 | LPSTR curr_name = (LPSTR)(*nameRVA + (BYTE*)module); 93 | size_t k; 94 | for (k = 0; func_name[k] != 0 && curr_name[k] != 0; k++) { 95 | if (func_name[k] != curr_name[k]) break; 96 | } 97 | if (func_name[k] == 0 && curr_name[k] == 0) { 98 | //found 99 | return (BYTE*)module + (*funcRVA); 100 | } 101 | } 102 | return nullptr; 103 | } 104 | -------------------------------------------------------------------------------- /demos/pop_calc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "peb_lookup.h" 3 | 4 | int main() 5 | { 6 | LPVOID base = get_module_by_name((const LPWSTR)L"kernel32.dll"); 7 | if (!base) { 8 | return 1; 9 | } 10 | auto _WinExec = reinterpret_cast(get_func_by_name((HMODULE)base, (LPSTR)"WinExec")); 11 | if (!_WinExec) return 4; 12 | 13 | _WinExec("calc.exe", SW_SHOWNORMAL); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /demos/popup.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "peb_lookup.h" 3 | 4 | int main() 5 | { 6 | LPVOID base = get_module_by_name((const LPWSTR)L"kernel32.dll"); 7 | if (!base) { 8 | return 1; 9 | } 10 | 11 | LPVOID load_lib = get_func_by_name((HMODULE)base, (LPSTR)"LoadLibraryA"); 12 | if (!load_lib) { 13 | return 2; 14 | } 15 | LPVOID get_proc = get_func_by_name((HMODULE)base, (LPSTR)"GetProcAddress"); 16 | if (!get_proc) { 17 | return 3; 18 | } 19 | auto _LoadLibraryA = reinterpret_cast(load_lib); 20 | auto _GetProcAddress = reinterpret_cast(get_proc); 21 | 22 | LPVOID u32_dll = _LoadLibraryA("user32.dll"); 23 | 24 | auto _MessageBoxW = reinterpret_cast(_GetProcAddress((HMODULE)u32_dll, "MessageBoxW")); 25 | if (!_MessageBoxW) return 4; 26 | 27 | _MessageBoxW(0, L"Hello World!", L"Demo!", MB_OK); 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /demos/popup_arr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "peb_lookup.h" 3 | 4 | int main() 5 | { 6 | LPVOID base = get_module_by_name((const LPWSTR)L"kernel32.dll"); 7 | if (!base) { 8 | return 1; 9 | } 10 | 11 | LPVOID load_lib = get_func_by_name((HMODULE)base, (LPSTR)"LoadLibraryA"); 12 | if (!load_lib) { 13 | return 2; 14 | } 15 | LPVOID get_proc = get_func_by_name((HMODULE)base, (LPSTR)"GetProcAddress"); 16 | if (!get_proc) { 17 | return 3; 18 | } 19 | auto _LoadLibraryA = reinterpret_cast(load_lib); 20 | auto _GetProcAddress = reinterpret_cast(get_proc); 21 | 22 | LPVOID u32_dll = _LoadLibraryA("user32.dll"); 23 | 24 | auto _MessageBoxW = reinterpret_cast(_GetProcAddress((HMODULE)u32_dll, "MessageBoxW")); 25 | if (!_MessageBoxW) return 4; 26 | 27 | wchar_t* temp[] = { L"123", L"xxx", L"bbb" }; 28 | for (size_t i = 0; i < _countof(temp); i++) { 29 | _MessageBoxW(0, temp[i], L"Demo", MB_OK); 30 | } 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /docs/FromaCprojectthroughassemblytoshellcode.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasherezade/masm_shc/a2931aa7c884c897feac07009b96be582752cd04/docs/FromaCprojectthroughassemblytoshellcode.pdf -------------------------------------------------------------------------------- /masm_shc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.0) 2 | 3 | project ( masm_shc ) 4 | 5 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") 6 | 7 | set (srcs 8 | main.cpp 9 | string_util.cpp 10 | ) 11 | 12 | set (hdrs 13 | string_util.h 14 | ) 15 | 16 | add_executable ( ${PROJECT_NAME} ${hdrs} ${srcs} ) 17 | 18 | INSTALL( TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX} COMPONENT ${PROJECT_NAME} ) 19 | 20 | -------------------------------------------------------------------------------- /masm_shc/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "string_util.h" 13 | #define VERSION "0.3.1" 14 | 15 | bool g_is32bit = false; 16 | 17 | typedef struct { 18 | std::string infile; 19 | std::string outfile; 20 | 21 | bool inlineStrings; 22 | bool removeCRT; 23 | bool appendRSPStub; 24 | } t_params; 25 | 26 | 27 | bool has_token(std::vector &tokens, const std::string &token) 28 | { 29 | std::vector::iterator itr; 30 | for (itr = tokens.begin(); itr != tokens.end(); itr++) { 31 | if (*itr == token) { 32 | return true; 33 | } 34 | } 35 | return false; 36 | } 37 | 38 | std::string get_constant(std::map &consts_lines, std::vector &tokens_line) 39 | { 40 | std::map::iterator itr; 41 | for (itr = consts_lines.begin(); itr != consts_lines.end(); itr++) { 42 | std::string const_name = itr->first; 43 | if (has_token(tokens_line, const_name)) { 44 | return const_name; 45 | } 46 | } 47 | return ""; 48 | } 49 | 50 | std::vector split_to_tokens(std::string orig_line) 51 | { 52 | const char to_replace[] = { '\t', ',' }; 53 | std::string line = orig_line; 54 | for (size_t i = 0; i < _countof(to_replace); i++) { 55 | replace_char(line, to_replace[i], ' '); 56 | } 57 | 58 | std::vector tokens = split_by_delimiter(line, ' '); 59 | 60 | //post-process tokens 61 | std::vector::iterator itr; 62 | for (itr = tokens.begin(); itr != tokens.end(); itr++) { 63 | std::string &token = *itr; 64 | remove_prefix(token, "FLAT:"); 65 | } 66 | return tokens; 67 | } 68 | 69 | void append_align_rsp(std::ofstream &ofile) 70 | { 71 | char stub[] = "PUBLIC AlignRSP\n" 72 | "_TEXT SEGMENT\n" 73 | " ; AlignRSP - by @mattifestation (http://www.exploit-monday.com/2013/08/writing-optimized-windows-shellcode-in-c.html)\n" 74 | " ; AlignRSP is a simple call stub that ensures that the stack is 16 - byte aligned prior\n" 75 | " ; to calling the entry point of the payload.This is necessary because 64 - bit functions\n" 76 | " ; in Windows assume that they were called with 16 - byte stack alignment.When amd64\n" 77 | " ; shellcode is executed, you can't be assured that you stack is 16-byte aligned. For example,\n" 78 | " ; if your shellcode lands with 8 - byte stack alignment, any call to a Win32 function will likely\n" 79 | " ; crash upon calling any ASM instruction that utilizes XMM registers(which require 16 - byte)\n" 80 | " ; alignment.\n\n" 81 | " AlignRSP PROC\n" 82 | " push rsi; Preserve RSI since we're stomping on it\n" 83 | " mov rsi, rsp; Save the value of RSP so it can be restored\n" 84 | " and rsp, 0FFFFFFFFFFFFFFF0h; Align RSP to 16 bytes\n" 85 | " sub rsp, 020h; Allocate homing space for ExecutePayload\n" 86 | " call main; Call the entry point of the payload\n" 87 | " mov rsp, rsi; Restore the original value of RSP\n" 88 | " pop rsi; Restore RSI\n" 89 | " ret; Return to caller\n" 90 | " AlignRSP ENDP\n" 91 | "_TEXT ENDS\n\n"; 92 | 93 | ofile << stub; 94 | } 95 | 96 | bool process_file(t_params ¶ms) 97 | { 98 | std::ifstream file(params.infile); 99 | if (!file.is_open()) { 100 | std::cerr << "[ERROR] Opening the input file failed!\n"; 101 | return false; 102 | } 103 | 104 | std::ofstream ofile(params.outfile); 105 | if (!ofile.is_open()) { 106 | std::cerr << "[ERROR] Opening the output file failed!\n"; 107 | return false; 108 | } 109 | std::map consts_lines; 110 | 111 | std::string seg_name = ""; 112 | std::string const_name = ""; 113 | bool code_start = false; 114 | 115 | std::string line; 116 | for (size_t line_count = 0; std::getline(file, line); line_count++) { 117 | 118 | std::vector tokens = split_to_tokens(line); 119 | 120 | if (tokens.size() == 0) { 121 | ofile << line << "\n"; //copy empty line 122 | continue; 123 | } 124 | if (tokens[0] == ".686P") { 125 | g_is32bit = true; 126 | } 127 | 128 | if (tokens[0] == "EXTRN") { 129 | std::cerr << "[ERROR] Line " << std::dec << line_count << ": External dependency detected:\n" << line << "\n"; 130 | } 131 | 132 | bool in_skipped = false; 133 | bool in_const = false; 134 | 135 | if (tokens.size() >= 2) { 136 | if (tokens[1] == "SEGMENT") { 137 | seg_name = tokens[0]; 138 | if (!code_start && seg_name == "_TEXT") { 139 | code_start = true; 140 | if (g_is32bit) { 141 | ofile << "assume fs:nothing\n"; 142 | } 143 | else if (params.appendRSPStub) { 144 | append_align_rsp(ofile); 145 | std::cout << "[INFO] Entry Point: AlignRSP\n"; 146 | } 147 | 148 | } 149 | if (seg_name == "_BSS") { 150 | std::cerr << "[ERROR] Line " << std::dec << line_count << ": _BSS segment detected! Remove all global and static variables!\n"; 151 | } 152 | } 153 | 154 | if (seg_name == "pdata" || seg_name == "xdata" || seg_name == "voltbl") { 155 | in_skipped = true; 156 | } 157 | if (seg_name == "CONST" || seg_name == "_DATA") { 158 | in_const = true; 159 | } 160 | if (tokens[1] == "ENDS" && tokens[0] == seg_name) { 161 | seg_name = ""; 162 | 163 | if (in_const) continue; // skip the ending of the CONST section 164 | } 165 | } 166 | if (in_skipped) { 167 | continue; 168 | } 169 | if (params.removeCRT && tokens[0] == "INCLUDELIB") { 170 | if (tokens[1] == "LIBCMT" || tokens[1] == "OLDNAMES") { 171 | ofile << "; " << line << "\n"; //copy commented out line 172 | continue; 173 | } 174 | std::cerr << "[ERROR] Line " << std::dec << line_count << ": INCLUDELIB detected! Remove all external dependencies!\n"; 175 | } 176 | if (params.inlineStrings && in_const) { 177 | if (tokens[1] == "DB") { 178 | const_name = tokens[0]; 179 | //ofile << ";Token name: " << const_name << "\n"; 180 | } 181 | if (const_name != "") { 182 | if (consts_lines.find(const_name) == consts_lines.end()) { 183 | consts_lines[const_name] = line; 184 | } 185 | else { 186 | consts_lines[const_name] += "\n" + line; 187 | } 188 | } 189 | continue; 190 | } 191 | if (tokens[0] == "rex_jmp") { 192 | replace_str(line, "rex_jmp", "JMP"); 193 | } 194 | std::string curr_const = get_constant(consts_lines, tokens); 195 | if (params.inlineStrings && curr_const != "") { 196 | //ofile << ";Token found: " << const_name << "\n"; 197 | std::string label_after = "after_" + curr_const; 198 | ofile << "\tCALL " << label_after << "\n"; 199 | ofile << consts_lines[curr_const] << "\n"; 200 | ofile << label_after << ":\n"; 201 | if (tokens.size() > 2 && (tokens[0] == "lea" || tokens[0] == "mov")) { 202 | auto offset_index = find(tokens.begin() + 1, tokens.end(), "OFFSET"); 203 | std::string instructions = tokens[1]; 204 | if(std::distance(tokens.begin(), offset_index) == 4) { 205 | instructions = tokens[1] + " " + tokens[2]+ " " + tokens[3]; 206 | } 207 | ofile << "\tPOP " << instructions << "\n"; 208 | } 209 | ofile << "\n"; 210 | ofile << "; " << line << "\n"; //copy commented out line 211 | continue; 212 | } 213 | if (!g_is32bit && has_token(tokens, "gs:96")) { 214 | replace_str(line, "gs:96", "gs:[96]"); 215 | } 216 | 217 | ofile << line << "\n"; //copy line 218 | } 219 | file.close(); 220 | ofile.close(); 221 | 222 | if (params.inlineStrings) { 223 | std::cout << "[INFO] Strings have been inlined. It may require to change some short jumps (jmp SHORT) into jumps (jmp)\n"; 224 | } 225 | return true; 226 | } 227 | 228 | int main(int argc, char *argv[]) 229 | { 230 | if (argc < 3) { 231 | std::cout << "~ masm_shc v." << VERSION << " ~\n\n" 232 | << "A helper utility for creating shellcodes.\nCleans MASM file generated by MSVC, gives refactoring hints.\n\n"; 233 | std::cout << "Args: \n"; 234 | system("pause"); 235 | return 0; 236 | } 237 | 238 | t_params params; 239 | params.appendRSPStub = true; 240 | params.inlineStrings = true; 241 | params.removeCRT = true; 242 | 243 | params.infile = argv[1]; 244 | params.outfile = argv[2]; 245 | 246 | if (process_file(params)) { 247 | return 0; 248 | } 249 | return 1; 250 | } 251 | -------------------------------------------------------------------------------- /masm_shc/string_util.cpp: -------------------------------------------------------------------------------- 1 | #include "string_util.h" 2 | 3 | std::vector split_by_delimiter(const std::string& line, char delim) 4 | { 5 | std::string split; 6 | std::istringstream ss(line); 7 | std::vector tokens; 8 | 9 | while (std::getline(ss, split, delim)) { 10 | split = trim(split); 11 | if (split.length() > 0) { 12 | tokens.push_back(split); 13 | } 14 | } 15 | return tokens; 16 | } 17 | 18 | size_t replace_char(std::string& str, const char from, const char to) 19 | { 20 | size_t replaced = 0; 21 | for (size_t i = 0; i < str.length(); i++) { 22 | const char c = str[i]; 23 | if (c == from) { 24 | str[i] = to; 25 | replaced++; 26 | } 27 | } 28 | return replaced; 29 | } 30 | 31 | 32 | void remove_prefix(std::string &str, const std::string &prefix) 33 | { 34 | std::string::size_type i = str.find(prefix); 35 | 36 | if (i != std::string::npos) 37 | str.erase(i, prefix.length()); 38 | } 39 | 40 | void replace_str(std::string &my_str, const std::string& from_str, const std::string& to_str) 41 | { 42 | size_t index; 43 | while ((index = my_str.find(from_str)) != std::string::npos) { 44 | my_str.replace(index, from_str.length(), to_str); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /masm_shc/string_util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | inline std::string& trim(std::string& s) 15 | { 16 | const char* t = " \t\n\r\f\v"; 17 | s.erase(s.find_last_not_of(t) + 1); 18 | s.erase(0, s.find_first_not_of(t)); 19 | return s; 20 | } 21 | 22 | std::vector split_by_delimiter(const std::string& line, char delim); 23 | 24 | size_t replace_char(std::string &str, const char from, const char to); 25 | 26 | void remove_prefix(std::string &str, const std::string &prefix); 27 | 28 | void replace_str(std::string &my_str, const std::string& from_str, const std::string& to_str); 29 | -------------------------------------------------------------------------------- /runshc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.0) 2 | 3 | project ( runshc ) 4 | 5 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") 6 | 7 | set (srcs 8 | util.cpp 9 | ) 10 | 11 | set (hdrs 12 | util.h 13 | ) 14 | 15 | add_executable ( ${PROJECT_NAME} ${hdrs} ${srcs} ${rsrc} main.cpp ) 16 | 17 | INSTALL( TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX} COMPONENT ${PROJECT_NAME} ) 18 | -------------------------------------------------------------------------------- /runshc/LICENSE: -------------------------------------------------------------------------------- 1 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 2 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 3 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 4 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 5 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 6 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 7 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 8 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 9 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 10 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /runshc/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "util.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | if (argc < 2) { 8 | std::cout << "~ runshc ~\n\n" 9 | << "Run shellcode: loads and deploys shellcode file.\n"; 10 | #ifdef _WIN64 11 | std::cout << "For 64-bit shellcodes.\n"; 12 | #else 13 | std::cout << "For 32-bit shellcodes.\n"; 14 | #endif 15 | std::cout << "\n"; 16 | std::cout << "Args: " << std::endl; 17 | system("pause"); 18 | return 0; 19 | } 20 | 21 | size_t exe_size = 0; 22 | char* in_path = argv[1]; 23 | 24 | std::cout << "[*] Reading module from: " << in_path << std::endl; 25 | BYTE *my_exe = util::load_file(in_path, exe_size); 26 | if (!my_exe) { 27 | std::cout << "[-] Loading file failed" << std::endl; 28 | return -1; 29 | } 30 | BYTE *test_buf = util::alloc_aligned(exe_size, PAGE_EXECUTE_READWRITE); 31 | if (!test_buf) { 32 | util::free_file(my_exe); 33 | std::cout << "[-] Allocating buffer failed" << std::endl; 34 | return -2; 35 | } 36 | //copy file content into executable buffer: 37 | memcpy(test_buf, my_exe, exe_size); 38 | 39 | //free the original buffer: 40 | util::free_file(my_exe); 41 | 42 | std::cout << "[*] Running the shellcode:" << std::endl; 43 | //run it: 44 | int(*my_main)() = (int(*)()) ((ULONGLONG)test_buf); 45 | int ret_val = my_main(); 46 | 47 | util::free_aligned(test_buf); 48 | std::cout << "[+] The shellcode finished with a return value: " << std::hex << ret_val << std::endl; 49 | return ret_val; 50 | } 51 | -------------------------------------------------------------------------------- /runshc/util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include 3 | 4 | BYTE* util::alloc_aligned(size_t buffer_size, DWORD protect, ULONGLONG desired_base) 5 | { 6 | if (!buffer_size) return nullptr; 7 | 8 | BYTE* buf = (BYTE*)VirtualAlloc((LPVOID)desired_base, buffer_size, MEM_COMMIT | MEM_RESERVE, protect); 9 | return buf; 10 | } 11 | 12 | bool util::free_aligned(BYTE* buffer) 13 | { 14 | if (buffer == nullptr) return true; 15 | if (!VirtualFree(buffer, 0, MEM_RELEASE)) { 16 | #ifdef _DEBUG 17 | std::cerr << "Releasing failed" << std::endl; 18 | #endif 19 | return false; 20 | } 21 | return true; 22 | } 23 | 24 | BYTE* util::load_file(IN const char *filename, OUT size_t &read_size) 25 | { 26 | HANDLE file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); 27 | if (file == INVALID_HANDLE_VALUE) { 28 | #ifdef _DEBUG 29 | std::cerr << "Could not open file!" << std::endl; 30 | #endif 31 | return nullptr; 32 | } 33 | HANDLE mapping = CreateFileMappingA(file, nullptr, PAGE_READONLY, 0, 0, nullptr); 34 | if (!mapping) { 35 | #ifdef _DEBUG 36 | std::cerr << "Could not create mapping!" << std::endl; 37 | #endif 38 | CloseHandle(file); 39 | return nullptr; 40 | } 41 | BYTE *dllRawData = (BYTE*)MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0); 42 | if (!dllRawData) { 43 | #ifdef _DEBUG 44 | std::cerr << "Could not map view of file" << std::endl; 45 | #endif 46 | CloseHandle(mapping); 47 | CloseHandle(file); 48 | return nullptr; 49 | } 50 | size_t r_size = GetFileSize(file, nullptr); 51 | if (read_size != 0 && read_size <= r_size) { 52 | r_size = read_size; 53 | } 54 | if (IsBadReadPtr(dllRawData, r_size)) { 55 | std::cerr << "[-] Mapping of " << filename << " is invalid!" << std::endl; 56 | UnmapViewOfFile(dllRawData); 57 | CloseHandle(mapping); 58 | CloseHandle(file); 59 | return nullptr; 60 | } 61 | BYTE* localCopyAddress = alloc_aligned(r_size, PAGE_READWRITE); 62 | if (localCopyAddress != nullptr) { 63 | memcpy(localCopyAddress, dllRawData, r_size); 64 | read_size = r_size; 65 | } 66 | else { 67 | read_size = 0; 68 | #ifdef _DEBUG 69 | std::cerr << "Could not allocate memory in the current process" << std::endl; 70 | #endif 71 | } 72 | UnmapViewOfFile(dllRawData); 73 | CloseHandle(mapping); 74 | CloseHandle(file); 75 | return localCopyAddress; 76 | } 77 | 78 | void util::free_file(BYTE* buffer) 79 | { 80 | free_aligned(buffer); 81 | } 82 | -------------------------------------------------------------------------------- /runshc/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace util { 7 | 8 | BYTE* alloc_aligned(size_t buffer_size, DWORD protect, ULONGLONG desired_base=0); 9 | 10 | bool free_aligned(BYTE* buffer); 11 | 12 | BYTE* load_file(IN const char *filename, OUT size_t &read_size); 13 | 14 | void free_file(BYTE* buffer); 15 | } 16 | --------------------------------------------------------------------------------