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创建的虚拟环境下的运行效果:(使用的命令 ...
随机推荐
- python之---类和实例
类和实例: 面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但 ...
- xapian安装
xapian安装:$ su enter your root password # rpm -ivh http://rpm.eprints.org/rpm-eprints-org-key-1-1.noa ...
- Java运算符(一)equals方法与“==”
超类Object的equals只是比较两者之间的引用对象是否相同,这一点跟操作符“==”是一样的. 在基本数据类型中,“==”用于比较两者之间的值(内容)是否相等. 在引用类型中,“==”用于比较两者 ...
- MySQL 5.6 root密码丢失
windows下mysql密码忘记了 第一步:netstat -nat(可以查看mysql是否启动了,如果启动了,可以用输入net stop mysql(或者通过任务管理器结束进程)) 第二步:my ...
- C++ Primer 随笔 Chapter 4 数组和指针
1.数组:数组是由类型名.标识符和维数组成的符合数据类型,类型名规定了存放在数组中的元素类型,维数规定数组中包含元素的个数而标识符就是数组的名称.例如: int arr[10]; 其中 int 是类 ...
- bzoj2285
完全是为了拼凑才出出来的吧先分数规划求出到基地入口的最小安全系数然后再最小点权覆盖集,只不过这里是带一定精度实数的流,其实是一样的 ; eps=0.001; type way=record po,ne ...
- 解决MySQL服务启动时报1067错误
工具/原料 MySQL_5.6.24_win32 方法/步骤 当我们安装完Mysql时,如果在服务当中(可以在“运行”-->"service.msc"打开并查看)无法看到My ...
- Windows SharePoint Services 3.0编码开发工具和技巧(Part 1 of 2)
转:http://blog.csdn.net/mattwin/article/details/2074984 WSSv3 Technical Articles_Windows SharePoint S ...
- (转载)C++ const成员初始化问题
(转载)http://www.189works.com/article-45135-1.html Const成员如其它任何成员一样,简单考虑其出现在三个位置:全局作用域.普通函数内部.类里面. 下面请 ...
- Interviews3D: APlatform for Interactive Handing of Massive Data Sets 读后感
横向比较: Inadequacy of current system design( 现代系统和一些软件的不足) 软件特点: Output sensitivity Out-of core data h ...