linux ptrace II

时间:2022-09-02 10:21:53

第一篇 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继续执行。

示意图如下

linux ptrace II

代码注入

接下来我们实现最后一个功能,进行代码注入,让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&comma; 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. 数据结构和算法 &ndash&semi; 6&period;构建字典: DictionaryBase 类和 SortedList 类

      6.1.DictionaryBase 类的基础方法和属性 大家可以把字典数据结构看成是一种计算机化的词典.要查找的词就是关键字,而词的定义就是值. DictionaryBase 类是一种用作专有字 ...

  2. 基于流的自动化构建工具------gulp &lpar;简单配置&rpar;

    项目上线也有一阵子,回头过来看了看从最初的项目配置到开发的过程,总有些感慨,疲软期,正好花点时间,看看最初的配置情况 随着前端的发展,前端工程化慢慢成为业内的主流方式,项目开发的各种构建工具,也出现了 ...

  3. cJSON学习笔记

    1.JSON格式简述 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.易于人阅读和编写,同时也易于机器解析和生成.它基于JavaScript(Standa ...

  4. zoj 3805 Machine

    Machine Time Limit: 2 Seconds      Memory Limit: 65536 KB In a typical assembly line, machines are c ...

  5. ThinkPHP框架二

    ThinkPHP笔记二 1.1 TP框架的配置文件 在TP框架中,所有的配置文件都是自动加载的,加载的顺序:惯例配置<应用配置<调试配置<模块配置<动态配置 1. 惯例配置(T ...

  6. 【翻译mos文章】Linux x86 and x86-64 系统SHMMAX最大

    Linux x86 and x86-64 系统SHMMAX最大值 参考原始: Maximum SHMMAX values for Linux x86 and x86-64 (文件 ID 567506. ...

  7. SPOJ 839 OPTM - Optimal Marks (最小割)(权值扩大,灵活应用除和取模)

    http://www.spoj.com/problems/OPTM/ 题意: 给出一张图,点有点权,边有边权 定义一条边的权值为其连接两点的异或和 定义一张图的权值为所有边的权值之和 已知部分点的点权 ...

  8. springmvc配置文件&lt&semi;context&colon;component-scan&gt&semi;

    在spring.xml配置了这个标签后,spring可以自动去扫描base-pack下面或者子包下面的java文件,如果扫描到有@Component @Controller@Service等这些注解的 ...

  9. 分页函数 pager&period;py

    #!/usr/bin/python env # coding:utf-8 class Pagination(object): # 数据总条数 当前页 每页显示条数 最多显示页面 def __init_ ...

  10. 网络配置br0 brtcl

    1.brctl addbr br0        如果根据第3步,那这里不用写 2.brctl addif br0 eth0    如果第3步写了,这里也不用 这时候用ssh应该会断网... 3.设置 ...