linux ptrace II
第一篇 linux ptrace I
在之前的文章中我们用ptrace函数实现了查看系统调用参数的功能。在这篇文章中,我们会用ptrace函数实现设置断点,跟代码注入功能。
参考资料
Playing with ptrace, Part I
Playing with ptrace, Part II
英文好的推荐直接看老外的文章。但是其代码是运行在x86系统上的,本文中将其移植到了x86_64系统。
进程附加
在之前的文章中,我们都是trace自己程序fork出来的子进程,现在我们来看一下如何trace一个正在运行的进程。
trace一个正在运行的进程称为进程附加(attach)。使用的是ptrace函数的PTRACE_ATTACH参数。当一个进程成功附加到一个正在运行的进程时,此进程会成为被附加进程的父进程,同时向被附加的进程发送一个SIGSTOP信号,让其停止,这时我们就可以对其进行操纵。当我们完成对tracee的操作后就可以使用ptrace的PTRACE_DETACH参数停止附加。
我们用一个循环来模拟一个正在运行的进程,下边称此程序为hello
int main()
{ int i;
for(i = ;i < ; ++i) {
printf("My counter: %d\n", i);
sleep();
}
return ;
}
本文之后所有的程序都以此程序当作被附加的进程。在其运行之后我们可以使用 ps -h 命令查看其进程号(pid),以便我们通过进程号对其附加。
接下来看一个简单的进程附加的例子
#include <sys/types.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> int main(int argc, char *argv[])
{
pid_t traced_process;
struct user_regs_struct regs;
long ins;
if(argc != )
{
puts("no pid input");
exit();
}
traced_process = atoi(argv[]);
printf("try to trace pid :%u\n",traced_process);
if(ptrace(PTRACE_ATTACH,traced_process,NULL,NULL)==-)
{
perror("trace error:");
}
wait(NULL);
if(ptrace(PTRACE_GETREGS,traced_process,NULL,®s)==-)
{
perror("trace error:");
}
ins = ptrace(PTRACE_PEEKTEXT,traced_process,regs.rip,NULL);
if(ins == -)
{
perror("trace error:");
}
printf("EIP:%llx Instruction executed: %lx\n",regs.rip,ins);
if(ptrace(PTRACE_DETACH,traced_process,NULL,NULL)==-)
{
perror("trace error:");
}
return ;
}
上边的程序对hello进行了附加,等其停下来以后,读取hello要运行的下一条指令的内容(地址存在rip中)。读取之后停止附加,让hello继续运行。
设置断点
下面要实现的是许多调试器都拥有的设置断点功能。
设置断点的原理:假如要在A地址处设置断点,可以把A地址处的指令替换为一条trap指令,就是说当tracee运行完这条被替换的指令后会自动停止,然后告知tracer自己已停止。
代码如下:
#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h> #define LONG_SIZE 8 void getdata(pid_t child, long addr,char *str,int len)
{
char *laddr = str;
int i = ,j = len/LONG_SIZE;
union u{
long val;
char chars[LONG_SIZE];
} word;
while(i<j)
{
word.val = ptrace(PTRACE_PEEKDATA,child,addr + i*LONG_SIZE,NULL);
if(word.val == -)
perror("trace error");
memcpy(laddr,word.chars,LONG_SIZE);
++i;
laddr += LONG_SIZE;
}
j = len %LONG_SIZE;
if(j!=)
{
word.val == ptrace(PTRACE_PEEKDATA,child,addr + i*LONG_SIZE,NULL);
if(word.val == -)
perror("trace error");
}
str[len] = '\0';
} void putdata(pid_t child,long addr,char *str,int len)
{
char *laddr = str;
int i = , j = len/LONG_SIZE;
union u{
long val;
char chars[LONG_SIZE];
}word;
while(i<j)
{
memcpy(word.chars,laddr,LONG_SIZE);
if(ptrace(PTRACE_POKEDATA,child,addr+i*LONG_SIZE,word.val) == -)
perror("trace error");
++i;
laddr += LONG_SIZE;
}
j = len % LONG_SIZE;
if(j != )
{
word.val = ;
memcpy(word.chars,laddr,j);
if(ptrace(PTRACE_POKEDATA,child,addr+i*LONG_SIZE,word.val) == -)
perror("trace error");
}
} void printBytes(const char* tip,char* codes,int len)
{
int i;
printf("%s :",tip);
for(i = ;i<len;++i)
{
printf("%02x ",(unsigned char)codes[i]);
}
puts("");
} #define CODE_SIZE 8 int main(int argc ,char *argv[])
{
if(argc != )
{
puts("no pid input");
exit();
}
pid_t traced_process;
struct user_regs_struct regs;
long ins;
char code[LONG_SIZE] = {0xcc};
char backup[LONG_SIZE];
traced_process = atoi(argv[]);
printf("try to attach pid:%u\n",traced_process);
if(ptrace(PTRACE_ATTACH,traced_process,NULL,NULL) == -)
{
perror("trace attach error");
}
wait(NULL);
if(ptrace(PTRACE_GETREGS,traced_process,NULL,®s) == -)
{
perror("trace get regs error");
}
//copy instructions into backup variable
getdata(traced_process,regs.rip,backup,CODE_SIZE);
printBytes("get tracee instuction",backup,LONG_SIZE);
puts("try to set breakpoint");
printBytes("set breakpoint instruction",code,LONG_SIZE);
putdata(traced_process,regs.rip,code,CODE_SIZE);
if(ptrace(PTRACE_CONT,traced_process,NULL,NULL) == -)
{
perror("trace continue error");
}
wait(NULL);
puts("the process stopped Press <Enter> to continue");
getchar();
printBytes("place breakpoint instruction with tracee instruction",backup,LONG_SIZE);
putdata(traced_process,regs.rip,backup,CODE_SIZE);
ptrace(PTRACE_SETREGS,traced_process,NULL,®s);
ptrace(PTRACE_DETACH,traced_process,NULL,NULL);
return ;
}
这里在hello程序停下来后将rip指向的指令备份在了backup数组中,同时把rip指向的指令替换为code数组中的指令。
code数组中0xcc对应为汇编指令的
int3 ;软中断指令,执行后程序会陷入中断停止,同时发送signal,可被tracer接收
当tracer接收到软中断指令发送的signal时,从等待(wait)中被唤醒,把被替换的指令还原回去,同时也将寄存器还原,最后停止附加,让hello继续执行。
示意图如下

代码注入
接下来我们实现最后一个功能,进行代码注入,让hello程序打印出一行 “hello world"
要进行代码注入,首先我们要知道被注入代码的机器指令是什么。
先写出要进行注入代码的汇编代码
section .text global main main:
jmp forward
backward:
pop rsi
mov rax,
mov rdi,
mov rdx,
syscall
int3
forward:
call backward
db "Hello world",0xa
注意,这里为什么要跳来跳去呢,因为我们要定位字符串的地址,在执行 call backward 时,会把其下一条指令的地址,也就是字符串的地址压入栈中。然后 pop rsi 将其取出。
使用nasm工具对其进行汇编
nasm -f elf64 inject.asm
再使用objdump工具查看对应的机器码
objdump -d inject.o
现在就可以将其注入到hello的进程里了。方法同设置断点一样,这里只贴出主函数代码
#define CODE_SIZE 48
int main(int argc ,char *argv[])
{
if(argc<)
{
puts("no pid input");
exit();
}
pid_t tracee = atoi(argv[]);
char code_inject[CODE_SIZE] ={0xeb,0x13,0x5e,0xb8,0x01,0x00,0x00,0x00,0xbf,0x01,0x00,0x00,0x00,0xba,0x0d,0x00,0x00,0x00,0x0f,0x05,0xcc,0xe8,0xe8,0xff,0xff,0xff,0x48,0x65,0x6c,0x6c,0x6f,0x20,0x77,0x6f,0x72,0x6c,0x64,0xa};
char code_backup[CODE_SIZE];
struct user_regs_struct oldregs,regs;
long ins;
if(ptrace(PTRACE_ATTACH,tracee,NULL,NULL) == -)
{
perror("attach error");
}
wait(NULL);
puts("attach success");
ptrace(PTRACE_GETREGS,tracee,NULL,®s);
long addr = freeSpaceAddr(tracee);
// long addr = regs.rip;
printf("find free addr %lx\n",addr);
getdata(tracee,addr,code_backup,CODE_SIZE);
putdata(tracee,addr,code_inject,CODE_SIZE);
memcpy(&oldregs,®s,sizeof(regs));
regs.rip = addr;
printf("new rip :%llx\n",regs.rip);
if(ptrace(PTRACE_SETREGS,tracee,NULL,®s) == -)
{
perror("set regs error");
}
puts("replace instructions success, continue tracee");
if(ptrace(PTRACE_CONT,tracee,NULL,NULL) == -)
{
perror("continue tracee error");
}
wait(NULL);
ptrace(PTRACE_GETREGS,tracee,NULL,®s);
printf("tracee end at rip: %llx\n",regs.rip);
puts("tracee has stopped, putting back original instructions");
putdata(tracee,addr,code_backup,CODE_SIZE);
if(ptrace(PTRACE_SETREGS,tracee,NULL,&oldregs) == -)
{
perror("put original instuctions error");
}
ptrace(PTRACE_DETACH,tracee,NULL,NULL);
return ;
}
以上我们完成了设置断点和代码注入的功能。
linux ptrace II的更多相关文章
- Linux Ptrace 详解
转 https://blog.csdn.net/u012417380/article/details/60470075 Linux Ptrace 详解 2017年03月05日 18:59:58 阅读数 ...
- linux ptrace I
这几天通过<游戏安全--手游安全技术入门这本书>了解到linux系统中ptrace()这个函数可以实现外挂功能,于是在ubuntu 16.04 x86_64系统上对这个函数进行了学习. 参 ...
- linux ptrace I【转】
转自:https://www.cnblogs.com/mmmmar/p/6040325.html 这几天通过<游戏安全——手游安全技术入门这本书>了解到linux系统中ptrace()这个 ...
- linux ptrace学习
ptrace提供了一种使父进程得以监视和控制其它进程的方式,它还能够改变子进程中的寄存器和内核映像,因而可以实现断点调试和系统调用的跟踪.学习linux的ptrace是为学习android adbi框 ...
- Android Ptrace Inject
之前介绍了Android平台上3种常见的hook方法,而hook的前提是进程注入,通过进程注入我们可以将模块或代码注入到目标进程中以便对其空间内的数据进行操作,本篇文章介绍基于ptrace函数的注入技 ...
- linux下 玩转ptrace
译者序:在开发Hust Online Judge的过程中,查阅了不少资料,关于调试器技术的资料在网上是很少,即便是UNIX编程巨著<UNIX环境高级编程>中,相关内容也不多,直到我在 ht ...
- Playing with ptrace, Part I
X86_64 的 Redhat / Centos / Scientific 下面,若要编译.运行32位程序,需要安装以下包: yum install libgcc.i686 yum install g ...
- linux应用调试技术之GDB和GDBServer
1.调试原理 GDB调试是应用程序在开发板上运行,然后在PC机上对开发板上得应用程序进行调试,PC机运行GDB,开发板上运行GDBServer.在应用程序调试的时候,pc机上的gdb向开发板上的GDB ...
- linux内核学习之二 一个精简内核的分析(基于时间片轮转)
一 实验过程及效果 1.准备好相关的代码,分别是mymain.c,mypcb.h,myinterrupt.c ,如下图,make make成功: 在qemu创建的虚拟环境下的运行效果:(使用的命令 ...
随机推荐
- JVM 学习笔记(二)
JVM 堆中几乎存放着java中所有的对象实例,在在垃圾回收前先要判断对象是否已死,这里对对象的判断主要有: 1. 引用计数法 给对象中添加一个引用计数器,每当有一个地方引用他时,计数器就加1:当引 ...
- uva 1482 - Playing With Stones
对于组合游戏的题: 首先把问题建模成NIM等经典的组合游戏模型: 然后打表找出,或者推出SG函数值: 最后再利用SG定理判断是否必胜必败状态: #include<cstdio> #defi ...
- hdu 4739
一个超级超级水的题,不明白当时比赛的时候没有出来: 思路很简单,dfs暴力一下就行,枚举每个顶点,题目一共才20个点,就是20^4方的时间复杂度,完全可以承受: 代码: #include<cst ...
- 【整理】各种Python的IDE(集成开发环境)的总结和对比
原地址:http://www.tuicool.com/articles/rMVJNn 原文 http://www.crifan.com/summary_common_python_ide_pyscr ...
- 练习PYTHON协程之GREENLET
STACKLESS就算了,了解一下原理即可. GREENLET,GEVENT,EVENTLET这些,比较好测试,还是都 撸一次,得个印象. 测试代码都是网上的大路货. from greenlet im ...
- Android 观察系统中短信内容的变化(内容观察者)
//内容观察者(如果系统的短信发生了变化,比如刚获取一条短信,那么将触发onChange方法) ContentResolver contentResolver = getContentResolver ...
- c#4.0新特性之协变与逆变
1.C#3.0以前的协变与逆变 如果你是第一次听说这个两个词,别担心,他们其实很常见.C#4.0中的协变与逆变[1](Covariance and contravariance)有了进一步的完善,主要 ...
- java中计时器的用法Timer和TimerTask的用法__java中利用Timer与TImerTask 计时器间隔执行任务
经常我们都会有这样的需求,要固定的每隔一段时间执行某一个任务.比如: 我们做一个缓存来减少与数据库的交互,而为了使缓存与数据库中的数据尽量达到同步,需要每个固定的一段时间去数据库中的数 ...
- [转]NHibernate之旅(6):探索NHibernate中的事务
本节内容 事务概述 1.新建对象 [测试成功提交] [测试失败回滚] 2.删除对象 3.更新对象 4.保存更新对象 结语 上一篇我们介绍了NHibernate中的Insert, Update, Del ...
- 切换Oracle数据库实例
如果多个实例,需要切换可以采用以下命令: export ORACLE_SID=SID