├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build-* 2 | .idea 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | project(linux_injector) 3 | 4 | set(CMAKE_CXX_STANDARD 11) 5 | 6 | add_executable(linux_injector main.cpp) 7 | 8 | target_compile_options("${PROJECT_NAME}" PRIVATE -fno-exceptions -fno-rtti) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 namazso 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 | # linux_injector 2 | 3 | A simple ptrace-less shared library injector for x64 Linux. 4 | 5 | ## Usage 6 | 7 | `linux_injector ` 8 | 9 | **mode** 10 | 11 | 1. normal dlopen 12 | 2. memfd + dlopen (for injecting into containers) 13 | 3. raw shellcode 14 | 15 | **pid** 16 | 17 | Target process id. Must have ptrace rights to it (required for accessing `/proc/$pid/mem`). Not ptraced, so another process can freely ptrace it. 18 | 19 | **file** 20 | 21 | For mode 1: Module to inject, will be `dlopen`ed in the remote process. Should probably be a full path, because the remote LD_LIBRARY_PATH is used otherwise for resolution. 22 | 23 | For mode 2: Module to inject, will be `dlopen`ed in the remote process from a memfd, where the content will be copied. 24 | 25 | For mode 3: Raw shellcode to inject. You will be mapped at a 16-aligned address, and start execution on a random hijacked thread. Place hooks or create a thread, then return. 26 | 27 | ## Modification 28 | 29 | For control flow hijacking, this program needs a hijacking candidate. The code presented here uses `malloc`, this can be changed by editing `FUN_NAME` and recompiling. Make sure the hooked function can run under 100ms, so that it won't be overwritten while it executes. This means calls like `sleep` or `wait` are bad candidates for the initial shellcode. The function in question also needs to be more than `0x50` long for the shellcode not to overwrite other functions. 30 | 31 | ## Supported platforms 32 | 33 | Glibc and musl are supported for the both the target and the source process. The target process can be running in a container, and can use a different libc. Modes 2 and 3 will not require any paths accessible to the target process. 34 | 35 | Mode 2 requires Linux 3.17. 36 | 37 | Tested on Oracle Linux 7 (mode 2 not supported), Fedora 37, and Alpine Linux 3.17 38 | 39 | ## License 40 | 41 | [MIT License](LICENSE) 42 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 namazso 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | #define _LARGEFILE64_SOURCE 22 | #define _FILE_OFFSET_BITS 64 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #define VAR_NAME "timezone" 43 | #define FUN_NAME "malloc" 44 | 45 | __asm__(R"( 46 | .globl shell_begin 47 | .globl shell_first_patch_rip 48 | .globl shell_second_patch_rip 49 | .globl shell_end 50 | 51 | shell_begin: 52 | lock btsq $0, shell_begin(%rip) 53 | shell_first_patch_rip: # actually for previous instruction 54 | jc shell_begin 55 | 56 | // push stuff to preserve 57 | push %rax 58 | push %rdi 59 | push %rsi 60 | push %rdx 61 | push %r10 62 | push %r8 63 | push %r9 64 | 65 | xor %edi, %edi # addr = 0 66 | mov $0x10000, %esi # length = 64k 67 | shell_alloclen_patch_rip: # actually for previous instruction 68 | mov $7, %edx # prot = PROT_READ | PROT_WRITE | PROT_EXEC 69 | mov $0x22, %r10d # flags = MAP_PRIVATE | MAP_ANON 70 | xor %r8, %r8 # fd = NULL 71 | xor %r9, %r9 # offset = 0 72 | mov $9, %eax # __NR_mmap 73 | syscall 74 | 75 | orb $1, %al # keep other threads spinning 76 | mov %rax, shell_begin(%rip) # store the allocation in our variable 77 | shell_second_patch_rip: # actually for previous instruction 78 | 79 | xor %al, %al 80 | movw $0xfeeb, (%rax) 81 | jmp *%rax 82 | 83 | shell_end: 84 | .byte 0xcc 85 | )"); 86 | 87 | extern "C" void shell_begin(); 88 | extern "C" void shell_first_patch_rip(); 89 | extern "C" void shell_second_patch_rip(); 90 | extern "C" void shell_alloclen_patch_rip(); 91 | extern "C" void shell_end(); 92 | 93 | constexpr static size_t shell_size = 0x50; 94 | 95 | void make_shell(uint8_t* p, void* target_addr, void* var_addr, int32_t alloclen) 96 | { 97 | alloclen = (alloclen + 0x1000 - 1) / 0x1000 * 0x1000; 98 | 99 | const auto delta = (uint32_t)((uint8_t*)var_addr - (uint8_t*)target_addr); 100 | 101 | const auto og_begin = (uint8_t*)&shell_begin; 102 | const auto first_offset = (uint8_t*)&shell_first_patch_rip - og_begin; 103 | const auto second_offset = (uint8_t*)&shell_second_patch_rip - og_begin; 104 | const auto alloclen_offset = (uint8_t*)&shell_alloclen_patch_rip - og_begin; 105 | const auto size = (uint8_t*)&shell_end - og_begin; 106 | 107 | assert(size <= shell_size); 108 | 109 | memcpy(p, og_begin, size); 110 | 111 | *(uint32_t*)(p + first_offset - 5) += delta; 112 | *(uint32_t*)(p + second_offset - 4) += delta; 113 | *(int32_t*)(p + alloclen_offset - 4) = alloclen; 114 | } 115 | 116 | struct shell_raw_dlopen_params 117 | { 118 | void* p_dlopen; 119 | void* p_oldfun; 120 | uint64_t flags; 121 | }; 122 | 123 | __asm__(R"( 124 | 125 | .struct 0 126 | shell_raw_dlopen_params.p_dlopen: 127 | .space 8 128 | shell_raw_dlopen_params.p_oldfun: 129 | .space 8 130 | shell_raw_dlopen_params.flags: 131 | .space 8 132 | shell_raw_dlopen_params.path: 133 | 134 | 135 | .text 136 | 137 | .globl shell_raw_dlopen_begin 138 | .globl shell_raw_dlopen_end 139 | 140 | .balign 16 141 | 142 | shell_raw_dlopen_begin: 143 | .2byte 0x9066 144 | 145 | mov shell_raw_dlopen_end+shell_raw_dlopen_params.flags(%rip), %rsi 146 | lea shell_raw_dlopen_end+shell_raw_dlopen_params.path(%rip), %rdi 147 | 148 | call *shell_raw_dlopen_end+shell_raw_dlopen_params.p_dlopen(%rip) 149 | 150 | pop %r9 151 | pop %r8 152 | pop %r10 153 | pop %rdx 154 | pop %rsi 155 | pop %rdi 156 | pop %rax 157 | 158 | jmp *shell_raw_dlopen_end+shell_raw_dlopen_params.p_oldfun(%rip) 159 | 160 | .balign 16 161 | 162 | shell_raw_dlopen_end: 163 | .byte 0xcc 164 | )"); 165 | 166 | 167 | extern "C" void shell_raw_dlopen_begin(); 168 | extern "C" void shell_raw_dlopen_end(); 169 | 170 | struct shell_memfd_dlopen_params 171 | { 172 | void* p_dlopen; 173 | void* p_sprintf; 174 | void* p_oldfun; 175 | uint64_t flags; 176 | uint64_t data_len; 177 | }; 178 | 179 | __asm__(R"( 180 | .globl shell_memfd_dlopen_begin 181 | .globl shell_memfd_dlopen_end 182 | 183 | .struct 0 184 | shell_memfd_dlopen_params.p_dlopen: 185 | .space 8 186 | shell_memfd_dlopen_params.p_sprintf: 187 | .space 8 188 | shell_memfd_dlopen_params.p_oldfun: 189 | .space 8 190 | shell_memfd_dlopen_params.flags: 191 | .space 8 192 | shell_memfd_dlopen_params.data_len: 193 | .space 8 194 | shell_memfd_dlopen_params.data: 195 | 196 | 197 | .text 198 | 199 | .balign 16 200 | 201 | shell_memfd_dlopen_begin: 202 | .2byte 0x9066 203 | 204 | push %rbx 205 | push %r12 206 | 207 | lea shell_memfd_null(%rip), %rdi # name = "" 208 | xor %esi, %esi # flags = 0 209 | mov $319, %eax # __NR_memfd_create 210 | 211 | syscall 212 | 213 | mov %rax, %rbx 214 | 215 | mov %rax, %rdi # fd 216 | lea shell_memfd_dlopen_end+shell_memfd_dlopen_params.data(%rip), %rsi # buf 217 | mov shell_memfd_dlopen_end+shell_memfd_dlopen_params.data_len(%rip), %rdx # count 218 | mov $1, %eax # __NR_write 219 | 220 | syscall 221 | 222 | lea shell_memfd_format_out(%rip), %rdi # str 223 | lea shell_memfd_format_str(%rip), %rsi # format 224 | mov %rbx, %rdx # fd 225 | 226 | call *shell_memfd_dlopen_end+shell_memfd_dlopen_params.p_sprintf(%rip) # sprintf 227 | 228 | lea shell_memfd_format_out(%rip), %rdi # filename 229 | mov shell_memfd_dlopen_end+shell_memfd_dlopen_params.flags(%rip), %rsi # flag 230 | 231 | call *shell_memfd_dlopen_end+shell_memfd_dlopen_params.p_dlopen(%rip) # dlopen 232 | 233 | pop %r12 234 | pop %rbx 235 | 236 | pop %r9 237 | pop %r8 238 | pop %r10 239 | pop %rdx 240 | pop %rsi 241 | pop %rdi 242 | pop %rax 243 | 244 | jmp *shell_memfd_dlopen_end+shell_memfd_dlopen_params.p_oldfun(%rip) 245 | 246 | shell_memfd_format_str: 247 | .ascii "/proc/self/fd/%lu" 248 | shell_memfd_null: 249 | .byte 0 250 | shell_memfd_format_out: 251 | .fill 32, 1, 0 252 | 253 | .balign 16 254 | 255 | shell_memfd_dlopen_end: 256 | .byte 0xcc 257 | )"); 258 | 259 | 260 | extern "C" void shell_memfd_dlopen_begin(); 261 | extern "C" void shell_memfd_dlopen_end(); 262 | 263 | struct shell_raw_shellcode_params 264 | { 265 | void* p_oldfun; 266 | uint64_t _padding; 267 | }; 268 | 269 | __asm__(R"( 270 | .globl shell_raw_shellcode_begin 271 | .globl shell_raw_shellcode_end 272 | 273 | 274 | .balign 16 275 | 276 | shell_raw_shellcode_begin: 277 | .2byte 0x9066 278 | 279 | call shell_raw_shellcode_end+16 280 | 281 | pop %r9 282 | pop %r8 283 | pop %r10 284 | pop %rdx 285 | pop %rsi 286 | pop %rdi 287 | pop %rax 288 | 289 | jmp *shell_raw_shellcode_end(%rip) 290 | 291 | .balign 16 292 | 293 | shell_raw_shellcode_end: 294 | .byte 0xcc 295 | )"); 296 | 297 | 298 | extern "C" void shell_raw_shellcode_begin(); 299 | extern "C" void shell_raw_shellcode_end(); 300 | 301 | 302 | class remote_mem 303 | { 304 | int f; 305 | public: 306 | explicit remote_mem(unsigned long pid) : f(-1) 307 | { 308 | char name[32]; 309 | snprintf(name, sizeof(name), "/proc/%lu/mem", pid); 310 | name[31] = 0; 311 | f = open(name, O_RDWR | O_LARGEFILE); 312 | } 313 | 314 | ~remote_mem() 315 | { 316 | if (f != -1) 317 | close(f); 318 | } 319 | 320 | int fd() const 321 | { 322 | return f; 323 | } 324 | 325 | bool good() const 326 | { 327 | return f != -1; 328 | } 329 | 330 | ssize_t read_mem(void* addr, void* buf, size_t len) const 331 | { 332 | return pread(f, buf, len, (int64_t)addr); 333 | } 334 | 335 | ssize_t write_mem(void* addr, const void* buf, size_t len) const 336 | { 337 | return pwrite(f, buf, len, (int64_t)addr); 338 | } 339 | 340 | ssize_t write_code(void* addr, const void* buf, size_t len) const 341 | { 342 | if (len <= 2) 343 | { 344 | return write_mem(addr, buf, len); 345 | } 346 | constexpr uint16_t ebfe = 0xfeeb; 347 | if (!write_mem(addr, ebfe)) 348 | return 0; 349 | // sleep 100ms - wait for any currently ongoing calls to finish 350 | usleep(100000); 351 | if (!write_mem((uint8_t*)addr + 2, (char*)buf + 2, len - 2)) 352 | return 0; 353 | return write_mem(addr, (char*)buf, 2); 354 | } 355 | 356 | template 357 | ssize_t read_mem(void* addr, T& t) const 358 | { 359 | return read_mem(addr, &t, sizeof(T)); 360 | } 361 | 362 | template 363 | ssize_t write_mem(void* addr, const T& t) const 364 | { 365 | return write_mem(addr, &t, sizeof(T)); 366 | } 367 | 368 | template 369 | ssize_t write_code(void* addr, const T& t) const 370 | { 371 | return write_code(addr, &t, sizeof(T)); 372 | } 373 | }; 374 | 375 | 376 | void* remote_dlsym(remote_mem& mem, void* elf, const char* name) 377 | { 378 | Elf64_Ehdr elfh{}; 379 | if (!mem.read_mem(elf, elfh)) 380 | return nullptr; 381 | if (0 != memcmp(elfh.e_ident, ELFMAG, SELFMAG)) 382 | { 383 | return nullptr; 384 | } 385 | size_t dynrva = 0; 386 | size_t dynvsz = 0; 387 | for (size_t i = 0; i < elfh.e_phnum; ++i) 388 | { 389 | Elf64_Phdr phdr{}; 390 | if (!mem.read_mem((char*)elf + elfh.e_phoff + i * elfh.e_phentsize, phdr)) 391 | return nullptr; 392 | if (phdr.p_type != PT_DYNAMIC) 393 | continue; 394 | dynrva = phdr.p_vaddr; 395 | dynvsz = phdr.p_memsz; 396 | } 397 | if (!dynrva || !dynvsz) 398 | return nullptr; 399 | uintptr_t symtab = 0; 400 | uintptr_t strtab = 0; 401 | uintptr_t syment = 0; 402 | for (size_t i = 0; i < dynvsz; i += sizeof(Elf64_Dyn)) 403 | { 404 | Elf64_Dyn dyn{}; 405 | if (!mem.read_mem((char*)elf + dynrva + i, dyn)) 406 | return nullptr; 407 | if (dyn.d_tag == DT_STRTAB) 408 | strtab = dyn.d_un.d_ptr; 409 | if (dyn.d_tag == DT_SYMTAB) 410 | symtab = dyn.d_un.d_ptr; 411 | if (dyn.d_tag == DT_SYMENT) 412 | syment = dyn.d_un.d_val; 413 | } 414 | if (!symtab || !strtab || !syment) 415 | return nullptr; 416 | 417 | // this is actually incorrect 418 | if (symtab < (uintptr_t)elf) 419 | symtab += (uintptr_t)elf; 420 | if (strtab < (uintptr_t)elf) 421 | strtab += (uintptr_t)elf; 422 | 423 | char namecpy[256]; 424 | const auto namebuflen = std::min(strlen(name) + 1, (size_t)256); 425 | 426 | for (size_t i = symtab; i < strtab; i += syment) 427 | { 428 | Elf64_Sym sym{}; 429 | if (!mem.read_mem((void*)i, sym)) 430 | return nullptr; 431 | if (!sym.st_name || !sym.st_value) 432 | continue; 433 | if (!mem.read_mem((char*)strtab + sym.st_name, namecpy, namebuflen)) 434 | continue; 435 | namecpy[255] = 0; 436 | if (0 == strcmp(namecpy, name)) 437 | return (char*)elf + sym.st_value; 438 | } 439 | return nullptr; 440 | } 441 | 442 | struct libc_syms 443 | { 444 | void* var; // VAR_NAME 445 | void* fun; // FUN_NAME 446 | void* p_dlopen; 447 | void* p_sprintf; 448 | }; 449 | 450 | bool is_remote_libc(remote_mem& mem, void* maybe_libc, libc_syms& out) 451 | { 452 | libc_syms syms{}; 453 | syms.var = remote_dlsym(mem, maybe_libc, VAR_NAME); 454 | if (!syms.var) 455 | return false; 456 | syms.fun = remote_dlsym(mem, maybe_libc, FUN_NAME); 457 | if (!syms.fun) 458 | return false; 459 | syms.p_dlopen = remote_dlsym(mem, maybe_libc, "dlopen"); 460 | if (!syms.p_dlopen) 461 | syms.p_dlopen = remote_dlsym(mem, maybe_libc, "__libc_dlopen_mode"); 462 | if (!syms.p_dlopen) 463 | return false; 464 | syms.p_sprintf = remote_dlsym(mem, maybe_libc, "sprintf"); 465 | if (!syms.p_sprintf) 466 | return false; 467 | out = syms; 468 | return true; 469 | } 470 | 471 | 472 | void* get_remote_libc(remote_mem& mem, unsigned long pid, libc_syms& syms) 473 | { 474 | syms = {}; 475 | char name[32]; 476 | snprintf(name, sizeof(name), "/proc/%lu/maps", pid); 477 | name[31] = 0; 478 | const auto f = fopen(name, "r"); 479 | if (!f) 480 | return nullptr; 481 | 482 | char line[512]; 483 | void* base = nullptr; 484 | while (fgets(line, sizeof(line), f)) 485 | { 486 | uint64_t begin, end; 487 | if (2 != sscanf(line, "%lx-%lx ", &begin, &end)) 488 | continue; 489 | if (is_remote_libc(mem, (void*)begin, syms)) 490 | { 491 | base = (void*)begin; 492 | break; 493 | } 494 | } 495 | fclose(f); 496 | return base; 497 | } 498 | 499 | static std::pair map_file_for_read(const char* path) 500 | { 501 | const auto fd = open(path, O_RDONLY); 502 | if (fd == -1) 503 | return {}; 504 | struct stat statbuf{}; 505 | if (-1 == fstat(fd, &statbuf)) 506 | return {}; 507 | const auto p = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 508 | if ((void*)-1 == p) 509 | return {}; 510 | return { p, statbuf.st_size }; 511 | } 512 | 513 | std::pair make_shell_2(unsigned long mode, const char* module, const libc_syms& syms) 514 | { 515 | size_t size; 516 | uint8_t* ptr; 517 | if (mode == 1) 518 | { 519 | 520 | shell_raw_dlopen_params p{}; 521 | p.p_dlopen = syms.p_dlopen; 522 | p.p_oldfun = syms.fun; 523 | p.flags = RTLD_NOW; 524 | 525 | const auto size_shell = (uint8_t*)&shell_raw_dlopen_end - (uint8_t*)&shell_raw_dlopen_begin; 526 | const auto size_params = sizeof(shell_raw_dlopen_params); 527 | const auto size_data = strlen(module) + 1; 528 | size = size_shell + size_params + size_data; 529 | ptr = (uint8_t*)malloc(size); 530 | memcpy(ptr, (uint8_t*)&shell_raw_dlopen_begin, size_shell); 531 | memcpy(ptr + size_shell, &p, size_params); 532 | memcpy(ptr + size_shell + size_params, module, size_data); 533 | } 534 | else if (mode == 2) 535 | { 536 | const auto file = map_file_for_read(module); 537 | if (!file.first) 538 | { 539 | printf("Invalid file: %d\n", errno); 540 | exit(-EINVAL); 541 | } 542 | 543 | shell_memfd_dlopen_params p{}; 544 | p.p_dlopen = syms.p_dlopen; 545 | p.p_oldfun = syms.fun; 546 | p.p_sprintf = syms.p_sprintf; 547 | p.flags = RTLD_NOW; 548 | p.data_len = file.second; 549 | 550 | const auto size_shell = (uint8_t*)&shell_memfd_dlopen_end - (uint8_t*)&shell_memfd_dlopen_begin; 551 | const auto size_params = sizeof(shell_memfd_dlopen_params); 552 | const auto size_data = file.second; 553 | size = size_shell + size_params + size_data; 554 | ptr = (uint8_t*)malloc(size); 555 | memcpy(ptr, (uint8_t*)&shell_memfd_dlopen_begin, size_shell); 556 | memcpy(ptr + size_shell, &p, size_params); 557 | memcpy(ptr + size_shell + size_params, file.first, size_data); 558 | } 559 | else if (mode == 3) 560 | { 561 | const auto file = map_file_for_read(module); 562 | if (!file.first) 563 | { 564 | printf("Invalid file: %d\n", errno); 565 | exit(-EINVAL); 566 | } 567 | 568 | shell_raw_shellcode_params p{}; 569 | p.p_oldfun = syms.fun; 570 | 571 | const auto size_shell = (uint8_t*)&shell_raw_shellcode_end - (uint8_t*)&shell_raw_shellcode_begin; 572 | const auto size_params = sizeof(shell_raw_shellcode_params); 573 | const auto size_data = file.second; 574 | size = size_shell + size_params + size_data; 575 | ptr = (uint8_t*)malloc(size); 576 | memcpy(ptr, (uint8_t*)&shell_raw_shellcode_begin, size_shell); 577 | memcpy(ptr + size_shell, &p, size_params); 578 | memcpy(ptr + size_shell + size_params, file.first, size_data); 579 | } 580 | else 581 | { 582 | puts("Invalid mode"); 583 | exit(-EINVAL); 584 | } 585 | return { ptr, size }; 586 | } 587 | 588 | int main(int argc, char** argv) 589 | { 590 | if (argc < 4) 591 | { 592 | fputs("Usage: linux_injector \n", stderr); 593 | fputs(" Mode 1: normal dlopen\n", stderr); 594 | fputs(" Mode 2: memfd + dlopen (for injecting into containers)\n", stderr); 595 | fputs(" Mode 3: raw shellcode\n", stderr); 596 | return -EINVAL; 597 | } 598 | 599 | const auto mode = strtoul(argv[1], nullptr, 10); 600 | const auto remote_pid = strtoul(argv[2], nullptr, 10); 601 | const auto file = argv[3]; 602 | printf("Injecting into %lu\n", remote_pid); 603 | 604 | remote_mem mem{ remote_pid }; 605 | 606 | if (!mem.good()) 607 | { 608 | fputs("Cannot open remote memory!\n", stderr); 609 | return -EACCES; 610 | } 611 | 612 | libc_syms syms{}; 613 | const auto libc = get_remote_libc(mem, remote_pid, syms); 614 | if (!libc) 615 | { 616 | fputs("Cannot find suitable remote libc!\n", stderr); 617 | return -EFAULT; 618 | } 619 | 620 | const auto shell_2 = make_shell_2(mode, file, syms); 621 | 622 | printf("Remote libc: %p. Starting injection...\n", libc); 623 | 624 | std::array old_code{}; 625 | mem.read_mem(syms.fun, old_code); 626 | void* old_var{}; 627 | mem.read_mem(syms.var, old_var); 628 | 629 | uintptr_t new_var = 0; 630 | mem.write_mem(syms.var, new_var); 631 | mem.read_mem(syms.var, new_var); 632 | if (new_var != 0) 633 | { 634 | fputs("Sanity check failed!\n", stderr); 635 | return -EACCES; 636 | } 637 | 638 | std::array shell_code{}; 639 | make_shell(shell_code.data(), syms.fun, syms.var, (int32_t)shell_2.second); 640 | 641 | mem.write_code(syms.fun, shell_code); 642 | 643 | puts("Wrote shellcode, waiting for it to trigger."); 644 | 645 | do 646 | mem.read_mem(syms.var, new_var); 647 | while (!((new_var & 1) && (new_var & ~(uintptr_t)1))); 648 | 649 | new_var &= ~(uintptr_t)1; 650 | 651 | printf("Triggered, new executable memory at %lx\n", new_var); 652 | 653 | constexpr uint16_t ebfe = 0xfeeb; 654 | mem.write_mem(syms.fun, ebfe); 655 | 656 | // sleep 100ms 657 | usleep(100000); 658 | 659 | mem.write_mem(syms.var, old_var); 660 | mem.write_code(syms.fun, old_code); 661 | 662 | mem.write_code((void*)new_var, shell_2.first, shell_2.second); 663 | 664 | puts("Done!"); 665 | 666 | return 0; 667 | } 668 | --------------------------------------------------------------------------------