├── .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 | [](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 |
--------------------------------------------------------------------------------