从NDK在非Root手机上的调试原理探讨Android的安全机制Word格式文档下载.docx

上传人:b****7 文档编号:22768975 上传时间:2023-02-05 格式:DOCX 页数:18 大小:321.72KB
下载 相关 举报
从NDK在非Root手机上的调试原理探讨Android的安全机制Word格式文档下载.docx_第1页
第1页 / 共18页
从NDK在非Root手机上的调试原理探讨Android的安全机制Word格式文档下载.docx_第2页
第2页 / 共18页
从NDK在非Root手机上的调试原理探讨Android的安全机制Word格式文档下载.docx_第3页
第3页 / 共18页
从NDK在非Root手机上的调试原理探讨Android的安全机制Word格式文档下载.docx_第4页
第4页 / 共18页
从NDK在非Root手机上的调试原理探讨Android的安全机制Word格式文档下载.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

从NDK在非Root手机上的调试原理探讨Android的安全机制Word格式文档下载.docx

《从NDK在非Root手机上的调试原理探讨Android的安全机制Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《从NDK在非Root手机上的调试原理探讨Android的安全机制Word格式文档下载.docx(18页珍藏版)》请在冰豆网上搜索。

从NDK在非Root手机上的调试原理探讨Android的安全机制Word格式文档下载.docx

Linux中的每一个进程都关联有一个用户,也就是对应有一个UID,如图4所示:

由于每一个用户都对应有一个主用户组,以及若干个补充用户组,因此,每一个进程除了有一个对应的UID之外,还对应有一个主GID,以及若干个SupplementaryGIDs。

这些UID和GID就决定了一个进程所能访问的文件或者所能调用的系统API。

例如,在图4中,PID为340的进程一般来说,就只能访问所有者为u0_a19的文件。

一个进程的UID是怎么来的呢?

在默认情况下,就等于创建它的进程的UID,也就是它的父进程的UID。

Linux的第一个进程是init进程,它是由内核在启动完成后创建的,它的UID是root。

然后系统中的所有其它进程都是直接由init进程或者间接由init进程的子进程来创建。

所以默认情况下,系统的所有进程的UID都应该是root。

但是实际情况并非如此,因为父进程在创建子进程之后,也就是在fork之后,可以调用setuid来改变它的UID。

例如,在PC中,init进程启动之后,会先让用户登录。

用户登录成功后,就对应有一个shell进程。

该shell进程的UID就会被setuid修改为所登录的用户。

之后系统中创建的其余进程的UID为所登录的用户。

进程的UID除了来自于父进程之外,还有另外一种途径。

上面我们说到,Linux的文件有三种权限,分别是Read、Wirte和Execute。

其实还有另外一个种权限,叫做SUID。

例如,我们对Android手机进行root的过程中,会在里面放置一个su文件。

这个su文件就具有SUID权限,如图5所示:

一个可执行文件一旦被设置了SUID位,那么当它被一个进程通过exec加载之后,该进程的UID就会变成该可执行文件的所有者的UID。

也就是说,当上述的su被执行的时候,它所运行在的进程的UID是root,于是它就具有最高级别的权限,想干什么就干什么。

与SUI类似,文件还有另外一个称为SGID的权限,不过它描述的是用户组。

也就是说,一个可执行文件一旦被设置了GUID位,么当它被一个进程通过exec加载之后,该进程的主UID就会变成该可执行文件的所有者的主UID。

现在,小伙伴们应该可以理解Android手机的root原理了吧:

一个普通的进程通过执行su,从而获得一个具有root权限的进程。

有了这个具有root权限的进程之后,就可以想干什么就干什么了。

su所做的事情其实很简单,它再fork另外一个子进程来做真正的事情,也就是我们在执行su的时候,后面所跟的那些参数。

由于su所运行在的进程的UID是root,因此由它fork出来的子进程的UID也是root。

于是,子进程也可以想干什么就干什么了。

不过呢,用来root手机的su还会配合另外一个称为superuser的app来使用。

su在fork子进程来做真正的事情之前,会将superuser启动起来,询问用户是否允许fork一个UID是root的子进程。

这样就可以对root权限进行控制,避免被恶意应用偷偷地使用。

这里是su的源代码,小伙伴们可以根据上面所讲的知识读一读:

在传统的UNIX以及类UNIX系统中,进程的权限只划分两种:

特权和非特权。

UID等于0的进程就是特权进程,它们可以通过一切的权限检查。

UID不等于0的进程就非特权进程,它们在访问一些敏感资源或者调用一个敏感API时,需要进行权限检查。

这种纯粹通过UID来做权限检查的安全机制来粗放了。

于是,Linux从2.2开始,从进程的权限进行了细分,称为Capabilities。

一个进程所具有Capabilities可以通过capset和prctl等系统API来设置。

也就是说,当一个进程调用一个敏感的系统API时,Linux内核除了考虑它的UID之外,还会考虑它是否具有对应的Capability。

这里就是Linux所设计的Capabilities列表,有兴趣的小伙伴可以再读一读:

以上就是Linux基于UID/GID的安全机制的核心内容。

接下来我们再看Android基于Permission的安全机制,它也有三个角色:

apk、signature和permission,如图6所示:

Android的APK经过PackageManagerService安装之后,就相当于Linux里面的User,它们都会被分配到一个UID和一个主GID,而APK所申请的Permission就相当于是Linux里面的SupplementaryGID。

我们知道,Android的APK都是运行在独立的应用程序进程里面的,并且这些应用程序进程都是Zygote进程fork出来的。

Zygote进程又是由init进程fork出来的,并且它被init进程fork出来后,没有被setuid降权,也就是它的uid仍然是root。

按照我们前面所说的,应用程序进程被Zygote进程fork出来的时候,它的UID也应当是root。

但是,它们的UID会被setuid修改为所加载的APK被分配的UID。

参照一文的分析,ActivityManagerService在请求Zygote创建应用程序进程的时候,会将这个应用程序所加载的APK所分配得到的UID和GID(包括主GID和SupplementaryGID)都收集起来,并且将它们作为参数传递给Zygote进程。

Zygote进程通过执行函数来fork应用程序进程:

[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片

/*

*Utilityroutinetoforkzygoteandspecializethechildprocess.

*/

staticpid_tforkAndSpecializeCommon(constu4*args,boolisSystemServer)

{

pid_tpid;

uid_tuid=(uid_t)args[0];

gid_tgid=(gid_t)args[1];

ArrayObject*gids=(ArrayObject*)args[2];

......

pid=fork();

if(pid==0){

err=setgroupsIntarray(gids);

err=setgid(gid);

err=setuid(uid);

}

.....

returnpid;

}

参数args[0]、args[1]和args[]保存的就是APK分配到的UID、主GID和SupplementaryGID,它们分别通过setuid、setgid和setgroupsIntarray设置给当前fork出来的应用程序进程,于是应用程序进程就不再具有root权限了。

那么,Signature又充当什么作用呢?

两个作用:

1.控制哪些APK可以共享同一个UID;

2.控制哪些APK可以申请哪些Permission。

我们知道,如果要让两个APK共享同一个UID,那么就需要在AndroidManifest中配置android:

sharedUserId属性。

PackageManagerService在安装APK的时候,如果发现两个APK具有相同的android:

sharedUserId属性,那么它们就会被分配到相同的UID。

当然这有一个前提,就是这两个APK必须具有相同的Signature。

这很重要,否则的话,如果我知道别人的APK设置了android:

sharedUserId属性,那么我也在自己的APK中设置相同的android:

sharedUserId属性,就可以去访问别人APK的数据了。

除了可以通过android:

sharedUserId属性申请让两个APK共享同一个UID之外,我们还可以将android:

sharedUserId属性的值设置为“android.uid.system”,从而让一个APK的UID设置为1000。

UID是1000的用户是system,系统的关键服务都是运行在的进程的UID就是它。

它的权限虽然不等同于root,不过也足够大了。

我们可以通过MasterKey漏洞来看一下有多大。

MasterKey漏洞发布时,曾轰动了整个Android界,它的具体情况老罗就不分析了,网上很多,这里是一篇官方的文章:

现在就简单说说它是怎么利用的:

1.找到一个具有系统签名的APP,并且这个APP通过android:

sharedUserId属性申请了android.uid.system这个UID。

2.通过MasterKey向这个APP注入恶意代码。

3.注入到这个APP的恶意代码在运行时就获得了system用户身份。

4.修改/data/local.prop文件,将属性ro.kernel.qemu的值设置为1。

5.重启手机,由于ro.kernel.qemu的值等于1,这时候手机里面的adb进程不会被setuid剥夺掉root权限。

6.通过具有root权限的adb进程就可以向系统注入我们熟悉的su和superuser.apk,于是整个root过程完成。

注意,第1步之所以要找一个具有系统签名的APP,是因为通过android:

sharedUserId属性申请android.uid.system这个UID需要有系统签名,也就是说不是谁可以申请system这个UID的。

另外,/data/local.prop文件的Owner是system,因此,只有获得了system这个UID的进程,才可以对它进行修改。

再说说Signature与Permission的关系。

有些Permission,例如INSTALL_PACKAGE,不是谁都可以申请的,必须要具有系统签名才可以,这样就可以控制SuppementaryGID的分配,从而控制应用程序进程的权限。

具有哪些Permission是具有系统签名才可以申请的,可以参考官方文档:

,就是哪些标记为“Notforusebythird-partyapplications”的Permission。

了解了Android的Permission机制之后,我们就可以知道:

1.Android的APK就相当于是Linux的UID。

2.Android的Permission就相当于是Linux的GID。

3.Android的Signature就是用来控制APK的UID和GID分配的。

这就是Android基于Permission的安全机制与Linux基于UID/GID的安全机制的关系,概括来说,我们常说的应用程序沙箱就是这样的:

接下来我们就终于可以步入正题分析NDK在非root手机上调试APP的原理了。

首先们需要知道的是,NDK是通过gdbclient和gdbserver来调试APP的。

具体来说,就是通过gdbserver通过ptrace附加上目标APP进程去,然后gdbclient再通过socket或者pipe来链接gdbserver,并且向它发出命令来对APP进程进行调试。

这个具体的过程可以参考这篇文章,讲得很详细的了:

老罗希望小伙伴们认真看完这篇文章再来看接下来的内容,因为接下来我们只讲这篇文章的关键点。

第一个关键点是每一个需要调试的APK在打包的时候,都会带上一个gdbserver。

因为手机上面不带有gdbserver这个工具。

这个gdbserver就负责用来ptrace到要调度的APP进程去。

第二个关键点是ptrace的调用。

一般来说,只有root权限的进程只可以调用。

例如,如果我们想通过ptrace向目标进程注入一个SO,那么就需要在root过的手机上通过向su申请root权限。

但是,这不是绝对的。

如果一个进程与目标进程的UID是相同的,那么该进程就具有调用ptrace的权限。

我们可以看看ptrace_attach函数的实现:

staticintptrace_attach(structtask_struct*task,longrequest,

unsignedlongaddr,

unsignedlongflags)

task_lock(task);

retval=__ptrace_may_access(task,PTRACE_MODE_ATTACH);

task_unlock(task);

if(retval)

gotounlock_creds;

unlock_creds:

mutex_unlock(&

task->

signal->

cred_guard_mutex);

out:

returnretval;

gdbserver在调试一个APP之前,首先要通过ptrace_attach来附加到该APP进程去。

ptrace_attach在执行实际操作之后,会调用__ptrace_may_access来检查调用进程的权限:

int__ptrace_may_access(structtask_struct*task,unsignedintmode)

conststructcred*cred=current_cred(),*tcred;

if(task==current)

return0;

rcu_read_lock();

tcred=__task_cred(task);

if(cred->

user->

user_ns==tcred->

user_ns&

&

(cred->

uid==tcred->

euid&

cred->

suid&

uid&

gid==tcred->

egid&

sgid&

gid))

gotook;

if(ptrace_has_cap(tcred->

user_ns,mode))

rcu_read_unlock();

return-EPERM;

ok:

returnsecurity_ptrace_access_check(task,mode);

这里我们就可以看到,如果调用进程与目标进程具有相同的UID和GID,那么权限检查就通过。

否则的话,就要求调用者进程具有执行ptrace的capability,这是通过另外一个函数ptrace_has_cap来检查的。

如果是调用进程的UID是root,那么ptrace_has_cap一定会检查通过。

当然,通过了上述两个权限检查之后,还要接受内核安全模块的检查,这个就不是通过UID或者Capability这一套机制来控制的了,我们可以忽略这个话题。

第三个关键点是如何让gdbserver进程的UID与要调试的APP进程的UID一样。

因为在没有root过的手机上,要想获得root权限是不可能的了,因此只能选择以目标进程相同的UID运行这个方法。

这就要用到另外一个工具了:

run-as。

runs-as其实是一个与su类似的工具,它在设备上是自带的,位于/system/bin目录下,它的SUID位也是被设置了,并且它的所有者也是root,我们可以通过ls-l/system/bin/run-as来看到:

[plain]viewplaincopy在CODE上查看代码片派生到我的代码片

root@android:

/#ls-l/system/bin/run-as

-rwsr-s---rootshell95282013-12-0505:

32run-as

但是与su不同,run-as不是让一个进程以root身份运行,而是让一个进程以指定的UID来运行,这也是通过setuid来实现的。

run-as能够这样做是因为它运行的时候,所获得的UID是root。

第四个关键点是被调试的APK在其AndroidManifext.xml里必须将android:

debuggable属性设置为true。

这是为什么呢?

原来,当一个进程具有ptrace到目标进程的权限时,还不能够对目标进程进行调试,还要求目标进程将自己设置为可dumpable的。

我们再回过头来进一步看看__ptrace_may_access的实现:

intdumpable=0;

smp_rmb();

if(task->

mm)

dumpable=get_dumpable(task->

mm);

if(!

dumpable&

!

ptrace_has_cap(task_user_ns(task),mode))

我们再来看看当一个APK在其AndroidManifext.xml里必须将android:

debuggable属性设置为true时会发生什么事情。

ActivityManagerService在请求Zygote进程为其fork一个应用程序进程时,会将它的DEBUG_ENABLE_DEBUGGER标志位设置为1,并且以参数的形式传递给Zygote进程。

Zygote进程在调用我们在上面分析的函数forkAndSpecializeCommon来fork应用程序进程时,就会相应的处理,如下所示:

u4debugFlags=args[3];

/*configureadditionaldebugoptions*/

enableDebugFeatures(debugFlags);

参数args[3]包含的就是调试标志位,函数enableDebugFeatures的实现如下所示:

voidenableDebugFeatures(u4debugFlags)

if((debugFlags&

DEBUG_ENABLE_DEBUGGER)!

=0){

/*Toletanon-privilegedgdbserverattachtothis

*process,wemustsetitsdumpablebitflag.However

*wearenotinterestedingeneratingacoredumpin

*caseofacrash,soalsosetthecoredumpsizeto0

*todisablethat

if(prctl(PR_SET_DUMPABLE,1,0,0,0)<

0){

ALOGE("

couldnotsetdumpablebitflagforpid%d:

%s"

getpid(),strerror(errno));

}else{

structrlimitrl;

rl.rlim_cur=0;

rl.rlim_max=RLIM_INFINITY;

if(setrlimit(RLIMIT_CORE,&

rl)<

couldnotdisablecor

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 党团工作 > 入党转正申请

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1