当前位置:网站首页>iOS中的内嵌汇编
iOS中的内嵌汇编
2020-11-09 13:44:00 【osc_zvjde550】
Python实战社群
Java实战社群
长按识别下方二维码,按需求添加
扫码关注添加客服
进Python社群▲
扫码关注添加客服
进Java社群▲
作者 | sindrilin
来源 | sindrilin的小巢
http://sindrilin.com/2019/10/23/write_assembly_in_ios.html
点击阅读原文查看作者更多文章
写一篇在iOS上使用汇编的文章的想法在脑袋里面停留了很久了,但是迟迟没有动手。虽然早前在做启动耗时优化的工作中,也做过通过拦截objc_msgSend并插入汇编指令来统计方法调用耗时的工作,但也只仅此而已。刚好最近的时间项目在做安全加固,需要写更多的汇编来提高安全性(文章内汇编使用指令集为ARM64),也就有了本文
内嵌汇编格式
__asm__ [关键词](
指令
: [输出操作数列表]
: [输入操作数列表]
: [被污染的寄存器列表]
);
比如函数中存在a、b、c三个变量,要实现a = b + c这句代码,汇编代码如下:
__asm__ volatile(
"mov x0, %[b]\n"
"mov x1, %[c]\n"
"add x2, x0, x1\n"
"mov %[a], x2\n"
: [a]"=r"(a)
: [b]"r"(b), [c]"r"(c)
);
volatile
volatile关键字表示禁止编译器对汇编代码进行再优化,但基本上有没有声明编译后指令都没区别
操作数
操作数格式为"[limits]constraint",分为权限和限定符两部分。比如"=r"表示参数是只写并存放在通用寄存器上
limits
关键字 | 表意 |
---|---|
= | 只写,通用用于输出操作数 |
+ | 读写,只能用于输出操作数 |
& | 声明寄存器只能用于输出 |
constraint
关键字 | 表意 |
---|---|
f | 浮点寄存器f0~f7 |
G/H | 浮点常量立即数 |
I/L/K | 数据处理用到的立即数 |
J | 值为-4095~4095的索引 |
l/r | 寄存器r0~r15 |
M | 0~32/2的幂次方的常量 |
m | 内存地址 |
w | 向量寄存器s0~s31 |
X | 任何类型的操作数 |
指令
由于ARM64的指令过多,可通过文末的扩展阅读查阅指令,这里只讲解指令中的一些关键字:
%0~%N / %[param]
在使用C代码和汇编混编的情况下,%起头用来关联参数,通过%[param]可以声明参数名称,也可以使用匿名参数格式%N的方式顺序对应参数(abc参数会按照012的顺序匹配):
__asm__ volatile(
"mov x0, %1\n"
"mov x1, %2\n"
"add x2, x0, x1\n"
"mov %0, x2\n"
: "=r"(a)
: "r"(b), "r"(c)
);
在实操过程中,设备不一定支持%N的匿名参数格式,建议使用%[param]使可读性更强
[reg]
程序运行的多数情况下,寄存器内存储的是存放数据的地址,使用[]包裹住寄存器,表示将寄存器的存储值作为地址访问数据。下面的指令分别是取出地址0x10086存储的数据存放在x1寄存器上,然后存放到地址0x100086的内存中:
"mov x0, #0x10086\n"
"mov x1, [x0]\n"
"mov x2, #0x100086\n"
"str x1, [x2]\n"
#1 / #0x1
使用#起头表示立即数(常数),建议使用16进制书写
调用规范
ARM64调用约定采用AAPCS64,参数从左到右存放到x0~x7寄存器中,参数超出8个时,多余的从右往左入栈,根据返回值大小不同存放在x0/x8返回。寄存器规则如下:
寄存器 | 特殊名称 | 规则 |
---|---|---|
r31 | SP | 存放栈顶地址 |
r30 | LR | 存放函数返回地址 |
r29 | FP | 存放函数使用栈帧地址 |
r19~r28 | 被调用方需要保护的寄存器 | |
r18 | 平台寄存器,不建议当做临时寄存器使用 | |
r17 | IP1 | 进程内使用寄存器,不建议当做临时寄存器使用 |
r16 | IP0 | 同r17,同时作为软中断svc中的系统调用参数 |
r9~r15 | 临时寄存器(汇编指令中嵌入函数地址参数时,会用于保存函数地址) | |
r8 | 返回值寄存器(其他时候同r9~r15) | |
r0~r7 | 传递存储调用参数,r0可作为返回值寄存器 | |
NZCV | 状态寄存器 |
实战
调试检测
在iOS应用安全加固中,通过sysctl + kinfo_proc的方案可以检测应用是否被调试:
__attribute__((__always_inline)) bool checkTracing() {
size_t size = sizeof(struct kinfo_proc);
struct kinfo_proc proc;
memset(&proc, 0, size);
int name[4];
name[0] = CTL_KERN;
name[1] = KERN_PROC;
name[2] = KERN_PROC_PID;
name[3] = getpid();
sysctl(name, 4, &proc, &size, NULL, 0);
return proc.kp_proc.p_flag & P_TRACED;
}
但由于fishhook这种直接修改懒符号地址的方案存在,直接使用sysctl是不安全的,因此多数开发者会将这一调用替换成内嵌汇编的方案执行:
size_t size = sizeof(struct kinfo_proc);
struct kinfo_proc proc;
memset(&proc, 0, size);
int name[4];
name[0] = CTL_KERN;
name[1] = KERN_PROC;
name[2] = KERN_PROC_PID;
name[3] = getpid();
__asm__(
"mov x0, %[name_ptr]\n"
"mov x1, #4\n"
"mov x2, %[proc_ptr]\n"
"mov x3, %[size_ptr]\n"
"mov x4, #0x0\n"
"mov x5, #0x0\n"
"mov w16, #202\n"
"svc #0x80\n"
:
:[name_ptr]"r"(&name), [proc_ptr]"r"(&proc), [size_ptr]"r"(&size)
);
return proc.kp_proc.p_flag & P_TRACED;
踩坑
使用C代码内嵌汇编开发的时候,有个致命的问题是函数入口会将临时变量入栈,并且将这些变量存放到寄存器中。上面的混编代码实际运行时,会出现下面的情况:
// 函数入口生成的临时变量代码
add x0, sp, #0x24 // x0存放name
add x1, sp, #0x34 // x1存放proc
add x2, sp, #020 // x2存放size
......
// 内嵌汇编
mov x0, x0 // name正常赋值
mov x1, #4 // proc数据被破坏
mov x2, x1 // size数据被破坏
mov x3, x2
mov x4, #0x0
mov x5, #0x0
mov x12, #0xca
svc #0x80
编译后的代码由于临时变量顺序问题,导致了svc中断调用sysctl无法传入正确参数,最终卡死应用
修复
插入临时变量
通过编译后的指令得到一张对应表:
变量 | 寄存器 | 入参寄存器 |
---|---|---|
name | x0 | x0 |
proc | x1 | x2 |
size | x2 | X3 |
如果能够让存储临时变量的寄存器和svc中断时的入参寄存器保持一致,就不会遭到破坏
ARM64调用约定,参数从右往左入栈
因为检测函数无入参,所以临时参数入参后依次存放到了x0~x2寄存器中,顺序为name、proc、size,因此需要只需要在name和proc中插入一个无用的临时变量,就能让参数对应起来:
size_t size = sizeof(struct kinfo_proc);
struct kinfo_proc proc;
memset(&proc, 0, size);
int placeholder;
int name[4];
name[0] = CTL_KERN;
name[1] = KERN_PROC;
name[2] = KERN_PROC_PID;
name[3] = getpid();
编译后指令变为:
// 函数入口生成的临时变量代码
add x0, sp, #0x24 // x0存放name
add x1, sp, #0x34 // x1存放placeholder
add x2, sp, 0x38 // x2存放proc
add x3, sp, #020 // x3存放size
......
// 内嵌汇编
mov x0, x0
mov x1, #4
mov x2, x2
mov x3, x3
mov x4, #0x0
mov x5, #0x0
mov x12, #0xca
svc #0x80
修改指令顺序
设置入参的指令会破坏寄存器上已有的值,那么保证设置入参之前,寄存器没被破坏就可以了:
__asm__(
"mov x0, %[name_ptr]\n"
"mov x3, %[size_ptr]\n"
"mov x2, %[proc_ptr]\n"
"mov x1, #4\n"
"mov x4, #0x0\n"
"mov x5, #0x0\n"
"mov w16, #202\n"
"svc #0x80\n"
:
:[name_ptr]"r"(&name), [proc_ptr]"r"(&proc), [size_ptr]"r"(&size)
);
编译后指令如下:
// 内嵌汇编
mov x0, x0 // x0保存name
mov x3, x2 // x3保存size
mov x2, x1 // x2保存proc
mov x1, #4
mov x4, #0x0
mov x5, #0x0
mov x12, #0xca
svc #0x80
全汇编实现
在和C代码混编的情况下,无法保证哪些寄存器会被破坏,那么直接使用汇编实现整个逻辑是一个不错的选择,需要注意2个问题:
保证函数调用前后不会生成出入口指令,使用__attribute__((naked))来处理
所有变量存储在栈上,需要把控制好栈的使用
使用安全的寄存器(r19~r28)
首先先判断需要多长的栈空间,根据函数sysctl(name, 4, &proc, &size, NULL, 0)判断
参数name总共占用 4 * int空间,记为0x10
参数proc在arm64下,sizof()计算长度为0x288
参数&size指针长度为0x8
共计0x2a0
函数入口时,需要对FP/LR寄存器进行入栈,保证函数能正确退出。另外r19~r28共计10个寄存器需要进行入栈保护,最终得出函数运行时的栈空间图:
----------
| FP |
---------- sp + 0x2f8
| LR |
---------- sp + 0x2f0
| r20 |
---------- sp + 0x2e8
| r19 |
---------- sp + 0x2e0
| r22 |
---------- sp + 0x2d8
| r21 |
---------- sp + 0x2d0
| r24 |
---------- sp + 0x2c8
| r23 |
---------- sp + 0x2c0
| r26 |
---------- sp + 0x2b8
| r25 |
---------- sp + 0x2b0
| r28 |
---------- sp + 0x2a8
| r27 |
---------- sp + 0x2a0
| p_size |
---------- sp + 0x298
| proc |
---------- sp + 0x10
| name |
---------- sp
在保存r19~r28寄存器入栈后,使用其中五个寄存器来保存一些参数:
------------------
| 参数 | 寄存器 |
------------------
| name | r19 |
------------------
| proc | r20 |
------------------
| p_size | r21 |
------------------
| size | r22 |
------------------
| sp | r23 |
------------------
| temp | r24 |
------------------
确认好栈上空间的使用后,可以开始分步骤实现:
函数出入口
在函数的出入口负责两件事情:FP/LR的出入栈、r19~r28的出入栈
__asm__ volatile(
"stp x29, x30, [sp, #-0x10]!\n"
"stp x19, x20, [sp, #-0x10]!\n"
"stp x21, x22, [sp, #-0x10]!\n"
"stp x23, x24, [sp, #-0x10]!\n"
"stp x25, x26, [sp, #-0x10]!\n"
"stp x27, x28, [sp, #-0x10]!\n"
......
"ldp x19, x20, [sp], #0x10\n"
"ldp x21, x22, [sp], #0x10\n"
"ldp x23, x24, [sp], #0x10\n"
"ldp x25, x26, [sp], #0x10\n"
"ldp x27, x28, [sp], #0x10\n"
"ldp x29, x30, [sp], #0x10\n"
);
栈开辟空间
临时变量总共用到0x2a0的空间,并且需要使用5个寄存器保存变量
__asm__ volatile(
......
"sub sp, sp, #0x2a0\n"
// 开辟栈空间,寄存器保存变量
"mov x19, sp\n" // x19 = name
"add, x20, sp, #0x10\n" // x20 = proc
"add, x21, sp, #0x298\n" // x21 = p_size
"mov x22, #0x288\n" // x22 = size
"mov x23, sp\n" // x23 = sp
"str x22, [x21]\n" // p_size = &size
"add sp, sp, #0x2a0\n"
......
);
kinfo_proc
确定proc的内存之后,需要将:
size_t size = sizeof(struct kinfo_proc);
struct kinfo_proc proc;
memset(&proc, 0, size);
转换成对应的汇编,其中proc存储在x20,x22存储了size,memset一共需要三个参数,分别入参:
__asm__ volatile(
......
"mov x24, %[memset_ptr]\n"
"mov x0, x20\n"
"mov x1, #0x0\n"
"mov x2, x12\n"
"blr x24\n"
......
:
:[memset_ptr]"r"(memset)
);
name
由于name是int数组,在明确其存储位置的情况下,需要分别将4个4字节的参数存储到对应的内存位置,其位置分布如下:
-------------
| name[3] |
------------- sp + 0xc
| name[2] |
------------- sp + 0x8
| name[1] |
------------- sp + 0x4
| name[0] |
------------- sp
另外name需要使用到getpid()来配置参数,通过svc的中断可以获取这一参数(svc系统调用参数可以参考扩展阅读中的Kernel Syscalls)
#define CTL_KERN 1
#define KERN_PROC 14
#define KERN_PROC_PID 1
__asm__ volatile(
......
// getpid
"mov x0, #0\n"
"mov w16, #20\n"
"mov x3, x0\n" // name[3]=getpid()
// 设置参数并存储
"mov x0, #0x1\n"
"mov x1, #0xe\n"
"mov x2, #0x1\n"
"str w0, [x23, 0x0]\n"
"str w1, [x23, 0x4]\n"
"str w2, [x23, 0x8]\n"
"str w3, [x23, 0xc]\n"
......
);
sysctl
最后是调用sysctl,根据参数和寄存器对应关系入参调用即可:
__asm__ volatile(
......
"mov x0, x19\n"
"mov x1, #0x4\n"
"mov x2, x20\n"
"mov x3, x21\n"
"mov x4, #0x0\n"
"mov x5, #0x0\n"
"mov w16, #202\n"
"svc #0x80\n"
......
);
flag检测
最终需要返回p_flag和P_TRACED的与比较检测,这里需要通过获取p_flag在结构体中的偏移来访问数据,struct extern_proc的结构如下:
struct extern_proc {
union {
struct {
struct proc *__p_forw; /* Doubly-linked run/sleep queue. */
struct proc *__p_back;
} p_st1;
struct timeval __p_starttime; /* process start time */
} p_un;
#define p_forw p_un.p_st1.__p_forw
#define p_back p_un.p_st1.__p_back
#define p_starttime p_un.__p_starttime
struct vmspace *p_vmspace; /* Address space. */
struct sigacts *p_sigacts; /* Signal actions, state (PROC ONLY). */
int p_flag; /* P_* flags. */
char p_stat; /* S* process status. */
pid_t p_pid; /* Process identifier. */
pid_t p_oppid; /* Save parent pid during ptrace. XXX */
int p_dupfd; /* Sideways return value from fdopen. XXX */
/* Mach related */
caddr_t user_stack; /* where user stack was allocated */
void *exit_thread; /* XXX Which thread is exiting? */
int p_debugger; /* allow to debug */
boolean_t sigwait; /* indication to suspend */
/* scheduling */
u_int p_estcpu; /* Time averaged value of p_cpticks. */
int p_cpticks; /* Ticks of cpu time. */
fixpt_t p_pctcpu; /* %cpu for this process during p_swtime */
void *p_wchan; /* Sleep address. */
char *p_wmesg; /* Reason for sleep. */
u_int p_swtime; /* Time swapped in or out. */
u_int p_slptime; /* Time since last blocked. */
struct itimerval p_realtimer; /* Alarm timer. */
struct timeval p_rtime; /* Real time. */
u_quad_t p_uticks; /* Statclock hits in user mode. */
u_quad_t p_sticks; /* Statclock hits in system mode. */
u_quad_t p_iticks; /* Statclock hits processing intr. */
int p_traceflag; /* Kernel trace points. */
struct vnode *p_tracep; /* Trace to vnode. */
int p_siglist; /* DEPRECATED. */
struct vnode *p_textvp; /* Vnode of executable. */
int p_holdcnt; /* If non-zero, don't swap. */
sigset_t p_sigmask; /* DEPRECATED. */
sigset_t p_sigignore; /* Signals being ignored. */
sigset_t p_sigcatch; /* Signals being caught by user. */
u_char p_priority; /* Process priority. */
u_char p_usrpri; /* User-priority based on p_cpu and p_nice. */
char p_nice; /* Process "nice" value. */
char p_comm[MAXCOMLEN + 1];
struct pgrp *p_pgrp; /* Pointer to process group. */
struct user *p_addr; /* Kernel virtual addr of u-area (PROC ONLY). */
u_short p_xstat; /* Exit status for wait; also stop signal. */
u_short p_acflag; /* Accounting flags. */
struct rusage *p_ru; /* Exit information. XXX */
};
其中union p_un的size为0x10,以及p_flag前面的两个指针分别占用0x8,可以确认结构体的内存占用图:
-------------------
| p_flag |
------------------- kinfo_proc + 0x20
| p_sigacts |
------------------- kinfo_proc + 0x18
| p_vmspace |
------------------- kinfo_proc + 0x10
| union p_un |
------------------- kinfo_proc
比对标记并且将检测结果存放到x0中返回:
#define P_TRACED 0x00000800
__asm__ volatile(
......
"ldr, x24, [x20, #0x20]\n" // x24 = proc.kp_proc.p_flag
"mov x25, #0x800\n" // x25 = P_TRACED
"blc x0, x24, x25\n" // x0 = x24 & x25
......
);
扩展阅读
[1]https://www.theiphonewiki.com/wiki/Kernel_Syscalls
[2]https://juejin.im/post/5cadeda55188251ad87b0eed
[3]https://juejin.im/post/5a786c555188257a6854b18c
[4]http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
程序员专栏 扫码关注填加客服 长按识别下方二维码进群
近期精彩内容推荐:
在看点这里好文分享给更多人↓↓
版权声明
本文为[osc_zvjde550]所创,转载请带上原文链接,感谢
https://my.oschina.net/u/4404812/blog/4709576
边栏推荐
- Nine kinds of distributed primary key ID generation schemes of sub database and sub table are quite comprehensive
- 要我说,多线程事务它必须就是个伪命题!
- Clock service Android implementation of alarm clock
- EFF 认为 RIAA 正在“滥用 DMCA”来关闭 YouTube-DL
- In the future, China Telecom will make cloud computing service the main business of China Telecom
- Learning history of C language
- 接口测试如何在post请求中传递文件
- What are the implementations of distributed locks?
- Efficient estimation of word representations in vector space
- C language -- game of Sanzi
猜你喜欢
零基础IM开发入门(四):什么是IM系统的消息时序一致性?
JVM学习(四)-垃圾回收器和内存分配
What are the implementations of distributed locks?
Idea solves garbled Chinese output of YML configuration file
Rainbow sorting | Dutch flag problem
Tidb x micro banking reduces time consumption by 58%, and distributed architecture helps to realize inclusive finance
Well, these four ways to query the maximum value of sliding window are good
ImmutableMap的put方法问题
Clock service Android implementation of alarm clock
What really drags you down is sunk costs
随机推荐
Nine kinds of distributed primary key ID generation schemes of sub database and sub table are quite comprehensive
分库分表的 9种分布式主键ID 生成方案,挺全乎的
在企业的降本增效诉求下,Cube如何助力科盾业务容器化“一步到位”?
JS判断对象类型方法_typeof怎么用_instanceof怎么用_constructor怎么用_Object.prototype.toString()怎么用
中国程序员 VS 日本程序员,满屏的羞羞!
Android NDK development and actual combat WeChat official account 2-D code detection
Well, the four ways to query the maximum value of sliding window are good
块级元素和行内元素
要我说,多线程事务它必须就是个伪命题!
Android NDK 开发实战 - 微信公众号二维码检测
Adobe experience design / XD 2020 software installation package (with installation tutorial)
PAT_甲级_1074 Reversing Linked List
Use treeview tree menu bar (recursively call database to create menu automatically)
苏宁基于知识图谱的大规模告警收敛和根因定位实践
IDEA解决yml配置文件中文输出乱码问题
Adobe Experience Design /Xd 2020软件安装包(附安装教程)
iPhone“连到系统上的设备没有发挥作用”原因分析及解决方法 20200105
Why I strongly recommend custom development of small programs, these benefits you need to understand
关于无相互作用极化率的计算
未来中国电信将把云计算服务打造成为中国电信的主业