`
ruguomingtian1
  • 浏览: 5311 次
  • 性别: Icon_minigender_2
  • 来自: 沈阳
社区版块
存档分类
最新评论

利用Ptrace在Android平台实现应用程序控制

阅读更多
利用Ptrace在Android平台实现应用程序控制
但凡做过安全软件的人都知道,API Hook和App Control是经常要实现的功能。

为了实现这两个功能,最常用的方法就是写driver,在kernel中拦截检查相应的调用。这种做法的好处是大小通吃,不用关心系统里面到底有多少进程,反正你要做的操作最终总要过我这一关。而缺点就是在kernel中拦截往往得不到我想要的一些参数而无法做出正确的判断。举个例子,手机平台中很多应用都会发短信,我想组织某些应用发短信而允许另一些应用发短信。而发短信的操作并不是由每个应用直接调用的,它们把发送请求发给一个叫SmsService的服务进程,由这个服务进程再调用系统API来发短信。当我们在Kernel里面拦截到这个API的时候,发现调用者都是SmsService,无法区分原始请求者,因而无法做到有目的的拦截。

为了解决上面提到的问题,很多厂商开始想办法Hook SmsService,在SmsService里面可以区分请求的应用和请求的内容,进而做到有区分的拦截。那么如何Hook SmsService 呢?在Linux/Unix/Mac/iOS系统里面最简单的办法可能就是Ptrace了。所以说利用Ptrace可以很容易的做到精准打击,可以具体到Hook每个进程里面的每个点,这是它的优点。那么再说一下它的缺点,如果你想Hook多个进程,你就要给每个进程准备不同的代码,除非你可以找到SmsService这样的“关口”,即便找到了关口,还要提放有没有办法绕过它。

综上所述,Ptrace是一个用来实现API Hook和App Control 的好工具。很多情况下它的缺点可以被忽视,尤其在Android这种很容易找到"关口"的平台上。因此很多Android上的安全软件都是利用Ptrace来实现API Hook和App Control的。

前面扯的有点多,先来说一下这次的目标:
平台:Android 2.3
实现:利用Ptrace剥夺某些应用请求service的功能

1. 编写测试程序SmsSender1
SmsSender1非常简单,它有一个Activity,在onCreate的时候发送一条SMS. 我们的目标就是拦截这个应用请求系统Service的能力而不影响其他应用。


public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
                                         
        SmsManager smsM = SmsManager.getDefault();
        if(smsM != null)
        {
            smsM.sendTextMessage("123456", null, "abc", null, null);
                                                     
            Toast.makeText(this, "send sms", Toast.LENGTH_SHORT).show(); 
        }
        else {
            Toast.makeText(this, "sm is null", Toast.LENGTH_SHORT).show(); 
        }
    }


2. 研究servicemanager
servicemanager是我们用Ptrace要修改的目标。为什么选servicemanager? 根据Android的架构,系统的各种service (比如phone, sms, camera)是手机核心功能的“关口”,而servicemanager就是这些service的总"关口"。每个service都要到servicemanager注册,而每个app要想获取服务,需要先到servicemanager来查询各个服务的ID,然后才能根据ID和相应的service利用binder进行通信。所以说把住了servicemanager,就把住了所有关键的服务。

那么从何下手呢?

俗话说源码之前,了无秘密。既然有Android源码,就看源码来加速我们的研究。看了源码以后发现servicemanager里面有一个loop,用来处理各种请求,算是个下手的好地方:

int svcmgr_handler(struct binder_state *bs,
                   struct binder_txn *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    struct svcinfo *si;
    uint16_t *s;
    unsigned len;
    void *ptr;
    uint32_t strict_policy;
                                  
//    LOGI("target=%p code=%d pid=%d uid=%d\n",
//         txn->target, txn->code, txn->sender_pid, txn->sender_euid);
                                  
    if (txn->target != svcmgr_handle)
        return -1;
                                  
    // Equivalent to Parcel::enforceInterface(), reading the RPC
    // header with the strict mode policy mask and the interface name.
    // Note that we ignore the strict_policy and don't propagate it
    // further (since we do no outbound RPCs anyway).
    strict_policy = bio_get_uint32(msg);
    s = bio_get_string16(msg, &len);
    if ((len != (sizeof(svcmgr_id) / 2)) ||
        memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {
        fprintf(stderr,"invalid id %s\n", str8(s));
        return -1;
    }
                                  
    switch(txn->code) {
    case SVC_MGR_GET_SERVICE:
    case SVC_MGR_CHECK_SERVICE:
        s = bio_get_string16(msg, &len);
        ptr = do_find_service(bs, s, len);
        if (!ptr)
            break;
        bio_put_ref(reply, ptr);
        return 0;
                                  
    case SVC_MGR_ADD_SERVICE:
        s = bio_get_string16(msg, &len);
        ptr = bio_get_ref(msg);
        if (do_add_service(bs, s, len, ptr, txn->sender_euid))
            return -1;
        break;
                                  
    case SVC_MGR_LIST_SERVICES: {
        unsigned n = bio_get_uint32(msg);
                                  
        si = svclist;
        while ((n-- > 0) && si)
            si = si->next;
        if (si) {
            bio_put_string16(reply, si->name);
            return 0;
        }
        return -1;
    }
    default:
        LOGE("unknown code %d\n", txn->code);
        return -1;
    }
                                  
    bio_put_uint32(reply, 0);
    return 0;
}

Hook这个函数,通过参数txn和msg可以获知是谁在发起请求,请求什么东西。我们的计划是通过txn->sender_euid来判断是哪个app在请求,然后屏蔽前面写的SmsSender1的所有请求,让它什么都干不了。

具体如何实现?我们现在有两个选择:

选择1, 写一个trace程序,利用Ptrace attach到servicemanager进程,在函数svcmgr_handler中添加断点。中断之后trace根据txn->sender_euid的值来修改相应的寄存器或者内存值,达到改变程序流程的目的。这种做法需要trace一直运行,处于调试servicemanager的状态,每次svcmgr_handler都需要从trace过一次。如果trace挂了会导致不可预料的结果。

选项2,写一个trace程序,只运行一次,利用Ptrace attach到servicemanager进程,在text段修改svcmgr_handler的逻辑并插入我们的代码,然后dettach. 这样的话trace不必长期运行,也不用每次调用都过trace,稳定性和效率都大大提高。

就技术而言,选项2需要考虑更多的问题,也更复杂。但我还是喜欢选项2,呵呵,后面就按照选项2来实现。

3. 编写injection的汇编代码
反编译servicemanager,定位到svcmgr_handler函数:


其中0x8950,0x8952,0x8954,0x8956四条指令对应于源码中这两行:

if (txn->target != svcmgr_handle)
        return -1;


我们就从0x8950开始修改,将LDR R3,[R0]; LDR R2,[R4];替换成我们的跳转指令BL,跳转的目的地是我们即将插入的逻辑代码,就是判断txn->sender_euid并决定是否屏蔽请求的代码。

我们先将SmsSender1安装到Android上,并查看它的uid. 我们知道在Android系统中每个app都被分配了一个用户,利用用户来进行权限管理,而uid就是用户id,用户分配表可以在/data/system/packages.list里面找到:


可见我们要屏蔽的uid是10038,所以我们的判断逻辑代码编写如下:
push {r1,lr}
push {r0-r7}
LDR R7, [R4,#0x14]
ldr r3,=10013
CMP R7, R3
pop {r0-r7}
BEQ loc_ret_1
LDR R3, [R0]
LDR R2, [R4]
pop {r1,pc}
loc_ret_1:
mov r3, #0
mov r2, #1
pop {r1,pc}

逻辑很简单,line3是获取txn->sender_euid,并和10013比较,如果相等,则将r2和r3的值分别设为1和0,目的是让它们不等,因为servicemanager后续的逻辑如果r2!=r3就会return -1;
另外别忘了我们覆盖了servicemanager两条指令LDR R3,[R0]; LDR R2,[R4];,如果uid!=10013,我们需要让程序按照原来的逻辑执行下去,所以line8-9是补充执行被我们覆盖的两条指令。

好了,万事俱备只欠东风,接下来我们可以开始编写trace程序了。

3. 编写trace程序

我们最终完成的trace程序如下:

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/syscall.h>
        
int long_size = sizeof(long);
        
void append_asm(char* asm_bin, int* len, unsigned long aasm, int alen)
{
    int i;
    char* aasmp = &aasm;
            
    for(i=0; i<alen; i++)
    {
        *(asm_bin+(*len)) = *(aasmp+i);
        (*len)++;
    }
}
        
void getdata(pid_t pid, long addr,
        char *str, int len)
{   
    char *laddr;
    int i, j;
    union u {
        long val;
        char chars[long_size];
    }data;
    i = 0;
    j = len / long_size;
    laddr = str;
    while(i < j) {
        data.val = ptrace(PTRACE_PEEKDATA,
                pid, addr + i * 4,
                NULL);
        memcpy(laddr, data.chars, long_size);
        ++i;
        laddr += long_size;
    }
    j = len % long_size;
    if(j != 0) {
        data.val = ptrace(PTRACE_PEEKDATA,
                pid, addr + i * 4,
                NULL);
        memcpy(laddr, data.chars, j);
    }
    str[len] = '\0';
}
        
void putdata(pid_t pid, long addr,
        char *str, int len)
{   
    char *laddr;
    int i, j;
    union u {
        long val;
        char chars[long_size];
    }data;
    i = 0;
    j = len / long_size;
    laddr = str;
    while(i < j) {
        memcpy(data.chars, laddr, long_size);
        ptrace(PTRACE_POKEDATA, pid,
                addr + i * 4, data.val);
        ++i;
        laddr += long_size;
    }
    j = len % long_size;
    if(j != 0) {
        memcpy(data.chars, laddr, j);
        ptrace(PTRACE_POKEDATA, pid,
                addr + i * 4, data.val);
    }
}
        
void build_jmp_asm(char* asm_bin, int* len, unsigned long freeaddr)
{
    append_asm(asm_bin, len, 0xFCA0F001, 4); //b from 0x8950 to 0xa294
}
        
void build_fun_asm(char* asm_bin, int* len)
{
    unsigned long block_uid = 10038;
            
    append_asm(asm_bin, len, 0xB502, 2); //push {r1,lr}
            
    //fun asm
    append_asm(asm_bin, len, 0xB4FF, 2); // push {r0-r7}
    append_asm(asm_bin, len, 0x6967, 2); // LDR R7, [R4,#0x14]
    append_asm(asm_bin, len, 0x4B05, 2); // ldr r3,=block_uid
    append_asm(asm_bin, len, 0x429F, 2); // CMP R7, R3
    append_asm(asm_bin, len, 0xBCFF, 2); // pop {r0-r7}
    append_asm(asm_bin, len, 0xD002, 2); // BEQ return -1
            
    //return
    append_asm(asm_bin, len, 0x6803, 2); //LDR R3, [R0]
    append_asm(asm_bin, len, 0x6822, 2); //LDR R2, [R4]
    append_asm(asm_bin, len, 0xBD02, 2); //pop {r1,pc}
            
    //return -1
    append_asm(asm_bin, len, 0x2300, 2); //mov r3, #0
    append_asm(asm_bin, len, 0x2201, 2); //mov r2, #1
            
    append_asm(asm_bin, len, 0xBD02, 2); //pop {r1,pc}
    append_asm(asm_bin, len, 0x1C00, 2); //nop
            
    //write return address
    append_asm(asm_bin, len, block_uid, 4);
}
        
void print_asm(char* asm_bin, int len)
{
    int i;
    for(i=0; i<len; i++)
    {
        printf("%x ", *(asm_bin+i));
    }
    printf("\n\n");
}
        
void tracePro(int pid)
{
    unsigned long replace_addr;
    unsigned long freeaddr;
            
    char asm_jump[32];
    int asm_jump_len=0;
    char asm_fun[1024];
    int asm_fun_len=0;
            
    char temp[1024];
            
    replace_addr = 0x8950;
    freeaddr = 0xA294;
        
    build_jmp_asm(asm_jump, &asm_jump_len, freeaddr);
    build_fun_asm(asm_fun, &asm_fun_len);
                
                
    putdata(pid, replace_addr, asm_jump, asm_jump_len);
    putdata(pid, freeaddr, asm_fun, asm_fun_len);
                
                
    getdata(pid, replace_addr, temp, 64);
    print_asm(temp, 64);
    getdata(pid, freeaddr, temp, 64);
    print_asm(temp, 64);
        
}
        
int main(int argc, char *argv[])
{   
    if(argc != 2) {
        printf("Usage: %s <pid to be traced>\n", argv[0], argv[1]);
    return 1;
    }
            
    pid_t traced_process;
    int status;
    traced_process = atoi(argv[1]);
            
    if(0 != ptrace(PTRACE_ATTACH, traced_process, NULL, NULL))
    {
        printf("Trace process failed:%d.\n", errno);
    return 1;
    }
            
    tracePro(traced_process);
    ptrace(PTRACE_DETACH, traced_process, NULL, NULL);
            
    return 0;
}


其中replace_addr = 0x8950;是我们修改svcmgr_handler函数进行拦截的地址。freeaddr = 0xA294;是我们找到的可以插入我们的逻辑代码并且不会影响原有程序的地址。

我觉得程序已经写的很明白了,不需要再进一步解释了,后面开始实测。

4. 测试
先运行模拟器,上传trace程序。运行SmsSender1和系统自带的SMS程序,均可正常运行发送短信。

然后ps查看servicemanager的pid,发现是28. 运行trace 28, 修改内存,inject代码,发现我们的代码已成功写入:



然后运行系统自带的SMS,可正常运行发送短信。运行SmsSender1,无法正常运行。

事实证明App Control获得成功,哇咔咔~~
分享到:
评论

相关推荐

    ptrace安卓程序注入例子

    在这个“ptrace安卓程序注入例子”中,我们将深入探讨如何利用ptrace机制在Android平台上进行程序注入,特别是与Java Native Interface(JNI)相结合的应用。 首先,ptrace是一个强大的调试工具,它允许tracer读取...

    Ptrace-for-Android:一个用户级调试应用程序,可以跟踪 Android 中正在运行的应用程序

    所以在这个项目中,我们设计了一个应用程序跟踪器,它基本上适用于 android 平台,它具有跟踪应用程序与内核交互的能力。我们的跟踪器列出了所有交互,这些交互基本上是系统调用,并为任何调试器提供了足够的信息。...

    Android逆向-基础与实践 (七)-ptrace注入

    Android逆向工程作为移动安全领域的重要分支,涵盖了对Android应用程序的代码和行为进行分析、理解、修改和重建的一系列技术。在这一过程中,“ptrace注入”是一种高级技术,它涉及到ptrace系统调用的使用,这是Unix...

    Android_Anti_Debug,android反调试的一个例子。.zip

    2. 反调试:反调试技术则是应用程序采取的一种自我保护机制,用来检测并可能阻止调试器的运行,防止恶意攻击者利用调试器分析和篡改应用逻辑。 二、Android_Anti_Debug项目详解 1. 检测调试器:项目主要通过检查...

    Android中的反调试代码

    在Android应用开发中,反调试技术是保护应用安全的重要手段之一。它主要用于防止恶意的逆向工程分析,保护代码不被篡改或盗用。本文将深入探讨Android应用中的反调试策略及其实施方法。 首先,我们需要理解调试的...

    Android双进程守护

    在Android系统中,进程守护是一种确保应用程序即使在主进程被杀死的情况下也能持续运行的技术。"Android双进程守护"是Android开发中的一个高级话题,它涉及到系统级服务、进程管理和NDK(Native Development Kit)的...

    Android SO文件保护加固——混淆篇(一)源代码

    在Android应用开发中,SO(Shared Object)文件是C或C++编译的库,用于提供Java层无法实现的性能优化或系统级别的功能。然而,由于SO文件通常包含敏感的原生代码,它们成为了黑客攻击的目标。为了提高应用的安全性,...

    MemDump:android 内存dump,不会触发ptrace检测

    【标题】"MemDump: android 内存dump,不会触发ptrace检测"涉及到的是在Android平台上进行内存dump操作的一种技术,其特点是能够绕过系统的ptrace保护机制,从而更便捷地获取进程的内存快照。在Android系统中,...

    ptrace-master.zip

    在Android系统中,动态链接库(SO文件)是应用程序扩展功能的重要手段,而SO注入则是开发者用于调试、性能优化或安全检测的一种技术。随着Android系统的不断升级,对系统安全性的要求也在提高,如Android 11对权限的...

    PtraceInject.rar

    在Android系统中,Ptrace注入是一种高级的系统级调试和注入技术,它允许一个进程(tracer)监控和控制另一个进程(tracee)。Ptrace是Linux内核提供的一种系统调用,它使得一个进程能够观察和影响另一个进程的执行,...

    HelloJni.tar.gz

    在Android平台上,进程注入是一种高级技术,用于在不同的应用程序或服务之间传递数据、控制行为,甚至执行隐蔽操作。"HelloJni.tar.gz"文件显然与Android的本地开发有关,特别是利用JNI(Java Native Interface)来...

    InjectDemo:实现对Android进程注入功能

    6. **源码分析**:InjectDemo-master压缩包可能包含了项目的源代码,包括C语言编写的动态库和Android应用程序部分。通过分析这些代码,我们可以学习如何使用ptrace、系统调用和其他Android API来实现进程注入。 7. ...

    安卓反调试-解决方案一

    本文将深入探讨“安卓反调试-解决方案一”,这个主题主要关注如何通过定时检测应用程序是否处于调试状态,如果检测到被调试,则退出程序。我们将基于描述中提到的“尼古拉斯.赵四”大师的博客内容进行解析,虽然具体...

    nixcon-2019-nix-on-droid.slides.pdf

    Nix-on-Droid是一个创新项目,旨在让Linux用户能够在Android设备上运行他们熟悉的用户空间应用,而无需依赖于原生的Android系统。这个项目的概念是将Nix包管理器移植到Android系统下,通过Proot实现用户空间的chroot...

    安卓手机APP钩子技术

    在安卓系统中,钩子(Hook)技术是一种高级编程手段,它允许开发者在不修改原始应用程序代码的情况下,拦截和修改程序的关键行为。这种技术在安卓APP开发、调试、性能优化以及安全研究等领域都有广泛的应用。Xposed...

    trace_natives-main.zip

    1. **Android Native Development Kit (NDK)**: Android NDK 是一套工具,允许开发者使用C和C++编写部分应用程序,以利用原生代码的高性能。这些原生代码编译为.so(共享对象)文件,与Dalvik或ART运行时环境一起...

    进程间注入的demo

    在Android系统中,进程间注入(Inter-Process Injection,IPI)是一种高级技术,它允许一个应用程序在另一个进程中运行代码或访问数据,从而突破了进程间的隔离性。本示例"进程间注入的demo"将深入探讨这一概念,并...

    Linux动态代码注入调研1.2Lei1

    1. **linux-inject**: 这是一个可以直接注入.so文件到运行中的应用进程的工具,类似LD_PRELOAD,但可以在程序运行过程中动态注入。 2. **ptrace**: ptrace系统调用提供了一个强大的接口,允许父进程控制和监视子进程...

    And64InlineHook:适用于Android CC ++的轻量级ARMv8-A(ARM64,AArch64,Little-Endian)内联挂钩库

    通过理解和熟练使用这个库,开发者可以创建出具有高度灵活性和可扩展性的应用程序,例如进行复杂的性能优化、日志记录、调试和自定义行为的插入。不过,使用内联挂钩技术需要对底层系统和编程有深入理解,以确保代码...

Global site tag (gtag.js) - Google Analytics