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创建的虚拟环境下的运行效果:(使用的命令 ...
随机推荐
- ECMall关于数据查询缓存的问题
刚接触Ecmall的二次开发不久,接到一个任务.很常见的任务,主要是对数据库进行一些操作,其中查询的方法我写成这样: 01 function get_order_data($goods_id) 02 ...
- 创建支持CRUD(增删改查)操作的Web API(二)
一:准备工作 你可以直接下载源码查看 Download the completed project. 下载完整的项目 CRUD是指“创建(C).读取(R).更新(U)和删除(D)”,它们是四个 ...
- 使用IRP进行文件操作
使用IRP进行文件操作 首先声明这个是菜鸟—我的学习日记,不是什么高深文章,高手们慎看. 一定要先感谢为技术的进步而付出辛勤汗水的人,感谢他们对技术的共享. 一个通用IRP访问文件的十六进制编辑器(开 ...
- Qt创建窗体的过程
版权声明 本文为原创作品,请尊重作者的劳动成果.转载必须保持文章完整性,并以超链接形式注明原始作者“ tingsking18”和 主站点地址,方便其他朋友提问和指正. QT源码解析(一) QT创建窗口 ...
- Android 自定义组件随着手指自动画圆
首先自定义一个View子类: package com.example.androidtest0.myView; import android.content.Context; import andro ...
- android ListView异步加载图片(双缓存)
首先声明,参考博客地址:http://www.iteye.com/topic/685986 对于ListView,相信很多人都很熟悉,因为确实太常见了,所以,做的用户体验更好,就成了我们的追求... ...
- C++解析JSON之JsonCPP
一.JSON简介 JSON全称为JavaScript ObjectNotation,它是一种轻量级的数据交换格式,易于阅读.编写.解析. JSON由两种基本结构构成: )"名称/值" ...
- 手机归属地查询-IP地址查询-身份证查询-域名备案查询--Api接口
使用这些接口是需要密钥的 公共密钥 appkey: 10003 secret: d1149a30182aa2088ef645309ea193bf 生成后sign: b59bc3ef6191eb9f ...
- WordPress Woopra Analytics插件‘ofc_upload_image.php’任意PHP代码执行漏洞
漏洞名称: WordPress Woopra Analytics插件‘ofc_upload_image.php’任意PHP代码执行漏洞 CNNVD编号: CNNVD-201310-195 发布时间: ...
- 【转】Android4.4(MT8685)源码蓝牙解析--BLE搜索
原文网址:http://blog.csdn.net/u013467735/article/details/41962075 BLE:全称为Bluetooth Low Energy.蓝牙规范4.0最重要 ...