第一篇 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,&regs)==-)
{
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,&regs) == -)
{
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,&regs);
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,&regs);
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,&regs,sizeof(regs));
regs.rip = addr;
printf("new rip :%llx\n",regs.rip);
if(ptrace(PTRACE_SETREGS,tracee,NULL,&regs) == -)
{
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,&regs);
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的更多相关文章

  1. Linux Ptrace 详解

    转 https://blog.csdn.net/u012417380/article/details/60470075 Linux Ptrace 详解 2017年03月05日 18:59:58 阅读数 ...

  2. linux ptrace I

    这几天通过<游戏安全--手游安全技术入门这本书>了解到linux系统中ptrace()这个函数可以实现外挂功能,于是在ubuntu 16.04 x86_64系统上对这个函数进行了学习. 参 ...

  3. linux ptrace I【转】

    转自:https://www.cnblogs.com/mmmmar/p/6040325.html 这几天通过<游戏安全——手游安全技术入门这本书>了解到linux系统中ptrace()这个 ...

  4. linux ptrace学习

    ptrace提供了一种使父进程得以监视和控制其它进程的方式,它还能够改变子进程中的寄存器和内核映像,因而可以实现断点调试和系统调用的跟踪.学习linux的ptrace是为学习android adbi框 ...

  5. Android Ptrace Inject

    之前介绍了Android平台上3种常见的hook方法,而hook的前提是进程注入,通过进程注入我们可以将模块或代码注入到目标进程中以便对其空间内的数据进行操作,本篇文章介绍基于ptrace函数的注入技 ...

  6. linux下 玩转ptrace

    译者序:在开发Hust Online Judge的过程中,查阅了不少资料,关于调试器技术的资料在网上是很少,即便是UNIX编程巨著<UNIX环境高级编程>中,相关内容也不多,直到我在 ht ...

  7. Playing with ptrace, Part I

    X86_64 的 Redhat / Centos / Scientific 下面,若要编译.运行32位程序,需要安装以下包: yum install libgcc.i686 yum install g ...

  8. linux应用调试技术之GDB和GDBServer

    1.调试原理 GDB调试是应用程序在开发板上运行,然后在PC机上对开发板上得应用程序进行调试,PC机运行GDB,开发板上运行GDBServer.在应用程序调试的时候,pc机上的gdb向开发板上的GDB ...

  9. linux内核学习之二 一个精简内核的分析(基于时间片轮转)

    一   实验过程及效果 1.准备好相关的代码,分别是mymain.c,mypcb.h,myinterrupt.c ,如下图,make make成功: 在qemu创建的虚拟环境下的运行效果:(使用的命令 ...

随机推荐

  1. 【网络流24题】No.7 试题库问题 (最大流,二分图多重匹配)

    [题意] 假设一个试题库中有 n 道试题. 每道试题都标明了所属类别. 同一道题可能有多个类别属性.现要从题库中抽取 m 道题组成试卷.并要求试卷包含指定类型的试题. 试设计一个满足要求的组卷算法. ...

  2. 【HDU 2855】 Fibonacci Check-up (矩阵乘法)

    Fibonacci Check-up Problem Description Every ALPC has his own alpc-number just like alpc12, alpc55, ...

  3. gdb调试高级用法

    Linux下进程崩溃时定位源代码位置 如何在调试内核时,同时可以调试应用程序的做法: (cskygdb) c Continuing. ^C Program received signal SIGINT ...

  4. 如何在C++中使用WebService

    gsoap主页 http://sourceforge.net/projects/gsoap2   使用gsoap生成所需的WebService 下载后的gsoap包为:(点击到我的资源中下载) 将他解 ...

  5. View以自身中心旋转的代码解惑

    matrix.preTranslate(-centerX, -centerY); matrix.postTranslate(centerX, centerY); 经常在中心旋转的应用中看到这段代码. ...

  6. 【CF】110 Div.1 B. Suspects

    这题目乍眼一看还以为是2-sat.其实很水的,O(n)就解了.枚举每个人,假设其作为凶手.观察是否满足条件.然后再对满足的数目分类讨论,进行求解. /* 156B */ #include <io ...

  7. oracle命中率模型计算

    命中率模型是在owi之前比较常用的一种诊断性能问题的方法,通过命中率的计算,发现系统中的一些设置是否合理,当命中率不高的时候,通过调整一些参数和设置,提高命中率,有效的提高系统的性能和吞吐量.但当系统 ...

  8. Xcode5下去除Icon高光

    1:Images.xcassets中, 选中图片, 查看属性. 在“iOS icon is pre-rendered” 打勾 2:Info.plist中修改 (1):添加 Icon already i ...

  9. 昂贵的聘礼--POJ1062

    昂贵的聘礼 Time Limit : 2000/1000ms (Java/Other)   Memory Limit : 20000/10000K (Java/Other) Total Submiss ...

  10. Git起步--git安装与初次运行git前配置

    在你开始使用 Git 前,需要将它安装在你的计算机上. 即便已经安装,最好将它升级到最新的版本. 你可以通过软件包或者其它安装程序来安装,或者下载源码编译安装. 一.Git安装 1. 在linux上安 ...