写一篇在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关键字表示禁止编译器对汇编代码进行再优化,但基本上有没有声明编译后指令都没区别
操作数格式为"[limits]constraint",分为权限和限定符两部分。比如"=r"表示参数是只写并存放在通用寄存器上
关键字 | 表意 |
---|---|
= | 只写,通用用于输出操作数 |
+ | 读写,只能用于输出操作数 |
& | 声明寄存器只能用于输出 |
关键字 | 表意 |
---|---|
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的指令过多,可通过文末的扩展阅读查阅指令,这里只讲解指令中的一些关键字:
在使用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]使可读性更强
程序运行的多数情况下,寄存器内存储的是存放数据的地址,使用[]包裹住寄存器,表示将寄存器的存储值作为地址访问数据。下面的指令分别是取出地址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个问题:
首先先判断需要多长的栈空间,根据函数sysctl(name, 4, &proc, &size, NULL, 0)判断
函数入口时,需要对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"
......
);
确定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是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,根据参数和寄存器对应关系入参调用即可:
__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"
......
);
最终需要返回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
......
);
本文由哈喽比特于4年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/tmPLMgMQdzeeddiN2nCCyg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。