距离我上一篇研究ptrace的随笔http://www.cnblogs.com/zealotrouge/p/3544147.html已经过去半年了,最近不忙的时候抽空继续研究了下。同样,参考了Pradeep Padala的博文http://www.linuxjournal.com/article/6210,对其中断点部分比较感兴趣。因为从开始学习编程之日起,调试就是我们不可或缺的重要工具,而调试的基础就在于断点,那么,断点是如何让一个运行中的程序暂停的呢?背后的机制又是什么?为了探寻这个问题,我研究了上面的文章,并在自己的机器上(环境为Ubuntu12.04 + Intel x86_64 i5)动手实现了一下x64版本。

  下面是整个问题研究的思路,按这个思路来给大家展示代码。首先,我们有一个简单的源程序tracedProcess.c,简单到仅仅是每隔1s输出一行"I'm running",这种简单的程序比较适合初学者分析汇编代码的语义,并找到容易设置断点的地方:

 /*
tracedProcess.c
author: pengyiming
*/ #include <stdio.h> void main()
{
while()
{
printf("I'm running\n"); sleep();
}
}

  gcc编译如上代码:

gcc -o tracedProcess.o tracedProcess.c

  objdump分析目标文件tracedProcess.o,得到如下输出(截取main部分):

objdump -d tracedProcess.o

 <main>:
: push %rbp
: e5 mov %rsp,%rbp
: bf 5c mov $0x40065c,%edi
40054d: e8 de fe ff ff callq <puts@plt>
: bf mov $0x1,%edi
: b8 mov $0x0,%eax
40055c: e8 ef fe ff ff callq <sleep@plt>
: eb e5 jmp <main+0x4>

  简单分析下:

  0x400544  这个虚地址是main函数的入口

  0x400544~0x400547  是所有函数的默认动作,新建一个函数栈

  0x400548~0x40054c  将0x40065c传给edi寄存器,edi是字符串操作寄存器,存储的是字符串地址,0x40065c是只读区地址,用后面的getData()函数可以打出来,发现就是"I'm running"这个字符串

  0x40054d~0x400551  callq执行一个函数调用,0x400430是此函数的入口,不难看出就是printf()函数链接到此目标文件的地址

  0x400552~0x400556  清空edi寄存器

0x400557~0x40055b  printf()无返回值,无需传递返回值地址给eax

0x40055c~0x400560  callq执行一个函数调用,sleep()

0x400561  跳转到0x400548进入下一个循环

  分析完后,可以分析出,如果想把断点加在printf("I'm running\n");这条语句上,我们可以把0x400548作为断点。

  合适打断点的地址找到后,我们就要想想如何让程序暂停,继续参考上一篇博客中提到的Intel处理器开发手册,发现可以使用Trap指令使程序暂停运行。具体是使用int 0x80进入内核态,然后调用Trap指令——int3,只要CPU执行了这个指令,即可让程序暂停并处于一直等待状态,所以我们需要用ptrace在tracedProcess.o运行时,操作CPU寄存器和注入int 0x80 int3指令,一旦程序执行完int3,即可断点成功;当然,在断点后,我们希望程序能恢复运行,我们还需要备份CPU寄存器和原来代码段中的指令,以便之后的恢复。

  小结一下,断点+恢复需要两次注入来实现:

  一、断点注入步骤

  (1)PTRACE_ATTACH附着被注入进程(会暂停被注入进程),备份当前的寄存器值

  (2)备份注入地址处指令

  (3)替换注入地址处指令为Trap指令

  (4)PTRACE_CONT使被注入进程继续执行,直到执行完Trap指令

  二、恢复注入步骤

  (1)恢复之前的寄存器值(注:为了方便,所有的寄存器都备份了,实质上是为了恢复栈指针rsp&rbp和指令指针rip)

  (2)恢复注入地址处指令

  (3)PTRACE_DETACH使被注入进程继续执行,并脱离被注入进程

  代码如下:

 /*
ptrace4.c
author: pengyiming
description:
1, attach a test process, insert a break point
2, sleep for 5s then continue it
*/ #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <sys/syscall.h>
#include <unistd.h> #define WORD_SIZE sizeof(long) static unsigned long injectAddress = 0x400548; // converter long to char[]
union
{
long rawData;
char strData[WORD_SIZE];
} converter; void getData(pid_t pid, unsigned long dataAddr, unsigned long dataLen, char * const p_data)
{
// PEEKDATA counter
int counter = ;
// PEEKDATA max count
int maxCount = dataLen / WORD_SIZE;
if (dataLen % WORD_SIZE != )
{
maxCount++;
}
// moving pointer
void * p_moving = p_data; while (counter < maxCount)
{
memset(&converter, , WORD_SIZE);
converter.rawData = ptrace(PTRACE_PEEKDATA, pid, dataAddr + counter * WORD_SIZE, NULL); memcpy(p_moving, converter.strData, WORD_SIZE);
p_moving += WORD_SIZE;
counter++;
}
p_data[dataLen] = '\0';
} void setData(pid_t pid, unsigned long dataAddr, unsigned long dataLen, char * const p_data)
{
// POKEDATA counter
int counter = ;
// POKEDATA max count
int maxCount = dataLen / WORD_SIZE;
// data left length (prevent out of range in memory when written)
int dataLeftLen = dataLen % WORD_SIZE;
// moving pointer
void * p_moving = p_data; // write part of data which align to WORD_SIZE
int ret;
while (counter < maxCount)
{
memset(&converter, , WORD_SIZE);
memcpy(converter.strData, p_moving, WORD_SIZE);
ret = ptrace(PTRACE_POKEDATA, pid, dataAddr + counter * WORD_SIZE, converter.rawData); p_moving += WORD_SIZE;
counter++;
} // write data left
if (dataLeftLen != )
{
memset(&converter, , WORD_SIZE);
memcpy(converter.strData, p_moving, dataLeftLen);
ret = ptrace(PTRACE_POKEDATA, pid, dataAddr + counter * WORD_SIZE, converter.rawData);
}
} void debugRegs(char * pTag, pid_t pid)
{
struct user_regs_struct regs;
memset(&regs, , sizeof(struct user_regs_struct));
ptrace(PTRACE_GETREGS, pid, NULL, &regs); printf("----%s -----\n", pTag);
printf("regs.cs = 0x%lx\n", regs.cs);
printf("regs.rip = 0x%lx\n", regs.rip);
printf("regs.rsp = 0x%lx\n", regs.rsp);
printf("regs.rbp = 0x%lx\n", regs.rbp); printf("regs.rax = 0x%lx\n", regs.rax);
printf("regs.rbx = 0x%lx\n", regs.rbx);
printf("regs.rcx = 0x%lx\n", regs.rcx);
printf("regs.rdx = 0x%lx\n", regs.rdx);
printf("regs.rsi = 0x%lx\n", regs.rsi);
printf("regs.rdi = 0x%lx\n", regs.rdi);
printf("regs.orig_rax = 0x%lx\n", regs.orig_rax);
printf("regs.eflags = 0x%lx\n", regs.eflags);
printf("regs.ds = 0x%lx\n", regs.ds);
printf("regs.es = 0x%lx\n", regs.es);
printf("regs.fs = 0x%lx\n", regs.fs);
printf("regs.gs = 0x%lx\n", regs.gs);
printf("regs.fs_base = 0x%lx\n", regs.fs_base);
printf("regs.gs_base = 0x%lx\n", regs.gs_base);
printf("----%s -----\n", pTag);
} void debugInstructionByAddr(char * pTag, pid_t pid, unsigned long addr)
{
char instruction[WORD_SIZE];
memset(instruction, , WORD_SIZE);
getData(pid, addr, WORD_SIZE, instruction); printf("0x%lx, %s instruction =", addr, pTag);
int index;
for (index = ; index < WORD_SIZE; index++)
{
printf(" 0x%02x ", (unsigned char) instruction[index]);
}
printf("\n");
} int main()
{
pid_t pid = ;
int waitStatus = -; // enter the pid of the process you want attach
printf("enter pid that you want attach : ");
scanf("%d", &pid); int ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
if (ret == -)
{
printf("attach error\n");
return ;
}
printf("attach success\n"); // wait
waitpid(pid, &waitStatus, ); /* pause the process */
// 1, backup the orignal regs
struct user_regs_struct backupRegs;
memset(&backupRegs, , sizeof(struct user_regs_struct));
ptrace(PTRACE_GETREGS, pid, NULL, &backupRegs);
// 2, backup the instruction
char backupInstruction[WORD_SIZE];
memset(backupInstruction, , WORD_SIZE);
getData(pid, injectAddress, WORD_SIZE, backupInstruction);
// 3, insert break instruction —— "int 0x80, int3"
char breakInstruction[WORD_SIZE] = { 0xcd, 0x80, 0xcc, 0x0, 0x0, 0x0, 0x0, 0x0 };
setData(pid, injectAddress, WORD_SIZE, breakInstruction);
// 4, PTRACE_CONT for execute "int 0x80, int3" instruction
ret = ptrace(PTRACE_CONT, pid, NULL, NULL);
if (ret == -)
{
printf("continue error\n");
return ;
}
printf("continue success\n"); // wait
waitpid(pid, &waitStatus, );
/* pause the process */ // wait for 5 seconds
printf("the process is paused for 5s...\n");
sleep(); /* continue the process */
// 1, restore regs to the orignal address
ptrace(PTRACE_SETREGS, pid, NULL, &backupRegs);
// 2, restore instruction
setData(pid, injectAddress, WORD_SIZE, backupInstruction);
// 3, PTRACE_DETACH for execute the orignal code
ret = ptrace(PTRACE_DETACH, pid, NULL, NULL);
if (ret == -)
{
printf("detach error\n");
return ;
}
printf("detach success\n");
/* continue the process */ return ;
}

  代码执行结果:被注入进程输出"I'm running",ptrace4进程注入后,被注入进程暂停输出"I'm running",5s后恢复。

  最后说下调试过程中遇到的一个问题,在恢复被注入进程的运行时,总是会导致被注入进程segment fault,代码中加入了大量的debug函数用于分析问题,最后利用dmesg工具发现原因是恢复时写入注入地址指令有误,根本原因是之前认为breakInstruction只有3bytes,所以备份指令也只需要3bytes,于是便存储在char backupInstruction[3]中,而ptrace的读写单位都是word,在x64下是8bytes,所以在恢复时写入的指令为了3bytes的正确指令+5bytes的0x00空指令所致~

玩转Hook——Android权限管理功能探讨(二)的更多相关文章

  1. 玩转Hook——Android权限管理功能探讨(一)

    随着Android设备上的隐私安全问题越来越被公众重视,恶意软件对用户隐私,尤其是对电话.短信等私密信息的威胁日益突出,各大主流安全软件均推出了自己的隐私行为监控功能,在root情况下能有效防止恶意软 ...

  2. Android权限管理之Android 6.0运行时权限及解决办法

    前言: 今天还是围绕着最近面试的一个热门话题Android 6.0权限适配来总结学习,其实Android 6.0权限适配我们公司是在今年5月份才开始做,算是比较晚的吧,不过现在Android 6.0以 ...

  3. Android权限管理之Permission权限机制及使用

    前言: 最近突然喜欢上一句诗:"宠辱不惊,看庭前花开花落:去留无意,望天空云卷云舒." 哈哈~,这个和今天的主题无关,最近只要不学习总觉得生活中少了点什么,所以想着围绕着最近面试过 ...

  4. Android权限管理之RxPermission解决Android 6.0 适配问题

    前言: 上篇重点学习了Android 6.0的运行时权限,今天还是围绕着Android 6.0权限适配来总结学习,这里主要介绍一下我们公司解决Android 6.0权限适配的方案:RxJava+RxP ...

  5. android: Android 权限管理小结

    一. 概述 感谢郭神,自从Android6.0发布以来,在权限上做出了很大的变动,不再是之前的只要在manifest设置就可以任意获取权限,而是更加的注重用户的隐私和体验,不会再强迫用户因拒绝不该拥有 ...

  6. Android权限管理PermissionsDispatcher2.3.2使用+原生6.0权限使用

    PermissionsDispatcher2.3.2使用 Android6.0权限官网https://developer.android.com/about/versions/marshmallow/ ...

  7. Android 权限管理

    从 Android 6.0(API 级别 23)开始,用户开始在应用运行时向其授予权限,而不是在应用安装时授予.此方法可以简化应用安装过程,因为用户在安装或更新应用时不需要授予权限.它还让用户可以对应 ...

  8. Android权限管理知识学习记录

    一.Android权限背景知识 在Android 6.0之前,所申请的权限只需要在AndroidManifest.xml列举就可以了,从而容易导致一些安全隐患,因此,在Android 6.0时,Goo ...

  9. PHP实现权限管理功能

    权限管理系统,它主要是为了给不同的用户设定不同的权限,从而实现不同权限的用户登录之后使用的功能不一样. 首先先看下数据库 总共有5张表,users,roles和roleswork 3张表与另外2张表形 ...

随机推荐

  1. PHP学习 函数 function

    参数默认值function drink($kind ='tea'){echo 'would you please a cup'.$kind.'<br>';} drink();drink(' ...

  2. Mac下搭建lamp

    Mac下搭建lamp Mac 自带了Apache,并默认支持PHP环境,只需要配置Apache和PHP即可使用.需要单独安装mysql服务端. Apache 基础配置 Apache支持PHP配置 Ap ...

  3. SQL执行结果操作

    1. 返回执行结果中的一行 mysql_fetch_row() 返回执行结果的当前行的数值数组,执行这个函数后,结果指向下一行. e.g. $row = mysql_fetch_row($result ...

  4. 【Beta阶段】第五次Scrum Meeting!

    每日任务内容: 本次会议为第五次Scrum Meeting会议~ 由于本次会议项目经理召开时间依旧较晚,在公寓7层召开,女生参与了线上会议. 队员 昨日完成任务 明日要完成任务 刘乾 #167(未完成 ...

  5. Linux 第五章 学习笔记

    ---恢复内容开始--- 第五章 系统调用 一.与内核通信 1.系统调用在用户控件进程和硬件设备之间添加了一个中间层. 为用户空间提供了一种硬件的抽象接口 系统调用保证了系统的稳定和安全 每个进程都运 ...

  6. UIO,大页内存,CPU亲和性,NUMA机制等

    Linux环境下的UIO(Userspace I/O) UIO 用户空间下驱动程序的支持机制.DPDK使用UIO机制使网卡驱动程序运行在用户态,并采用轮询和零拷贝方式从网卡收取报文,提高收发报文的性能 ...

  7. 第二个Sprint冲刺第 八天(燃尽图)

    因为今天停电了,所以我们也休息一天!

  8. 作业四 任务分解(WBS)

    近日忙于实验,未来得及完成任务分解昨晚召开了紧急会议,才确定了任务划分.主体分配如下:三名编程人员,一个主编两个辅编,一人做需求分析,一人做程序测试,一人专司文档. 具体细节如下:在剩余的三周左右的时 ...

  9. ELK Stack (1) —— ELK + Redis安装

    ELK Stack (1) -- ELK + Redis安装 摘要 安装Elasticsearch.Logstash.Kibana与Redis以实现一个日志收集平台 版本 elasticsearch版 ...

  10. 【壹拾壹周】final_review

    项目名:俄罗斯方块 组名:新蜂 组长:武志远 组员:宫成荣 杨柳 谢孝淼 李桥 final Review会议 时间:2016.12.3 会议内容 设想和目标 1.在final阶段发布时的预期目标是什么 ...