├── .gitignore ├── src ├── windows │ ├── injector.def │ ├── GNUmakefile │ └── Makefile.win32 ├── linux │ ├── Makefile │ ├── shellcode.S │ ├── util.c │ ├── ptrace.c │ ├── injector_internal.h │ ├── injector.c │ └── elf.c └── macos │ ├── Makefile │ ├── ptrace.c │ ├── mach.c │ ├── injector_internal.h │ ├── util.c │ ├── remote_call.c │ ├── injector.c │ ├── mach_exc.h │ └── exc_handler.c ├── cmd ├── macos-sign │ ├── entitlement.xml │ ├── sign.sh │ └── genkey.sh ├── Makefile ├── Makefile.win32 └── main.c ├── Makefile ├── Makefile.win32 ├── tests ├── test-target.c ├── Makefile.win32 ├── test-library.c ├── Makefile └── test-prog.c ├── .github └── workflows │ ├── gh-pages.yml │ └── test.yml ├── util ├── ya_getopt.h └── ya_getopt.c ├── include └── injector.h ├── README.md └── LICENSE_GPL.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.a 3 | *.dll 4 | *.exe 5 | *.exp 6 | *.lib 7 | *.o 8 | *.obj 9 | *.so 10 | *.dylib 11 | /cmd/injector 12 | /tests/test-prog 13 | /tests/test-prog-* 14 | /tests/test-target 15 | /tests/test-target-* 16 | -------------------------------------------------------------------------------- /src/windows/injector.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | injector_attach 3 | injector_inject 4 | injector_detach 5 | injector_error 6 | injector_inject_w 7 | injector_remote_func_addr 8 | injector_remote_call 9 | injector_remote_vcall 10 | -------------------------------------------------------------------------------- /cmd/macos-sign/entitlement.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.debugger 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /cmd/macos-sign/sign.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if test -z "$1" 3 | then 4 | key="injector-key" 5 | else 6 | key="$1" 7 | fi 8 | 9 | if test -z "$2" 10 | then 11 | executable="../injector" 12 | else 13 | executable="$2" 14 | fi 15 | 16 | /usr/bin/codesign --entitlements entitlement.xml --force --sign "$key" "$executable" -------------------------------------------------------------------------------- /src/windows/GNUmakefile: -------------------------------------------------------------------------------- 1 | INJECTOR_OBJS = injector.o 2 | CFLAGS = -I../../include 3 | 4 | all: injector.dll injector-static.lib 5 | 6 | injector.dll: $(INJECTOR_OBJS) 7 | $(CC) -shared -o $@ $(INJECTOR_OBJS) $(CFLAGS) -Wl,--out-implib,injector.lib -ldbghelp 8 | 9 | injector-static.lib: $(INJECTOR_OBJS) 10 | $(RM) $@ 11 | $(AR) rcs -o $@ $(INJECTOR_OBJS) 12 | 13 | clean: 14 | $(RM) injector.dll injector.lib injector-static.lib $(INJECTOR_OBJS) 15 | 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(OS),Windows_NT) 2 | SRC_DIR = src/windows 3 | else 4 | UNAME_S := $(shell uname -s) 5 | ifeq ($(UNAME_S),Darwin) 6 | SRC_DIR = src/macos 7 | else 8 | SRC_DIR = src/linux 9 | endif 10 | endif 11 | 12 | all: 13 | cd $(SRC_DIR) && $(MAKE) 14 | cd cmd && $(MAKE) 15 | 16 | check: 17 | cd tests && $(MAKE) check 18 | 19 | clean: 20 | cd $(SRC_DIR) && $(MAKE) clean 21 | cd cmd && $(MAKE) clean 22 | cd tests && $(MAKE) clean 23 | -------------------------------------------------------------------------------- /Makefile.win32: -------------------------------------------------------------------------------- 1 | 2 | RTFLAG = -MD # link with dynamic runtime library 3 | # RTFLAG = -MT # link with static runtime library 4 | 5 | MAKE_CMD = nmake -nologo -f Makefile.win32 RTFLAG=$(RTFLAG) ARM64EC=$(ARM64EC) 6 | 7 | all: 8 | cd src/windows && $(MAKE_CMD) 9 | cd cmd && $(MAKE_CMD) 10 | 11 | check: all 12 | cd tests && $(MAKE_CMD) check 13 | 14 | clean: 15 | cd src/windows && $(MAKE_CMD) clean 16 | cd cmd && $(MAKE_CMD) clean 17 | cd tests && $(MAKE_CMD) clean 18 | -------------------------------------------------------------------------------- /tests/test-target.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef _WIN32 4 | #include 5 | #define sleep(secs) Sleep(1000 * (secs)) 6 | #define DLLEXPORT __declspec(dllexport) 7 | #else 8 | #include 9 | #define DLLEXPORT 10 | #endif 11 | 12 | #define SLEEP_SECS 6 13 | 14 | DLLEXPORT int exit_value = 0; 15 | 16 | int main() 17 | { 18 | int i; 19 | 20 | /* Use loop instead of sleep(SLEEP_SECS) because 21 | * it may be interrupted on Linux. 22 | */ 23 | for (i = 0; i < SLEEP_SECS; i++) { 24 | sleep(1); 25 | printf("target exit after %i seconds\n", SLEEP_SECS - i); 26 | } 27 | printf("exit...\n"); 28 | return exit_value; 29 | } 30 | -------------------------------------------------------------------------------- /cmd/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -Wall -I../include 2 | OBJS = main.o 3 | LIBS = 4 | 5 | ifeq ($(OS),Windows_NT) 6 | SRC_DIR = ../src/windows 7 | LIBS += -ldbghelp 8 | OBJS += ya_getopt.o 9 | else 10 | UNAME_S := $(shell uname -s) 11 | ifeq ($(UNAME_S),Darwin) 12 | SRC_DIR = ../src/macos 13 | OBJS += ya_getopt.o 14 | else 15 | SRC_DIR = ../src/linux 16 | endif 17 | endif 18 | 19 | all: injector 20 | 21 | injector: $(OBJS) $(SRC_DIR)/libinjector.a 22 | $(CC) -o injector $(OBJS) $(SRC_DIR)/libinjector.a $(LIBS) 23 | 24 | main.o: main.c 25 | ya_getopt.o: ../util/ya_getopt.c 26 | $(CC) $(CFLAGS) -c ../util/ya_getopt.c 27 | $(SRC_DIR)/libinjlib.a: 28 | cd $(SRC_DIR) && $(MAKE) 29 | 30 | clean: 31 | $(RM) injector $(OBJS) 32 | -------------------------------------------------------------------------------- /cmd/Makefile.win32: -------------------------------------------------------------------------------- 1 | # -*- Makefile -*- 2 | 3 | RTFLAG = -MD # link with dynamic runtime library 4 | # RTFLAG = -MT # link with static runtime library 5 | 6 | CFLAGS = -nologo $(RTFLAG) -I../include 7 | 8 | !IF "$(ARM64EC)" != "" 9 | CFLAGS = $(CFLAGS) -arm64EC 10 | !ENDIF 11 | 12 | OBJS = main.obj ya_getopt.obj 13 | INJECTOR_LIB = ..\src\windows\injector-static.lib 14 | 15 | all: injector.exe 16 | 17 | injector.exe: $(OBJS) $(INJECTOR_LIB) 18 | $(CC) $(CFLAGS) -Feinjector.exe $(OBJS) $(INJECTOR_LIB) 19 | 20 | main.obj: main.c 21 | $(CC) $(CFLAGS) -c main.c 22 | ya_getopt.obj: ..\util\ya_getopt.c 23 | $(CC) $(CFLAGS) -c ..\util\ya_getopt.c 24 | $(INJECTOR_LIB): 25 | cd ..\src\windows && nmake -f Makefile.win32 26 | 27 | clean: 28 | del injector.exe $(OBJS) 29 | -------------------------------------------------------------------------------- /src/linux/Makefile: -------------------------------------------------------------------------------- 1 | INJECTOR_OBJS = elf.o injector.o ptrace.o remote_call.o util.o shellcode.o 2 | CFLAGS = -Wall -fPIC -I../../include 3 | 4 | all: libinjector.so libinjector.a 5 | 6 | libinjector.so: $(INJECTOR_OBJS) 7 | $(CC) -shared -o libinjector.so $(INJECTOR_OBJS) 8 | 9 | libinjector.a: $(INJECTOR_OBJS) 10 | $(AR) rcs libinjector.a $(INJECTOR_OBJS) 11 | 12 | elf.o: elf.c injector_internal.h ../../include/injector.h 13 | injector.o: injector.c injector_internal.h ../../include/injector.h 14 | ptrace.o: ptrace.c injector_internal.h ../../include/injector.h 15 | remote_call.o: remote_call.c injector_internal.h ../../include/injector.h 16 | util.o: util.c injector_internal.h ../../include/injector.h 17 | shellcode.o: shellcode.S 18 | 19 | clean: 20 | $(RM) libinjector.so libinjector.a $(INJECTOR_OBJS) 21 | -------------------------------------------------------------------------------- /tests/Makefile.win32: -------------------------------------------------------------------------------- 1 | all: test-prog.exe test-target.exe test-library.dll 2 | 3 | RTFLAG = -MD # link with dynamic runtime library 4 | # RTFLAG = -MT # link with static runtime library 5 | 6 | CFLAGS = -nologo $(RTFLAG) 7 | 8 | !IF "$(ARM64EC)" != "" 9 | CFLAGS = $(CFLAGS) -arm64EC 10 | !ENDIF 11 | 12 | check: all 13 | .\test-prog.exe 14 | 15 | test-prog.exe: test-prog.c ..\src\windows\injector-static.lib 16 | $(CC) $(CFLAGS) /Fetest-prog.exe test-prog.c ..\src\windows\injector-static.lib 17 | 18 | test-target.exe: test-target.c 19 | $(CC) $(CFLAGS) /Fetest-target.exe test-target.c 20 | 21 | test-library.dll: test-library.c 22 | $(CC) /LD $(CFLAGS) /Fetest-library.dll test-library.c 23 | 24 | clean: 25 | del test-target.exe test-target.exp test-target.obj test-target.lib test-prog.exe test-prog.obj test-library.dll test-library.obj 26 | -------------------------------------------------------------------------------- /src/macos/Makefile: -------------------------------------------------------------------------------- 1 | INJECTOR_OBJS = injector.o util.o exc_handler.o mach.o mach_excServer.o ptrace.o remote_call.o 2 | CFLAGS = -Wall -fPIC -I../../include 3 | 4 | all: libinjector.dylib libinjector.a 5 | 6 | libinjector.dylib: $(INJECTOR_OBJS) 7 | $(CC) -shared -o libinjector.dylib $(INJECTOR_OBJS) 8 | 9 | libinjector.a: $(INJECTOR_OBJS) 10 | $(AR) rcs libinjector.a $(INJECTOR_OBJS) 11 | 12 | injector.o: injector.c injector_internal.h ../../include/injector.h 13 | util.o: util.c injector_internal.h ../../include/injector.h 14 | exc_handler.o: exc_handler.c injector_internal.h ../../include/injector.h 15 | mach.o: mach.c injector_internal.h ../../include/injector.h 16 | mach_excServer.o: mach_excServer.c mach_exc.h injector_internal.h ../../include/injector.h 17 | ptrace.o: ptrace.c injector_internal.h ../../include/injector.h 18 | remote_call.o: remote_call.c mach_exc.h injector_internal.h ../../include/injector.h 19 | clean: 20 | $(RM) libinjector.dylib libinjector.a $(INJECTOR_OBJS) -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [master] 4 | 5 | jobs: 6 | gh-pages: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: install doxygen 11 | run: | 12 | sudo apt-get update 13 | sudo apt-get install -y doxygen 14 | - name: genereate docs 15 | run: | 16 | doxygen 17 | sed -i -e 's/:smiley:/\😃/g' -e 's/:skull:/\💀/g' html/index.html 18 | - name: push docs 19 | run: | 20 | git fetch --depth=1 origin gh-pages 21 | git checkout gh-pages 22 | rm -rf docs 23 | mv html docs 24 | git config --local user.email "docs-action@github.com" 25 | git config --local user.name "GitHub Action (docs)" 26 | git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} 27 | git diff --quiet || (git add docs && git commit -m 'Add docs' && git push origin gh-pages && echo Add docs) 28 | -------------------------------------------------------------------------------- /src/windows/Makefile.win32: -------------------------------------------------------------------------------- 1 | # -*- Makefile -*- 2 | CFLAGS = -nologo -O2 -I../../include 3 | 4 | RTFLAG = -MD # link with dynamic runtime library 5 | # RTFLAG = -MT # link with static runtime library 6 | 7 | LINKFLAGS = -nologo 8 | 9 | RELEASE_DLL = injector.dll 10 | DEBUG_DLL = injectord.dll 11 | RELEASE_STATIC_LIB = injector-static.lib 12 | DEBUG_STATIC_LIB = injectord-static.lib 13 | 14 | RELEASE_OBJS = injector.obj 15 | DEBUG_OBJS = injectord.obj 16 | 17 | TARGETS = $(RELEASE_DLL) $(DEBUG_DLL) $(RELEASE_STATIC_LIB) $(DEBUG_STATIC_LIB) 18 | 19 | !IF "$(ARM64EC)" != "" 20 | CFLAGS = $(CFLAGS) -arm64EC 21 | LINKFLAGS = $(LINKFLAGS) -machine:arm64ec 22 | !ENDIF 23 | 24 | all: $(TARGETS) 25 | 26 | injector.obj: injector.c 27 | $(CC) $(CFLAGS) $(RTFLAG) -c /Foinjector.obj injector.c 28 | 29 | injectord.obj: injector.c 30 | $(CC) $(CFLAGS) $(RTFLAG)d -c /Foinjectord.obj injector.c 31 | 32 | injector.dll: $(RELEASE_OBJS) 33 | link $(LINKFLAGS) /DLL /DEF:injector.def /OUT:injector.dll $(RELEASE_OBJS) 34 | 35 | injectord.dll: $(DEBUG_OBJS) 36 | link $(LINKFLAGS) /DLL /DEF:injector.def /OUT:injectord.dll $(DEBUG_OBJS) 37 | 38 | injector-static.lib: $(RELEASE_OBJS) 39 | lib $(LINKFLAGS) /OUT:injector-static.lib $(RELEASE_OBJS) 40 | 41 | injectord-static.lib: $(DEBUG_OBJS) 42 | lib $(LINKFLAGS) /OUT:injectord-static.lib $(DEBUG_OBJS) 43 | 44 | clean: 45 | del $(TARGETS) $(RELEASE_OBJS) $(DEBUG_OBJS) injector.lib injectord.lib injector.exp injectord.exp 46 | -------------------------------------------------------------------------------- /tests/test-library.c: -------------------------------------------------------------------------------- 1 | #define INCR_ON_INJECTION 13 2 | #define INCR_ON_UNINJECTION 17 3 | 4 | #include 5 | 6 | #ifdef _WIN32 7 | #include 8 | 9 | intptr_t __declspec(dllexport) sum_integers(intptr_t a1, intptr_t a2, intptr_t a3, intptr_t a4, intptr_t a5, intptr_t a6); 10 | 11 | BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) 12 | { 13 | HMODULE hMod; 14 | static int *exit_value_addr; 15 | 16 | switch (reason) { 17 | case DLL_PROCESS_ATTACH: 18 | hMod = GetModuleHandle(NULL); 19 | exit_value_addr = (int *)GetProcAddress(hMod, "exit_value"); 20 | if (exit_value_addr != NULL) { 21 | *exit_value_addr += INCR_ON_INJECTION; 22 | } else { 23 | return FALSE; 24 | } 25 | break; 26 | case DLL_PROCESS_DETACH: 27 | *exit_value_addr += INCR_ON_UNINJECTION; 28 | break; 29 | } 30 | return TRUE; 31 | } 32 | #elif __APPLE__ 33 | #include 34 | static int *exit_value_addr; 35 | 36 | __attribute__((constructor)) 37 | void init() 38 | { 39 | exit_value_addr = dlsym(RTLD_DEFAULT, "exit_value"); 40 | *exit_value_addr += INCR_ON_INJECTION; 41 | } 42 | 43 | __attribute__((destructor)) 44 | void fini() 45 | { 46 | *exit_value_addr += INCR_ON_UNINJECTION; 47 | } 48 | #else //linux 49 | extern int exit_value; 50 | 51 | __attribute__((constructor)) 52 | void init() 53 | { 54 | exit_value += INCR_ON_INJECTION; 55 | } 56 | 57 | __attribute__((destructor)) 58 | void fini() 59 | { 60 | exit_value += INCR_ON_UNINJECTION; 61 | } 62 | #endif 63 | 64 | intptr_t sum_integers(intptr_t a1, intptr_t a2, intptr_t a3, intptr_t a4, intptr_t a5, intptr_t a6) 65 | { 66 | return a1 + a2 + a3 + a4 + a5 + a6; 67 | } 68 | -------------------------------------------------------------------------------- /src/linux/shellcode.S: -------------------------------------------------------------------------------- 1 | #if defined(__x86_64__) 2 | #define handle_offset 0 3 | #define dlopen_addr_offset 8 4 | #define dlerror_addr_offset 16 5 | #define dlflags_offset 24 6 | #define file_path_offset 28 7 | #define page_size 4096 8 | .text 9 | .global injector_shellcode 10 | .hidden injector_shellcode 11 | .type injector_shellcode, @function 12 | // void *injector_shellcode(injector_shellcode_arg_t *arg) { 13 | injector_shellcode: 14 | // // prolog 15 | pushq %rbx 16 | movq %rdi, %rbx 17 | // int dlflags = arg->dlflags; 18 | movl dlflags_offset(%rbx), %esi 19 | // const char *file_path = arg->file_path; 20 | leaq file_path_offset(%rbx), %rdi 21 | // void *handle = dlopen(file_path, dlflags); 22 | call *dlopen_addr_offset(%rbx) 23 | // arg->handle = handle; 24 | movq %rax, handle_offset(%rbx) 25 | // arg->file_path[0] = '\0'; 26 | movb $0, file_path_offset(%rbx) 27 | // if (handle != NULL) return; 28 | test %rax, %rax 29 | jnz .exit 30 | // if (arg->dlerror_addr == 0) return; 31 | cmpq $0, dlerror_addr_offset(%rbx) 32 | je .exit 33 | // char *errmsg = dlerror(); 34 | call *dlerror_addr_offset(%rbx) 35 | // if (errmsg == NULL) return; 36 | test %rax, %rax 37 | jz .exit 38 | // char *dest = arg->file_path 39 | leaq file_path_offset(%rbx), %rdi 40 | // char *end = (char*)arg + page_size; 41 | leaq page_size(%rbx), %rcx 42 | .loop: 43 | // char c = *(errmsg++); 44 | movb (%rax), %dl 45 | addq $1, %rax 46 | // *(dest++) = c; 47 | movb %dl, (%rdi) 48 | addq $1, %rdi 49 | // if (c == 0) return; 50 | testb %dl, %dl 51 | jz .exit 52 | // if (dest < end) goto loop; 53 | cmpq %rdi, %rcx 54 | ja .loop 55 | .exit: 56 | // // epilog 57 | popq %rbx 58 | ret 59 | // } 60 | .size injector_shellcode, . - injector_shellcode 61 | 62 | .balign 4 63 | .global injector_shellcode_size 64 | .hidden injector_shellcode_size 65 | .type injector_shellcode_size, @object 66 | .size injector_shellcode_size, 4 67 | injector_shellcode_size: 68 | // distance from injector_shellcode to current. 69 | .int . - injector_shellcode 70 | #endif 71 | -------------------------------------------------------------------------------- /cmd/macos-sign/genkey.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script is copied from https://github.com/llvm/llvm-project/blob/main/lldb/scripts/macos-setup-codesign.sh 4 | if test -z "$1" 5 | then 6 | CERT="injector-key" 7 | else 8 | CERT="$1" 9 | fi 10 | 11 | 12 | function error() { 13 | echo error: "$@" 1>&2 14 | exit 1 15 | } 16 | 17 | function cleanup { 18 | # Remove generated files 19 | rm -f "$TMPDIR/$CERT.tmpl" "$TMPDIR/$CERT.cer" "$TMPDIR/$CERT.key" > /dev/null 2>&1 20 | } 21 | 22 | trap cleanup EXIT 23 | 24 | # Check if the certificate is already present in the system keychain 25 | security find-certificate -Z -p -c "$CERT" /Library/Keychains/System.keychain > /dev/null 2>&1 26 | if [ $? -eq 0 ]; then 27 | echo Certificate has already been generated and installed 28 | exit 0 29 | fi 30 | 31 | # Create the certificate template 32 | cat <$TMPDIR/$CERT.tmpl 33 | [ req ] 34 | default_bits = 2048 # RSA key size 35 | encrypt_key = no # Protect private key 36 | default_md = sha512 # MD to use 37 | prompt = no # Prompt for DN 38 | distinguished_name = codesign_dn # DN template 39 | [ codesign_dn ] 40 | commonName = "$CERT" 41 | [ codesign_reqext ] 42 | keyUsage = critical,digitalSignature 43 | extendedKeyUsage = critical,codeSigning 44 | EOF 45 | 46 | echo Generating and installing "$CERT" certificate 47 | 48 | # Generate a new certificate 49 | openssl req -new -newkey rsa:2048 -x509 -days 3650 -nodes -config "$TMPDIR/$CERT.tmpl" -extensions codesign_reqext -batch -out "$TMPDIR/$CERT.cer" -keyout "$TMPDIR/$CERT.key" > /dev/null 2>&1 50 | [ $? -eq 0 ] || error Something went wrong when generating the certificate 51 | 52 | # Install the certificate in the system keychain 53 | sudo security authorizationdb read com.apple.trust-settings.admin > "$TMPDIR/rights" 54 | sudo security authorizationdb write com.apple.trust-settings.admin allow 55 | sudo security add-trusted-cert -d -r trustRoot -p codeSign -k /Library/Keychains/System.keychain "$TMPDIR/$CERT.cer" > /dev/null 2>&1 56 | result=$? 57 | sudo security authorizationdb write com.apple.trust-settings.admin < "$TMPDIR/rights" 58 | [ $result -eq 0 ] || error Something went wrong when installing the certificate 59 | 60 | # Install the key for the certificate in the system keychain 61 | sudo security import "$TMPDIR/$CERT.key" -A -k /Library/Keychains/System.keychain > /dev/null 2>&1 62 | [ $? -eq 0 ] || error Something went wrong when installing the key 63 | 64 | # Kill task_for_pid access control daemon 65 | sudo pkill -f /usr/libexec/taskgated > /dev/null 2>&1 66 | 67 | # Exit indicating the certificate is now generated and installed 68 | exit 0 -------------------------------------------------------------------------------- /src/linux/util.c: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2018 Kubo Takehiro 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #include 26 | #include 27 | #include "injector_internal.h" 28 | 29 | char injector__errmsg[512]; 30 | char injector__errmsg_is_set; 31 | 32 | void injector__set_errmsg(const char *format, ...) 33 | { 34 | va_list ap; 35 | int rv; 36 | 37 | /* prevent the error message from being overwritten. */ 38 | if (injector__errmsg_is_set) { 39 | return; 40 | } 41 | injector__errmsg_is_set = 1; 42 | 43 | va_start(ap, format); 44 | rv = vsnprintf(injector__errmsg, sizeof(injector__errmsg), format, ap); 45 | va_end(ap); 46 | if (rv == -1 || rv >= sizeof(injector__errmsg)) { 47 | injector__errmsg[sizeof(injector__errmsg) - 1] = '\0'; 48 | } 49 | } 50 | 51 | const char *injector__arch2name(arch_t arch) 52 | { 53 | switch (arch) { 54 | case ARCH_X86_64: 55 | return "x86_64"; 56 | case ARCH_X86_64_X32: 57 | return "x86_64 x32-ABI"; 58 | case ARCH_I386: 59 | return "i386"; 60 | case ARCH_ARM64: 61 | return "ARM64"; 62 | case ARCH_ARM_EABI_THUMB: 63 | return "ARM EABI thumb"; 64 | case ARCH_ARM_EABI: 65 | return "ARM EABI"; 66 | case ARCH_MIPS_64: 67 | return "MIPS 64"; 68 | case ARCH_MIPS_N32: 69 | return "MIPS N32 ABI"; 70 | case ARCH_MIPS_O32: 71 | return "MIPS O32 ABI"; 72 | case ARCH_POWERPC_64: 73 | return "PowerPC 64-bit"; 74 | case ARCH_POWERPC: 75 | return "PowerPC"; 76 | case ARCH_RISCV_64: 77 | return "RISC-V 64"; 78 | case ARCH_RISCV_32: 79 | return "RISC-V 32"; 80 | } 81 | return "?"; 82 | } 83 | -------------------------------------------------------------------------------- /src/macos/ptrace.c: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2022 TheOiseth 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #include "injector_internal.h" 26 | #include 27 | #include 28 | #include 29 | #include 30 | #define PTRACE_OR_RETURN(request, injector, addr, data) do { \ 31 | int rv = injector__ptrace(request, injector->pid, addr, data, #request); \ 32 | if (rv != 0) { \ 33 | return rv; \ 34 | } \ 35 | } while (0) 36 | static int set_ptrace_error(const char *request_name) 37 | { 38 | int err = errno; 39 | injector__set_errmsg("%s error : %s", request_name, strerror(errno)); 40 | switch (err) { 41 | case EFAULT: 42 | return INJERR_INVALID_MEMORY_AREA; 43 | case EPERM: 44 | return INJERR_PERMISSION; 45 | case ESRCH: 46 | return INJERR_NO_PROCESS; 47 | } 48 | return INJERR_OTHER; 49 | } 50 | 51 | int injector__ptrace(int request, pid_t pid, long addr, long data, const char *request_name) 52 | { 53 | if (ptrace(request, pid, (caddr_t)addr, data) != 0) { 54 | return set_ptrace_error(request_name); 55 | } 56 | return 0; 57 | } 58 | 59 | int injector__ptrace_attach(const injector_t *injector) 60 | { 61 | PTRACE_OR_RETURN(PT_ATTACHEXC, injector, 0, 0); 62 | return 0; 63 | } 64 | 65 | int injector__ptrace_detach(const injector_t *injector) 66 | { 67 | PTRACE_OR_RETURN(PT_DETACH, injector, 0, 0); 68 | return 0; 69 | } 70 | 71 | int injector__ptrace_continue(const injector_t *injector) 72 | { 73 | PTRACE_OR_RETURN(PT_CONTINUE, injector, 1, 0); 74 | return 0; 75 | } 76 | 77 | int injector__ptrace_update(const injector_t *injector, long thread_port) 78 | { 79 | PTRACE_OR_RETURN(PT_THUPDATE, injector, thread_port, 0); 80 | return 0; 81 | } -------------------------------------------------------------------------------- /util/ya_getopt.h: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * ya_getopt - Yet another getopt 4 | * https://github.com/kubo/ya_getopt 5 | * 6 | * Copyright 2015 Kubo Takehiro 7 | * 8 | * Redistribution and use in source and binary forms, with or without modification, are 9 | * permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, this list of 12 | * conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list 15 | * of conditions and the following disclaimer in the documentation and/or other materials 16 | * provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS OR IMPLIED 19 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 20 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR 21 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 26 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | * 28 | * The views and conclusions contained in the software and documentation are those of the 29 | * authors and should not be interpreted as representing official policies, either expressed 30 | * or implied, of the authors. 31 | * 32 | */ 33 | #ifndef YA_GETOPT_H 34 | #define YA_GETOPT_H 1 35 | 36 | #if defined(__cplusplus) 37 | extern "C" { 38 | #endif 39 | 40 | #define ya_no_argument 0 41 | #define ya_required_argument 1 42 | #define ya_optional_argument 2 43 | 44 | struct option { 45 | const char *name; 46 | int has_arg; 47 | int *flag; 48 | int val; 49 | }; 50 | 51 | int ya_getopt(int argc, char * const argv[], const char *optstring); 52 | int ya_getopt_long(int argc, char * const argv[], const char *optstring, 53 | const struct option *longopts, int *longindex); 54 | int ya_getopt_long_only(int argc, char * const argv[], const char *optstring, 55 | const struct option *longopts, int *longindex); 56 | 57 | extern char *ya_optarg; 58 | extern int ya_optind, ya_opterr, ya_optopt; 59 | 60 | #ifndef YA_GETOPT_NO_COMPAT_MACRO 61 | #define getopt ya_getopt 62 | #define getopt_long ya_getopt_long 63 | #define getopt_long_only ya_getopt_long_only 64 | #define optarg ya_optarg 65 | #define optind ya_optind 66 | #define opterr ya_opterr 67 | #define optopt ya_optopt 68 | #define no_argument ya_no_argument 69 | #define required_argument ya_required_argument 70 | #define optional_argument ya_optional_argument 71 | #endif 72 | 73 | #if defined(__cplusplus) 74 | } 75 | #endif 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: 3 | push: 4 | pull_request: 5 | 6 | jobs: 7 | tests_on_ubuntu_x86_64: 8 | name: Tests on Ubuntu (x86_64 and i686) 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | with: 13 | submodules: true 14 | - name: Install packages 15 | run: | 16 | sudo dpkg --add-architecture i386 17 | sudo apt-get update 18 | sudo apt-get install -y gcc-multilib gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 libgcc-s1:i386 libstdc++6:i386 wine32 wine64 wine-binfmt 19 | sudo apt-get remove mono-runtime 20 | echo WINEPATH=$(ls -d /usr/lib/gcc/*-mingw*/*-win32 | tr '\n' ';') >> $GITHUB_ENV 21 | - name: build 22 | run: | 23 | cd tests && make cross 24 | - name: x86_64 -> x86_64 25 | run: | 26 | cd tests && ./test-prog-x86_64 x86_64 27 | - name: x86_64 -> i386 28 | run: | 29 | cd tests && ./test-prog-x86_64 i386 30 | - name: i386 -> i386 31 | run: | 32 | cd tests && ./test-prog-i386 i386 33 | - name: x86_64 -> x86_64 (cloned thread) 34 | run: | 35 | cd tests && ./test-prog-x86_64 x86_64 --cloned-thread 36 | - name: mingw-x64 mingw-x64 37 | run: | 38 | cd tests && ./test-prog-mingw-x64.exe mingw-x64 39 | - name: mingw-32 mingw-32 40 | run: | 41 | cd tests && ./test-prog-mingw-32.exe mingw-32 42 | 43 | tests_on_mac: 44 | name: Tests on MacOS (x86_64) 45 | runs-on: macos-latest 46 | steps: 47 | - uses: actions/checkout@v3 48 | with: 49 | submodules: true 50 | - name: run tests 51 | run: | 52 | sudo make check 53 | 54 | tests_on_windows: 55 | name: Tests on Windows (x64 and x86) 56 | runs-on: windows-latest 57 | steps: 58 | - uses: actions/checkout@v3 59 | with: 60 | submodules: true 61 | - uses: ilammy/msvc-dev-cmd@v1 62 | with: 63 | arch: x64 64 | - name: x64 -> x64 tests on Windows 65 | shell: cmd 66 | run: | 67 | nmake -nologo -f Makefile.win32 check 68 | if %ERRORLEVEL% neq 0 exit /b %ERRORLEVEL% 69 | copy tests\test-prog.exe tests\test-prog-x64.exe 70 | nmake -nologo -f Makefile.win32 clean 71 | - uses: ilammy/msvc-dev-cmd@v1 72 | with: 73 | arch: x86 74 | - name: x86 -> x86 tests on Windows 75 | shell: cmd 76 | run: | 77 | nmake -nologo -f Makefile.win32 check 78 | - name: x64 -> x86 tests on Windows 79 | shell: cmd 80 | run: | 81 | cd tests && .\test-prog-x64.exe 82 | - name: mingw32-w64 (x86_64) 83 | shell: bash 84 | run: | 85 | CC=x86_64-w64-mingw32-gcc make clean check 86 | 87 | tests_on_alpine_x86_64: 88 | name: Tests on Alpine (x86_64 only) 89 | runs-on: ubuntu-latest 90 | container: alpine 91 | steps: 92 | - name: Install packages 93 | run: | 94 | apk add gcc musl-dev git make 95 | - uses: actions/checkout@v3 96 | with: 97 | submodules: true 98 | - name: run tests 99 | run: | 100 | make check 101 | -------------------------------------------------------------------------------- /src/macos/mach.c: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2022 TheOiseth 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #include "injector_internal.h" 26 | #include 27 | #include 28 | static int set_mach_error(const char *request_name, int err) 29 | { 30 | injector__set_errmsg("%s error : %s", request_name, mach_error_string(err)); 31 | switch (err) { 32 | case KERN_INVALID_ADDRESS: 33 | return INJERR_INVALID_MEMORY_AREA; 34 | case KERN_NO_ACCESS: 35 | return INJERR_PERMISSION; 36 | } 37 | return INJERR_OTHER; 38 | } 39 | 40 | static int set_error(const char *request_name) 41 | { 42 | int err = errno; 43 | injector__set_errmsg("%s error : %s", request_name, strerror(errno)); 44 | switch (err) { 45 | case EFAULT: 46 | return INJERR_INVALID_MEMORY_AREA; 47 | case EPERM: 48 | return INJERR_PERMISSION; 49 | case ESRCH: 50 | return INJERR_NO_PROCESS; 51 | } 52 | return INJERR_OTHER; 53 | } 54 | 55 | int injector__task_pid(injector_t *injector) 56 | { 57 | int rv = kill(injector->pid, 0); 58 | if(rv != 0){ 59 | return set_error("TASK_FOR_PID"); 60 | } 61 | task_t remote_task; 62 | rv = task_for_pid(mach_task_self(), injector->pid, &remote_task); 63 | 64 | if (rv != KERN_SUCCESS) { 65 | return set_mach_error("TASK_FOR_PID", rv); 66 | } 67 | injector->remote_task = remote_task; 68 | return 0; 69 | } 70 | 71 | int injector__allocate(const injector_t *injector, mach_vm_address_t *address, mach_vm_size_t size, int flags) 72 | { 73 | int rv = mach_vm_allocate(injector->remote_task, address, size, flags); 74 | if (rv != KERN_SUCCESS) { 75 | return set_mach_error("ALLOCATE", rv); 76 | } 77 | return 0; 78 | } 79 | 80 | int injector__deallocate(const injector_t *injector, mach_vm_address_t address, mach_vm_size_t size){ 81 | int rv = mach_vm_deallocate(injector->remote_task, address, size); 82 | if (rv != KERN_SUCCESS) { 83 | return set_mach_error("DEALLOCATE", rv); 84 | } 85 | return 0; 86 | } 87 | 88 | int injector__protect(const injector_t *injector, mach_vm_address_t address, mach_vm_size_t size, boolean_t set_maximum, vm_prot_t new_protection) 89 | { 90 | int rv = mach_vm_protect(injector->remote_task, address, size, set_maximum, new_protection); 91 | if (rv != KERN_SUCCESS) { 92 | return set_mach_error("PROTECT", rv); 93 | } 94 | return 0; 95 | } 96 | 97 | int injector__write(const injector_t *injector, size_t addr, const void *buf, size_t len) { 98 | int rv = mach_vm_write(injector->remote_task, addr, (vm_offset_t)buf, len); 99 | if (rv != KERN_SUCCESS) { 100 | return set_mach_error("WRITE", rv); 101 | } 102 | return 0; 103 | } 104 | int injector__read(const injector_t *injector, size_t addr, void *buf, size_t len){ 105 | mach_vm_size_t readed; 106 | int rv = mach_vm_read_overwrite(injector->remote_task, addr, len, (mach_vm_address_t)buf, &readed); 107 | if (rv != KERN_SUCCESS) { 108 | return set_mach_error("READ", rv); 109 | } 110 | return 0; 111 | } 112 | 113 | -------------------------------------------------------------------------------- /src/macos/injector_internal.h: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2022 TheOiseth 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #include "injector.h" 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | typedef enum { 33 | STOP_CONTINUE, 34 | STOP_DETACH, 35 | TRAP_SETREGS, 36 | TRAP_GETREGS 37 | } handle_action_t; 38 | 39 | struct injector { 40 | pid_t pid; 41 | uint8_t attached; 42 | uint8_t allocated; 43 | uint8_t ptrace_attached; 44 | uint8_t shellcode_writed; 45 | task_t remote_task; 46 | size_t code_addr; 47 | size_t code2_addr; 48 | size_t code_size; 49 | size_t text; 50 | size_t text_size; 51 | size_t stack; 52 | size_t stack_size; 53 | 54 | thread_act_t mach_thread; 55 | thread_act_t remote_thread; 56 | #if defined(__arm64__) || defined(__aarch64__) 57 | arm_thread_state64_t remote_thread_saved_state; 58 | #else 59 | x86_thread_state64_t remote_thread_saved_state; 60 | #endif 61 | uint8_t state_saved; 62 | long func_addr; 63 | long arg1; 64 | long arg2; 65 | long arg3; 66 | long arg4; 67 | long arg5; 68 | long arg6; 69 | mach_port_name_t exc_port; 70 | exception_mask_t saved_masks[EXC_TYPES_COUNT]; 71 | mach_port_t saved_ports[EXC_TYPES_COUNT]; 72 | exception_behavior_t saved_behaviors[EXC_TYPES_COUNT]; 73 | thread_state_flavor_t saved_flavors[EXC_TYPES_COUNT]; 74 | mach_msg_type_number_t saved_exception_types_count; 75 | handle_action_t handle_action; 76 | long retval; 77 | int handle_err; 78 | 79 | }; 80 | 81 | typedef struct{ 82 | char stub[120]; 83 | injector_t *injector; 84 | } mach_msg_header_with_injector; 85 | 86 | typedef enum { 87 | ARCH_X86_64, 88 | ARCH_I386, 89 | ARCH_ARM64, 90 | ARCH_POWERPC_64, 91 | ARCH_POWERPC, 92 | ARCH_UNKNOWN 93 | } arch_t; 94 | 95 | 96 | 97 | typedef int (*pcfmt_t)(pthread_t* ,pthread_attr_t* ,void *, void*); 98 | 99 | int injector__task_pid(injector_t *injector); 100 | int injector__allocate(const injector_t *injector, mach_vm_address_t *address, mach_vm_size_t size, int flags); 101 | int injector__deallocate(const injector_t *injector, mach_vm_address_t address, mach_vm_size_t size); 102 | int injector__protect(const injector_t *injector, mach_vm_address_t address, mach_vm_size_t size, boolean_t set_maximum, vm_prot_t new_protection); 103 | int injector__write(const injector_t *injector, size_t addr, const void *buf, size_t len); 104 | int injector__read(const injector_t *injector, size_t addr, void *buf, size_t len); 105 | int injector__ptrace_attach(const injector_t *injector); 106 | int injector__ptrace_detach(const injector_t *injector); 107 | int injector__ptrace_continue(const injector_t *injector); 108 | int injector__ptrace_update(const injector_t *injector, long thread_port); 109 | 110 | int injector__create_exc_handler(injector_t *injector); 111 | int injector__release_exc_handler(injector_t *injector); 112 | int injector__handle_exc(injector_t *injector); 113 | 114 | int injector__call_function(injector_t *injector, long *retval, long function_addr, ...); 115 | /* util.c */ 116 | extern char injector__errmsg[]; 117 | extern char injector__errmsg_is_set; 118 | void injector__set_errmsg(const char *format, ...); 119 | const char *injector__arch2name(arch_t arch); 120 | int injector__get_process_arch(pid_t pid, arch_t *arch); 121 | arch_t injector__get_system_arch(); -------------------------------------------------------------------------------- /src/macos/util.c: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2022 TheOiseth 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #include "injector_internal.h" 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | 34 | char injector__errmsg[512]; 35 | char injector__errmsg_is_set; 36 | 37 | void injector__set_errmsg(const char *format, ...) 38 | { 39 | va_list ap; 40 | int rv; 41 | 42 | /* prevent the error message from being overwritten. */ 43 | if (injector__errmsg_is_set) { 44 | return; 45 | } 46 | injector__errmsg_is_set = 1; 47 | 48 | va_start(ap, format); 49 | rv = vsnprintf(injector__errmsg, sizeof(injector__errmsg), format, ap); 50 | va_end(ap); 51 | if (rv == -1 || rv >= sizeof(injector__errmsg)) { 52 | injector__errmsg[sizeof(injector__errmsg) - 1] = '\0'; 53 | } 54 | } 55 | #ifndef P_TRANSLATED 56 | #define P_TRANSLATED 0x00020000 57 | #endif 58 | int injector__get_process_arch(pid_t pid, arch_t *arch){ 59 | int mib[CTL_MAXNAME] = {0}; 60 | mib[0] = CTL_KERN; 61 | mib[1] = KERN_PROC; 62 | mib[2] = KERN_PROC_PID; 63 | mib[3] = pid; 64 | size_t length = 4; 65 | struct kinfo_proc proc_info = {0}; 66 | size_t size = sizeof(proc_info); 67 | 68 | if(sysctl(mib, (u_int)length, &proc_info, &size, NULL, 0) != 0) { 69 | *arch = ARCH_UNKNOWN; 70 | return INJERR_SUCCESS; 71 | } 72 | if (size == 0) { 73 | injector__set_errmsg("Process %d not found", pid); 74 | return INJERR_NO_PROCESS; 75 | } 76 | 77 | if(P_TRANSLATED == (P_TRANSLATED & proc_info.kp_proc.p_flag)){ 78 | if(P_LP64 == (P_LP64 & proc_info.kp_proc.p_flag)){ 79 | *arch = ARCH_X86_64; 80 | return INJERR_SUCCESS; 81 | } else { 82 | *arch = ARCH_I386; 83 | return INJERR_SUCCESS; 84 | } 85 | } else { 86 | arch_t sys_arch = injector__get_system_arch(); 87 | if(sys_arch == ARCH_ARM64){ 88 | *arch = ARCH_ARM64; 89 | return INJERR_SUCCESS; 90 | } 91 | #if defined(__arm64__) || defined(__aarch64__) 92 | if(sys_arch == ARCH_UNKNOWN){ 93 | *arch = ARCH_ARM64; 94 | return INJERR_SUCCESS; 95 | } 96 | #endif 97 | } 98 | 99 | if(P_LP64 == (P_LP64 & proc_info.kp_proc.p_flag)){ 100 | *arch = ARCH_X86_64; 101 | return INJERR_SUCCESS; 102 | } 103 | *arch = ARCH_I386; 104 | return INJERR_SUCCESS; 105 | } 106 | 107 | #ifndef CPU_TYPE_ARM64 108 | #define CPU_TYPE_ARM ((cpu_type_t) 12) 109 | #define CPU_ARCH_ABI64 0x01000000 110 | #define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64) 111 | #endif 112 | arch_t injector__get_system_arch(){ 113 | size_t size; 114 | cpu_type_t type = -1; 115 | int mib[CTL_MAXNAME] = {0}; 116 | size_t length = CTL_MAXNAME; 117 | 118 | if (sysctlnametomib("sysctl.proc_cputype", mib, &length) != 0){ 119 | return ARCH_UNKNOWN; 120 | } 121 | 122 | mib[length] = getpid(); 123 | length++; 124 | size = sizeof(cpu_type_t); 125 | 126 | if (sysctl(mib, (u_int)length, &type, &size, 0, 0) != 0){ 127 | return ARCH_UNKNOWN; 128 | } 129 | if (CPU_TYPE_X86_64 == type) { 130 | return ARCH_X86_64; 131 | } 132 | 133 | if (CPU_TYPE_ARM64 == type) { 134 | return ARCH_ARM64; 135 | } 136 | return ARCH_UNKNOWN; 137 | } 138 | const char *injector__arch2name(arch_t arch) 139 | { 140 | switch (arch) { 141 | case ARCH_X86_64: 142 | return "x86_64"; 143 | case ARCH_I386: 144 | return "i386"; 145 | case ARCH_ARM64: 146 | return "ARM64"; 147 | case ARCH_POWERPC_64: 148 | return "PowerPC 64-bit"; 149 | case ARCH_POWERPC: 150 | return "PowerPC"; 151 | case ARCH_UNKNOWN: 152 | return "Unknown"; 153 | } 154 | return "?"; 155 | } -------------------------------------------------------------------------------- /src/linux/ptrace.c: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2018 Kubo Takehiro 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #include "injector_internal.h" 26 | 27 | #if defined(__aarch64__) || defined(__riscv) 28 | #define USE_REGSET 29 | #include /* for NT_PRSTATUS */ 30 | #include /* for struct iovec */ 31 | #endif 32 | 33 | static int set_ptrace_error(const char *request_name) 34 | { 35 | int err = errno; 36 | injector__set_errmsg("%s error : %s", request_name, strerror(errno)); 37 | switch (err) { 38 | case EFAULT: 39 | return INJERR_INVALID_MEMORY_AREA; 40 | case EPERM: 41 | return INJERR_PERMISSION; 42 | case ESRCH: 43 | return INJERR_NO_PROCESS; 44 | } 45 | return INJERR_OTHER; 46 | } 47 | 48 | int injector__ptrace(int request, pid_t pid, long addr, long data, const char *request_name) 49 | { 50 | if (ptrace(request, pid, addr, data) != 0) { 51 | return set_ptrace_error(request_name); 52 | } 53 | return 0; 54 | } 55 | 56 | int injector__attach_process(const injector_t *injector) 57 | { 58 | PTRACE_OR_RETURN(PTRACE_ATTACH, injector, 0, 0); 59 | return 0; 60 | } 61 | 62 | int injector__detach_process(const injector_t *injector) 63 | { 64 | PTRACE_OR_RETURN(PTRACE_DETACH, injector, 0, 0); 65 | return 0; 66 | } 67 | 68 | int injector__get_regs(const injector_t *injector, struct user_regs_struct *regs) 69 | { 70 | #ifdef USE_REGSET 71 | struct iovec iovec = { regs, sizeof(*regs) }; 72 | PTRACE_OR_RETURN(PTRACE_GETREGSET, injector, NT_PRSTATUS, (long)&iovec); 73 | #else 74 | PTRACE_OR_RETURN(PTRACE_GETREGS, injector, 0, (long)regs); 75 | #endif 76 | return 0; 77 | } 78 | 79 | int injector__set_regs(const injector_t *injector, const struct user_regs_struct *regs) 80 | { 81 | #ifdef USE_REGSET 82 | struct iovec iovec = { (void*)regs, sizeof(*regs) }; 83 | PTRACE_OR_RETURN(PTRACE_SETREGSET, injector, NT_PRSTATUS, (long)&iovec); 84 | #else 85 | PTRACE_OR_RETURN(PTRACE_SETREGS, injector, 0, (long)regs); 86 | #endif 87 | return 0; 88 | } 89 | 90 | int injector__read(const injector_t *injector, size_t addr, void *buf, size_t len) 91 | { 92 | pid_t pid = injector->pid; 93 | long word; 94 | char *dest = (char *)buf; 95 | 96 | errno = 0; 97 | while (len >= sizeof(long)) { 98 | word = ptrace(PTRACE_PEEKTEXT, pid, addr, 0); 99 | if (word == -1 && errno != 0) { 100 | return set_ptrace_error("PTRACE_PEEKTEXT"); 101 | } 102 | *(long*)dest = word; 103 | addr += sizeof(long); 104 | dest += sizeof(long); 105 | len -= sizeof(long); 106 | } 107 | if (len != 0) { 108 | char *src = (char *)&word; 109 | word = ptrace(PTRACE_PEEKTEXT, pid, addr, 0); 110 | if (word == -1 && errno != 0) { 111 | return set_ptrace_error("PTRACE_PEEKTEXT"); 112 | } 113 | while (len--) { 114 | *(dest++) = *(src++); 115 | } 116 | } 117 | return 0; 118 | } 119 | 120 | int injector__write(const injector_t *injector, size_t addr, const void *buf, size_t len) 121 | { 122 | pid_t pid = injector->pid; 123 | const char *src = (const char *)buf; 124 | 125 | while (len >= sizeof(long)) { 126 | PTRACE_OR_RETURN(PTRACE_POKETEXT, injector, addr, *(long*)src); 127 | addr += sizeof(long); 128 | src += sizeof(long); 129 | len -= sizeof(long); 130 | } 131 | if (len != 0) { 132 | long word = ptrace(PTRACE_PEEKTEXT, pid, addr, 0); 133 | char *dest = (char*)&word; 134 | if (word == -1 && errno != 0) { 135 | return set_ptrace_error("PTRACE_PEEKTEXT"); 136 | } 137 | while (len--) { 138 | *(dest++) = *(src++); 139 | } 140 | PTRACE_OR_RETURN(PTRACE_POKETEXT, injector, addr, word); 141 | } 142 | return 0; 143 | } 144 | 145 | int injector__continue(const injector_t *injector) 146 | { 147 | PTRACE_OR_RETURN(PTRACE_CONT, injector, 0, 0); 148 | return 0; 149 | } 150 | -------------------------------------------------------------------------------- /src/linux/injector_internal.h: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2018-2023 Kubo Takehiro 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #ifndef INJECTOR_INTERNAL_H 26 | #define INJECTOR_INTERNAL_H 1 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "injector.h" 35 | 36 | #ifdef __LP64__ 37 | #define SIZE_T_FMT "l" 38 | #else 39 | #define SIZE_T_FMT "" 40 | #endif 41 | 42 | #ifdef __arm__ 43 | #define user_regs_struct user_regs 44 | #endif 45 | 46 | #ifdef __mips__ 47 | #include 48 | #define user_regs_struct pt_regs 49 | #endif 50 | 51 | #ifdef __powerpc__ 52 | #include 53 | #define user_regs_struct pt_regs 54 | #endif 55 | 56 | #ifdef __riscv 57 | #include 58 | #endif 59 | 60 | #define PTRACE_OR_RETURN(request, injector, addr, data) do { \ 61 | int rv = injector__ptrace(request, injector->pid, addr, data, #request); \ 62 | if (rv != 0) { \ 63 | return rv; \ 64 | } \ 65 | } while (0) 66 | 67 | typedef enum { 68 | /* use dlopen/dlsym/dlclose (glibc 2.34 or later) */ 69 | DLFUNC_POSIX, 70 | /* use __libc_dlopen_mode/__libc_dlsym/__libc_dlclose" (glibc 2.33 or earlier) */ 71 | DLFUNC_INTERNAL, 72 | } dlfunc_type_t; 73 | 74 | typedef enum { 75 | LIBC_TYPE_UNKNOWN = 0, 76 | LIBC_TYPE_GNU, 77 | LIBC_TYPE_MUSL, 78 | } libc_type_t; 79 | 80 | typedef enum { 81 | ARCH_X86_64, 82 | ARCH_X86_64_X32, 83 | ARCH_I386, 84 | ARCH_ARM64, 85 | ARCH_ARM_EABI_THUMB, 86 | ARCH_ARM_EABI, 87 | ARCH_MIPS_64, 88 | ARCH_MIPS_N32, 89 | ARCH_MIPS_O32, 90 | ARCH_POWERPC_64, 91 | ARCH_POWERPC, 92 | ARCH_RISCV_64, 93 | ARCH_RISCV_32, 94 | } arch_t; 95 | 96 | typedef union { 97 | #if defined(__x86_64__) || defined(__i386__) 98 | uint8_t u8[sizeof(long)]; 99 | #elif defined(__aarch64__) || defined(__arm__) 100 | uint16_t u16[4]; 101 | uint32_t u32[2]; 102 | #elif defined(__mips__) 103 | uint32_t u32[4]; 104 | #elif defined(__powerpc__) 105 | uint32_t u32[2]; 106 | #elif defined(__riscv) 107 | uint32_t u32[2]; 108 | #endif 109 | long dummy; 110 | } code_t; 111 | 112 | struct injector { 113 | pid_t pid; 114 | uint8_t attached; 115 | uint8_t mmapped; 116 | arch_t arch; 117 | libc_type_t libc_type; 118 | struct user_regs_struct regs; 119 | dlfunc_type_t dlfunc_type; 120 | size_t dlopen_addr; 121 | size_t dlclose_addr; 122 | size_t dlsym_addr; 123 | size_t dlerror_addr; 124 | #ifdef INJECTOR_HAS_INJECT_IN_CLONED_THREAD 125 | size_t clone_addr; 126 | #endif 127 | size_t code_addr; /* address where instructions are written */ 128 | code_t backup_code; 129 | long sys_mmap; 130 | long sys_mprotect; 131 | long sys_munmap; 132 | 133 | /* memory layout allocated in the target process 134 | * 135 | * high +----------------------+ 136 | * | stack area | 137 | * | size: 2MB | 138 | * |----------------------| 139 | * | inaccessible area | 140 | * | size: 4096 | 141 | * |----------------------| 142 | * | data area | 143 | * | size: 4096 | 144 | * low +----------------------+ 145 | */ 146 | size_t data; /* read-write region */ 147 | size_t data_size; /* page size */ 148 | size_t stack; /* stack area */ 149 | size_t stack_size; /* 2MB */ 150 | #ifdef INJECTOR_HAS_INJECT_IN_CLONED_THREAD 151 | size_t shellcode; 152 | #endif 153 | }; 154 | 155 | /* elf.c */ 156 | int injector__collect_libc_information(injector_t *injector); 157 | 158 | /* ptrace.c */ 159 | int injector__ptrace(int request, pid_t pid, long addr, long data, const char *request_name); 160 | int injector__attach_process(const injector_t *injector); 161 | int injector__detach_process(const injector_t *injector); 162 | int injector__get_regs(const injector_t *injector, struct user_regs_struct *regs); 163 | int injector__set_regs(const injector_t *injector, const struct user_regs_struct *regs); 164 | int injector__read(const injector_t *injector, size_t addr, void *buf, size_t len); 165 | int injector__write(const injector_t *injector, size_t addr, const void *buf, size_t len); 166 | int injector__continue(const injector_t *injector); 167 | 168 | /* remote_call.c - call functions and syscalls in the target process */ 169 | int injector__call_syscall(const injector_t *injector, intptr_t *retval, long syscall_number, ...); 170 | int injector__call_function(const injector_t *injector, intptr_t *retval, long function_addr, ...); 171 | int injector__call_function_va_list(const injector_t *injector, intptr_t *retval, long function_addr, va_list ap); 172 | 173 | /* util.c */ 174 | extern char injector__errmsg[]; 175 | extern char injector__errmsg_is_set; 176 | void injector__set_errmsg(const char *format, ...) __attribute__((format (printf, 1, 2))); 177 | const char *injector__arch2name(arch_t arch); 178 | 179 | /* shellcode.S */ 180 | #ifdef INJECTOR_HAS_INJECT_IN_CLONED_THREAD 181 | typedef struct { 182 | void *handle; 183 | size_t dlopen_addr; 184 | size_t dlerror_addr; 185 | int dlflags; 186 | char file_path[0]; // dummy size. 187 | } injector_shellcode_arg_t; 188 | 189 | void *injector_shellcode(injector_shellcode_arg_t *arg); 190 | extern int injector_shellcode_size; 191 | #endif 192 | 193 | #endif 194 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | TEST_PROG_LIBS = 2 | TEST_TARGET_LDFLAGS = 3 | TEST_LIBRARY_LDFLAGS = -shared -fPIC 4 | 5 | ifeq ($(OS),Windows_NT) 6 | SRC_DIR = ../src/windows 7 | SO_EXT = dll 8 | EXE_EXT = .exe 9 | INJECTOR_STATIC_LIB = injector-static.lib 10 | TEST_PROG_LIBS += -ldbghelp 11 | else 12 | UNAME_S := $(shell uname -s) 13 | ifeq ($(UNAME_S),Darwin) 14 | SRC_DIR = ../src/macos 15 | SO_EXT = dylib 16 | else 17 | SRC_DIR = ../src/linux 18 | SO_EXT = so 19 | endif 20 | EXE_EXT = 21 | INJECTOR_STATIC_LIB = libinjector.a 22 | TEST_TARGET_LDFLAGS += -rdynamic 23 | endif 24 | 25 | BUILD_TARGETS = 26 | 27 | ifneq ($(wildcard /usr/bin/x86_64-linux-gnu-gcc),) 28 | BUILD_TARGETS += x86_64 29 | ifneq ($(wildcard /usr/lib/gcc/x86_64-linux-gnu/*/32/libgcc.a),) 30 | BUILD_TARGETS += i386 31 | endif 32 | ifneq ($(wildcard /usr/lib/gcc/x86_64-linux-gnu/*/x32/libgcc.a),) 33 | BUILD_TARGETS += x32 34 | endif 35 | endif 36 | 37 | ifneq ($(wildcard /usr/bin/aarch64-linux-gnu-gcc),) 38 | BUILD_TARGETS += arm64 39 | endif 40 | 41 | ifneq ($(wildcard /usr/bin/arm-linux-gnueabihf-gcc),) 42 | BUILD_TARGETS += armhf 43 | endif 44 | 45 | ifneq ($(wildcard /usr/bin/arm-linux-gnueabi-gcc),) 46 | BUILD_TARGETS += armel 47 | endif 48 | 49 | ifneq ($(wildcard /usr/bin/mips-linux-gnu-gcc),) 50 | BUILD_TARGETS += mips 51 | endif 52 | 53 | ifneq ($(wildcard /usr/bin/mipsel-linux-gnu-gcc),) 54 | BUILD_TARGETS += mipsel 55 | endif 56 | 57 | ifneq ($(wildcard /usr/bin/mips64-linux-gnuabi64-gcc),) 58 | BUILD_TARGETS += mips64 59 | endif 60 | 61 | ifneq ($(wildcard /usr/bin/mips64el-linux-gnuabi64-gcc),) 62 | BUILD_TARGETS += mips64el 63 | endif 64 | 65 | ifneq ($(wildcard /usr/bin/mipsisa32r6-linux-gnu-gcc),) 66 | BUILD_TARGETS += mipsisa32r6 67 | endif 68 | 69 | ifneq ($(wildcard /usr/bin/mipsisa32r6el-linux-gnu-gcc),) 70 | BUILD_TARGETS += mipsisa32r6el 71 | endif 72 | 73 | ifneq ($(wildcard /usr/bin/mipsisa64r6-linux-gnuabi64-gcc),) 74 | BUILD_TARGETS += mipsisa64r6 75 | endif 76 | 77 | ifneq ($(wildcard /usr/bin/mipsisa64r6el-linux-gnuabi64-gcc),) 78 | BUILD_TARGETS += mipsisa64r6el 79 | endif 80 | 81 | ifneq ($(wildcard /usr/bin/riscv64-linux-gnu-gcc),) 82 | BUILD_TARGETS += riscv64 83 | endif 84 | 85 | ifneq ($(wildcard /usr/bin/x86_64-w64-mingw32-gcc),) 86 | BUILD_TARGETS += mingw-x64 87 | endif 88 | 89 | ifneq ($(wildcard /usr/bin/i686-w64-mingw32-gcc),) 90 | BUILD_TARGETS += mingw-32 91 | endif 92 | 93 | CHECK_TARGETS = \ 94 | "x86_64 x86_64" \ 95 | "x86_64 i386" \ 96 | "x86_64 x32" \ 97 | "i386 i386" \ 98 | "x32 i386" \ 99 | "arm64 arm64" \ 100 | "arm64 armhf" \ 101 | "arm64 armel" \ 102 | "armhf armhf" \ 103 | "armhf armel" \ 104 | "armel armhf" \ 105 | "armel armel" \ 106 | 107 | all: 108 | SUFFIX= $(MAKE) build 109 | 110 | cross: $(BUILD_TARGETS) 111 | 112 | x86_64: 113 | @echo "=============== build: x86_64 ==============="; \ 114 | CC='cc -m64' SUFFIX=-x86_64 $(MAKE) build 115 | 116 | i386: 117 | @echo "=============== build: i386 ==============="; \ 118 | CC='cc -m32' SUFFIX=-i386 $(MAKE) build 119 | 120 | x32: 121 | @echo "=============== build: x32 ==============="; \ 122 | CC='cc -mx32' SUFFIX=-x32 $(MAKE) build 123 | 124 | arm64: 125 | @echo "=============== build: arm64 ==============="; \ 126 | CC=aarch64-linux-gnu-gcc SUFFIX=-arm64 $(MAKE) build 127 | 128 | armhf: 129 | @echo "=============== build: armhf ==============="; \ 130 | CC=arm-linux-gnueabihf-gcc SUFFIX=-armhf $(MAKE) build 131 | 132 | armel: 133 | @echo "=============== build: armel ==============="; \ 134 | CC=arm-linux-gnueabi-gcc SUFFIX=-armel $(MAKE) build 135 | 136 | mips: 137 | @echo "=============== build: mips ==============="; \ 138 | CC='mips-linux-gnu-gcc -mabi=32' SUFFIX=-mips32 $(MAKE) build 139 | CC='mips-linux-gnu-gcc -mabi=n32' SUFFIX=-mipsn32 $(MAKE) build 140 | 141 | mipsel: 142 | @echo "=============== build: mipsel ==============="; \ 143 | CC='mipsel-linux-gnu-gcc -mabi=32' SUFFIX=-mips32el $(MAKE) build 144 | CC='mipsel-linux-gnu-gcc -mabi=n32' SUFFIX=-mipsn32el $(MAKE) build 145 | 146 | mips64: 147 | @echo "=============== build: mips64 ==============="; \ 148 | CC=mips64-linux-gnuabi64-gcc SUFFIX=-mips64 $(MAKE) build 149 | 150 | mips64el: 151 | @echo "=============== build: mips64el ==============="; \ 152 | CC=mips64el-linux-gnuabi64-gcc SUFFIX=-mips64el $(MAKE) build 153 | 154 | mipsisa32r6: 155 | @echo "=============== build: mipsisa32r6 ==============="; \ 156 | CC='mipsisa32r6-linux-gnu-gcc -mabi=32' SUFFIX=-mipsisa32r6 $(MAKE) build 157 | CC='mipsisa32r6-linux-gnu-gcc -mabi=n32' SUFFIX=-mipsisan32r6 $(MAKE) build 158 | 159 | mipsisa32r6el: 160 | @echo "=============== build: mipsisa32r6el ==============="; \ 161 | CC='mipsisa32r6el-linux-gnu-gcc -mabi=32' SUFFIX=-mipsisa32r6el $(MAKE) build 162 | CC='mipsisa32r6el-linux-gnu-gcc -mabi=n32' SUFFIX=-mipsisan32r6el $(MAKE) build 163 | 164 | mipsisa64r6: 165 | @echo "=============== build: mipsisa64r6 ==============="; \ 166 | CC=mipsisa64r6-linux-gnuabi64-gcc SUFFIX=-mipsisa64r6 $(MAKE) build 167 | 168 | mipsisa64r6el: 169 | @echo "=============== build: mipsisa64r6el ==============="; \ 170 | CC=mipsisa64r6el-linux-gnuabi64-gcc SUFFIX=-mipsisa64r6el $(MAKE) build 171 | 172 | riscv64: 173 | @echo "=============== build: riscv64 ==============="; \ 174 | CC=riscv64-linux-gnu-gcc SUFFIX=-riscv64 $(MAKE) build 175 | 176 | mingw-x64: 177 | @echo "=============== build: mingw-x64 ==============="; \ 178 | CC=x86_64-w64-mingw32-gcc SUFFIX=-mingw-x64 OS=Windows_NT $(MAKE) build 179 | 180 | mingw-32: 181 | @echo "=============== build: mingw-32 ==============="; \ 182 | CC=i686-w64-mingw32-gcc SUFFIX=-mingw-32 OS=Windows_NT $(MAKE) build 183 | 184 | build: test-prog$(SUFFIX)$(EXE_EXT) test-target$(SUFFIX)$(EXE_EXT) test-library$(SUFFIX).$(SO_EXT) 185 | 186 | test-prog$(SUFFIX)$(EXE_EXT): test-prog.c 187 | cd $(SRC_DIR) && $(MAKE) clean && $(MAKE) 188 | $(CC) $(CFLAGS) -o $@ $^ $(SRC_DIR)/$(INJECTOR_STATIC_LIB) $(TEST_PROG_LIBS) 189 | 190 | test-target$(SUFFIX)$(EXE_EXT): test-target.c 191 | $(CC) $(CFLAGS) $(TEST_TARGET_LDFLAGS) -o $@ $^ 192 | 193 | test-library$(SUFFIX).$(SO_EXT): test-library.c 194 | $(CC) $(CFLAGS) $(TEST_LIBRARY_LDFLAGS) -o $@ $^ 195 | 196 | check: all 197 | @if test -x test-prog$(EXE_EXT) -a -x test-target$(EXE_EXT); then \ 198 | echo "=============== injector: default, target: default ==============="; \ 199 | ./test-prog$(EXE_EXT) || exit $$?; \ 200 | fi; 201 | @for target_pair in $(CHECK_TARGETS); do \ 202 | set -- $$target_pair; \ 203 | prog=$$1; target=$$2; \ 204 | if test -x test-prog-$$prog -a -x test-target-$$target; then \ 205 | echo "=============== injector: $$prog, target: $$target ==============="; \ 206 | ./test-prog-$$prog $$target || exit $$?; \ 207 | fi; \ 208 | done 209 | 210 | clean: 211 | $(RM) test-prog$(EXE_EXT) test-prog-* test-target$(EXE_EXT) test-target-* test-library.$(SO_EXT) test-library-*.$(SO_EXT) 212 | 213 | .PHONY: $(BUILD_TARGETS) build check clean 214 | -------------------------------------------------------------------------------- /cmd/main.c: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2018 Kubo Takehiro 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License as 13 | * published by the Free Software Foundation; either version 2 of 14 | * the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #include 26 | #include 27 | #include 28 | #include "injector.h" 29 | 30 | #ifdef __linux 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #define INVALID_PID -1 38 | static pid_t find_process(const char *name) 39 | { 40 | DIR *dir = opendir("/proc"); 41 | struct dirent *dent; 42 | pid_t pid = -1; 43 | 44 | if (dir == NULL) { 45 | fprintf(stderr, "Failed to read proc file system.\n"); 46 | exit(1); 47 | } 48 | while ((dent = readdir(dir)) != NULL) { 49 | char path[sizeof(dent->d_name) + 11]; 50 | char exepath[PATH_MAX]; 51 | ssize_t len; 52 | char *exe; 53 | 54 | if (dent->d_name[0] < '1' || '9' < dent->d_name[0]) { 55 | continue; 56 | } 57 | sprintf(path, "/proc/%s/exe", dent->d_name); 58 | len = readlink(path, exepath, sizeof(exepath) - 1); 59 | if (len == -1) { 60 | continue; 61 | } 62 | exepath[len] = '\0'; 63 | exe = strrchr(exepath, '/'); 64 | if (exe != NULL && strcmp(exe + 1, name) == 0) { 65 | pid = atoi(dent->d_name); 66 | break; 67 | } 68 | } 69 | closedir(dir); 70 | return pid; 71 | } 72 | #endif 73 | 74 | #ifdef _WIN32 75 | #include 76 | #include 77 | #include "../util/ya_getopt.h" 78 | 79 | #define INVALID_PID 0 80 | static DWORD find_process(const char *name) 81 | { 82 | HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 83 | DWORD pid = 0; 84 | size_t namelen = strlen(name); 85 | 86 | if (hSnapshot != INVALID_HANDLE_VALUE) { 87 | PROCESSENTRY32 pe; 88 | pe.dwSize = sizeof(pe); 89 | 90 | if (Process32First(hSnapshot, &pe)) { 91 | do { 92 | if (_strnicmp(pe.szExeFile, name, namelen) == 0) { 93 | if (pe.szExeFile[namelen] == '\0' || stricmp(pe.szExeFile + namelen, ".exe") == 0) { 94 | pid = pe.th32ProcessID; 95 | break; 96 | } 97 | } 98 | } while (Process32Next(hSnapshot, &pe)); 99 | } 100 | CloseHandle(hSnapshot); 101 | } 102 | return pid; 103 | } 104 | 105 | #endif 106 | #ifdef __APPLE__ 107 | #define INVALID_PID -1 108 | #import 109 | #include "../util/ya_getopt.h" 110 | static pid_t find_process(const char *name) 111 | { 112 | pid_t pid = -1; 113 | int max_arg_size = 0; 114 | size_t size = sizeof(max_arg_size); 115 | if (sysctl((int[]){ CTL_KERN, KERN_ARGMAX }, 2, &max_arg_size, &size, NULL, 0) != 0) { 116 | max_arg_size = 4096; 117 | } 118 | 119 | int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL}; 120 | struct kinfo_proc *processes = NULL; 121 | char* buffer = NULL; 122 | size_t length; 123 | int count; 124 | 125 | if (sysctl(mib, 3, NULL, &length, NULL, 0) < 0){ 126 | goto clean; 127 | } 128 | processes = malloc(length); 129 | if (processes == NULL){ 130 | goto clean; 131 | } 132 | if (sysctl(mib, 3, processes, &length, NULL, 0) < 0) { 133 | goto clean; 134 | } 135 | count = length / sizeof(struct kinfo_proc); 136 | mib[0] = CTL_KERN; 137 | mib[1] = KERN_PROCARGS2; 138 | 139 | buffer = (char *)malloc(max_arg_size); 140 | for (int i = 0; i < count; i++) { 141 | pid_t p_pid = processes[i].kp_proc.p_pid; 142 | if (pid == 0) { 143 | continue; 144 | } 145 | mib[2] = p_pid; 146 | size = max_arg_size; 147 | 148 | if (sysctl(mib, 3, buffer, &size, NULL, 0) == 0) { 149 | char* exe_path = buffer + sizeof(int); 150 | char* exe_name = exe_path; 151 | char* next = 0; 152 | do{ 153 | next = strchr(exe_name, '/'); 154 | if(next != NULL){ 155 | exe_name = next + 1; 156 | } 157 | } while (next != NULL); 158 | if(strcmp(exe_name, name) == 0){ 159 | pid = p_pid; 160 | goto clean; 161 | } 162 | } 163 | } 164 | clean: 165 | if(buffer != 0){ 166 | free(buffer); 167 | } 168 | if(processes != 0){ 169 | free(processes); 170 | } 171 | 172 | return pid; 173 | } 174 | #endif 175 | int main(int argc, char **argv) 176 | { 177 | injector_pid_t pid = INVALID_PID; 178 | injector_t *injector; 179 | int opt; 180 | int i; 181 | char *endptr; 182 | int rv = 0; 183 | #ifdef INJECTOR_HAS_INJECT_IN_CLONED_THREAD 184 | const char *optstring = "n:p:T"; 185 | int cloned_thread = 0; 186 | #else 187 | const char *optstring = "n:p:"; 188 | #endif 189 | 190 | while ((opt = getopt(argc, argv, optstring)) != -1) { 191 | switch (opt) { 192 | case 'n': 193 | pid = find_process(optarg); 194 | if (pid == INVALID_PID) { 195 | fprintf(stderr, "could not find the process: %s\n", optarg); 196 | return 1; 197 | } 198 | printf("targeting process \"%s\" with pid %d\n", optarg, pid); 199 | break; 200 | case 'p': 201 | pid = strtol(optarg, &endptr, 10); 202 | if (pid <= 0 || *endptr != '\0') { 203 | fprintf(stderr, "invalid process id number: %s\n", optarg); 204 | return 1; 205 | } 206 | printf("targeting process with pid %d\n", pid); 207 | break; 208 | #ifdef INJECTOR_HAS_INJECT_IN_CLONED_THREAD 209 | case 'T': 210 | cloned_thread = 1; 211 | break; 212 | #endif 213 | } 214 | } 215 | if (pid == INVALID_PID) { 216 | fprintf(stderr, "Usage: %s [-n process-name] [-p pid] library-to-inject ...\n", argv[0]); 217 | return 1; 218 | } 219 | 220 | if (injector_attach(&injector, pid) != 0) { 221 | printf("%s\n", injector_error()); 222 | return 1; 223 | } 224 | for (i = optind; i < argc; i++) { 225 | char *libname = argv[i]; 226 | #ifdef INJECTOR_HAS_INJECT_IN_CLONED_THREAD 227 | if (cloned_thread) { 228 | if (injector_inject_in_cloned_thread(injector, libname, NULL) == 0) { 229 | printf("clone thread to inject \"%s\" was created.\n", libname); 230 | } else { 231 | fprintf(stderr, "could not create cloned thread \"%s\"\n", libname); 232 | fprintf(stderr, " %s\n", injector_error()); 233 | rv = 1; 234 | } 235 | continue; 236 | } 237 | #endif 238 | if (injector_inject(injector, libname, NULL) == 0) { 239 | printf("\"%s\" successfully injected\n", libname); 240 | } else { 241 | fprintf(stderr, "could not inject \"%s\"\n", libname); 242 | fprintf(stderr, " %s\n", injector_error()); 243 | rv = 1; 244 | } 245 | } 246 | injector_detach(injector); 247 | return rv; 248 | } 249 | -------------------------------------------------------------------------------- /src/macos/remote_call.c: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2022 TheOiseth 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #include "injector_internal.h" 26 | #include "mach_exc.h" 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | //Before change shellcode, see: 35 | //injector.c -> injector_detach 36 | //exc_handler.c -> catch_mach_exception_raise 37 | #if defined(__arm64__) || defined(__aarch64__) 38 | char* shellcode = 39 | "\x00\x01\x3f\xd6" //blr x8 40 | "\x00\x00\x00\x14" //b //infinity loop, we will terminate this thread later 41 | 42 | //second thread 43 | "\x00\x00\x20\xd4" //brk 0 44 | "\x00\x01\x3f\xd6" //blr x8 45 | "\x00\x00\x20\xd4" //brk 0 46 | "\xc0\x03\x5f\xd6" //ret 47 | ; 48 | int shellcode1_len = 8; 49 | int shellcode_length = 24; 50 | #else 51 | 52 | char* shellcode = 53 | //"\x55" //push rbp 54 | //"\x48\x89\xE5" //mov rbp, rsp 55 | //"\x48\x83\xEC\x10" //sub rsp, 0x10 56 | //"\x48\x8D\x7D\xF8" //lea rdi, [rbp - 8] 57 | "\x90" //nop 58 | "\x90" //nop 59 | "\x90" //nop 60 | "\xFF\xD0" //call rax 61 | //"\x48\x83\xC4\x10" //add rsp, 0x10 62 | //"\x5D" //pop rbp 63 | "\xeb\xfe" //jmp 0 //infinity loop, we will terminate this thread later 64 | 65 | //second thread 66 | "\xcc" //int3 67 | //"\x55" //push rbp 68 | //"\x48\x89\xe5" //mov rbp, rsp 69 | "\xff\xd0" //call rax 70 | //"\x5D" //pop rbp 71 | "\xcc" //int3 72 | "\xc3" //ret 73 | ; 74 | int shellcode1_len = 7; 75 | int shellcode_length = 12; 76 | #endif 77 | int injector__call_function(injector_t *injector, long *retval, long function_addr, ...) 78 | { 79 | va_list ap; 80 | va_start(ap, function_addr); 81 | long arg1, arg2, arg3, arg4, arg5, arg6; 82 | arg1 = va_arg(ap, long); 83 | arg2 = va_arg(ap, long); 84 | arg3 = va_arg(ap, long); 85 | arg4 = va_arg(ap, long); 86 | arg5 = va_arg(ap, long); 87 | arg6 = va_arg(ap, long); 88 | va_end(ap); 89 | int rv; 90 | 91 | if(injector->shellcode_writed == 0){ 92 | pcfmt_t pcfmt = (pcfmt_t)dlsym(RTLD_DEFAULT, "pthread_create_from_mach_thread"); 93 | 94 | 95 | rv = injector__write(injector, injector->code_addr, shellcode, shellcode_length); 96 | if(rv != 0){ 97 | return rv; 98 | } 99 | if(pcfmt == 0){ 100 | //char* legacy_append = 101 | //"\xFF\xD0" //call rax 102 | //"\xcc" //int3 103 | //; 104 | //rv = injector__write(injector, injector->code_addr, legacy_append, 3); 105 | 106 | //It turns out that we can call pthread_create in mach thread without _pthread_set_self on MacOS < 10.12 107 | pcfmt = (pcfmt_t)dlsym(RTLD_DEFAULT, "pthread_create"); 108 | } 109 | rv = injector__protect(injector, injector->code_addr, injector->code_size, FALSE, VM_PROT_READ | VM_PROT_EXECUTE); 110 | if (rv != 0) { 111 | return rv; 112 | } 113 | injector->shellcode_writed = 1; 114 | injector->code2_addr = injector->code_addr + shellcode1_len; 115 | 116 | thread_act_t mach_thread; 117 | #if defined(__arm64__) || defined(__aarch64__) 118 | arm_thread_state64_t state; 119 | memset(&state, '\0', sizeof(state)); 120 | state.__pc = injector->code_addr; 121 | state.__sp = injector->stack; 122 | state.__x[8] = (uint64_t)pcfmt; 123 | state.__x[0] = injector->stack - 32; 124 | 125 | state.__x[1] = 0; 126 | state.__x[2] = injector->code2_addr; 127 | state.__x[3] = 0; 128 | rv = thread_create_running(injector->remote_task, ARM_THREAD_STATE64, (thread_state_t)&state, ARM_THREAD_STATE64_COUNT , &mach_thread); 129 | #else 130 | x86_thread_state64_t state; 131 | memset(&state, '\0', sizeof(state)); 132 | state.__rip = injector->code_addr; 133 | state.__rsp = injector->stack - 0x10; 134 | state.__rbp = injector->stack; 135 | if(pcfmt == NULL){ 136 | state.__rax = (uint64_t)dlsym(RTLD_DEFAULT, "_pthread_set_self"); 137 | state.__rdi = 0; 138 | injector->func_addr = (uint64_t)dlsym(RTLD_DEFAULT, "pthread_create"); 139 | injector->arg1 = injector->stack - 0x8; 140 | injector->arg2 = 0; 141 | injector->arg3 = injector->code2_addr; 142 | injector->arg4 = 0; 143 | } else { 144 | state.__rax = (uint64_t)pcfmt; 145 | state.__rdi = injector->stack - 0x8;//&thread 146 | } 147 | 148 | state.__rsi = 0; 149 | state.__rdx = injector->code2_addr; 150 | state.__rcx = 0; 151 | 152 | rv = thread_create_running(injector->remote_task, x86_THREAD_STATE64, (thread_state_t)&state, x86_THREAD_STATE64_COUNT, &mach_thread); 153 | #endif 154 | if(rv != 0){ 155 | injector__set_errmsg("%s error : %s", "CREATE_THREAD", mach_error_string(rv)); 156 | return INJERR_ERROR_IN_TARGET; 157 | } 158 | injector->mach_thread = mach_thread; 159 | if(pcfmt == NULL){ 160 | injector->handle_action = TRAP_SETREGS; 161 | injector__handle_exc(injector); 162 | } 163 | injector->func_addr = function_addr; 164 | injector->arg1 = arg1; 165 | injector->arg2 = arg2; 166 | injector->arg3 = arg3; 167 | injector->arg4 = arg4; 168 | injector->arg5 = arg5; 169 | injector->arg6 = arg6; 170 | 171 | injector->handle_action = TRAP_SETREGS; 172 | injector__handle_exc(injector); 173 | } else { 174 | #if defined(__arm64__) || defined(__aarch64__) 175 | 176 | mach_msg_type_number_t state_count = ARM_THREAD_STATE64_COUNT; 177 | arm_thread_state64_t state; 178 | rv = thread_get_state(injector->remote_thread, ARM_THREAD_STATE64, (thread_state_t)&state, &state_count); 179 | if(rv != 0){ 180 | injector__set_errmsg("%s error : %s", "THREAD_GET_STATE", mach_error_string(rv)); 181 | return INJERR_ERROR_IN_TARGET; 182 | } 183 | state.__pc = injector->code2_addr + 4; 184 | state.__x[0] = arg1; 185 | state.__x[1] = arg2; 186 | state.__x[2] = arg3; 187 | state.__x[3] = arg4; 188 | state.__x[4] = arg5; 189 | state.__x[5] = arg6; 190 | state.__x[8] = function_addr; 191 | rv = thread_set_state(injector->remote_thread, ARM_THREAD_STATE64, (thread_state_t)&state, ARM_THREAD_STATE64_COUNT); 192 | #else 193 | mach_msg_type_number_t state_count = x86_THREAD_STATE64_COUNT; 194 | x86_thread_state64_t state; 195 | rv = thread_get_state(injector->remote_thread, x86_THREAD_STATE64, (thread_state_t)&state, &state_count); 196 | if(rv != 0){ 197 | injector__set_errmsg("%s error : %s", "THREAD_GET_STATE", mach_error_string(rv)); 198 | return INJERR_ERROR_IN_TARGET; 199 | } 200 | state.__rip = injector->code2_addr + 1; 201 | state.__rax = function_addr; 202 | state.__rdi = arg1; 203 | state.__rsi = arg2; 204 | state.__rdx = arg3; 205 | state.__rcx = arg4; 206 | state.__r8 = arg5; 207 | state.__r9 = arg6; 208 | rv = thread_set_state(injector->remote_thread, x86_THREAD_STATE64, (thread_state_t)&state, x86_THREAD_STATE64_COUNT); 209 | #endif 210 | if(rv != 0){ 211 | injector__set_errmsg("%s error : %s", "THREAD_SET_STATE", mach_error_string(rv)); 212 | return INJERR_ERROR_IN_TARGET; 213 | } 214 | rv = thread_resume(injector->remote_thread); 215 | if(rv != 0){ 216 | injector__set_errmsg("%s error : %s", "THREAD_RESUME", mach_error_string(rv)); 217 | return INJERR_ERROR_IN_TARGET; 218 | } 219 | } 220 | 221 | injector->handle_action = TRAP_GETREGS; 222 | injector__handle_exc(injector); 223 | if(injector->handle_err != 0){ 224 | return injector->handle_err; 225 | } 226 | *retval = injector->retval; 227 | return 0; 228 | } 229 | -------------------------------------------------------------------------------- /src/macos/injector.c: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2022 TheOiseth 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #include "injector_internal.h" 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #define STACK_SIZE 2 * 1024 * 1024 33 | #define CODE_SIZE 512 34 | int injector_attach(injector_t **injector_out, pid_t pid) 35 | { 36 | injector_t *injector; 37 | arch_t self_arch, target_arch; 38 | int rv = 0; 39 | 40 | injector__errmsg_is_set = 0; 41 | 42 | injector = calloc(1, sizeof(injector_t)); 43 | if (injector == NULL) { 44 | injector__set_errmsg("malloc error: %s", strerror(errno)); 45 | return INJERR_NO_MEMORY; 46 | } 47 | injector->pid = pid; 48 | rv = injector__get_process_arch(getpid(), &self_arch); 49 | if (rv != 0) { 50 | goto error_exit; 51 | } 52 | rv = injector__get_process_arch(pid, &target_arch); 53 | if (rv != 0) { 54 | goto error_exit; 55 | } 56 | arch_t sys_arch = injector__get_system_arch(); 57 | 58 | if(self_arch != ARCH_UNKNOWN && target_arch != ARCH_UNKNOWN){ 59 | if(self_arch != target_arch){ 60 | injector__set_errmsg("%s target process isn't supported by %s process.", injector__arch2name(target_arch), injector__arch2name(self_arch)); 61 | rv = INJERR_UNSUPPORTED_TARGET; 62 | goto error_exit; 63 | } 64 | if(sys_arch == ARCH_ARM64 && self_arch != ARCH_ARM64){ 65 | injector__set_errmsg("%s target process isn't supported by %s process on ARM64 machine.", injector__arch2name(target_arch), injector__arch2name(self_arch)); 66 | rv = INJERR_UNSUPPORTED_TARGET; 67 | goto error_exit; 68 | } 69 | } 70 | 71 | rv = injector__task_pid(injector); 72 | if (rv != 0) { 73 | goto error_exit; 74 | } 75 | injector->attached = 1; 76 | 77 | rv = injector__create_exc_handler(injector); 78 | 79 | if (rv != 0) { 80 | goto error_exit; 81 | } 82 | rv = injector__ptrace_attach(injector); 83 | if(rv != 0){ 84 | return rv; 85 | } 86 | injector->handle_action = STOP_CONTINUE; 87 | injector->handle_err = 0; 88 | do{ 89 | injector__handle_exc(injector); 90 | } while(injector->handle_err != 0); 91 | 92 | injector->ptrace_attached = 1; 93 | 94 | injector->text_size = sysconf(_SC_PAGESIZE); 95 | injector->stack_size = STACK_SIZE; 96 | injector->code_size = CODE_SIZE; 97 | 98 | size_t alloc_size = injector->text_size + injector->stack_size; 99 | 100 | mach_vm_address_t addr = (vm_address_t)NULL; 101 | rv = injector__allocate(injector, &addr, alloc_size, VM_FLAGS_ANYWHERE); 102 | if (rv != 0) { 103 | goto error_exit; 104 | } 105 | 106 | mach_vm_address_t code_addr = (vm_address_t)NULL; 107 | rv = injector__allocate(injector, &code_addr, CODE_SIZE, VM_FLAGS_ANYWHERE); 108 | if (rv != 0) { 109 | goto error_exit; 110 | } 111 | 112 | injector->allocated = 1; 113 | injector->text = (size_t)addr; 114 | injector->stack = injector->text + injector->text_size + injector->stack_size / 2; 115 | injector->stack &= 0xFFFFFFFFFFFFFFF0; //alignment 116 | injector->code_addr = (size_t)code_addr; 117 | 118 | rv = injector__protect(injector, addr, alloc_size, FALSE, VM_PROT_READ | VM_PROT_WRITE); 119 | if (rv != 0) { 120 | goto error_exit; 121 | } 122 | 123 | rv = injector__protect(injector, code_addr, CODE_SIZE, FALSE, VM_PROT_READ | VM_PROT_WRITE); 124 | if (rv != 0) { 125 | goto error_exit; 126 | } 127 | *injector_out = injector; 128 | return 0; 129 | error_exit: 130 | injector_detach(injector); 131 | return rv; 132 | } 133 | 134 | int injector_inject(injector_t *injector, const char *path, void **handle) 135 | { 136 | char abspath[PATH_MAX]; 137 | int dlflags = RTLD_LAZY; 138 | size_t len; 139 | int rv; 140 | long retval; 141 | injector__errmsg_is_set = 0; 142 | if (realpath(path, abspath) == NULL) { 143 | injector__set_errmsg("failed to get the full path of '%s': %s", 144 | path, strerror(errno)); 145 | return INJERR_FILE_NOT_FOUND; 146 | } 147 | len = strlen(abspath) + 1; 148 | if (len > injector->text_size) { 149 | injector__set_errmsg("too long file path: %s", path); 150 | return INJERR_FILE_NOT_FOUND; 151 | } 152 | 153 | rv = injector__write(injector, injector->text, abspath, len); 154 | if (rv != 0) { 155 | return rv; 156 | } 157 | 158 | rv = injector__call_function(injector, &retval, (long)dlopen, injector->text, dlflags); 159 | if (rv != 0) { 160 | return rv; 161 | } 162 | if (retval == 0) { 163 | char buf[256 + 1] = {0,}; 164 | rv = injector__call_function(injector, &retval, (long)dlerror); 165 | if (rv == 0 && retval != 0) { 166 | injector__read(injector, retval, buf, sizeof(buf) - 1); 167 | } 168 | 169 | if (buf[0] != '\0') { 170 | injector__set_errmsg("dlopen failed: %s", buf); 171 | } else { 172 | injector__set_errmsg("dlopen failed"); 173 | } 174 | 175 | return INJERR_ERROR_IN_TARGET; 176 | } 177 | if (handle != NULL) { 178 | *handle = (void*)retval; 179 | } 180 | return 0; 181 | } 182 | 183 | int injector_call(injector_t *injector, void *handle, const char* name) 184 | { 185 | int rv; 186 | long retval; 187 | size_t len = strlen(name) + 1; 188 | 189 | injector__errmsg_is_set = 0; 190 | 191 | if (len > injector->text_size) { 192 | injector__set_errmsg("too long function name: %s", name); 193 | return INJERR_FUNCTION_MISSING; 194 | } 195 | rv = injector__write(injector, injector->text, name, len); 196 | if (rv != 0) { 197 | return rv; 198 | } 199 | rv = injector__call_function(injector, &retval, (long)dlsym, handle, injector->text); 200 | if (retval == 0) { 201 | injector__set_errmsg("function not found: %s", name); 202 | return INJERR_FUNCTION_MISSING; 203 | } 204 | return injector__call_function(injector, &retval, retval); 205 | } 206 | 207 | int injector_uninject(injector_t *injector, void *handle) 208 | { 209 | int rv; 210 | long retval; 211 | 212 | injector__errmsg_is_set = 0; 213 | 214 | rv = injector__call_function(injector, &retval, (long)dlclose, handle); 215 | if (rv != 0) { 216 | return rv; 217 | } 218 | if (retval != 0) { 219 | injector__set_errmsg("dlclose failed"); 220 | return INJERR_ERROR_IN_TARGET; 221 | } 222 | return 0; 223 | } 224 | 225 | int injector_detach(injector_t *injector) 226 | { 227 | int rv = 0; 228 | injector__errmsg_is_set = 0; 229 | if (injector->remote_thread != 0) { 230 | //For some reasons on MacOS ARM64 (tested on 12.0.1) thread_terminate() returns unknown error, so let it end by itslef 231 | if(injector->state_saved){ 232 | #if defined(__arm64__) || defined(__aarch64__) 233 | injector->remote_thread_saved_state.__pc = injector->code2_addr + 12; 234 | rv = thread_set_state(injector->remote_thread, ARM_THREAD_STATE64, (thread_state_t)&injector->remote_thread_saved_state, ARM_THREAD_STATE64_COUNT); 235 | if (rv != KERN_SUCCESS) { 236 | injector__set_errmsg("%s error : %s", "GET_THREAD_STATE", mach_error_string(rv)); 237 | rv = INJERR_ERROR_IN_TARGET; 238 | } 239 | #else 240 | injector->remote_thread_saved_state.__rip = injector->code2_addr + 4; 241 | rv = thread_set_state(injector->remote_thread, x86_THREAD_STATE64, (thread_state_t)&injector->remote_thread_saved_state, x86_THREAD_STATE64_COUNT); 242 | if (rv != KERN_SUCCESS) { 243 | injector__set_errmsg("%s error : %s", "GET_THREAD_STATE", mach_error_string(rv)); 244 | rv = INJERR_ERROR_IN_TARGET; 245 | } 246 | #endif 247 | rv = thread_resume(injector->remote_thread); 248 | if(rv != 0){ 249 | injector__set_errmsg("Remote thread resume error: %s\n", mach_error_string(rv)); 250 | rv = INJERR_ERROR_IN_TARGET; 251 | } 252 | 253 | //wait thread for end 254 | #if defined(__arm64__) || defined(__aarch64__) 255 | thread_state_flavor_t flavor = ARM_THREAD_STATE64; 256 | #else 257 | thread_state_flavor_t flavor = x86_THREAD_STATE64; 258 | #endif 259 | mach_msg_type_number_t state_count; 260 | int counter = 0; 261 | while(thread_get_state(injector->remote_thread, flavor, (thread_state_t)&injector->remote_thread_saved_state, &state_count) == 0){ 262 | counter++; 263 | usleep(10); 264 | if(counter > 1000){ 265 | break; 266 | } 267 | } 268 | } 269 | } 270 | 271 | if (injector->ptrace_attached) { 272 | injector->handle_action = STOP_DETACH; 273 | kill(injector->pid, SIGSTOP); 274 | injector->handle_err = 0; 275 | do{ 276 | injector__handle_exc(injector); 277 | } while(injector->handle_err != 0); 278 | injector__release_exc_handler(injector); 279 | } 280 | 281 | if (injector->allocated) { 282 | injector__deallocate(injector, injector->text, injector->text_size + injector->stack_size); 283 | injector__deallocate(injector, injector->code_addr, injector->code_size); 284 | } 285 | if (injector->attached) { 286 | mach_port_deallocate(mach_task_self(), injector->remote_task); 287 | } 288 | 289 | free(injector); 290 | return rv; 291 | } 292 | const char *injector_error(void) 293 | { 294 | return injector__errmsg; 295 | } -------------------------------------------------------------------------------- /src/macos/mach_exc.h: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * this code is generated automatically by mig 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #ifndef _mach_exc_user_ 26 | #define _mach_exc_user_ 27 | 28 | /* Module mach_exc */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | /* BEGIN VOUCHER CODE */ 41 | 42 | #ifndef KERNEL 43 | #if defined(__has_include) 44 | #if __has_include() 45 | #ifndef USING_VOUCHERS 46 | #define USING_VOUCHERS 47 | #endif 48 | #ifndef __VOUCHER_FORWARD_TYPE_DECLS__ 49 | #define __VOUCHER_FORWARD_TYPE_DECLS__ 50 | #ifdef __cplusplus 51 | extern "C" { 52 | #endif 53 | extern boolean_t voucher_mach_msg_set(mach_msg_header_t *msg) __attribute__((weak_import)); 54 | #ifdef __cplusplus 55 | } 56 | #endif 57 | #endif // __VOUCHER_FORWARD_TYPE_DECLS__ 58 | #endif // __has_include() 59 | #endif // __has_include 60 | #endif // !KERNEL 61 | 62 | /* END VOUCHER CODE */ 63 | 64 | 65 | /* BEGIN MIG_STRNCPY_ZEROFILL CODE */ 66 | 67 | #if defined(__has_include) 68 | #if __has_include() 69 | #ifndef USING_MIG_STRNCPY_ZEROFILL 70 | #define USING_MIG_STRNCPY_ZEROFILL 71 | #endif 72 | #ifndef __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ 73 | #define __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ 74 | #ifdef __cplusplus 75 | extern "C" { 76 | #endif 77 | extern int mig_strncpy_zerofill(char *dest, const char *src, int len) __attribute__((weak_import)); 78 | #ifdef __cplusplus 79 | } 80 | #endif 81 | #endif /* __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ */ 82 | #endif /* __has_include() */ 83 | #endif /* __has_include */ 84 | 85 | /* END MIG_STRNCPY_ZEROFILL CODE */ 86 | 87 | 88 | #ifdef AUTOTEST 89 | #ifndef FUNCTION_PTR_T 90 | #define FUNCTION_PTR_T 91 | typedef void (*function_ptr_t)(mach_port_t, char *, mach_msg_type_number_t); 92 | typedef struct { 93 | char *name; 94 | function_ptr_t function; 95 | } function_table_entry; 96 | typedef function_table_entry *function_table_t; 97 | #endif /* FUNCTION_PTR_T */ 98 | #endif /* AUTOTEST */ 99 | 100 | #ifndef mach_exc_MSG_COUNT 101 | #define mach_exc_MSG_COUNT 3 102 | #endif /* mach_exc_MSG_COUNT */ 103 | 104 | #include 105 | #include 106 | #include 107 | #include 108 | 109 | #ifdef __BeforeMigUserHeader 110 | __BeforeMigUserHeader 111 | #endif /* __BeforeMigUserHeader */ 112 | 113 | #include 114 | __BEGIN_DECLS 115 | 116 | 117 | /* Routine mach_exception_raise */ 118 | #ifdef mig_external 119 | mig_external 120 | #else 121 | extern 122 | #endif /* mig_external */ 123 | kern_return_t mach_exception_raise 124 | ( 125 | mach_port_t exception_port, 126 | mach_port_t thread, 127 | mach_port_t task, 128 | exception_type_t exception, 129 | mach_exception_data_t code, 130 | mach_msg_type_number_t codeCnt 131 | ); 132 | 133 | /* Routine mach_exception_raise_state */ 134 | #ifdef mig_external 135 | mig_external 136 | #else 137 | extern 138 | #endif /* mig_external */ 139 | kern_return_t mach_exception_raise_state 140 | ( 141 | mach_port_t exception_port, 142 | exception_type_t exception, 143 | const mach_exception_data_t code, 144 | mach_msg_type_number_t codeCnt, 145 | int *flavor, 146 | const thread_state_t old_state, 147 | mach_msg_type_number_t old_stateCnt, 148 | thread_state_t new_state, 149 | mach_msg_type_number_t *new_stateCnt 150 | ); 151 | 152 | /* Routine mach_exception_raise_state_identity */ 153 | #ifdef mig_external 154 | mig_external 155 | #else 156 | extern 157 | #endif /* mig_external */ 158 | kern_return_t mach_exception_raise_state_identity 159 | ( 160 | mach_port_t exception_port, 161 | mach_port_t thread, 162 | mach_port_t task, 163 | exception_type_t exception, 164 | mach_exception_data_t code, 165 | mach_msg_type_number_t codeCnt, 166 | int *flavor, 167 | thread_state_t old_state, 168 | mach_msg_type_number_t old_stateCnt, 169 | thread_state_t new_state, 170 | mach_msg_type_number_t *new_stateCnt 171 | ); 172 | 173 | __END_DECLS 174 | 175 | /********************** Caution **************************/ 176 | /* The following data types should be used to calculate */ 177 | /* maximum message sizes only. The actual message may be */ 178 | /* smaller, and the position of the arguments within the */ 179 | /* message layout may vary from what is presented here. */ 180 | /* For example, if any of the arguments are variable- */ 181 | /* sized, and less than the maximum is sent, the data */ 182 | /* will be packed tight in the actual message to reduce */ 183 | /* the presence of holes. */ 184 | /********************** Caution **************************/ 185 | 186 | /* typedefs for all requests */ 187 | 188 | #ifndef __Request__mach_exc_subsystem__defined 189 | #define __Request__mach_exc_subsystem__defined 190 | 191 | #ifdef __MigPackStructs 192 | #pragma pack(push, 4) 193 | #endif 194 | typedef struct { 195 | mach_msg_header_t Head; 196 | /* start of the kernel processed data */ 197 | mach_msg_body_t msgh_body; 198 | mach_msg_port_descriptor_t thread; 199 | mach_msg_port_descriptor_t task; 200 | /* end of the kernel processed data */ 201 | NDR_record_t NDR; 202 | exception_type_t exception; 203 | mach_msg_type_number_t codeCnt; 204 | int64_t code[2]; 205 | } __Request__mach_exception_raise_t __attribute__((unused)); 206 | #ifdef __MigPackStructs 207 | #pragma pack(pop) 208 | #endif 209 | 210 | #ifdef __MigPackStructs 211 | #pragma pack(push, 4) 212 | #endif 213 | typedef struct { 214 | mach_msg_header_t Head; 215 | NDR_record_t NDR; 216 | exception_type_t exception; 217 | mach_msg_type_number_t codeCnt; 218 | int64_t code[2]; 219 | int flavor; 220 | mach_msg_type_number_t old_stateCnt; 221 | natural_t old_state[614]; 222 | } __Request__mach_exception_raise_state_t __attribute__((unused)); 223 | #ifdef __MigPackStructs 224 | #pragma pack(pop) 225 | #endif 226 | 227 | #ifdef __MigPackStructs 228 | #pragma pack(push, 4) 229 | #endif 230 | typedef struct { 231 | mach_msg_header_t Head; 232 | /* start of the kernel processed data */ 233 | mach_msg_body_t msgh_body; 234 | mach_msg_port_descriptor_t thread; 235 | mach_msg_port_descriptor_t task; 236 | /* end of the kernel processed data */ 237 | NDR_record_t NDR; 238 | exception_type_t exception; 239 | mach_msg_type_number_t codeCnt; 240 | int64_t code[2]; 241 | int flavor; 242 | mach_msg_type_number_t old_stateCnt; 243 | natural_t old_state[614]; 244 | } __Request__mach_exception_raise_state_identity_t __attribute__((unused)); 245 | #ifdef __MigPackStructs 246 | #pragma pack(pop) 247 | #endif 248 | #endif /* !__Request__mach_exc_subsystem__defined */ 249 | 250 | /* union of all requests */ 251 | 252 | #ifndef __RequestUnion__mach_exc_subsystem__defined 253 | #define __RequestUnion__mach_exc_subsystem__defined 254 | union __RequestUnion__mach_exc_subsystem { 255 | __Request__mach_exception_raise_t Request_mach_exception_raise; 256 | __Request__mach_exception_raise_state_t Request_mach_exception_raise_state; 257 | __Request__mach_exception_raise_state_identity_t Request_mach_exception_raise_state_identity; 258 | }; 259 | #endif /* !__RequestUnion__mach_exc_subsystem__defined */ 260 | /* typedefs for all replies */ 261 | 262 | #ifndef __Reply__mach_exc_subsystem__defined 263 | #define __Reply__mach_exc_subsystem__defined 264 | 265 | #ifdef __MigPackStructs 266 | #pragma pack(push, 4) 267 | #endif 268 | typedef struct { 269 | mach_msg_header_t Head; 270 | NDR_record_t NDR; 271 | kern_return_t RetCode; 272 | } __Reply__mach_exception_raise_t __attribute__((unused)); 273 | #ifdef __MigPackStructs 274 | #pragma pack(pop) 275 | #endif 276 | 277 | #ifdef __MigPackStructs 278 | #pragma pack(push, 4) 279 | #endif 280 | typedef struct { 281 | mach_msg_header_t Head; 282 | NDR_record_t NDR; 283 | kern_return_t RetCode; 284 | int flavor; 285 | mach_msg_type_number_t new_stateCnt; 286 | natural_t new_state[614]; 287 | } __Reply__mach_exception_raise_state_t __attribute__((unused)); 288 | #ifdef __MigPackStructs 289 | #pragma pack(pop) 290 | #endif 291 | 292 | #ifdef __MigPackStructs 293 | #pragma pack(push, 4) 294 | #endif 295 | typedef struct { 296 | mach_msg_header_t Head; 297 | NDR_record_t NDR; 298 | kern_return_t RetCode; 299 | int flavor; 300 | mach_msg_type_number_t new_stateCnt; 301 | natural_t new_state[614]; 302 | } __Reply__mach_exception_raise_state_identity_t __attribute__((unused)); 303 | #ifdef __MigPackStructs 304 | #pragma pack(pop) 305 | #endif 306 | #endif /* !__Reply__mach_exc_subsystem__defined */ 307 | 308 | /* union of all replies */ 309 | 310 | #ifndef __ReplyUnion__mach_exc_subsystem__defined 311 | #define __ReplyUnion__mach_exc_subsystem__defined 312 | union __ReplyUnion__mach_exc_subsystem { 313 | __Reply__mach_exception_raise_t Reply_mach_exception_raise; 314 | __Reply__mach_exception_raise_state_t Reply_mach_exception_raise_state; 315 | __Reply__mach_exception_raise_state_identity_t Reply_mach_exception_raise_state_identity; 316 | }; 317 | #endif /* !__RequestUnion__mach_exc_subsystem__defined */ 318 | 319 | #ifndef subsystem_to_name_map_mach_exc 320 | #define subsystem_to_name_map_mach_exc \ 321 | { "mach_exception_raise", 2405 },\ 322 | { "mach_exception_raise_state", 2406 },\ 323 | { "mach_exception_raise_state_identity", 2407 } 324 | #endif 325 | 326 | #ifdef __AfterMigUserHeader 327 | __AfterMigUserHeader 328 | #endif /* __AfterMigUserHeader */ 329 | 330 | #endif /* _mach_exc_user_ */ 331 | -------------------------------------------------------------------------------- /util/ya_getopt.c: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * ya_getopt - Yet another getopt 4 | * https://github.com/kubo/ya_getopt 5 | * 6 | * Copyright 2015 Kubo Takehiro 7 | * 8 | * Redistribution and use in source and binary forms, with or without modification, are 9 | * permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, this list of 12 | * conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list 15 | * of conditions and the following disclaimer in the documentation and/or other materials 16 | * provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS OR IMPLIED 19 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 20 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR 21 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 26 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | * 28 | * The views and conclusions contained in the software and documentation are those of the 29 | * authors and should not be interpreted as representing official policies, either expressed 30 | * or implied, of the authors. 31 | * 32 | */ 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include "ya_getopt.h" 38 | 39 | char *ya_optarg = NULL; 40 | int ya_optind = 1; 41 | int ya_opterr = 1; 42 | int ya_optopt = '?'; 43 | static char *ya_optnext = NULL; 44 | static int posixly_correct = -1; 45 | static int handle_nonopt_argv = 0; 46 | 47 | static void ya_getopt_error(const char *optstring, const char *format, ...); 48 | static void check_gnu_extension(const char *optstring); 49 | static int ya_getopt_internal(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex, int long_only); 50 | static int ya_getopt_shortopts(int argc, char * const argv[], const char *optstring, int long_only); 51 | static int ya_getopt_longopts(int argc, char * const argv[], char *arg, const char *optstring, const struct option *longopts, int *longindex, int *long_only_flag); 52 | 53 | static void ya_getopt_error(const char *optstring, const char *format, ...) 54 | { 55 | if (ya_opterr && optstring[0] != ':') { 56 | va_list ap; 57 | va_start(ap, format); 58 | vfprintf(stderr, format, ap); 59 | va_end(ap); 60 | } 61 | } 62 | 63 | static void check_gnu_extension(const char *optstring) 64 | { 65 | if (optstring[0] == '+' || getenv("POSIXLY_CORRECT") != NULL) { 66 | posixly_correct = 1; 67 | } else { 68 | posixly_correct = 0; 69 | } 70 | if (optstring[0] == '-') { 71 | handle_nonopt_argv = 1; 72 | } else { 73 | handle_nonopt_argv = 0; 74 | } 75 | } 76 | 77 | int ya_getopt(int argc, char * const argv[], const char *optstring) 78 | { 79 | return ya_getopt_internal(argc, argv, optstring, NULL, NULL, 0); 80 | } 81 | 82 | int ya_getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex) 83 | { 84 | return ya_getopt_internal(argc, argv, optstring, longopts, longindex, 0); 85 | } 86 | 87 | int ya_getopt_long_only(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex) 88 | { 89 | return ya_getopt_internal(argc, argv, optstring, longopts, longindex, 1); 90 | } 91 | 92 | static int ya_getopt_internal(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex, int long_only) 93 | { 94 | static int start, end; 95 | 96 | if (ya_optopt == '?') { 97 | ya_optopt = 0; 98 | } 99 | 100 | if (posixly_correct == -1) { 101 | check_gnu_extension(optstring); 102 | } 103 | 104 | if (ya_optind == 0) { 105 | check_gnu_extension(optstring); 106 | ya_optind = 1; 107 | ya_optnext = NULL; 108 | } 109 | 110 | switch (optstring[0]) { 111 | case '+': 112 | case '-': 113 | optstring++; 114 | } 115 | 116 | if (ya_optnext == NULL && start != 0) { 117 | int last_pos = ya_optind - 1; 118 | 119 | ya_optind -= end - start; 120 | if (ya_optind <= 0) { 121 | ya_optind = 1; 122 | } 123 | while (start < end--) { 124 | int i; 125 | char *arg = argv[end]; 126 | 127 | for (i = end; i < last_pos; i++) { 128 | ((char **)argv)[i] = argv[i + 1]; 129 | } 130 | ((char const **)argv)[i] = arg; 131 | last_pos--; 132 | } 133 | start = 0; 134 | } 135 | 136 | if (ya_optind >= argc) { 137 | ya_optarg = NULL; 138 | return -1; 139 | } 140 | if (ya_optnext == NULL) { 141 | const char *arg = argv[ya_optind]; 142 | if (*arg != '-') { 143 | if (handle_nonopt_argv) { 144 | ya_optarg = argv[optind++]; 145 | start = 0; 146 | return 1; 147 | } else if (posixly_correct) { 148 | ya_optarg = NULL; 149 | return -1; 150 | } else { 151 | int i; 152 | 153 | start = ya_optind; 154 | for (i = ya_optind + 1; i < argc; i++) { 155 | if (argv[i][0] == '-') { 156 | end = i; 157 | break; 158 | } 159 | } 160 | if (i == argc) { 161 | ya_optarg = NULL; 162 | return -1; 163 | } 164 | ya_optind = i; 165 | arg = argv[ya_optind]; 166 | } 167 | } 168 | if (strcmp(arg, "--") == 0) { 169 | ya_optind++; 170 | return -1; 171 | } 172 | if (longopts != NULL && arg[1] == '-') { 173 | return ya_getopt_longopts(argc, argv, argv[ya_optind] + 2, optstring, longopts, longindex, NULL); 174 | } 175 | } 176 | 177 | if (ya_optnext == NULL) { 178 | ya_optnext = argv[ya_optind] + 1; 179 | } 180 | if (long_only) { 181 | int long_only_flag = 0; 182 | int rv = ya_getopt_longopts(argc, argv, ya_optnext, optstring, longopts, longindex, &long_only_flag); 183 | if (!long_only_flag) { 184 | ya_optnext = NULL; 185 | return rv; 186 | } 187 | } 188 | 189 | return ya_getopt_shortopts(argc, argv, optstring, long_only); 190 | } 191 | 192 | static int ya_getopt_shortopts(int argc, char * const argv[], const char *optstring, int long_only) 193 | { 194 | int opt = *ya_optnext; 195 | const char *os = strchr(optstring, opt); 196 | 197 | if (os == NULL) { 198 | ya_optarg = NULL; 199 | if (long_only) { 200 | ya_getopt_error(optstring, "%s: unrecognized option '-%s'\n", argv[0], ya_optnext); 201 | ya_optind++; 202 | ya_optnext = NULL; 203 | } else { 204 | ya_optopt = opt; 205 | ya_getopt_error(optstring, "%s: invalid option -- '%c'\n", argv[0], opt); 206 | if (*(++ya_optnext) == 0) { 207 | ya_optind++; 208 | ya_optnext = NULL; 209 | } 210 | } 211 | return '?'; 212 | } 213 | if (os[1] == ':') { 214 | if (ya_optnext[1] == 0) { 215 | ya_optind++; 216 | if (os[2] == ':') { 217 | /* optional argument */ 218 | ya_optarg = NULL; 219 | } else { 220 | if (ya_optind == argc) { 221 | ya_optarg = NULL; 222 | ya_optopt = opt; 223 | ya_getopt_error(optstring, "%s: option requires an argument -- '%c'\n", argv[0], opt); 224 | if (optstring[0] == ':') { 225 | return ':'; 226 | } else { 227 | return '?'; 228 | } 229 | } 230 | ya_optarg = argv[ya_optind]; 231 | ya_optind++; 232 | } 233 | } else { 234 | ya_optarg = ya_optnext + 1; 235 | ya_optind++; 236 | } 237 | ya_optnext = NULL; 238 | } else { 239 | ya_optarg = NULL; 240 | if (ya_optnext[1] == 0) { 241 | ya_optnext = NULL; 242 | ya_optind++; 243 | } else { 244 | ya_optnext++; 245 | } 246 | } 247 | return opt; 248 | } 249 | 250 | static int ya_getopt_longopts(int argc, char * const argv[], char *arg, const char *optstring, const struct option *longopts, int *longindex, int *long_only_flag) 251 | { 252 | char *val = NULL; 253 | const struct option *opt; 254 | size_t namelen; 255 | int idx; 256 | 257 | for (idx = 0; longopts[idx].name != NULL; idx++) { 258 | opt = &longopts[idx]; 259 | namelen = strlen(opt->name); 260 | if (strncmp(arg, opt->name, namelen) == 0) { 261 | switch (arg[namelen]) { 262 | case '\0': 263 | switch (opt->has_arg) { 264 | case ya_required_argument: 265 | ya_optind++; 266 | if (ya_optind == argc) { 267 | ya_optarg = NULL; 268 | ya_optopt = opt->val; 269 | ya_getopt_error(optstring, "%s: option '--%s' requires an argument\n", argv[0], opt->name); 270 | if (optstring[0] == ':') { 271 | return ':'; 272 | } else { 273 | return '?'; 274 | } 275 | } 276 | val = argv[ya_optind]; 277 | break; 278 | } 279 | goto found; 280 | case '=': 281 | if (opt->has_arg == ya_no_argument) { 282 | const char *hyphens = (argv[ya_optind][1] == '-') ? "--" : "-"; 283 | 284 | ya_optind++; 285 | ya_optarg = NULL; 286 | ya_optopt = opt->val; 287 | ya_getopt_error(optstring, "%s: option '%s%s' doesn't allow an argument\n", argv[0], hyphens, opt->name); 288 | return '?'; 289 | } 290 | val = arg + namelen + 1; 291 | goto found; 292 | } 293 | } 294 | } 295 | if (long_only_flag) { 296 | *long_only_flag = 1; 297 | } else { 298 | ya_getopt_error(optstring, "%s: unrecognized option '%s'\n", argv[0], argv[ya_optind]); 299 | ya_optind++; 300 | } 301 | return '?'; 302 | found: 303 | ya_optarg = val; 304 | ya_optind++; 305 | if (opt->flag) { 306 | *opt->flag = opt->val; 307 | } 308 | if (longindex) { 309 | *longindex = idx; 310 | } 311 | return opt->flag ? 0 : opt->val; 312 | } 313 | -------------------------------------------------------------------------------- /include/injector.h: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2018-2023 Kubo Takehiro 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | 26 | /*! 27 | * \file injector.h 28 | * \brief Library for injecting a shared library into a Linux, Windows and macOS process 29 | */ 30 | #ifndef INJECTOR_H 31 | #define INJECTOR_H 32 | 33 | #if defined(_WIN32) 34 | #include 35 | typedef DWORD injector_pid_t; 36 | #else 37 | #include 38 | 39 | /*! 40 | * \brief Platform-dependent process id type (\c pid_t on Unix. \c DWORD on Windows) 41 | */ 42 | typedef pid_t injector_pid_t; 43 | #endif 44 | 45 | #ifdef __cplusplus 46 | extern "C" { 47 | #endif 48 | #if 0 49 | } 50 | #endif 51 | 52 | #define INJERR_SUCCESS 0 /* linux, windows, macos */ 53 | #define INJERR_OTHER -1 /* linux, windows, macos */ 54 | #define INJERR_NO_MEMORY -2 /* linux, windows, macos */ 55 | #define INJERR_NO_PROCESS -3 /* linux, windows, macos */ 56 | #define INJERR_NO_LIBRARY -4 /* linux */ 57 | #define INJERR_NO_FUNCTION -4 /* linux */ 58 | #define INJERR_ERROR_IN_TARGET -5 /* linux, windows, macos */ 59 | #define INJERR_FILE_NOT_FOUND -6 /* linux, windows, macos */ 60 | #define INJERR_INVALID_MEMORY_AREA -7 /* linux, macos */ 61 | #define INJERR_PERMISSION -8 /* linux, windows, macos */ 62 | #define INJERR_UNSUPPORTED_TARGET -9 /* linux, windows, macos */ 63 | #define INJERR_INVALID_ELF_FORMAT -10 /* linux */ 64 | #define INJERR_WAIT_TRACEE -11 /* linux */ 65 | #define INJERR_FUNCTION_MISSING -12 /* linux, windows, macos */ 66 | 67 | typedef struct injector injector_t; 68 | 69 | /*! 70 | * \brief Attach to the specified process. 71 | * \param[out] injector the address where the newly created injector handle will be stored 72 | * \param[in] pid the process id to be attached 73 | * \return zero on success. Otherwise, error code 74 | */ 75 | int injector_attach(injector_t **injector, injector_pid_t pid); 76 | 77 | /*! 78 | * \brief Detach from the attached process and destroy the specified handle. 79 | * \param[in] injector the injector handle to destroy 80 | * \return zero on success. Otherwise, error code 81 | */ 82 | int injector_detach(injector_t *injector); 83 | 84 | /*! 85 | * \brief Inject the specified shared library into the target process. 86 | * \param[in] injector the injector handle specifying the target process 87 | * \param[in] path the path name of the shared library 88 | * \param[out] handle the address where the newly created module handle will be stored 89 | * \return zero on success. Otherwise, error code 90 | * 91 | * Note on Linux: 92 | * This calls functions inside of the target process interrupted by \c ptrace(). 93 | * If the target process is interrupted while holding a non-reentrant lock and 94 | * injector calls a function requiring the same lock, the process stops forever. 95 | * If the lock type is reentrant, the status guarded by the lock may become inconsistent. 96 | * As far as I checked, \c dlopen() internally calls \c malloc() requiring non-reentrant 97 | * locks. \c dlopen() also uses a reentrant lock to guard information about loaded files. 98 | */ 99 | int injector_inject(injector_t *injector, const char *path, void **handle); 100 | 101 | /*! 102 | * \brief Uninject the shared library specified by \c handle. 103 | * \param[in] injector the injector handle specifying the target process 104 | * \param[in] handle the module handle created by \c injector_inject 105 | * \return zero on success. Otherwise, error code 106 | * \remarks This fearute isn't supported for musl-libc processes. 107 | * See [Functional differences from glibc](https://wiki.musl-libc.org/functional-differences-from-glibc.html#Unloading_libraries). 108 | */ 109 | int injector_uninject(injector_t *injector, void *handle); 110 | 111 | #if defined(INJECTOR_DOC) || defined(__linux__) || defined(__APPLE__) 112 | /*! 113 | * \brief Call the specified function taking no arguments in the target process (Linux and macOS only) 114 | * \param[in] injector the injector handle specifying the target process 115 | * \param[in] handle the module handle created by \c injector_inject or special-handles such as \c RTLD_DEFAULT 116 | * \param[in] name the function name 117 | * 118 | * The \c handle and \c name arguments are passed to \c dlsym ([Linux](https://man7.org/linux/man-pages/man3/dlvsym.3.html), [macOS](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlsym.3.html)) and then the return value of \c dlsym is called without arguments in the target process. 119 | * 120 | * This is same with the combination of injector_remote_func_addr() and injector_remote_call() without extra arguments. 121 | * 122 | * \note 123 | * (Linux only) 124 | * If the function in the target process internally calls non-[async-signal-safe]((https://man7.org/linux/man-pages/man7/signal-safety.7.html)) 125 | * functions, it may stop the target process or cause unexpected behaviour. 126 | * \sa injector_remote_func_addr(), injector_remote_call(), injector_remote_vcall() 127 | */ 128 | int injector_call(injector_t *injector, void *handle, const char* name); 129 | #endif 130 | 131 | /*! 132 | * \brief Get the message of the last error. 133 | * \remarks The message is updated only when \c injector functions return non-zero. 134 | */ 135 | const char *injector_error(void); 136 | 137 | #if defined(INJECTOR_DOC) || defined(__linux__) || defined(_WIN32) 138 | #define INJECTOR_HAS_REMOTE_CALL_FUNCS 1 139 | #include 140 | #include 141 | 142 | /*! 143 | * \brief Get the function address in the target process (Linux and Windows only) 144 | * \param[in] injector the injector handle specifying the target process 145 | * \param[in] handle the module handle created by \c injector_inject or special-handles such as \c RTLD_DEFAULT 146 | * \param[in] name the function name 147 | * \param[out] func_addr_out the address where the function address in the target process will be stored 148 | * \return zero on success. Otherwise, error code 149 | * 150 | * \b Example 151 | * 152 | * Inject libfoo.so and then call foo_func(1, 2, 3) in it. 153 | * \code 154 | * void *handle; 155 | * // inject libfoo.so and get the handle 156 | * if (injector_inject(injector, "libfoo.so", &handle) != 0) { 157 | * return; 158 | * } 159 | * size_t func_addr; 160 | * // get the address of foo_func in the handle 161 | * if (injector_remote_func_addr(injector, handle, "foo_func", &func_addr) != 0) { 162 | * return; 163 | * } 164 | * intptr_t retval; 165 | * // call foo_func 166 | * if (injector_remote_call(injector, &retval, func_addr, 1, 2, 3) != 0) { 167 | * return; 168 | * } 169 | * printf("The return value of foo_func(1, 2, 3) is %ld.\n", retval); 170 | * \endcode 171 | */ 172 | int injector_remote_func_addr(injector_t *injector, void *handle, const char* name, size_t *func_addr_out); 173 | 174 | /*! 175 | * \brief Call the function in the target process (Linux and Windows only) 176 | * \param[in] injector the injector handle specifying the target process 177 | * \param[out] retval \c NULL or the address where the return value of the function call will be stored 178 | * \param[in] func_addr the function address in the target process 179 | * \param[in] ... arguments passed to the function 180 | * \return zero on success. Otherwise, error code 181 | * \remarks 182 | * The types of the arguments must be integer or pointer. 183 | * If it is a pointer, it must point to a valid address in the target process. 184 | * The number of arguments must be less than or equal to six. 185 | * \note 186 | * If the function in the target process internally calls non-[async-signal-safe]((https://man7.org/linux/man-pages/man7/signal-safety.7.html)) 187 | * functions, it may stop the target process or cause unexpected behaviour. 188 | * \sa injector_remote_func_addr(), injector_remote_vcall() 189 | */ 190 | int injector_remote_call(injector_t *injector, intptr_t *retval, size_t func_addr, ...); 191 | 192 | /*! 193 | * \brief Call the function in the target process (Linux and Windows only) 194 | * \param[in] injector the injector handle specifying the target process 195 | * \param[out] retval \c NULL or the address where the return value of the function call will be stored 196 | * \param[in] func_addr the function address in the target process 197 | * \param[in] ap arguments passed to the function 198 | * \return zero on success. Otherwise, error code 199 | * \remarks 200 | * The types of the arguments must be integer or pointer. 201 | * If it is a pointer, it must point to a valid address in the target process. 202 | * The number of arguments must be less than or equal to six. 203 | * \note 204 | * If the function in the target process internally calls non-[async-signal-safe]((https://man7.org/linux/man-pages/man7/signal-safety.7.html)) 205 | * functions, it may stop the target process or cause unexpected behaviour. 206 | * \sa injector_remote_func_addr(), injector_remote_call() 207 | */ 208 | int injector_remote_vcall(injector_t *injector, intptr_t *retval, size_t func_addr, va_list ap); 209 | #endif 210 | 211 | #if defined(INJECTOR_DOC) || defined(_WIN32) 212 | /*! 213 | * \brief Same with \c injector_inject except the type of the \c path argument. (Windows only) 214 | * \param[in] injector the injector handle specifying the target process 215 | * \param[in] path the path name of the shared library 216 | * \param[out] handle the address where the newly created module handle will be stored 217 | * \return zero on success. Otherwise, error code 218 | */ 219 | int injector_inject_w(injector_t *injector, const wchar_t *path, void **handle); 220 | #endif 221 | 222 | #if defined(INJECTOR_DOC) || (defined(__linux__) && defined(__x86_64__)) 223 | #define INJECTOR_HAS_INJECT_IN_CLONED_THREAD 1 // feature test macro 224 | /*! 225 | * \brief Inject the specified shared library into the target process by the \c clone system call. (Linux x86_64 only) 226 | * \param[in] injector the injector handle specifying the target process 227 | * \param[in] path the path name of the shared library 228 | * \param[out] handle the address where the newly created module handle will be stored 229 | * \return zero on success. Otherwise, error code 230 | * 231 | * This calls `dlopen()` in a thread created by \c [clone()](https://man7.org/linux/man-pages/man2/clone.2.html). Note that no wonder there are unexpected 232 | * pitfalls because some resources allocated in \c [pthread_create()](https://man7.org/linux/man-pages/man3/pthread_create.3.html) lack in the \c clone()-ed thread. 233 | * Use it at your own risk. 234 | */ 235 | int injector_inject_in_cloned_thread(injector_t *injector, const char *path, void **handle); 236 | #endif 237 | 238 | #if 0 239 | { 240 | #endif 241 | #ifdef __cplusplus 242 | }; /* extern "C" */ 243 | #endif 244 | 245 | #endif 246 | -------------------------------------------------------------------------------- /src/macos/exc_handler.c: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2022 TheOiseth 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #include "injector_internal.h" 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #define HANDLE_EXC EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_SOFTWARE | EXC_MASK_BREAKPOINT | EXC_MASK_CRASH | EXC_MASK_CORPSE_NOTIFY 32 | boolean_t mach_exc_server(mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP); 33 | int injector__create_exc_handler(injector_t *injector) { 34 | mach_port_name_t exc_port = 0; 35 | mach_msg_type_number_t exception_types_count; 36 | int rv; 37 | injector->exc_port = 0; 38 | injector->saved_exception_types_count = 0; 39 | rv = task_get_exception_ports(injector->remote_task, 40 | EXC_MASK_ALL, 41 | injector->saved_masks, 42 | &exception_types_count, 43 | injector->saved_ports, 44 | injector->saved_behaviors, 45 | injector->saved_flavors); 46 | 47 | injector->saved_exception_types_count = exception_types_count; 48 | if(rv != 0){ 49 | injector__set_errmsg("%s error : %s", "EXC_GET_PORTS", mach_error_string(rv)); 50 | return INJERR_OTHER; 51 | } 52 | 53 | rv = mach_port_allocate(mach_task_self(), 54 | MACH_PORT_RIGHT_RECEIVE, 55 | &exc_port); 56 | injector->exc_port = exc_port; 57 | if(rv != 0){ 58 | injector__set_errmsg("%s error : %s", "EXC_PORT_ALLOCATE", mach_error_string(rv)); 59 | rv = INJERR_OTHER; 60 | goto cleanup; 61 | } 62 | rv = mach_port_insert_right(mach_task_self(), 63 | exc_port, 64 | exc_port, 65 | MACH_MSG_TYPE_MAKE_SEND); 66 | if(rv != 0){ 67 | injector__set_errmsg("%s error : %s", "EXC_INSERT_RIGHTS", mach_error_string(rv)); 68 | rv = INJERR_OTHER; 69 | goto cleanup; 70 | } 71 | 72 | rv = task_set_exception_ports(injector->remote_task, 73 | HANDLE_EXC, 74 | exc_port, 75 | EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, 76 | THREAD_STATE_NONE); 77 | if(rv != 0){ 78 | injector__set_errmsg("%s error : %s", "EXC_SET_PORTS", mach_error_string(rv)); 79 | rv = INJERR_OTHER; 80 | goto cleanup; 81 | } 82 | 83 | return 0; 84 | cleanup: 85 | injector__release_exc_handler(injector); 86 | return rv; 87 | } 88 | int injector__handle_exc(injector_t *injector) { 89 | char req[128], rpl[128]; 90 | mach_msg_header_with_injector *mmhwi; 91 | int rv; 92 | 93 | mmhwi = (mach_msg_header_with_injector*)req; 94 | mmhwi->injector = injector; 95 | rv = mach_msg((mach_msg_header_t *)req, MACH_RCV_MSG, 0, sizeof(req), injector->exc_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); 96 | if (rv != KERN_SUCCESS) { 97 | injector__set_errmsg("%s error : %s", "EXC_RECV_MACH_MSG", mach_error_string(rv)); 98 | return INJERR_OTHER; 99 | } 100 | /* suspend all threads in the process after an exception was received */ 101 | 102 | task_suspend(injector->remote_task); 103 | 104 | boolean_t message_parsed_correctly = mach_exc_server((mach_msg_header_t *)req, (mach_msg_header_t *)rpl); 105 | if (! message_parsed_correctly) { 106 | 107 | size_t parse_exc = ((mig_reply_error_t *)rpl)->RetCode; 108 | if(parse_exc != 0 ){ 109 | injector__set_errmsg("%s error : %s", "mach_exc_server", mach_error_string(parse_exc)); 110 | } 111 | } 112 | task_resume(injector->remote_task); 113 | mach_msg_size_t send_sz = ((mach_msg_header_t *)rpl)->msgh_size; 114 | 115 | rv = mach_msg((mach_msg_header_t *)rpl, MACH_SEND_MSG, send_sz, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); 116 | if (rv != KERN_SUCCESS) { 117 | injector__set_errmsg("%s error : %s", "EXC_SEND_MACH_MSG", mach_error_string(rv)); 118 | return INJERR_OTHER; 119 | } 120 | return 0; 121 | } 122 | 123 | static bool isSIGSTOP(exception_type_t exception_type, mach_exception_data_t codes){ 124 | return exception_type == EXC_SOFTWARE && codes[0] == EXC_SOFT_SIGNAL && codes[1] == SIGSTOP; 125 | } 126 | 127 | kern_return_t catch_mach_exception_raise( 128 | mach_port_t exception_port, 129 | mach_port_t thread_port, 130 | mach_port_t task_port, 131 | exception_type_t exception_type, 132 | mach_exception_data_t codes, 133 | mach_msg_type_number_t num_codes, 134 | injector_t *injector) 135 | { 136 | injector->handle_err = 0; 137 | bool bad_exc = true; 138 | int rv; 139 | switch (injector->handle_action) 140 | { 141 | case STOP_CONTINUE: 142 | if(isSIGSTOP(exception_type, codes)){ 143 | bad_exc = false; 144 | injector__ptrace_update(injector, thread_port); 145 | } 146 | break; 147 | case STOP_DETACH: 148 | if(isSIGSTOP(exception_type, codes)){ 149 | bad_exc = false; 150 | injector__ptrace_detach(injector); 151 | } 152 | break; 153 | case TRAP_GETREGS: 154 | if(exception_type == EXC_BREAKPOINT){ 155 | bad_exc = false; 156 | #if defined(__arm64__) || defined(__aarch64__) 157 | mach_msg_type_number_t thread_state_count = ARM_THREAD_STATE64_COUNT; 158 | arm_thread_state64_t state; 159 | rv = thread_get_state(thread_port, ARM_THREAD_STATE64, (thread_state_t)&state, &thread_state_count); 160 | if (rv == KERN_SUCCESS) { 161 | injector->retval = state.__x[0]; 162 | } else { 163 | injector__set_errmsg("%s error : %s", "GET_THREAD_STATE", mach_error_string(rv)); 164 | injector->handle_err = INJERR_ERROR_IN_TARGET; 165 | } 166 | #else 167 | mach_msg_type_number_t thread_state_count = x86_THREAD_STATE64_COUNT; 168 | x86_thread_state64_t state; 169 | rv = thread_get_state(thread_port, x86_THREAD_STATE64, (thread_state_t)&state, &thread_state_count); 170 | if (rv == KERN_SUCCESS) { 171 | injector->retval = state.__rax; 172 | } else { 173 | injector__set_errmsg("%s error : %s", "GET_THREAD_STATE", mach_error_string(rv)); 174 | injector->handle_err = INJERR_ERROR_IN_TARGET; 175 | } 176 | #endif 177 | if (injector->mach_thread != 0){ 178 | rv = thread_terminate(injector->mach_thread); 179 | injector->mach_thread = 0; 180 | } 181 | rv = thread_suspend(thread_port); 182 | if(rv != KERN_SUCCESS){ 183 | injector__set_errmsg("%s error : %s", "THREAD_SUSPEND", mach_error_string(rv)); 184 | injector->handle_err = INJERR_ERROR_IN_TARGET; 185 | } 186 | //we don't need to continue since we already called task_resume and mach_msg later 187 | //rv = injector__ptrace_continue(injector); 188 | 189 | } 190 | break; 191 | case TRAP_SETREGS: 192 | if(exception_type == EXC_BREAKPOINT){ 193 | bad_exc = false; 194 | bool thread_init = false; 195 | if(thread_port != injector->mach_thread){ 196 | thread_init = injector->remote_thread == 0; 197 | injector->remote_thread = thread_port; 198 | } 199 | #if defined(__arm64__) || defined(__aarch64__) 200 | mach_msg_type_number_t thread_state_count = ARM_THREAD_STATE64_COUNT; 201 | arm_thread_state64_t state; 202 | rv = thread_get_state(thread_port, ARM_THREAD_STATE64, (thread_state_t)&state, &thread_state_count); 203 | if (rv != KERN_SUCCESS) { 204 | injector__set_errmsg("%s error : %s", "GET_THREAD_STATE", mach_error_string(rv)); 205 | injector->handle_err = INJERR_ERROR_IN_TARGET; 206 | goto exit; 207 | } 208 | if(thread_init){ 209 | memcpy(&injector->remote_thread_saved_state, &state, sizeof(state)); 210 | injector->state_saved = 1; 211 | } 212 | state.__x[0] = injector->arg1; 213 | state.__x[1] = injector->arg2; 214 | state.__x[2] = injector->arg3; 215 | state.__x[3] = injector->arg4; 216 | state.__x[4] = injector->arg5; 217 | state.__x[5] = injector->arg6; 218 | state.__x[8] = injector->func_addr; 219 | state.__sp = injector->stack; 220 | state.__pc = injector->code2_addr + 4; 221 | rv = thread_set_state(thread_port, ARM_THREAD_STATE64, (thread_state_t)&state, ARM_THREAD_STATE64_COUNT); 222 | if (rv != KERN_SUCCESS) { 223 | injector__set_errmsg("%s error : %s", "SET_THREAD_STATE", mach_error_string(rv)); 224 | injector->handle_err = INJERR_ERROR_IN_TARGET; 225 | } 226 | #else 227 | mach_msg_type_number_t thread_state_count = x86_THREAD_STATE64_COUNT; 228 | x86_thread_state64_t state; 229 | 230 | rv = thread_get_state(thread_port, x86_THREAD_STATE64, (thread_state_t)&state, &thread_state_count); 231 | if (rv != KERN_SUCCESS) { 232 | injector__set_errmsg("%s error : %s", "GET_THREAD_STATE", mach_error_string(rv)); 233 | injector->handle_err = INJERR_ERROR_IN_TARGET; 234 | goto exit; 235 | } 236 | if(thread_init){ 237 | memcpy(&injector->remote_thread_saved_state, &state, sizeof(state)); 238 | injector->state_saved = 1; 239 | } 240 | state.__rax = injector->func_addr; 241 | state.__rdi = injector->arg1; 242 | state.__rsi = injector->arg2; 243 | state.__rdx = injector->arg3; 244 | state.__rcx = injector->arg4; 245 | state.__r8 = injector->arg5; 246 | state.__r9 = injector->arg6; 247 | state.__rsp = injector->stack; 248 | state.__rbp = injector->stack; 249 | rv = thread_set_state(thread_port, x86_THREAD_STATE64, (thread_state_t)&state, x86_THREAD_STATE64_COUNT); 250 | if (rv != KERN_SUCCESS) { 251 | injector__set_errmsg("%s error : %s", "SET_THREAD_STATE", mach_error_string(rv)); 252 | injector->handle_err = INJERR_ERROR_IN_TARGET; 253 | } 254 | #endif 255 | //we don't need to continue since we already called task_resume and mach_msg later 256 | //rv = injector__ptrace_continue(injector); 257 | 258 | } 259 | break; 260 | } 261 | 262 | if(bad_exc){ 263 | if(exception_type == EXC_SOFTWARE){ 264 | injector__set_errmsg("The target process got an unexpected signal %i.", codes[1]); 265 | } else { 266 | injector__set_errmsg("Got unhandled exception %i.", exception_type); 267 | } 268 | injector->handle_err = INJERR_OTHER; 269 | } 270 | exit: 271 | return KERN_SUCCESS; 272 | } 273 | 274 | kern_return_t catch_mach_exception_raise_state( 275 | mach_port_t exception_port, 276 | exception_type_t exception, 277 | const mach_exception_data_t code, 278 | mach_msg_type_number_t codeCnt, 279 | int *flavor, 280 | const thread_state_t old_state, 281 | mach_msg_type_number_t old_stateCnt, 282 | thread_state_t new_state, 283 | mach_msg_type_number_t *new_stateCnt) 284 | { 285 | return MACH_RCV_INVALID_TYPE; 286 | } 287 | 288 | kern_return_t catch_mach_exception_raise_state_identity( 289 | mach_port_t exception_port, 290 | mach_port_t thread, 291 | mach_port_t task, 292 | exception_type_t exception, 293 | mach_exception_data_t code, 294 | mach_msg_type_number_t codeCnt, 295 | int *flavor, 296 | thread_state_t old_state, 297 | mach_msg_type_number_t old_stateCnt, 298 | thread_state_t new_state, 299 | mach_msg_type_number_t *new_stateCnt) 300 | { 301 | return MACH_RCV_INVALID_TYPE; 302 | } 303 | 304 | int injector__release_exc_handler(injector_t *injector) { 305 | for (int i = 0; i < injector->saved_exception_types_count; i++) { 306 | task_set_exception_ports(injector->remote_task, injector->saved_masks[i], injector->saved_ports[i], injector->saved_behaviors[i], injector->saved_flavors[i]); 307 | } 308 | injector->saved_exception_types_count = 0; 309 | if (injector->exc_port != 0){ 310 | mach_port_deallocate(mach_task_self(), injector->exc_port); 311 | injector->exc_port = 0; 312 | } 313 | return 0; 314 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Injector 2 | 3 | [![Static Badge](https://img.shields.io/badge/docs-API_reference-blue)](http://www.jiubao.org/injector/injector_8h.html) 4 | 5 | **Library for injecting a shared library into a Linux, Windows and MacOS process** 6 | 7 | > [!CAUTION] 8 | > Due to a shift in my personal interests, this repository is no longer maintained and has been archived. 9 | > If you're looking for a successor project, please check [the network graph](https://github.com/kubo/injector/network) to see if any forks have continued development. 10 | 11 | ## Linux 12 | 13 | > [!WARNING] 14 | > Don't use this in production environments. It may stop target processes forever. See [Caveats](#caveats). 15 | 16 | I was inspired by [`linux-inject`][] and the basic idea came from it. 17 | However the way to call `__libc_dlopen_mode` in `libc.so.6` is 18 | thoroughly different. 19 | 20 | * `linux-inject` writes about 80 bytes of code to the target process 21 | on x86_64. This writes only 4 ~ 16 bytes. 22 | * `linux-inject` writes code at the firstly found executable region 23 | of memory, which may be referred by other threads. This writes it 24 | at [the entry point of `libc.so.6`][libc_main], which will be referred by 25 | nobody unless the libc itself is executed as a program. 26 | 27 | [libc_main]: https://sourceware.org/git/?p=glibc.git;a=blob;f=csu/version.c;h=8c0ed79c01223e1f12b54d19f90b5e5b7dd78d27;hb=c804cd1c00adde061ca51711f63068c103e94eef#l67 28 | 29 | ## Windows 30 | 31 | Windows version is also here. It uses well-known [`CreateRemoteThread+LoadLibrary`] 32 | technique to load a DLL into another process with some improvements. 33 | 34 | 1. It gets Win32 error messages when `LoadLibrary` fails by copying assembly 35 | code into the target process. 36 | 2. It can inject a 32-bit dll into a 32-bit process from x64 processes 37 | by checking the export entries in 32-bit kernel32.dll. 38 | 39 | **Note:** It may work on Windows on ARM though I have not tested it because 40 | I have no ARM machines. Let me know if it really works. 41 | 42 | ## MacOS 43 | The injector connects to the target process using task_for_pid and creates a mach-thread. If dlopen is called in this thread, the target process will fail with an error, however, it is possible to create another thread using pthread_create_from_mach_thread function for Mac >= 10.12 or pthread_create otherwise. In the created thread, the code for loading the library is executed. The second thread is created when injector_inject is called and terminated when injector_detach is called. 44 | # Compilation 45 | 46 | ## Linux 47 | 48 | ```shell 49 | $ git clone https://github.com/kubo/injector.git 50 | $ cd injector 51 | $ make 52 | ``` 53 | 54 | The `make` command creates: 55 | 56 | | filename | - | 57 | |---|---| 58 | |`src/linux/libinjector.a` |a static library| 59 | |`src/linux/libinjector.so` |a shared library| 60 | |`cmd/injector` |a command line program linked with the static library| 61 | 62 | ## Windows (MSVC) 63 | 64 | Open a Visual Studio command prompt and run the following commands: 65 | 66 | ```shell 67 | $ git clone https://github.com/kubo/injector.git # Or use any other tool 68 | $ cd injector 69 | $ nmake -f Makefile.win32 70 | ``` 71 | 72 | The `nmake` command creates: 73 | 74 | | filename | - | 75 | |---|---| 76 | |`src/windows/injector-static.lib` |a static library (release build) 77 | |`src/windows/injector.dll` |a shared library (release build) 78 | |`src/windows/injector.lib` |an import library for `injector.dll` 79 | |`src/windows/injectord-static.lib` |a static library (debug build) 80 | |`src/windows/injectord.dll` |a shared library (debug build) 81 | |`src/windows/injectord.lib` |an import library for `injectord.dll` 82 | |`cmd/injector.exe` |a command line program linked the static library (release build)| 83 | 84 | ## Windows (mingw-w64) 85 | 86 | On MSYS2: 87 | 88 | ```shell 89 | $ git clone https://github.com/kubo/injector.git 90 | $ cd injector 91 | $ CC=gcc make 92 | ``` 93 | 94 | Cross-compilation on Linux: 95 | 96 | ```shell 97 | $ git clone https://github.com/kubo/injector.git 98 | $ cd injector 99 | $ CC=x86_64-w64-mingw32-gcc OS=Windows_NT make 100 | ``` 101 | 102 | The environment variable `OS=Windows_NT` must be set on Linux. 103 | 104 | ## MacOS 105 | 106 | ```shell 107 | $ git clone https://github.com/TheOiseth/injector.git 108 | $ cd injector 109 | $ make 110 | ``` 111 | 112 | The `make` command creates: 113 | 114 | | filename | - | 115 | |---|---| 116 | |`src/macos/libinjector.a` |a static library| 117 | |`src/macos/libinjector.dylib` |a shared library| 118 | |`cmd/injector` |a command line program linked with the static library| 119 | 120 | **Important:** in order for the injector process to connect to another process using task_for_pid, it is necessary to [`disable SIP`][] or sign the injector with a self-signed certificate with debugging permission, for this: 121 | ```shell 122 | $ cd cmd/macos-sign 123 | $ chmod +x genkey.sh 124 | $ ./genkey.sh 125 | $ chmod +x sign.sh 126 | $ ./sign.sh 127 | ``` 128 | If injector still does not work after signing, reboot the system. 129 | 130 | # Usage 131 | 132 | ## C API 133 | 134 | ```c 135 | #include 136 | 137 | ... 138 | 139 | injector_t *injector; 140 | void *handle; 141 | 142 | /* attach to a process whose process id is 1234. */ 143 | if (injector_attach(&injector, 1234) != 0) { 144 | printf("ATTACH ERROR: %s\n", injector_error()); 145 | return; 146 | } 147 | /* inject a shared library into the process. */ 148 | if (injector_inject(injector, "/path/to/shared/library", NULL) != 0) { 149 | printf("INJECT ERROR: %s\n", injector_error()); 150 | } 151 | /* inject another shared library. */ 152 | if (injector_inject(injector, "/path/to/another/shared/library", &handle) != 0) { 153 | printf("INJECT ERROR: %s\n", injector_error()); 154 | } 155 | 156 | ... 157 | 158 | /* uninject the second shared library. */ 159 | if (injector_uninject(injector, handle) != 0) { 160 | printf("UNINJECT ERROR: %s\n", injector_error()); 161 | } 162 | 163 | /* cleanup */ 164 | injector_detach(injector); 165 | ``` 166 | 167 | ## Command line program 168 | 169 | See [`Usage` section and `Sample` section in linux-inject][`inject`] and substitute 170 | `inject` with `injector` in the page. 171 | 172 | # Tested Architectures 173 | 174 | ## Linux 175 | 176 | x86_64: 177 | 178 | injector process \ target process | x86_64 | i386 | x32(*1) 179 | ---|---|---|--- 180 | **x86_64** | :smiley: success(*2) | :smiley: success(*3) | :smiley: success(*6) 181 | **i386** | :skull: failure(*4) | :smiley: success(*3) | :skull: failure(*5) 182 | **x32**(*1) | :skull: failure(*4) | :smiley: success(*6) | :skull: failure(*5) 183 | 184 | *1: [x32 ABI](https://en.wikipedia.org/wiki/X32_ABI) 185 | *2: tested on github actions with both glibc and musl. 186 | *3: tested on github actions with glibc. 187 | *4: failure with `64-bit target process isn't supported by 32-bit process`. 188 | *5: failure with `x32-ABI target process is supported only by x86_64`. 189 | *6: tested on a local machine. `CONFIG_X86_X32` isn't enabled in github actions. 190 | 191 | ARM: 192 | 193 | injector process \ target process | arm64 | armhf | armel 194 | ---|---|---|--- 195 | **arm64** | :smiley: success | :smiley: success | :smiley: success 196 | **armhf** | :skull: failure(*1) | :smiley: success | :smiley: success 197 | **armel** | :skull: failure(*1) | :smiley: success | :smiley: success 198 | 199 | *1: failure with `64-bit target process isn't supported by 32-bit process`. 200 | 201 | MIPS: 202 | 203 | injector process \ target process | mips64el | mipsel (n32) | mipsel (o32) 204 | ---|---|---|--- 205 | **mips64el** | :smiley: success (*1) | :smiley: success (*1) | :smiley: success (*1) 206 | **mipsel (n32)** | :skull: failure(*2) | :smiley: success (*1) | :smiley: success (*1) 207 | **mipsel (o32)** | :skull: failure(*2) | :smiley: success (*1) | :smiley: success (*1) 208 | 209 | *1: tested on [debian 11 mips64el](https://www.debian.org/releases/bullseye/mips64el/ch02s01.en.html#idm271) on [QEMU](https://www.qemu.org/). 210 | *2: failure with `64-bit target process isn't supported by 32-bit process`. 211 | 212 | PowerPC: 213 | 214 | * **ppc64le** (tested on [alpine 3.16.2 ppc64le](https://dl-cdn.alpinelinux.org/alpine/v3.16/releases/ppc64le/) on [QEMU](https://www.qemu.org/)) 215 | * **powerpc (big endian)** (tested on [ubuntu 16.04 powerpc](https://old-releases.ubuntu.com/releases/xenial/) on [QEMU](https://www.qemu.org/) 216 | 217 | RISC-V: 218 | 219 | * **riscv64** (tested on [Ubuntu 22.04.1 riscv64 on QEMU](https://wiki.ubuntu.com/RISC-V#Booting_with_QEMU)) 220 | 221 | ## Windows 222 | 223 | Windows x64: 224 | 225 | injector process \ target process | x64 | x86 226 | ---|---|--- 227 | **x64** | :smiley: success(*2) | :smiley: success(*2) 228 | **x86** | :skull: failure(*1) | :smiley: success(*2) 229 | 230 | *1: failure with `x64 target process isn't supported by x86 process`. 231 | *2: tested on github actions 232 | 233 | Windows 11 on Arm: 234 | 235 | injector process \ target process | arm64 | arm64ec | x64 | x86 | arm32 236 | ---|---|---|---|---|--- 237 | **arm64** | :smiley: success | :skull: failure | :skull: failure | :skull: failure | :smiley: success 238 | **arm64ec** | :skull: failure | :smiley: success | :smiley: success | :skull: failure | :skull: failure 239 | **x64** | :skull: failure | :smiley: success | :smiley: success | :skull: failure | :skull: failure 240 | **x86** | :skull: failure | :skull: failure | :skull: failure | :smiley: success | :skull: failure 241 | **arm32** | :skull: failure | :skull: failure | :skull: failure | :skull: failure | :smiley: success 242 | 243 | [Wine](https://www.winehq.org/) (on Linux x86_64): 244 | 245 | injector process \ target process | x64 | x86 246 | ---|---|--- 247 | **x64** | :smiley: success | :skull: failure 248 | **x86** | :skull: failure | :smiley: success 249 | 250 | ## MacOS 251 | 252 | injector process \ target process | x64 | arm64 253 | ---|---|--- 254 | **x64** | :smiley: success(*1) | :skull: failure(*2) 255 | **arm64** | :skull: failure(*3) | :smiley: success 256 | 257 | *1: failure with `x86_64 target process isn't supported by x86_64 process on ARM64 machine`. Tested on github actions. 258 | *2: failure with `arm64 target process isn't supported by x86_64 process.` 259 | *3: failure with `x86_64 target process isn't supported by arm64 process.` 260 | 261 | # Caveats 262 | 263 | **The following restrictions are only on Linux.** 264 | 265 | Injector doesn't work where `ptrace()` is disallowed. 266 | 267 | * Non-children processes (See [Caveat about `ptrace()`][]) 268 | * Docker containers on docker version < 19.03 or linux kernel version < 4.8. You need to pass [`--cap-add=SYS_PTRACE`](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities) 269 | to `docker run` to allow it in the environments. 270 | * Linux inside of UserLAnd (Android App) (See [here](https://github.com/kubo/injector/issues/17#issuecomment-1113990177)) 271 | 272 | Injector calls functions inside of a target process interrupted by `ptrace()`. 273 | If the target process is interrupted while holding a non-reentrant lock and 274 | injector calls a function requiring the same lock, the process stops forever. 275 | If the lock type is reentrant, the status guarded by the lock may become inconsistent. 276 | As far as I checked, `dlopen()` internally calls `malloc()` requiring non-reentrant 277 | locks. `dlopen()` also uses a reentrant lock to guard information about loaded files. 278 | 279 | On Linux x86_64 `injector_inject_in_cloned_thread` in place of `injector_inject` 280 | may be a solution of the locking issue. It calls `dlopen()` in a thread created by 281 | [`clone()`]. Note that no wonder there are unexpected pitfalls because some resources 282 | allocated in [`pthread_create()`] lack in the `clone()`-ed thread. Use it at 283 | your own risk. 284 | 285 | # License 286 | 287 | Files under [`include`][] and [`src`][] are licensed under LGPL 2.1 or later. 288 | Files under [`cmd`][] are licensed under GPL 2 or later. 289 | Files under [`util`][] are licensed under 2-clause BSD. 290 | 291 | [`linux-inject`]: https://github.com/gaffe23/linux-inject 292 | [Caveat about `ptrace()`]: https://github.com/gaffe23/linux-inject#caveat-about-ptrace 293 | [`inject`]: https://github.com/gaffe23/linux-inject#usage 294 | [`clone()`]: https://man7.org/linux/man-pages/man2/clone.2.html 295 | [`cmd`]: cmd 296 | [`include`]: include 297 | [`pthread_create()`]: https://man7.org/linux/man-pages/man3/pthread_create.3.html 298 | [`src`]: src 299 | [`util`]: util 300 | [`CreateRemoteThread+LoadLibrary`]: https://www.google.com/search?&q=CreateRemoteThread+LoadLIbrary 301 | [`disable SIP`]: https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection 302 | -------------------------------------------------------------------------------- /src/linux/injector.c: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2018-2023 Kubo Takehiro 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #ifndef _GNU_SOURCE 26 | #define _GNU_SOURCE 27 | #endif 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include "injector_internal.h" 42 | 43 | static inline size_t remote_mem_size(injector_t *injector) { 44 | return 2 * injector->data_size + injector->stack_size; 45 | } 46 | 47 | int injector_attach(injector_t **injector_out, pid_t pid) 48 | { 49 | injector_t *injector; 50 | int status; 51 | intptr_t retval; 52 | int prot; 53 | int rv = 0; 54 | 55 | injector__errmsg_is_set = 0; 56 | 57 | injector = calloc(1, sizeof(injector_t)); 58 | if (injector == NULL) { 59 | injector__set_errmsg("malloc error: %s", strerror(errno)); 60 | return INJERR_NO_MEMORY; 61 | } 62 | injector->pid = pid; 63 | rv = injector__attach_process(injector); 64 | if (rv != 0) { 65 | goto error_exit; 66 | } 67 | injector->attached = 1; 68 | 69 | do { 70 | rv = waitpid(pid, &status, 0); 71 | } while (rv == -1 && errno == EINTR); 72 | if (rv == -1) { 73 | injector__set_errmsg("waitpid error while attaching: %s", strerror(errno)); 74 | rv = INJERR_WAIT_TRACEE; 75 | goto error_exit; 76 | } 77 | 78 | rv = injector__collect_libc_information(injector); 79 | if (rv != 0) { 80 | goto error_exit; 81 | } 82 | rv = injector__get_regs(injector, &injector->regs); 83 | if (rv != 0) { 84 | goto error_exit; 85 | } 86 | rv = injector__read(injector, injector->code_addr, &injector->backup_code, sizeof(injector->backup_code)); 87 | if (rv != 0) { 88 | goto error_exit; 89 | } 90 | 91 | injector->data_size = sysconf(_SC_PAGESIZE); 92 | injector->stack_size = 2 * 1024 * 1024; 93 | 94 | rv = injector__call_syscall(injector, &retval, injector->sys_mmap, 0, 95 | remote_mem_size(injector), PROT_READ | PROT_WRITE, 96 | MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0); 97 | if (rv != 0) { 98 | goto error_exit; 99 | } 100 | if (retval == -1) { 101 | injector__set_errmsg("mmap error: %s", strerror(errno)); 102 | rv = INJERR_ERROR_IN_TARGET; 103 | goto error_exit; 104 | } 105 | injector->mmapped = 1; 106 | injector->data = (size_t)retval; 107 | injector->stack = (size_t)retval + 2 * injector->data_size; 108 | #ifdef INJECTOR_HAS_INJECT_IN_CLONED_THREAD 109 | injector->shellcode = (size_t)retval + 1 * injector->data_size; 110 | prot = PROT_READ | PROT_EXEC; 111 | #else 112 | prot = PROT_NONE; 113 | #endif 114 | rv = injector__call_syscall(injector, &retval, injector->sys_mprotect, 115 | injector->data + injector->data_size, injector->data_size, 116 | prot); 117 | if (rv != 0) { 118 | goto error_exit; 119 | } 120 | if (retval != 0) { 121 | injector__set_errmsg("mprotect error: %s", strerror(errno)); 122 | rv = INJERR_ERROR_IN_TARGET; 123 | goto error_exit; 124 | } 125 | #ifdef INJECTOR_HAS_INJECT_IN_CLONED_THREAD 126 | rv = injector__write(injector, injector->shellcode, &injector_shellcode, injector_shellcode_size); 127 | if (rv != 0) { 128 | return rv; 129 | } 130 | #endif 131 | 132 | *injector_out = injector; 133 | return 0; 134 | error_exit: 135 | injector_detach(injector); 136 | return rv; 137 | } 138 | 139 | int injector_inject(injector_t *injector, const char *path, void **handle) 140 | { 141 | char abspath[PATH_MAX]; 142 | int dlflags = RTLD_LAZY; 143 | size_t len; 144 | int rv; 145 | intptr_t retval; 146 | 147 | injector__errmsg_is_set = 0; 148 | 149 | if (path[0] == '/') { 150 | len = strlen(path) + 1; 151 | } else if (realpath(path, abspath) != NULL) { 152 | path = abspath; 153 | len = strlen(abspath) + 1; 154 | } else { 155 | injector__set_errmsg("failed to get the full path of '%s': %s", 156 | path, strerror(errno)); 157 | return INJERR_FILE_NOT_FOUND; 158 | } 159 | 160 | if (len > injector->data_size) { 161 | injector__set_errmsg("too long file path: %s", path); 162 | return INJERR_FILE_NOT_FOUND; 163 | } 164 | 165 | rv = injector__write(injector, injector->data, path, len); 166 | if (rv != 0) { 167 | return rv; 168 | } 169 | if (injector->dlfunc_type == DLFUNC_INTERNAL) { 170 | #define __RTLD_DLOPEN 0x80000000 // glibc internal flag 171 | dlflags |= __RTLD_DLOPEN; 172 | } 173 | rv = injector__call_function(injector, &retval, injector->dlopen_addr, injector->data, dlflags); 174 | if (rv != 0) { 175 | return rv; 176 | } 177 | if (retval == 0) { 178 | char buf[256 + 1] = {0,}; 179 | if (injector->dlerror_addr != 0) { 180 | rv = injector__call_function(injector, &retval, injector->dlerror_addr); 181 | if (rv == 0 && retval != 0) { 182 | injector__read(injector, retval, buf, sizeof(buf) - 1); 183 | } 184 | } 185 | if (buf[0] != '\0') { 186 | injector__set_errmsg("dlopen failed: %s", buf); 187 | } else { 188 | injector__set_errmsg("dlopen failed"); 189 | } 190 | return INJERR_ERROR_IN_TARGET; 191 | } 192 | if (handle != NULL) { 193 | *handle = (void*)retval; 194 | } 195 | return 0; 196 | } 197 | 198 | #ifdef INJECTOR_HAS_INJECT_IN_CLONED_THREAD 199 | int injector_inject_in_cloned_thread(injector_t *injector, const char *path, void **handle_out) 200 | { 201 | void *data; 202 | injector_shellcode_arg_t *arg; 203 | const size_t file_path_offset = offsetof(injector_shellcode_arg_t, file_path); 204 | void * const invalid_handle = (void*)-3; 205 | char abspath[PATH_MAX]; 206 | size_t pathlen; 207 | int rv; 208 | intptr_t retval; 209 | 210 | injector__errmsg_is_set = 0; 211 | 212 | if (injector->arch != ARCH_X86_64) { 213 | injector__set_errmsg("injector_inject_in_cloned_thread doesn't support %s.", 214 | injector__arch2name(injector->arch)); 215 | return INJERR_UNSUPPORTED_TARGET; 216 | } 217 | 218 | if (realpath(path, abspath) == NULL) { 219 | injector__set_errmsg("failed to get the full path of '%s': %s", 220 | path, strerror(errno)); 221 | return INJERR_FILE_NOT_FOUND; 222 | } 223 | pathlen = strlen(abspath) + 1; 224 | 225 | if (file_path_offset + pathlen > injector->data_size) { 226 | injector__set_errmsg("too long path name: %s", path); 227 | return INJERR_FILE_NOT_FOUND; 228 | } 229 | 230 | data = alloca(injector->data_size); 231 | memset(data, 0, injector->data_size); 232 | arg = (injector_shellcode_arg_t *)data; 233 | 234 | arg->handle = invalid_handle; 235 | arg->dlopen_addr = injector->dlopen_addr; 236 | arg->dlerror_addr = injector->dlerror_addr; 237 | arg->dlflags = RTLD_LAZY; 238 | if (injector->dlfunc_type == DLFUNC_INTERNAL) { 239 | arg->dlflags |= __RTLD_DLOPEN; 240 | } 241 | memcpy(arg->file_path, abspath, pathlen); 242 | 243 | rv = injector__write(injector, injector->data, data, injector->data_size); 244 | if (rv != 0) { 245 | return rv; 246 | } 247 | rv = injector__call_function(injector, &retval, injector->clone_addr, 248 | injector->shellcode, injector->stack + injector->stack_size - 4096, 249 | //CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, 250 | CLONE_VM, 251 | injector->data); 252 | if (rv != 0) { 253 | return rv; 254 | } 255 | if (retval == -1) { 256 | injector__set_errmsg("clone error: %s", strerror(errno)); 257 | return INJERR_ERROR_IN_TARGET; 258 | } 259 | const struct timespec ts = {0, 100000000}; /* 0.1 second */ 260 | void *handle; 261 | int cnt = 0; 262 | 263 | retry: 264 | nanosleep(&ts, NULL); 265 | rv = injector__read(injector, injector->data, &handle, sizeof(handle)); 266 | if (rv != 0) { 267 | return rv; 268 | } 269 | if (handle == invalid_handle) { 270 | int max_retyr_cnt = 50; 271 | if (++cnt <= max_retyr_cnt) { 272 | goto retry; 273 | } 274 | injector__set_errmsg("dlopen doesn't return in %d seconds.", max_retyr_cnt / 10); 275 | return INJERR_ERROR_IN_TARGET; 276 | } 277 | if (handle_out != NULL) { 278 | *handle_out = handle; 279 | } 280 | if (handle == NULL) { 281 | arg->file_path[0] = '\0'; 282 | injector__read(injector, injector->data, data, injector->data_size); 283 | if (arg->file_path[0] != '\0') { 284 | injector__set_errmsg("%s", arg->file_path); 285 | } else { 286 | injector__set_errmsg("dlopen error"); 287 | } 288 | return INJERR_ERROR_IN_TARGET; 289 | } 290 | return 0; 291 | } 292 | #endif 293 | 294 | int injector_remote_func_addr(injector_t *injector, void *handle, const char* name, size_t *func_addr_out) 295 | { 296 | int rv; 297 | intptr_t retval; 298 | size_t len = strlen(name) + 1; 299 | 300 | injector__errmsg_is_set = 0; 301 | 302 | if (len > injector->data_size) { 303 | injector__set_errmsg("too long function name: %s", name); 304 | return INJERR_FUNCTION_MISSING; 305 | } 306 | rv = injector__write(injector, injector->data, name, len); 307 | if (rv != 0) { 308 | return rv; 309 | } 310 | rv = injector__call_function(injector, &retval, injector->dlsym_addr, handle, injector->data); 311 | if (rv != 0) { 312 | return rv; 313 | } 314 | if (retval == 0) { 315 | injector__set_errmsg("function not found: %s", name); 316 | return INJERR_FUNCTION_MISSING; 317 | } 318 | *func_addr_out = (size_t)retval; 319 | return 0; 320 | } 321 | 322 | int injector_remote_call(injector_t *injector, intptr_t *retval, size_t func_addr, ...) 323 | { 324 | va_list ap; 325 | int rv; 326 | injector__errmsg_is_set = 0; 327 | va_start(ap, func_addr); 328 | rv = injector__call_function_va_list(injector, retval, func_addr, ap); 329 | va_end(ap); 330 | return rv; 331 | } 332 | 333 | int injector_remote_vcall(injector_t *injector, intptr_t *retval, size_t func_addr, va_list ap) 334 | { 335 | injector__errmsg_is_set = 0; 336 | return injector__call_function_va_list(injector, retval, func_addr, ap); 337 | } 338 | 339 | int injector_call(injector_t *injector, void *handle, const char* name) 340 | { 341 | size_t func_addr; 342 | int rv = injector_remote_func_addr(injector, handle, name, &func_addr); 343 | if (rv != 0) { 344 | return rv; 345 | } 346 | return injector__call_function(injector, NULL, func_addr); 347 | } 348 | 349 | int injector_uninject(injector_t *injector, void *handle) 350 | { 351 | int rv; 352 | intptr_t retval; 353 | 354 | injector__errmsg_is_set = 0; 355 | if (injector->libc_type == LIBC_TYPE_MUSL) { 356 | /* Assume that libc is musl. */ 357 | injector__set_errmsg("Cannot uninject libraries under musl libc. See: https://wiki.musl-libc.org/functional-differences-from-glibc.html#Unloading_libraries"); 358 | return INJERR_UNSUPPORTED_TARGET; 359 | } 360 | 361 | rv = injector__call_function(injector, &retval, injector->dlclose_addr, handle); 362 | if (rv != 0) { 363 | return rv; 364 | } 365 | if (retval != 0) { 366 | injector__set_errmsg("dlclose failed"); 367 | return INJERR_ERROR_IN_TARGET; 368 | } 369 | return 0; 370 | } 371 | 372 | int injector_detach(injector_t *injector) 373 | { 374 | injector__errmsg_is_set = 0; 375 | 376 | if (injector->mmapped) { 377 | injector__call_syscall(injector, NULL, injector->sys_munmap, injector->data, remote_mem_size(injector)); 378 | } 379 | if (injector->attached) { 380 | injector__detach_process(injector); 381 | } 382 | free(injector); 383 | return 0; 384 | } 385 | 386 | const char *injector_error(void) 387 | { 388 | return injector__errmsg; 389 | } 390 | -------------------------------------------------------------------------------- /LICENSE_GPL.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | -------------------------------------------------------------------------------- /tests/test-prog.c: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2018-2023 Kubo Takehiro 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License as 13 | * published by the Free Software Foundation; either version 2 of 14 | * the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #ifdef _WIN32 33 | #include 34 | #include 35 | #else 36 | #include 37 | #include 38 | #include 39 | #include 40 | #endif 41 | #include "../include/injector.h" 42 | 43 | #define INCR_ON_INJECTION 13 44 | #define INCR_ON_UNINJECTION 17 45 | 46 | #ifdef _WIN32 47 | #define EXEEXT ".exe" 48 | #define DLLEXT ".dll" 49 | static BOOL is_under_wine() { 50 | static enum { 51 | ST_UNKNOWN, 52 | ST_WINE, 53 | ST_NOT_WINE, 54 | } state = ST_UNKNOWN; 55 | if (state == ST_UNKNOWN) { 56 | if (GetProcAddress(GetModuleHandleA("ntdll.dll"), "wine_get_version")) { 57 | state = ST_WINE; 58 | } else { 59 | state = ST_NOT_WINE; 60 | } 61 | } 62 | return state == ST_WINE; 63 | } 64 | 65 | static const char *inject_errmsg() 66 | { 67 | if (is_under_wine()) { 68 | return "LoadLibrary in the target process failed: Module not found."; 69 | } else { 70 | return "LoadLibrary in the target process failed: The specified module could not be found."; 71 | } 72 | } 73 | #define INJECT_ERRMSG inject_errmsg() 74 | 75 | static const char *uninject_errmsg() 76 | { 77 | if (is_under_wine()) { 78 | return "FreeLibrary in the target process failed: Invalid handle."; 79 | } else { 80 | return "FreeLibrary in the target process failed: The specified module could not be found."; 81 | } 82 | } 83 | #define UNINJECT_ERRMSG uninject_errmsg() 84 | 85 | #elif __APPLE__ 86 | #define EXEEXT "" 87 | #define DLLEXT ".dylib" 88 | #define INJECT_ERRMSG "failed to get the full path of 'no such library': No such file or directory" 89 | #else 90 | #define EXEEXT "" 91 | #define DLLEXT ".so" 92 | #define INJECT_ERRMSG "failed to get the full path of 'no such library': No such file or directory" 93 | #endif 94 | 95 | typedef struct process process_t; 96 | 97 | static int process_start(process_t *proc, char *test_target); 98 | static int process_check_module(process_t *proc, const char *module_name, int startswith); 99 | static int process_wait(process_t *proc, int wait_secs); 100 | static void process_terminate(process_t *proc); 101 | 102 | #ifdef _WIN32 103 | 104 | #define sleep(secs) Sleep(1000 * (secs)) 105 | 106 | struct process { 107 | DWORD pid; 108 | HANDLE hProcess; 109 | }; 110 | 111 | static int process_start(process_t *proc, char *test_target) 112 | { 113 | STARTUPINFOA si = {sizeof(STARTUPINFOA),}; 114 | PROCESS_INFORMATION pi; 115 | 116 | if (!CreateProcessA(NULL, test_target, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { 117 | printf("ERROR: failed to create process: %s\n", test_target); 118 | return 1; 119 | } 120 | CloseHandle(pi.hThread); 121 | proc->pid = pi.dwProcessId; 122 | proc->hProcess = pi.hProcess; 123 | return 0; 124 | } 125 | 126 | static int process_check_module(process_t *proc, const char *module_name, int startswith) 127 | { 128 | HANDLE hSnapshot; 129 | MODULEENTRY32 me; 130 | BOOL ok; 131 | int len = strlen(module_name); 132 | 133 | do { 134 | hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, proc->pid); 135 | } while (hSnapshot == INVALID_HANDLE_VALUE && GetLastError() == ERROR_BAD_LENGTH); 136 | 137 | if (hSnapshot == INVALID_HANDLE_VALUE) { 138 | printf("CreateToolhelp32Snapshot error: %d\n", GetLastError()); 139 | return -1; 140 | } 141 | me.dwSize = sizeof(me); 142 | ok = Module32First(hSnapshot, &me); 143 | while (ok) { 144 | if ((startswith ? memicmp(me.szModule, module_name, len) : stricmp(me.szModule, module_name)) == 0) { 145 | CloseHandle(hSnapshot); 146 | return 0; 147 | } 148 | ok = Module32Next(hSnapshot, &me); 149 | } 150 | CloseHandle(hSnapshot); 151 | return 1; 152 | } 153 | 154 | static int process_wait(process_t *proc, int wait_secs) 155 | { 156 | DWORD code; 157 | int rv = 1; 158 | 159 | code = WaitForSingleObject(proc->hProcess, wait_secs * 1000); 160 | switch (code) { 161 | case WAIT_OBJECT_0: 162 | GetExitCodeProcess(proc->hProcess, &code); 163 | switch (code) { 164 | case INCR_ON_INJECTION + INCR_ON_UNINJECTION: 165 | printf("SUCCESS: The injected library changed the exit_value variable in the target process!\n"); 166 | rv = 0; 167 | break; 168 | case INCR_ON_INJECTION: 169 | printf("ERROR: The library was injected but not uninjected.\n"); 170 | break; 171 | case 0: 172 | printf("ERROR: The injected library didn't change the return value of target process!\n"); 173 | break; 174 | default: 175 | printf("ERROR: The target process exited with exit code %d.\n", code); 176 | break; 177 | } 178 | break; 179 | case WAIT_TIMEOUT: 180 | printf("ERROR: The target process didn't exit.\n"); 181 | break; 182 | defualt: 183 | printf("ERROR: WaitForSingleObject\n"); 184 | break; 185 | } 186 | return rv; 187 | } 188 | 189 | static void process_terminate(process_t *proc) 190 | { 191 | TerminateProcess(proc->hProcess, 256); 192 | CloseHandle(proc->hProcess); 193 | } 194 | 195 | #else 196 | 197 | struct process { 198 | pid_t pid; 199 | int waited; 200 | #ifdef __linux__ 201 | int is_musl; 202 | #endif 203 | }; 204 | 205 | static int process_start(process_t *proc, char *test_target) 206 | { 207 | proc->pid = fork(); 208 | proc->waited = 0; 209 | if (proc->pid == 0) { 210 | execl(test_target, test_target, NULL); 211 | exit(2); 212 | } 213 | return 0; 214 | } 215 | 216 | #ifdef __APPLE__ 217 | 218 | static int process_check_module(process_t *proc, const char *module_name, int startswith) 219 | { 220 | char buf[PATH_MAX]; 221 | size_t len = strlen(module_name); 222 | FILE *fp; 223 | 224 | sprintf(buf, "vmmap -w %i", proc->pid); 225 | fp = popen(buf, "r"); 226 | if (fp == NULL) { 227 | printf("Could not open pipe %s\n", buf); 228 | return -1; 229 | } 230 | while (fgets(buf, sizeof(buf), fp) != NULL) { 231 | char *p = strrchr(buf, '/'); 232 | if(p == NULL){ 233 | continue; 234 | } 235 | if (p != NULL && memcmp(p + 1, module_name, len) == 0 && (startswith || p[len + 1] == '\n')) { 236 | pclose(fp); 237 | return 0; 238 | } 239 | } 240 | pclose(fp); 241 | return 1; 242 | } 243 | 244 | static int process_wait(process_t *proc, int wait_secs) 245 | { 246 | int status; 247 | long start_sec = time(0); 248 | pid_t pid = proc->pid; 249 | do{ 250 | if(time(0)-start_sec>wait_secs){ 251 | break; 252 | } 253 | if ((pid = waitpid(pid, &status, WNOHANG)) == -1) 254 | { 255 | printf("wait() error\n"); 256 | } else if(pid != 0){ 257 | if (WIFEXITED(status)) { 258 | int exitcode = WEXITSTATUS(status); 259 | if (exitcode == INCR_ON_INJECTION + INCR_ON_UNINJECTION) { 260 | printf("SUCCESS: The injected library changed the exit_value variable in the target process!\n"); 261 | return 0; 262 | } else if (exitcode == INCR_ON_INJECTION) { 263 | printf("ERROR: The library was injected but not uninjected.\n"); 264 | return 1; 265 | } else if (exitcode == 0) { 266 | printf("ERROR: The injected library didn't change the return value of target process!\n"); 267 | return 1; 268 | } else { 269 | printf("ERROR: The target process exited with exit code %d.\n", exitcode); 270 | return 1; 271 | } 272 | } else if (WIFEXITED(status)) { 273 | int signo = WTERMSIG(status); 274 | printf("ERROR: The target process exited by signal %d.\n", signo); 275 | return 1; 276 | } else if (WIFSTOPPED(status)) { 277 | int signo = WSTOPSIG(status); 278 | printf("ERROR: The target process stopped by signal %d.\n", signo); 279 | return 1; 280 | } else { 281 | printf("ERROR: Unexpected waitpid status: 0x%x\n", status); 282 | return 1; 283 | } 284 | } 285 | } while (pid == 0); 286 | printf("ERROR: The target process didn't exit.\n"); 287 | return 1; 288 | } 289 | 290 | #else //linux 291 | static volatile sig_atomic_t caught_sigalarm; 292 | 293 | static void sighandler(int signo) 294 | { 295 | caught_sigalarm = 1; 296 | } 297 | static int process_check_module(process_t *proc, const char *module_name, int startswith) 298 | { 299 | char buf[PATH_MAX]; 300 | size_t len = strlen(module_name); 301 | FILE *fp; 302 | 303 | sprintf(buf, "/proc/%d/maps", proc->pid); 304 | fp = fopen(buf, "r"); 305 | if (fp == NULL) { 306 | printf("Could not open %s\n", buf); 307 | return -1; 308 | } 309 | while (fgets(buf, sizeof(buf), fp) != NULL) { 310 | char *p = strrchr(buf, '/'); 311 | if (p != NULL && memcmp(p + 1, module_name, len) == 0 && (startswith || p[len + 1] == '\n')) { 312 | fclose(fp); 313 | return 0; 314 | } 315 | } 316 | fclose(fp); 317 | return 1; 318 | } 319 | 320 | static int process_wait(process_t *proc, int wait_secs) 321 | { 322 | struct sigaction sigact; 323 | int status; 324 | 325 | memset(&sigact, 0, sizeof(sigact)); 326 | sigact.sa_handler = sighandler; 327 | sigaction(SIGALRM, &sigact, NULL); 328 | alarm(wait_secs); 329 | 330 | if (waitpid(proc->pid, &status, 0) == proc->pid) { 331 | proc->waited = 1; 332 | if (WIFEXITED(status)) { 333 | int exitcode = WEXITSTATUS(status); 334 | if (exitcode == INCR_ON_INJECTION + INCR_ON_UNINJECTION) { 335 | if (proc->is_musl) { 336 | printf("ERROR: the library was uninjected, which shouldn't be possible on musl.\n"); 337 | return 0; 338 | } else { 339 | printf("SUCCESS: The injected library changed the exit_value variable in the target process!\n"); 340 | return 0; 341 | } 342 | } else if (exitcode == INCR_ON_INJECTION) { 343 | if (proc->is_musl) { 344 | printf("SUCCESS: The injected library changed the exit_value variable in the target process!\n"); 345 | return 0; 346 | } else { 347 | printf("ERROR: The library was injected but not uninjected.\n"); 348 | return 1; 349 | } 350 | } else if (exitcode == 0) { 351 | printf("ERROR: The injected library didn't change the return value of target process!\n"); 352 | return 1; 353 | } else { 354 | printf("ERROR: The target process exited with exit code %d.\n", exitcode); 355 | return 1; 356 | } 357 | } else if (WIFEXITED(status)) { 358 | int signo = WTERMSIG(status); 359 | printf("ERROR: The target process exited by signal %d.\n", signo); 360 | return 1; 361 | } else if (WIFSTOPPED(status)) { 362 | int signo = WSTOPSIG(status); 363 | printf("ERROR: The target process stopped by signal %d.\n", signo); 364 | return 1; 365 | } else { 366 | printf("ERROR: Unexpected waitpid status: 0x%x\n", status); 367 | return 1; 368 | } 369 | } 370 | if (caught_sigalarm) { 371 | printf("ERROR: The target process didn't exit.\n"); 372 | } else { 373 | printf("ERROR: waitpid failed. (%s)\n", strerror(errno)); 374 | } 375 | return 1; 376 | } 377 | #endif 378 | static void process_terminate(process_t *proc) 379 | { 380 | int status; 381 | if (!proc->waited) { 382 | kill(proc->pid, SIGKILL); 383 | kill(proc->pid, SIGCONT); 384 | waitpid(proc->pid, &status, 0); 385 | } 386 | } 387 | 388 | #endif 389 | 390 | static int test_remote_call(injector_t *injector, void *handle) 391 | { 392 | #ifdef INJECTOR_HAS_REMOTE_CALL_FUNCS 393 | printf("test remote call.\n"); 394 | fflush(stdout); 395 | 396 | size_t func_addr; 397 | if (injector_remote_func_addr(injector, handle, "sum_integers", &func_addr) != 0) { 398 | printf("injector_remote_func_addr error:\n %s\n", injector_error()); 399 | return -1; 400 | } 401 | intptr_t retval; 402 | intptr_t args[6] = {1, 2, 3, 4, 5, 6}; 403 | int i; 404 | for (i = 0; i < 6; i++) { 405 | args[i] += 10; 406 | intptr_t expected_retval = args[0] + args[1] + args[2] + args[3] + args[4] + args[5]; 407 | if (injector_remote_call(injector, &retval, func_addr, args[0], args[1], args[2], args[3], args[4], args[5]) != 0) { 408 | printf("injector_remote_call error:\n %s\n", injector_error()); 409 | return -1; 410 | } 411 | if (retval != expected_retval) { 412 | printf("sum_integers(%" PRIdPTR ", %" PRIdPTR ", %" PRIdPTR ", %" PRIdPTR ", %" PRIdPTR ", %" PRIdPTR ") returns %" PRIdPTR " (expected %" PRIdPTR ")\n", 413 | args[0], args[1], args[2], args[3], args[4], args[5], retval, expected_retval); 414 | return -1; 415 | } 416 | } 417 | #endif 418 | return 0; 419 | } 420 | 421 | int main(int argc, char **argv) 422 | { 423 | char suffix[20] = {0,}; 424 | char test_target[64]; 425 | char test_library[64]; 426 | injector_t *injector; 427 | process_t proc; 428 | void *handle = NULL; 429 | int rv = 1; 430 | int loop_cnt; 431 | int can_uninject = 1; 432 | int (*inject_func)(injector_t *, const char *, void **) = injector_inject; 433 | int i; 434 | 435 | for (i = 1; i < argc; i++) { 436 | if (argv[i][0] == '-') { 437 | if (strcmp(argv[i], "--cloned-thread") == 0) { 438 | #ifdef INJECTOR_HAS_INJECT_IN_CLONED_THREAD 439 | inject_func = injector_inject_in_cloned_thread; 440 | #else 441 | fprintf(stderr, "injector_inject_in_cloned_thread isn't suported\n"); 442 | return 1; 443 | #endif 444 | } else { 445 | fprintf(stderr, "unknown option %s\n", argv[i]); 446 | return 1; 447 | } 448 | } else { 449 | snprintf(suffix, sizeof(suffix), "-%s", argv[1]); 450 | suffix[sizeof(suffix) - 1] = '\0'; 451 | } 452 | } 453 | 454 | snprintf(test_target, sizeof(test_target), "test-target%s" EXEEXT, suffix); 455 | snprintf(test_library, sizeof(test_library), "test-library%s" DLLEXT, suffix); 456 | 457 | if (process_start(&proc, test_target) != 0) { 458 | return 1; 459 | } 460 | printf("target process started.\n"); 461 | fflush(stdout); 462 | 463 | sleep(1); 464 | 465 | #ifdef __linux__ 466 | // Sadly this is not known at compile time, see https://www.openwall.com/lists/musl/2013/03/29/13 467 | proc.is_musl = process_check_module(&proc, "ld-musl-", 1) == 0; 468 | // In musl, dlclose doesn't do anything - see https://wiki.musl-libc.org/functional-differences-from-glibc.html 469 | if (proc.is_musl) { 470 | can_uninject = 0; 471 | } 472 | #endif 473 | 474 | for (loop_cnt = 0; loop_cnt < 2; loop_cnt++) { 475 | const char *errmsg; 476 | 477 | if (injector_attach(&injector, proc.pid) != 0) { 478 | printf("inject error:\n %s\n", injector_error()); 479 | goto cleanup; 480 | } 481 | printf("attached.\n"); 482 | fflush(stdout); 483 | 484 | if (loop_cnt == 0) { 485 | if (inject_func(injector, test_library, &handle) != 0) { 486 | printf("inject error:\n %s\n", injector_error()); 487 | goto cleanup; 488 | } 489 | printf("injected. (handle=%p)\n", handle); 490 | fflush(stdout); 491 | 492 | if (inject_func(injector, "no such library", &handle) == 0) { 493 | printf("injection should fail but succeeded:\n"); 494 | goto cleanup; 495 | } 496 | errmsg = injector_error(); 497 | if (strncmp(errmsg, INJECT_ERRMSG, strlen(INJECT_ERRMSG)) != 0) { 498 | printf("unexpected injection error message: %s\nexpected: %s\n", errmsg, INJECT_ERRMSG); 499 | goto cleanup; 500 | } 501 | if (test_remote_call(injector, handle) != 0) { 502 | goto cleanup; 503 | } 504 | #ifdef __linux__ 505 | } else if (proc.is_musl) { 506 | int err = injector_uninject(injector, handle); 507 | if (err != INJERR_UNSUPPORTED_TARGET) { 508 | printf("uninject returns unexpected value: %d\n", err); 509 | goto cleanup; 510 | } 511 | #endif 512 | } else { 513 | if (injector_uninject(injector, handle) != 0) { 514 | printf("uninject error:\n %s\n", injector_error()); 515 | goto cleanup; 516 | } 517 | printf("uninjected.\n"); 518 | fflush(stdout); 519 | 520 | #ifdef _WIN32 521 | if (injector_uninject(injector, NULL) == 0) { 522 | printf("uninjection should fail but succeeded:\n"); 523 | goto cleanup; 524 | } 525 | errmsg = injector_error(); 526 | if (strcmp(errmsg, UNINJECT_ERRMSG) != 0) { 527 | printf("unexpected uninjection error message: %s\nexpected: %s\n", errmsg, UNINJECT_ERRMSG); 528 | goto cleanup; 529 | } 530 | #endif 531 | } 532 | 533 | if (injector_detach(injector) != 0) { 534 | printf("inject error:\n %s\n", injector_error()); 535 | goto cleanup; 536 | } 537 | printf("detached.\n"); 538 | fflush(stdout); 539 | 540 | if (can_uninject && process_check_module(&proc, test_library, 0) != loop_cnt) { 541 | if (loop_cnt == 0) { 542 | printf("%s wasn't found after injection\n", test_library); 543 | } else { 544 | printf("%s was found after uninjection\n", test_library); 545 | } 546 | goto cleanup; 547 | } 548 | } 549 | 550 | rv = process_wait(&proc, 8); 551 | cleanup: 552 | process_terminate(&proc); 553 | return rv; 554 | } 555 | 556 | -------------------------------------------------------------------------------- /src/linux/elf.c: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * injector - Library for injecting a shared library into a Linux process 4 | * 5 | * URL: https://github.com/kubo/injector 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright (C) 2018 Kubo Takehiro 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this library; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | */ 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include "injector_internal.h" 38 | 39 | #ifdef __LP64__ 40 | #define Elf_Ehdr Elf64_Ehdr 41 | #define Elf_Shdr Elf64_Shdr 42 | #define Elf_Sym Elf64_Sym 43 | #else 44 | #define Elf_Ehdr Elf32_Ehdr 45 | #define Elf_Shdr Elf32_Shdr 46 | #define Elf_Sym Elf32_Sym 47 | #endif 48 | 49 | // #define INJECTOR_DEBUG_ELF_C 1 50 | 51 | #ifdef INJECTOR_DEBUG_ELF_C 52 | #undef DEBUG 53 | #define DEBUG(...) fprintf(stderr, __VA_ARGS__) 54 | #else 55 | #undef DEBUG 56 | #define DEBUG(...) do {} while(0) 57 | #endif 58 | 59 | typedef struct { 60 | int dlfunc_type; /* -1, DLFUNC_POSIX or DLFUNC_INTERNAL */ 61 | FILE *fp; 62 | size_t libc_addr; 63 | size_t str_offset; 64 | size_t str_size; 65 | size_t sym_offset; 66 | size_t sym_num; 67 | size_t sym_entsize; 68 | } param_t; 69 | 70 | static int search_and_open_libc(FILE **fp_out, pid_t pid, size_t *addr, libc_type_t *libc_type); 71 | static int open_libc(FILE **fp_out, const char *path, pid_t pid, dev_t dev, ino_t ino); 72 | static FILE *fopen_with_ino(const char *path, dev_t dev, ino_t ino); 73 | static int read_elf_ehdr(FILE *fp, Elf_Ehdr *ehdr); 74 | static int read_elf_shdr(FILE *fp, Elf_Shdr *shdr, size_t shdr_size); 75 | static int read_elf_sym(FILE *fp, Elf_Sym *sym, size_t sym_size); 76 | static int find_symbol_addr(size_t *addr, param_t *prm, const char *posix_name, const char *internal_name); 77 | static size_t find_strtab_offset(const param_t *prm, const char *name); 78 | 79 | int injector__collect_libc_information(injector_t *injector) 80 | { 81 | pid_t pid = injector->pid; 82 | FILE *fp; 83 | Elf_Ehdr ehdr; 84 | Elf_Shdr shdr; 85 | param_t prm = {-1, }; 86 | size_t shstrtab_offset; 87 | int idx; 88 | int rv; 89 | 90 | rv = search_and_open_libc(&fp, pid, &prm.libc_addr, &injector->libc_type); 91 | if (rv != 0) { 92 | return rv; 93 | } 94 | rv = read_elf_ehdr(fp, &ehdr); 95 | if (rv != 0) { 96 | goto cleanup; 97 | } 98 | fseek(fp, ehdr.e_shoff + ehdr.e_shstrndx * ehdr.e_shentsize, SEEK_SET); 99 | rv = read_elf_shdr(fp, &shdr, ehdr.e_shentsize); 100 | if (rv != 0) { 101 | goto cleanup; 102 | } 103 | shstrtab_offset = shdr.sh_offset; 104 | 105 | fseek(fp, ehdr.e_shoff, SEEK_SET); 106 | for (idx = 0; idx < ehdr.e_shnum; idx++) { 107 | fpos_t pos; 108 | char buf[8]; 109 | 110 | rv = read_elf_shdr(fp, &shdr, ehdr.e_shentsize); 111 | if (rv != 0) { 112 | goto cleanup; 113 | } 114 | switch (shdr.sh_type) { 115 | case SHT_STRTAB: 116 | fgetpos(fp, &pos); 117 | fseek(fp, shstrtab_offset + shdr.sh_name, SEEK_SET); 118 | fgets(buf, sizeof(buf), fp); 119 | fsetpos(fp, &pos); 120 | if (strcmp(buf, ".dynstr") == 0) { 121 | prm.str_offset = shdr.sh_offset; 122 | prm.str_size = shdr.sh_size; 123 | } 124 | break; 125 | case SHT_DYNSYM: 126 | fgetpos(fp, &pos); 127 | fseek(fp, shstrtab_offset + shdr.sh_name, SEEK_SET); 128 | fgets(buf, sizeof(buf), fp); 129 | fsetpos(fp, &pos); 130 | if (strcmp(buf, ".dynsym") == 0) { 131 | prm.sym_offset = shdr.sh_offset; 132 | prm.sym_entsize = shdr.sh_entsize; 133 | prm.sym_num = shdr.sh_size / shdr.sh_entsize; 134 | } 135 | break; 136 | } 137 | if (prm.sym_offset != 0 && prm.str_offset != 0) { 138 | break; 139 | } 140 | } 141 | if (idx == ehdr.e_shnum) { 142 | injector__set_errmsg("failed to find the .dynstr and .dynsym sections."); 143 | rv = INJERR_INVALID_ELF_FORMAT; 144 | goto cleanup; 145 | } 146 | 147 | prm.fp = fp; 148 | 149 | rv = find_symbol_addr(&injector->dlopen_addr, &prm, "dlopen", "__libc_dlopen_mode"); 150 | if (rv != 0) { 151 | goto cleanup; 152 | } 153 | 154 | rv = find_symbol_addr(&injector->dlclose_addr, &prm, "dlclose", "__libc_dlclose"); 155 | if (rv != 0) { 156 | goto cleanup; 157 | } 158 | 159 | rv = find_symbol_addr(&injector->dlsym_addr, &prm, "dlsym", "__libc_dlsym"); 160 | if (rv != 0) { 161 | goto cleanup; 162 | } 163 | 164 | if (prm.dlfunc_type != DLFUNC_INTERNAL) { 165 | rv = find_symbol_addr(&injector->dlerror_addr, &prm, "dlerror", NULL); 166 | if (rv != 0) { 167 | goto cleanup; 168 | } 169 | } else { 170 | injector->dlerror_addr = 0; 171 | } 172 | 173 | #ifdef INJECTOR_HAS_INJECT_IN_CLONED_THREAD 174 | rv = find_symbol_addr(&injector->clone_addr, &prm, "clone", "clone"); 175 | if (rv != 0) { 176 | goto cleanup; 177 | } 178 | #endif 179 | 180 | rv = find_symbol_addr(NULL, &prm, "gnu_get_libc_release", "gnu_get_libc_release"); 181 | if (rv == 0) { 182 | /* GNU libc */ 183 | injector->libc_type = LIBC_TYPE_GNU; 184 | } 185 | 186 | injector->dlfunc_type = prm.dlfunc_type; 187 | injector->code_addr = prm.libc_addr + ehdr.e_entry; 188 | 189 | switch (ehdr.e_machine) { 190 | case EM_X86_64: 191 | if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) { 192 | /* LP64 */ 193 | injector->arch = ARCH_X86_64; 194 | injector->sys_mmap = 9; 195 | injector->sys_mprotect = 10; 196 | injector->sys_munmap = 11; 197 | } else { 198 | /* ILP32 */ 199 | injector->arch = ARCH_X86_64_X32; 200 | injector->sys_mmap = 0x40000000 + 9; 201 | injector->sys_mprotect = 0x40000000 + 10; 202 | injector->sys_munmap = 0x40000000 + 11; 203 | } 204 | break; 205 | case EM_386: 206 | injector->arch = ARCH_I386; 207 | injector->sys_mmap = 192; 208 | injector->sys_mprotect = 125; 209 | injector->sys_munmap = 91; 210 | break; 211 | case EM_AARCH64: 212 | injector->arch = ARCH_ARM64; 213 | injector->sys_mmap = 222; 214 | injector->sys_mprotect = 226; 215 | injector->sys_munmap = 215; 216 | break; 217 | case EM_ARM: 218 | if (EF_ARM_EABI_VERSION(ehdr.e_flags) == 0) { 219 | injector__set_errmsg("ARM OABI target process isn't supported."); 220 | rv = INJERR_UNSUPPORTED_TARGET; 221 | goto cleanup; 222 | } 223 | if (injector->code_addr & 1u) { 224 | injector->code_addr &= ~1u; 225 | injector->arch = ARCH_ARM_EABI_THUMB; 226 | } else { 227 | injector->arch = ARCH_ARM_EABI; 228 | } 229 | injector->sys_mmap = 192; 230 | injector->sys_mprotect = 125; 231 | injector->sys_munmap = 91; 232 | break; 233 | case EM_MIPS: 234 | if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) { 235 | /* MIPS 64 */ 236 | injector->arch = ARCH_MIPS_64; 237 | injector->sys_mmap = 5000 + 9; 238 | injector->sys_mprotect = 5000 + 10; 239 | injector->sys_munmap = 5000 + 11; 240 | } else if (ehdr.e_flags & EF_MIPS_ABI2) { 241 | /* MIPS N32 */ 242 | injector->arch = ARCH_MIPS_N32; 243 | injector->sys_mmap = 6000 + 9; 244 | injector->sys_mprotect = 6000 + 10; 245 | injector->sys_munmap = 6000 + 11; 246 | } else { 247 | /* MIPS O32 */ 248 | injector->arch = ARCH_MIPS_O32; 249 | injector->sys_mmap = 4000 + 90; 250 | injector->sys_mprotect = 4000 + 125; 251 | injector->sys_munmap = 4000 + 91; 252 | } 253 | break; 254 | case EM_PPC64: 255 | injector->arch = ARCH_POWERPC_64; 256 | injector->sys_mmap = 90; 257 | injector->sys_mprotect = 125; 258 | injector->sys_munmap = 91; 259 | break; 260 | case EM_PPC: 261 | injector->arch = ARCH_POWERPC; 262 | injector->sys_mmap = 90; 263 | injector->sys_mprotect = 125; 264 | injector->sys_munmap = 91; 265 | break; 266 | #ifdef EM_RISCV 267 | case EM_RISCV: 268 | if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) { 269 | injector->arch = ARCH_RISCV_64; 270 | } else { 271 | injector->arch = ARCH_RISCV_32; 272 | } 273 | injector->sys_mmap = 222; 274 | injector->sys_mprotect = 226; 275 | injector->sys_munmap = 215; 276 | break; 277 | #endif 278 | default: 279 | injector__set_errmsg("Unknown target process architecture: 0x%04x", ehdr.e_machine); 280 | rv = INJERR_UNSUPPORTED_TARGET; 281 | goto cleanup; 282 | } 283 | rv = 0; 284 | cleanup: 285 | fclose(fp); 286 | return rv; 287 | } 288 | 289 | static int search_and_open_libc(FILE **fp_out, pid_t pid, size_t *addr, libc_type_t *libc_type) 290 | { 291 | char buf[512]; 292 | FILE *fp = NULL; 293 | regex_t reg; 294 | regmatch_t match; 295 | 296 | sprintf(buf, "/proc/%d/maps", pid); 297 | fp = fopen(buf, "r"); 298 | if (fp == NULL) { 299 | injector__set_errmsg("failed to open %s. (error: %s)", buf, strerror(errno)); 300 | return INJERR_OTHER; 301 | } 302 | DEBUG("Open %s\n", buf); 303 | /* /libc.so.6 or /libc-2.{DIGITS}.so or /ld-musl-{arch}.so.1 */ 304 | if (regcomp(®, "/libc(\\.so\\.6|-2\\.[0-9]+\\.so)|/ld-musl-.+?\\.so\\.1", REG_EXTENDED) != 0) { 305 | injector__set_errmsg("regcomp failed!"); 306 | return INJERR_OTHER; 307 | } 308 | while (fgets(buf, sizeof(buf), fp) != NULL) { 309 | unsigned long saddr, eaddr; 310 | unsigned long long offset, inode; 311 | unsigned int dev_major, dev_minor; 312 | DEBUG(" %s", buf); 313 | if (sscanf(buf, "%lx-%lx %*s %llx %x:%x %llu", &saddr, &eaddr, &offset, &dev_major, &dev_minor, &inode) != 6) { 314 | continue; 315 | } 316 | if (offset != 0) { 317 | continue; 318 | } 319 | if (regexec(®, buf, 1, &match, 0) != 0) { 320 | continue; 321 | } 322 | char *p = buf + match.rm_eo; 323 | if (strcmp(p, " (deleted)\n") == 0) { 324 | injector__set_errmsg("The C library when the process started was removed"); 325 | fclose(fp); 326 | regfree(®); 327 | return INJERR_NO_LIBRARY; 328 | } 329 | if (strcmp(p, "\n") != 0) { 330 | continue; 331 | } 332 | fclose(fp); 333 | *addr = saddr; 334 | if (strstr(buf, "/ld-musl-") != NULL) { 335 | *libc_type = LIBC_TYPE_MUSL; 336 | } else { 337 | *libc_type = LIBC_TYPE_GNU; 338 | } 339 | regfree(®); 340 | *p = '\0'; 341 | p = strchr(buf, '/'); 342 | DEBUG(" libc in /proc/PID/maps: '%s'\n", p); 343 | return open_libc(fp_out, p, pid, makedev(dev_major, dev_minor), inode); 344 | } 345 | fclose(fp); 346 | injector__set_errmsg("Could not find libc"); 347 | regfree(®); 348 | return INJERR_NO_LIBRARY; 349 | } 350 | 351 | static int open_libc(FILE **fp_out, const char *path, pid_t pid, dev_t dev, ino_t ino) 352 | { 353 | FILE *fp = fopen_with_ino(path, dev, ino); 354 | 355 | if (fp != NULL) { 356 | goto found; 357 | } 358 | 359 | /* workaround for LXD */ 360 | const char *p = strstr(path, "/rootfs/"); 361 | if (p != NULL) { 362 | fp = fopen_with_ino(p + 7, dev, ino); 363 | if (fp != NULL) { 364 | goto found; 365 | } 366 | } 367 | 368 | // workaround for Flatpak (https://flatpak.org/) 369 | // 370 | // libc is under /proc//root. 371 | // The idea came from https://github.com/kubo/injector/pull/36. 372 | char buf[PATH_MAX]; 373 | snprintf(buf, sizeof(buf), "/proc/%d/root%s", pid, path); 374 | buf[sizeof(buf) - 1] = '\0'; 375 | fp = fopen_with_ino(buf, dev, ino); 376 | if (fp != NULL) { 377 | goto found; 378 | } 379 | 380 | // workaround for Snap 381 | // 382 | // libc is in a base snap (https://snapcraft.io/docs/base-snaps), 383 | glob_t globbuf; 384 | if (glob("/snap/core*/*", GLOB_NOSORT, NULL, &globbuf) == 0) { 385 | size_t idx; 386 | for (idx = 0; idx < globbuf.gl_pathc; idx++) { 387 | char buf[512]; 388 | snprintf(buf, sizeof(buf), "%s%s", globbuf.gl_pathv[idx], path); 389 | buf[sizeof(buf) - 1] = '\0'; 390 | fp = fopen_with_ino(buf, dev, ino); 391 | if (fp != NULL) { 392 | globfree(&globbuf); 393 | goto found; 394 | } 395 | } 396 | globfree(&globbuf); 397 | } 398 | injector__set_errmsg("failed to open %s. (dev:0x%" PRIx64 ", ino:%lu)", path, dev, ino); 399 | return INJERR_NO_LIBRARY; 400 | found: 401 | *fp_out = fp; 402 | return 0; 403 | } 404 | 405 | static inline int is_on_overlay_fs(int fd) 406 | { 407 | struct statfs sbuf; 408 | if (fstatfs(fd, &sbuf) != 0) { 409 | DEBUG(" fstatfs() error %s\n", strerror(errno)); 410 | return -1; 411 | } 412 | #ifndef OVERLAYFS_SUPER_MAGIC 413 | #define OVERLAYFS_SUPER_MAGIC 0x794c7630 414 | #endif 415 | return (sbuf.f_type == OVERLAYFS_SUPER_MAGIC) ? 1 : 0; 416 | } 417 | 418 | static FILE *fopen_with_ino(const char *path, dev_t dev, ino_t ino) 419 | { 420 | DEBUG(" checking: '%s' ...", path); 421 | struct stat sbuf; 422 | FILE *fp = fopen(path, "r"); 423 | 424 | if (fp == NULL) { 425 | DEBUG(" fopen() error %s\n", strerror(errno)); 426 | return NULL; 427 | } 428 | 429 | if (fstat(fileno(fp), &sbuf) != 0) { 430 | DEBUG(" fstat() error %s\n", strerror(errno)); 431 | goto cleanup; 432 | } 433 | if (sbuf.st_ino != ino) { 434 | DEBUG(" unexpected inode number: expected %llu but %llu\n", 435 | (unsigned long long)ino, (unsigned long long)sbuf.st_ino); 436 | goto cleanup; 437 | } 438 | if (sbuf.st_dev != dev) { 439 | int rv = is_on_overlay_fs(fileno(fp)); 440 | if (rv < 0) { 441 | goto cleanup; 442 | } 443 | if (rv != 1) { 444 | DEBUG(" unexpected device number: expected %llu but %llu\n", 445 | (unsigned long long)dev, (unsigned long long)sbuf.st_dev); 446 | goto cleanup; 447 | } 448 | DEBUG(" ignore device number mismatch (expected %llu but %llu) on overlay file system ... ", 449 | (unsigned long long)dev, (unsigned long long)sbuf.st_dev); 450 | } 451 | 452 | DEBUG(" OK\n"); 453 | return fp; 454 | cleanup: 455 | fclose(fp); 456 | return NULL; 457 | } 458 | 459 | static int read_elf_ehdr(FILE *fp, Elf_Ehdr *ehdr) 460 | { 461 | if (fread(ehdr, sizeof(*ehdr), 1, fp) != 1) { 462 | injector__set_errmsg("failed to read ELF header. (error: %s)", strerror(errno)); 463 | return INJERR_INVALID_ELF_FORMAT; 464 | } 465 | if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) { 466 | injector__set_errmsg("Invalid ELF header: 0x%02x,0x%02x,0x%02x,0x%02x", 467 | ehdr->e_ident[0], ehdr->e_ident[1], ehdr->e_ident[2], ehdr->e_ident[3]); 468 | return INJERR_INVALID_ELF_FORMAT; 469 | } 470 | switch (ehdr->e_ident[EI_CLASS]) { 471 | case ELFCLASS32: 472 | #ifdef __LP64__ 473 | { 474 | Elf32_Ehdr *ehdr32 = (Elf32_Ehdr *)ehdr; 475 | /* copy from last */ 476 | ehdr->e_shstrndx = ehdr32->e_shstrndx; 477 | ehdr->e_shnum = ehdr32->e_shnum; 478 | ehdr->e_shentsize = ehdr32->e_shentsize; 479 | ehdr->e_phnum = ehdr32->e_phnum; 480 | ehdr->e_phentsize = ehdr32->e_phentsize; 481 | ehdr->e_ehsize = ehdr32->e_ehsize; 482 | ehdr->e_flags = ehdr32->e_flags; 483 | ehdr->e_shoff = ehdr32->e_shoff; 484 | ehdr->e_phoff = ehdr32->e_phoff; 485 | ehdr->e_entry = ehdr32->e_entry; 486 | ehdr->e_version = ehdr32->e_version; 487 | ehdr->e_machine = ehdr32->e_machine; 488 | ehdr->e_type = ehdr32->e_type; 489 | } 490 | #endif 491 | break; 492 | case ELFCLASS64: 493 | #ifndef __LP64__ 494 | injector__set_errmsg("64-bit target process isn't supported by 32-bit process."); 495 | return INJERR_UNSUPPORTED_TARGET; 496 | #endif 497 | break; 498 | default: 499 | injector__set_errmsg("Invalid ELF class: 0x%x", ehdr->e_ident[EI_CLASS]); 500 | return INJERR_UNSUPPORTED_TARGET; 501 | } 502 | return 0; 503 | } 504 | 505 | static int read_elf_shdr(FILE *fp, Elf_Shdr *shdr, size_t shdr_size) 506 | { 507 | if (fread(shdr, shdr_size, 1, fp) != 1) { 508 | injector__set_errmsg("failed to read a section header. (error: %s)", strerror(errno)); 509 | return INJERR_INVALID_ELF_FORMAT; 510 | } 511 | #ifdef __LP64__ 512 | if (shdr_size == sizeof(Elf32_Shdr)) { 513 | Elf32_Shdr shdr32 = *(Elf32_Shdr *)shdr; 514 | shdr->sh_name = shdr32.sh_name; 515 | shdr->sh_type = shdr32.sh_type; 516 | shdr->sh_flags = shdr32.sh_flags; 517 | shdr->sh_addr = shdr32.sh_addr; 518 | shdr->sh_offset = shdr32.sh_offset; 519 | shdr->sh_size = shdr32.sh_size; 520 | shdr->sh_link = shdr32.sh_link; 521 | shdr->sh_info = shdr32.sh_info; 522 | shdr->sh_addralign = shdr32.sh_addralign; 523 | shdr->sh_entsize = shdr32.sh_entsize; 524 | } 525 | #endif 526 | return 0; 527 | } 528 | 529 | static int read_elf_sym(FILE *fp, Elf_Sym *sym, size_t sym_size) 530 | { 531 | if (fread(sym, sym_size, 1, fp) != 1) { 532 | injector__set_errmsg("failed to read a symbol table entry. (error: %s)", strerror(errno)); 533 | return INJERR_INVALID_ELF_FORMAT; 534 | } 535 | #ifdef __LP64__ 536 | if (sym_size == sizeof(Elf32_Sym)) { 537 | Elf32_Sym sym32 = *(Elf32_Sym *)sym; 538 | sym->st_name = sym32.st_name; 539 | sym->st_value = sym32.st_value; 540 | sym->st_size = sym32.st_size; 541 | sym->st_info = sym32.st_info; 542 | sym->st_other = sym32.st_other; 543 | sym->st_shndx = sym32.st_shndx; 544 | } 545 | #endif 546 | return 0; 547 | } 548 | 549 | static int find_symbol_addr(size_t *addr, param_t *prm, const char *posix_name, const char *internal_name) 550 | { 551 | size_t st_name; 552 | 553 | switch (prm->dlfunc_type) { 554 | case -1: 555 | st_name = find_strtab_offset(prm, posix_name); 556 | if (st_name != 0) { 557 | prm->dlfunc_type = DLFUNC_POSIX; 558 | } else { 559 | prm->dlfunc_type = DLFUNC_INTERNAL; 560 | st_name = find_strtab_offset(prm, internal_name); 561 | } 562 | break; 563 | case DLFUNC_POSIX: 564 | st_name = find_strtab_offset(prm, posix_name); 565 | break; 566 | case DLFUNC_INTERNAL: 567 | st_name = find_strtab_offset(prm, internal_name); 568 | break; 569 | } 570 | 571 | if (addr == NULL) { 572 | return st_name != 0 ? 0 : INJERR_NO_FUNCTION; 573 | } 574 | 575 | if (st_name != 0) { 576 | Elf_Sym sym; 577 | int idx; 578 | int rv; 579 | 580 | fseek(prm->fp, prm->sym_offset, SEEK_SET); 581 | for (idx = 0; idx < prm->sym_num; idx++) { 582 | rv = read_elf_sym(prm->fp, &sym, prm->sym_entsize); 583 | if (rv != 0) { 584 | return rv; 585 | } 586 | if (sym.st_name == st_name) { 587 | *addr = prm->libc_addr + sym.st_value; 588 | return 0; 589 | } 590 | } 591 | } 592 | injector__set_errmsg("failed to find %s%s%s in the .dynstr section.", 593 | posix_name, internal_name ? "/" : "", 594 | internal_name ? internal_name : ""); 595 | return INJERR_NO_FUNCTION; 596 | } 597 | 598 | static size_t find_strtab_offset(const param_t *prm, const char *name) 599 | { 600 | size_t off; 601 | size_t idx = 0; 602 | 603 | fseek(prm->fp, prm->str_offset, SEEK_SET); 604 | for (off = 0; off < prm->str_size; off++) { 605 | int c = fgetc(prm->fp); 606 | if (c == EOF) { 607 | return 0; 608 | } 609 | if (c == name[idx]) { 610 | if (c == 0) { 611 | return off - idx; 612 | } 613 | idx++; 614 | } else { 615 | idx = 0; 616 | } 617 | } 618 | return 0; 619 | } 620 | --------------------------------------------------------------------------------