iOS ARM64 系统调用
iOS ARM64 Syscalls
我正在学习更多关于 shellcode 和在 iOS 设备上使用 arm64 进行系统调用的知识。我正在测试的设备是 iPhone 6S.
我从这个 link (https://github.com/radare/radare2/blob/master/libr/include/sflib/darwin-arm-64/ios-syscalls.txt) 获得了系统调用列表。
我从这里了解到 x8 用于放置 arm64 的系统调用编号 (http://arm.ninja/2016/03/07/decoding-syscalls-in-arm64/)。
我认为用于传递arm64参数的各种寄存器应该与arm相同所以我参考了这个link(https://w3challs.com/syscalls/?arch=arm_strong), taken from https://azeria-labs.com/writing-arm-shellcode/。
我在 Xcode 中编写了内联汇编,这里有一些片段
//exit syscall
__asm__ volatile("mov x8, #1");
__asm__ volatile("mov x0, #0");
__asm__ volatile("svc 0x80");
但是,当我跨过这些代码时,应用程序并没有终止。
char write_buffer[]="console_text";
int write_buffer_size = sizeof(write_buffer);
__asm__ volatile("mov x8,#4;" //arm64 uses x8 for syscall number
"mov x0,#1;" //1 for stdout file descriptor
"mov x1,%0;" //the buffer to display
"mov x2,%1;" //buffer size
"svc 0x80;"
:
:"r"(write_buffer),"r"(write_buffer_size)
:"x0","x1","x2","x8"
);
如果此系统调用有效,它应该会在 Xcode 的控制台输出屏幕中打印出一些文本。但是,没有打印任何内容。
关于 ARM 汇编的在线文章很多,有些使用 svc 0x80
,有些使用 svc 0
等,因此可能会有一些变化。我尝试了各种方法,但无法让这两个代码片段起作用。
有人可以提供一些指导吗?
编辑:
这是我编写 C 函数系统调用时 Xcode 在其汇编视图中显示的内容 int return_value=syscall(1,0);
mov x1, sp
mov x30, #0
str x30, [x1]
orr w8, wzr, #0x1
stur x0, [x29, #-32] ; 8-byte Folded Spill
mov x0, x8
bl _syscall
我不确定为什么会发出此代码。
用于系统调用的寄存器完全是任意的,您选择的资源对于 XNU 来说肯定是错误的。
据我所知,用于 arm64 的 XNU 系统调用 ABI 是完全私有的,如有更改,恕不另行通知,因此它没有遵循任何已发布的标准,但您可以通过获取一份XNU 源代码(如 tarballs, or viewing it online,如果你愿意的话),grep 用于 handle_svc
函数,然后紧跟代码。
我不会详细说明您究竟在哪里找到哪些位,但最终结果是:
- 传递给
svc
的立即数被忽略,但标准库使用svc 0x80
。
x16
保存系统调用号
x0
到 x8
最多包含 9 个参数*
- 堆栈中没有参数
x0
和 x1
最多保存 2 个 return 值(例如 fork
)
- 进位位用于报错,此时
x0
保存错误码
* 这仅在具有 8 个参数的间接系统调用 (x16 = 0
) 的情况下使用。
* XNU 源代码中的评论也提到 x9
,但似乎编写该代码的工程师应该重温 off-by-one 错误。
然后是可用的实际系统调用编号:
- "UNIX syscalls" 的规范来源是 XNU 源代码树中的文件
bsd/kern/syscalls.master
。在最新的 iOS 13 beta 中,这些系统调用编号从 0
到大约 540
。
- "Mach syscalls" 的规范来源是 XNU 源代码树中的文件
osfmk/kern/syscall_sw.c
。这些系统调用使用 -10
和 -100
之间的 negative 数字调用(例如 -28
将是 task_self_trap
)。
- 与最后一点无关,两个系统调用
mach_absolute_time
和 mach_continuous_time
可以分别使用系统调用号 -3
和 -4
调用。
- 一些 low-level 操作可以通过
platform_syscall
使用系统调用号 0x80000000
。
这应该让你继续。正如@Siguza 提到的,您必须使用 x16
,而不是 x8
作为系统调用编号。
#import <sys/syscall.h>
char testStringGlobal[] = "helloWorld from global variable\n";
int main(int argc, char * argv[]) {
char testStringOnStack[] = "helloWorld from stack variable\n";
#if TARGET_CPU_ARM64
//VARIANT 1 suggested by @PeterCordes
//an an input it's a file descriptor set to STD_OUT 1 so the syscall write output appears in Xcode debug output
//as an output this will be used for returning syscall return value;
register long x0 asm("x0") = 1;
//as an input string to write
//as an output this will be used for returning syscall return value higher half (in this particular case 0)
register char *x1 asm("x1") = testStringOnStack;
//string length
register long x2 asm("x2") = strlen(testStringOnStack);
//syscall write is 4
register long x16 asm("x16") = SYS_write; //syscall write definition - see my footnote below
//full variant using stack local variables for register x0,x1,x2,x16 input
//syscall result collected in x0 & x1 using "semi" intrinsic assembler
asm volatile(//all args prepared, make the syscall
"svc #0x80"
:"=r"(x0),"=r"(x1) //mark x0 & x1 as syscall outputs
:"r"(x0), "r"(x1), "r"(x2), "r"(x16): //mark the inputs
//inform the compiler we read the memory
"memory",
//inform the compiler we clobber carry flag (during the syscall itself)
"cc");
//VARIANT 2
//syscall write for globals variable using "semi" intrinsic assembler
//args hardcoded
//output of syscall is ignored
asm volatile(//prepare x1 with the help of x8 register
"mov x1, %0 \t\n"
//set file descriptor to STD_OUT 1 so it appears in Xcode debug output
"mov x0, #1 \t\n"
//hardcoded length
"mov x2, #32 \t\n"
//syscall write is 4
"mov x16, #0x4 \t\n"
//all args prepared, make the syscall
"svc #0x80"
::"r"(testStringGlobal):
//clobbered registers list
"x1","x0","x2","x16",
//inform the compiler we read the memory
"memory",
//inform the compiler we clobber carry flag (during the syscall itself)
"cc");
//VARIANT 3 - only applicable to global variables using "page" address
//which is PC-relative addressing to load addresses at a fixed offset from the current location (PIC code).
//syscall write for global variable using "semi" intrinsic assembler
asm volatile(//set x1 on proper PAGE
"adrp x1,_testStringGlobal@PAGE \t\n" //notice the underscore preceding variable name by convention
//add the offset of the testStringGlobal variable
"add x1,x1,_testStringGlobal@PAGEOFF \t\n"
//set file descriptor to STD_OUT 1 so it appears in Xcode debug output
"mov x0, #1 \t\n"
//hardcoded length
"mov x2, #32 \t\n"
//syscall write is 4
"mov x16, #0x4 \t\n"
//all args prepared, make the syscall
"svc #0x80"
:::
//clobbered registers list
"x1","x0","x2","x16",
//inform the compiler we read the memory
"memory",
//inform the compiler we clobber carry flag (during the syscall itself)
"cc");
#endif
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
编辑
致@PeterCordes 的精彩评论,是的,有一个系统调用编号定义 header <sys/syscall.h>
,我将其包含在变体 1 的上述代码片段中。但重要的是要在其中提及它是由 Apple 定义的像这样:
#ifdef __APPLE_API_PRIVATE
#define SYS_syscall 0
#define SYS_exit 1
#define SYS_fork 2
#define SYS_read 3
#define SYS_write 4
我还没有听说过 iOS 应用程序由于直接通过 svc 0x80
使用系统调用而被 AppStore 拒绝的案例,但这绝对是 而不是 publicAPI。
至于@PeterCordes 建议的 "=@ccc"
,即进位标志(由系统调用在出错时设置)作为输出约束,最新的 XCode11 beta / LLVM 8.0.0 甚至不支持x86,绝对不适用于 ARM。
我正在学习更多关于 shellcode 和在 iOS 设备上使用 arm64 进行系统调用的知识。我正在测试的设备是 iPhone 6S.
我从这个 link (https://github.com/radare/radare2/blob/master/libr/include/sflib/darwin-arm-64/ios-syscalls.txt) 获得了系统调用列表。
我从这里了解到 x8 用于放置 arm64 的系统调用编号 (http://arm.ninja/2016/03/07/decoding-syscalls-in-arm64/)。
我认为用于传递arm64参数的各种寄存器应该与arm相同所以我参考了这个link(https://w3challs.com/syscalls/?arch=arm_strong), taken from https://azeria-labs.com/writing-arm-shellcode/。
我在 Xcode 中编写了内联汇编,这里有一些片段
//exit syscall
__asm__ volatile("mov x8, #1");
__asm__ volatile("mov x0, #0");
__asm__ volatile("svc 0x80");
但是,当我跨过这些代码时,应用程序并没有终止。
char write_buffer[]="console_text";
int write_buffer_size = sizeof(write_buffer);
__asm__ volatile("mov x8,#4;" //arm64 uses x8 for syscall number
"mov x0,#1;" //1 for stdout file descriptor
"mov x1,%0;" //the buffer to display
"mov x2,%1;" //buffer size
"svc 0x80;"
:
:"r"(write_buffer),"r"(write_buffer_size)
:"x0","x1","x2","x8"
);
如果此系统调用有效,它应该会在 Xcode 的控制台输出屏幕中打印出一些文本。但是,没有打印任何内容。
关于 ARM 汇编的在线文章很多,有些使用 svc 0x80
,有些使用 svc 0
等,因此可能会有一些变化。我尝试了各种方法,但无法让这两个代码片段起作用。
有人可以提供一些指导吗?
编辑:
这是我编写 C 函数系统调用时 Xcode 在其汇编视图中显示的内容 int return_value=syscall(1,0);
mov x1, sp
mov x30, #0
str x30, [x1]
orr w8, wzr, #0x1
stur x0, [x29, #-32] ; 8-byte Folded Spill
mov x0, x8
bl _syscall
我不确定为什么会发出此代码。
用于系统调用的寄存器完全是任意的,您选择的资源对于 XNU 来说肯定是错误的。
据我所知,用于 arm64 的 XNU 系统调用 ABI 是完全私有的,如有更改,恕不另行通知,因此它没有遵循任何已发布的标准,但您可以通过获取一份XNU 源代码(如 tarballs, or viewing it online,如果你愿意的话),grep 用于 handle_svc
函数,然后紧跟代码。
我不会详细说明您究竟在哪里找到哪些位,但最终结果是:
- 传递给
svc
的立即数被忽略,但标准库使用svc 0x80
。 x16
保存系统调用号x0
到x8
最多包含 9 个参数*- 堆栈中没有参数
x0
和x1
最多保存 2 个 return 值(例如fork
)- 进位位用于报错,此时
x0
保存错误码
* 这仅在具有 8 个参数的间接系统调用 (x16 = 0
) 的情况下使用。
* XNU 源代码中的评论也提到 x9
,但似乎编写该代码的工程师应该重温 off-by-one 错误。
然后是可用的实际系统调用编号:
- "UNIX syscalls" 的规范来源是 XNU 源代码树中的文件
bsd/kern/syscalls.master
。在最新的 iOS 13 beta 中,这些系统调用编号从0
到大约540
。 - "Mach syscalls" 的规范来源是 XNU 源代码树中的文件
osfmk/kern/syscall_sw.c
。这些系统调用使用-10
和-100
之间的 negative 数字调用(例如-28
将是task_self_trap
)。 - 与最后一点无关,两个系统调用
mach_absolute_time
和mach_continuous_time
可以分别使用系统调用号-3
和-4
调用。 - 一些 low-level 操作可以通过
platform_syscall
使用系统调用号0x80000000
。
这应该让你继续。正如@Siguza 提到的,您必须使用 x16
,而不是 x8
作为系统调用编号。
#import <sys/syscall.h>
char testStringGlobal[] = "helloWorld from global variable\n";
int main(int argc, char * argv[]) {
char testStringOnStack[] = "helloWorld from stack variable\n";
#if TARGET_CPU_ARM64
//VARIANT 1 suggested by @PeterCordes
//an an input it's a file descriptor set to STD_OUT 1 so the syscall write output appears in Xcode debug output
//as an output this will be used for returning syscall return value;
register long x0 asm("x0") = 1;
//as an input string to write
//as an output this will be used for returning syscall return value higher half (in this particular case 0)
register char *x1 asm("x1") = testStringOnStack;
//string length
register long x2 asm("x2") = strlen(testStringOnStack);
//syscall write is 4
register long x16 asm("x16") = SYS_write; //syscall write definition - see my footnote below
//full variant using stack local variables for register x0,x1,x2,x16 input
//syscall result collected in x0 & x1 using "semi" intrinsic assembler
asm volatile(//all args prepared, make the syscall
"svc #0x80"
:"=r"(x0),"=r"(x1) //mark x0 & x1 as syscall outputs
:"r"(x0), "r"(x1), "r"(x2), "r"(x16): //mark the inputs
//inform the compiler we read the memory
"memory",
//inform the compiler we clobber carry flag (during the syscall itself)
"cc");
//VARIANT 2
//syscall write for globals variable using "semi" intrinsic assembler
//args hardcoded
//output of syscall is ignored
asm volatile(//prepare x1 with the help of x8 register
"mov x1, %0 \t\n"
//set file descriptor to STD_OUT 1 so it appears in Xcode debug output
"mov x0, #1 \t\n"
//hardcoded length
"mov x2, #32 \t\n"
//syscall write is 4
"mov x16, #0x4 \t\n"
//all args prepared, make the syscall
"svc #0x80"
::"r"(testStringGlobal):
//clobbered registers list
"x1","x0","x2","x16",
//inform the compiler we read the memory
"memory",
//inform the compiler we clobber carry flag (during the syscall itself)
"cc");
//VARIANT 3 - only applicable to global variables using "page" address
//which is PC-relative addressing to load addresses at a fixed offset from the current location (PIC code).
//syscall write for global variable using "semi" intrinsic assembler
asm volatile(//set x1 on proper PAGE
"adrp x1,_testStringGlobal@PAGE \t\n" //notice the underscore preceding variable name by convention
//add the offset of the testStringGlobal variable
"add x1,x1,_testStringGlobal@PAGEOFF \t\n"
//set file descriptor to STD_OUT 1 so it appears in Xcode debug output
"mov x0, #1 \t\n"
//hardcoded length
"mov x2, #32 \t\n"
//syscall write is 4
"mov x16, #0x4 \t\n"
//all args prepared, make the syscall
"svc #0x80"
:::
//clobbered registers list
"x1","x0","x2","x16",
//inform the compiler we read the memory
"memory",
//inform the compiler we clobber carry flag (during the syscall itself)
"cc");
#endif
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
编辑
致@PeterCordes 的精彩评论,是的,有一个系统调用编号定义 header <sys/syscall.h>
,我将其包含在变体 1 的上述代码片段中。但重要的是要在其中提及它是由 Apple 定义的像这样:
#ifdef __APPLE_API_PRIVATE
#define SYS_syscall 0
#define SYS_exit 1
#define SYS_fork 2
#define SYS_read 3
#define SYS_write 4
我还没有听说过 iOS 应用程序由于直接通过 svc 0x80
使用系统调用而被 AppStore 拒绝的案例,但这绝对是 而不是 publicAPI。
至于@PeterCordes 建议的 "=@ccc"
,即进位标志(由系统调用在出错时设置)作为输出约束,最新的 XCode11 beta / LLVM 8.0.0 甚至不支持x86,绝对不适用于 ARM。