├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md └── src ├── inc.hpp ├── main.cpp ├── scan.cpp └── scan.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vscode/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/minhook"] 2 | path = vendor/minhook 3 | url = https://github.com/TsudaKageyu/minhook.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | project(CurlDump) 3 | set(CMAKE_CXX_STANDARD 20) 4 | set(CMAKE_CXX_STANDARD_REQUIRED True) 5 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 6 | include_directories(src inc vendor/minhook vendor/minhook/include) 7 | add_subdirectory(vendor/minhook) 8 | set(SOURCE src/main.cpp src/scan.cpp) 9 | add_library(CurlDump SHARED ${SOURCE}) 10 | target_link_libraries(CurlDump minhook) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 firah 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 | # curl-dump 2 | Intercept curl_easy_setopt calls and view or modify responses in any Windows application using cURL. 3 | 4 | Inject this dll into an application using cURL, and a console window will output any relevant info. 5 | -------------------------------------------------------------------------------- /src/inc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #undef max -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "inc.hpp" 2 | #include "scan.hpp" 3 | 4 | bool g_hooked_write = false; 5 | void *g_curl_address = nullptr; 6 | void *g_write_func_address = nullptr; 7 | 8 | // Some executables which have anti-VirtualProtect (ex. VMP/Themida packed) stuff won't hook as simple as this, you will need to disable the minhook vp check and find another way to get write priveleges to the memory 9 | // It will alert if it does fail due to this, generally with MH_ERROR_MEMORY_PROTECT or something similar 10 | // I might release a bypass for this later, it just would impact the cleanliness of this source 11 | 12 | void hook_func(void **orig, void *address, void *hook) 13 | { 14 | auto res = MH_CreateHook(address, hook, orig); 15 | if (res != MH_OK) 16 | { 17 | printf("CreateHook failed: %s\n", MH_StatusToString(res)); 18 | } 19 | res = MH_EnableHook(address); 20 | if (res != MH_OK) 21 | { 22 | printf("EnableHook failed: %s\n", MH_StatusToString(res)); 23 | } 24 | } 25 | 26 | void unhook_func(void *address) 27 | { 28 | auto res = MH_DisableHook(address); 29 | if (res != MH_OK) 30 | { 31 | printf("DisableHook failed: %s\n", MH_StatusToString(res)); 32 | } 33 | } 34 | 35 | size_t (*o_write_callback)(char *ptr, size_t size, size_t nmemb, void *userdata); 36 | 37 | 38 | /* 39 | 40 | To modify responses: 41 | 42 | if(modify_this) 43 | { 44 | auto resp = "New response!"; 45 | o_write_callback((char*)resp, 1, strlen(resp), userdata); 46 | return size * nmemb; 47 | } 48 | 49 | */ 50 | 51 | size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata) 52 | { 53 | std::string out(static_cast(ptr), size * nmemb); 54 | printf("Write Callback %s", out.c_str()); 55 | printf("\nEND\n"); 56 | return o_write_callback(ptr, size, nmemb, userdata); 57 | } 58 | 59 | uint32_t (*o_curl_easy_setopt)(void *handle, uint32_t option, void *parameter); 60 | 61 | uint32_t curl_easy_setopt(void *handle, uint32_t option, void *parameter) 62 | { 63 | if (option == 10002) 64 | { 65 | printf("URL: %d %s\n", option, (char *)parameter); 66 | } 67 | else if (option == 10015) 68 | { 69 | printf("Post Fields: %d %s\n", option, (char *)parameter); 70 | } 71 | else if (option == 20011) 72 | { 73 | if (!g_hooked_write) 74 | { 75 | printf("Write Function: %d %p, Hooking...\n", option, (void *)parameter); 76 | hook_func((void **)&o_write_callback, parameter, write_callback); 77 | g_write_func_address = parameter; 78 | g_hooked_write = true; 79 | } 80 | else 81 | { 82 | printf("Write Function: %d %p\n", option, (void *)parameter); 83 | } 84 | } 85 | else if (option == 10018) 86 | { 87 | printf("User Agent: %d %s\n", option, (char *)parameter); 88 | } 89 | else if (option == 10036) 90 | { 91 | printf("Custom Request: %d %s\n", option, (char *)parameter); 92 | } 93 | else if (option == 10103) 94 | { 95 | printf("Private Data: %d %p\n", option, parameter); 96 | } 97 | else 98 | { 99 | // https://gist.github.com/jseidl/3218673 Refer here if needed 100 | #if _WIN64 101 | printf("Unknown: %d %llx\n", option, (uint64_t)parameter); 102 | #else 103 | printf("Unknown: %d %x\n", option, (uint32_t)parameter); 104 | #endif 105 | } 106 | return o_curl_easy_setopt(handle, option, parameter); 107 | } 108 | 109 | void hook() 110 | { 111 | MH_Initialize(); 112 | 113 | #if _WIN64 114 | g_curl_address = scanner::scan("89 54 24 10 4C 89 44 24 ? 4C 89 4C 24 ? 48 83 EC 28 48 85 C9 75 08 8D 41 2B 48 83 C4 28 C3 4C 8D 44 24 ? E8 ? ? ? ? 48 83 C4 28 C3", "curl_easy_setopt", GetModuleHandleA(nullptr)); 115 | #else 116 | g_curl_address = scanner::scan("8B 44 24 04 85 C0 75 06 B8 ? ? ? ? C3 8D 4C 24 0C 51 FF 74 24 0C 50 E8 ? ? ? ? 83 C4 0C C3", "curl_easy_setopt", GetModuleHandleA(nullptr)); 117 | #endif 118 | 119 | //g_curl_address = (void*)((uintptr_t)GetModuleHandleA(nullptr) + 0xFFFFFF); // Replace offset if you want to hardcode it 120 | if (g_curl_address) 121 | hook_func((void **)&o_curl_easy_setopt, g_curl_address, curl_easy_setopt); 122 | } 123 | 124 | void unhook() 125 | { 126 | if (g_curl_address) 127 | unhook_func(g_curl_address); 128 | if (g_write_func_address) 129 | unhook_func(g_write_func_address); 130 | } 131 | 132 | DWORD WINAPI main_thread(PVOID module) 133 | { 134 | AllocConsole(); 135 | freopen("CONOUT$", "w", stdout); 136 | hook(); 137 | while (!GetAsyncKeyState(VK_DELETE)) 138 | { 139 | std::this_thread::yield(); 140 | } 141 | unhook(); 142 | fclose(stdout); 143 | FreeConsole(); 144 | FreeLibraryAndExitThread((HMODULE)module, 0); 145 | return 1; 146 | } 147 | 148 | // Entrypoint 149 | BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved) 150 | { 151 | if (reason == DLL_PROCESS_ATTACH) 152 | { 153 | CreateThread(nullptr, 0, &main_thread, (void *)module, 0, nullptr); 154 | } 155 | return TRUE; 156 | } -------------------------------------------------------------------------------- /src/scan.cpp: -------------------------------------------------------------------------------- 1 | #include "inc.hpp" 2 | #include "scan.hpp" 3 | 4 | namespace scanner 5 | { 6 | struct sig_byte 7 | { 8 | uint8_t val; 9 | bool wild; 10 | }; 11 | 12 | // Turns one char into a hex byte. 13 | char hex_char_to_byte(char ch) 14 | { 15 | if (ch >= '0' && ch <= '9') 16 | return ch - '0'; 17 | if (ch >= 'A' && ch <= 'F') 18 | return ch - 'A' + 10; 19 | if (ch >= 'a' && ch <= 'f') 20 | return ch - 'a' + 10; 21 | return 0; 22 | } 23 | 24 | // Turns hex string into numerical form, ex if uint8_t, it will read the next 2 bytes and turn it into a numerical form 25 | template 26 | T hex_to_num(const char *hex) 27 | { 28 | T value = 0; 29 | for (size_t i = 0; i < sizeof(T) * 2; ++i) 30 | value |= hex_char_to_byte(hex[i]) << (8 * sizeof(T) - 4 * (i + 1)); 31 | return value; 32 | } 33 | 34 | std::vector ida_to_bytes(std::string_view signature) // Turns IDA Signature to an easier form to search with 35 | { 36 | std::vector bytes; 37 | 38 | for (size_t i = 0; i < signature.length();) 39 | { 40 | if (signature[i] == ' ') 41 | { 42 | ++i; 43 | } 44 | else if (signature[i] == '?') 45 | { 46 | bytes.push_back({0x69, true}); 47 | i++; 48 | } 49 | else if (isxdigit(signature[i]) && isxdigit(signature[1 + i])) 50 | { 51 | bytes.push_back({hex_to_num(&signature[i]), false}); 52 | i += 2; 53 | } 54 | else 55 | { 56 | i++; 57 | } 58 | } 59 | 60 | return bytes; 61 | } 62 | 63 | // Get scanning range 64 | uintptr_t get_img_size(HMODULE mod) 65 | { 66 | auto dos_header = (IMAGE_DOS_HEADER *)mod; 67 | auto nt_header = (IMAGE_NT_HEADERS *)((uintptr_t)mod + dos_header->e_lfanew); 68 | return (uintptr_t)nt_header->OptionalHeader.SizeOfImage; 69 | } 70 | 71 | void *scan(std::string_view signature, std::string_view name, HMODULE mod) 72 | { 73 | auto converted_bytes = ida_to_bytes(signature); 74 | auto img_size = get_img_size(mod); 75 | auto data = (uint8_t *)mod; 76 | 77 | for (uintptr_t i = 0ull; i < img_size - converted_bytes.size(); i++) 78 | { 79 | for (uintptr_t j = 0ull; j < converted_bytes.size(); j++) 80 | { 81 | if (!converted_bytes[j].wild && converted_bytes[j].val != data[i + j]) 82 | { 83 | break; 84 | } 85 | else if (j == converted_bytes.size() - 1) 86 | { 87 | printf("Found %s at %p\n", name.data(), (void *)((uintptr_t)mod + i)); 88 | return (void *)((uintptr_t)mod + i); 89 | } 90 | } 91 | } 92 | printf("Failed to find %s\n", name.data()); 93 | return nullptr; 94 | } 95 | } -------------------------------------------------------------------------------- /src/scan.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace scanner 4 | { 5 | // Takes IDA Style Signatures and searches a module for matching bytes, returns first occurence 6 | void *scan(std::string_view signature, std::string_view name, HMODULE mod); 7 | } --------------------------------------------------------------------------------