实验要求

打开IDArepeat程序,可以看到先调用了puts,再调用sleep函数。所以实验要求就是将puts函数改成自己的。

要求最终做到,在不打断程序运行的同时,将输出的字符串替换成"My name is 姓名\n"。

实验过程

1. 64位Ubuntu下先安装32位库

我的实验环境是Ubuntu 20.04 x86_64,运行不了repeat程序,显示没有这个程序。

在群里问了一下同学,他们告诉我用readelf -h repeat查看一下文件的属性。

原来repeat是32位的程序,在64位的Ubuntu中运行需要提前安装32位的库。

添加方法如下,首先添加i32架构,然后更新镜像源,再安装相关库就可以了:

sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 -y
sudo apt install lib32z1 -y

2. 利用Preload Hook实现热补丁修补

我不清楚怎么在不使用stdio.h的情况下,输出字符串(因为putsprintf都在stdio.h里)。而且内联汇编也比较麻烦,所以我直接编写了AT&T的32位汇编程序,作为热补丁。

汇编程序patch.s如下,参考AT&T汇编语言初步_farthjun的博客-CSDN博客

.section .data
output:
.ascii "My name is shandianchengzi.\n" .section .text
.globl puts
puts:
movl $4, %eax
movl $1, %ebx
movl $output,%ecx
movl $26,%edx
int $0x80

意思是先声明一个output字符串,内容是"My name is shandianchengzi.\n",长度是26。然后重写puts函数,其中使用第4号系统调用write,构造write(1,output,26),从而输出字符串。

首先将其编译成静态链接库.o文件:

as --32 -o patch.o patch.s

然后用gcc编译成.so文件:

gcc -shared -m32 -o patch.so patch.o

利用Preload Hook直接传入环境变量,来实现热补丁修补:

LD_PRELOAD=./patch.so ./repeat

运行结果如下图所示:

可以看到,补丁是生效的,原来的puts函数被替换了。

3. 利用系统调用ptrace对运行状态的程序进行hook

参考实验指导书和博客:linux下实现在程序运行时的函数替换(热补丁) - __sipl - 博客园 (cnblogs.com)

因为ptrace允许做两个不同名字的函数的替换,因此完全可以再写一个更方便的patch.c程序,也不用顾忌头文件了。

3.1 编写补丁程序

补丁程序patch.c如下:

#include<stdio.h>

int newputs()
{
printf("My name is ShenShandian.\n");
return 0;
}

补丁文件编译:

gcc -m32 -fPIC --shared patch.c -o patch.so

编译报错了fatal error: bits/libc-header-start.h: No such file or directory,百度后发现还是32位系统的问题,再安装一个依赖,就可以编译了:

sudo apt-get install gcc-multilib

再次运行补丁编译指令完成编译。

3.2 编写hook程序

hook程序ptrace_patch.c如下:

#include <stdio.h>
#include <string.h>
#include <elf.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/errno.h>
#include <sys/user.h>
#include <link.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <bits/dlfcn.h> #define IMAGE_ADDR 0x08048000 int mode = 2; struct user_regs_struct oldregs;
Elf32_Addr phdr_addr;
Elf32_Addr dyn_addr;
Elf32_Addr map_addr;
Elf32_Addr symtab;
Elf32_Addr strtab;
Elf32_Addr jmprel;
Elf32_Addr reldyn;
Elf32_Word reldynsz;
Elf32_Word totalrelsize;
Elf32_Word relsize;
unsigned long link_addr;
int nrels;
int nreldyns;
int modifyflag = 0; // 读寄存器
void ptrace_readreg(int pid, struct user_regs_struct *regs)
{
if(ptrace(PTRACE_GETREGS, pid, NULL, regs))
printf("*** ptrace_readreg error ***\n");
} // 写寄存器
void ptrace_writereg(int pid, struct user_regs_struct *regs)
{
if(ptrace(PTRACE_SETREGS, pid, NULL, regs))
printf("*** ptrace_writereg error ***\n");
} // 关联到进程
void ptrace_attach(int pid)
{
if(ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
perror("ptrace_attach");
exit(-1);
}
waitpid(pid, NULL, 0);
ptrace_readreg(pid, &oldregs);
} // 让进程继续
void ptrace_cont(int pid)
{
int stat;
if(ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) {
perror("ptrace_cont");
exit(-1);
}
} // 脱离进程
void ptrace_detach(int pid)
{
ptrace_writereg(pid, &oldregs);
if(ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) {
perror("ptrace_detach");
exit(-1);
}
} // 写指定进程地址
void ptrace_write(int pid, unsigned long addr, void *vptr, int len)
{
int count;
long word;
count = 0;
while(count < len) {
memcpy(&word, vptr + count, sizeof(word));
word = ptrace(PTRACE_POKETEXT, pid, addr + count, word);
count += 4;
if(errno != 0)
printf("ptrace_write failed\t %ld\n", addr + count);
}
} // 读指定进程
int ptrace_read(int pid, unsigned long addr, void *vptr, int len)
{
int i,count;
long word;
unsigned long *ptr = (unsigned long *)vptr;
i = count = 0;
while (count < len) {
word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);
while(word < 0)
{
if(errno == 0)
break;
perror("ptrace_read failed");
return 2;
}
count += 4;
ptr[i++] = word;
}
return 0;
} // 在进程指定地址读一个字符串
char * ptrace_readstr(int pid, unsigned long addr)
{
char *str = (char *) malloc(64);
int i,count;
long word;
char *pa;
i = count = 0;
pa = (char *)&word;
while(i <= 60) {
word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);
count += 4;
if (pa[0] == 0) {
str[i] = 0;
break;
}
else
str[i++] = pa[0];
if (pa[1] == 0) {
str[i] = 0;
break;
}
else
str[i++] = pa[1];
if (pa[2] ==0) {
str[i] = 0;
break;
}
else
str[i++] = pa[2];
if (pa[3] ==0) {
str[i] = 0;
break;
}
else
str[i++] = pa[3];
}
return str;
} // 将指定数据压入进程堆栈并返回堆栈指针
void * ptrace_push(int pid, void *paddr, int size)
{
unsigned long esp;
struct user_regs_struct regs;
ptrace_readreg(pid, &regs);
esp = regs.esp;
esp -= size;
esp = esp - esp % 4;
regs.esp = esp;
ptrace_writereg(pid, &regs);
ptrace_write(pid, esp, paddr, size);
return (void *)esp;
} // 在进程内调用指定地址的函数
void ptrace_call(int pid, unsigned long addr)
{
void *pc;
struct user_regs_struct regs;
int stat;
void *pra;
pc = (void *) 0x41414140;
pra = ptrace_push(pid, &pc, sizeof(pc));
ptrace_readreg(pid, &regs);
regs.eip = addr;
ptrace_writereg(pid, &regs);
ptrace_cont(pid);
} // 得到指向link_map链表首项的指针
struct link_map * get_linkmap(int pid)
{
Elf32_Ehdr *ehdr = (Elf32_Ehdr *) malloc(sizeof(Elf32_Ehdr));
Elf32_Phdr *phdr = (Elf32_Phdr *) malloc(sizeof(Elf32_Phdr));
Elf32_Dyn *dyn = (Elf32_Dyn *) malloc(sizeof(Elf32_Dyn));
Elf32_Word got;
struct link_map *map = (struct link_map *)malloc(sizeof(struct link_map));
int i = 1;
unsigned long tmpaddr;
ptrace_read(pid, IMAGE_ADDR, ehdr, sizeof(Elf32_Ehdr));
phdr_addr = IMAGE_ADDR + ehdr->e_phoff;
printf("phdr_addr\t %p\n", phdr_addr);
ptrace_read(pid, phdr_addr, phdr, sizeof(Elf32_Phdr));
while(phdr->p_type != PT_DYNAMIC)
ptrace_read(pid, phdr_addr += sizeof(Elf32_Phdr), phdr,sizeof(Elf32_Phdr));
dyn_addr = phdr->p_vaddr;
printf("dyn_addr\t %p\n", dyn_addr);
ptrace_read(pid, dyn_addr, dyn, sizeof(Elf32_Dyn));
while(dyn->d_tag != DT_PLTGOT) {
tmpaddr = dyn_addr + i * sizeof(Elf32_Dyn);
ptrace_read(pid,tmpaddr, dyn, sizeof(Elf32_Dyn));
i++;
}
got = (Elf32_Word)dyn->d_un.d_ptr;
got += 4;
ptrace_read(pid, got, &map_addr, 4);
printf("map_addr\t %p\n", map_addr);
map = map_addr;
free(ehdr);
free(phdr);
free(dyn);
return map;
} // 取得给定link_map指向的SYMTAB、STRTAB、HASH、JMPREL、PLTRELSZ、RELAENT、RELENT信息
// 这些地址信息将被保存到全局变量中,以方便使用
void get_sym_info(int pid, struct link_map *lm)
{
Elf32_Dyn *dyn = (Elf32_Dyn *) malloc(sizeof(Elf32_Dyn));
unsigned long dyn_addr;
ptrace_read(pid,&(lm->l_ld) , &dyn_addr, sizeof(dyn_addr));
ptrace_read(pid,&(lm->l_addr) , &link_addr, sizeof(dyn_addr));
ptrace_read(pid, dyn_addr, dyn, sizeof(Elf32_Dyn));
while(dyn->d_tag != DT_NULL){
switch(dyn->d_tag)
{
case DT_SYMTAB:
symtab = dyn->d_un.d_ptr;
break;
case DT_STRTAB:
strtab = dyn->d_un.d_ptr;
break;
case DT_JMPREL:
jmprel = dyn->d_un.d_ptr;
break;
case DT_PLTRELSZ:
totalrelsize = dyn->d_un.d_val;
break;
case DT_RELAENT:
relsize = dyn->d_un.d_val;
break;
case DT_RELENT:
relsize = dyn->d_un.d_val;
break;
case DT_REL:
reldyn = dyn->d_un.d_ptr;
break;
case DT_RELSZ:
reldynsz = dyn->d_un.d_val;
break;
}
ptrace_read(pid, dyn_addr += sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn));
}
nrels = totalrelsize / relsize;
nreldyns = reldynsz/relsize;
free(dyn);
printf("get_sym_info exit\n");
} // 在指定的link_map指向的符号表查找符号,它仅仅是被上面的find_symbol使用
unsigned long find_symbol_in_linkmap(int pid, struct link_map *lm, char *sym_name)
{
Elf32_Sym *sym = (Elf32_Sym *) malloc(sizeof(Elf32_Sym));
int i = 0;
char *str;
unsigned long ret;
int flags = 0;
get_sym_info(pid, lm);
do{
if(ptrace_read(pid, symtab + i * sizeof(Elf32_Sym), sym, sizeof(Elf32_Sym)))
return 0;
i++;
if (!sym->st_name && !sym->st_size && !sym->st_value)
continue;
str = (char *) ptrace_readstr(pid, strtab + sym->st_name);
if (strcmp(str, sym_name) == 0) {
printf("\nfind_symbol_in_linkmap str = %s\n",str);
printf("\nfind_symbol_in_linkmap sym->st_value = %x\n",sym->st_value);
free(str);
if(sym->st_value == 0)
continue;
flags = 1;
break;
}
free(str);
}while(1);
if (flags != 1)
ret = 0;
else
ret = link_addr + sym->st_value;
free(sym);
return ret;
} // 解析指定符号
unsigned long find_symbol(int pid, struct link_map *map, char *sym_name)
{
struct link_map *lm = map;
unsigned long sym_addr;
char *str;
unsigned long tmp;
sym_addr = find_symbol_in_linkmap(pid, lm, sym_name);
while(!sym_addr ) {
ptrace_read(pid, (char *)lm+12, &tmp, 4);
if(tmp == 0)
return 0;
lm = tmp;
if ((sym_addr = find_symbol_in_linkmap(pid, lm, sym_name)))
break;
}
return sym_addr;
} // 查找符号的重定位地址
unsigned long find_sym_in_rel(int pid, char *sym_name)
{
Elf32_Rel *rel = (Elf32_Rel *) malloc(sizeof(Elf32_Rel));
Elf32_Sym *sym = (Elf32_Sym *) malloc(sizeof(Elf32_Sym));
int i;
char *str;
unsigned long ret;
struct link_map *lm;
lm = map_addr;
do{
get_sym_info(pid,lm);
ptrace_read(pid, (char *)lm+12, &lm, 4);
for(i = 0; i< nrels ;i++) {
ptrace_read(pid, (unsigned long)(jmprel + i * sizeof(Elf32_Rel)),
rel, sizeof(Elf32_Rel));
if(ELF32_R_SYM(rel->r_info)) {
ptrace_read(pid, symtab + ELF32_R_SYM(rel->r_info) *
sizeof(Elf32_Sym), sym, sizeof(Elf32_Sym));
str = ptrace_readstr(pid, strtab + sym->st_name);
if (strcmp(str, sym_name) == 0) {
if(sym->st_value != 0){
free(str);
continue;
}
modifyflag = 1;
free(str);
break;
}
free(str);
}
}
if(modifyflag == 1)
break;
for(i = 0; i< nreldyns;i++) {
ptrace_read(pid, (unsigned long)(reldyn+ i * sizeof(Elf32_Rel)),
rel, sizeof(Elf32_Rel));
if(ELF32_R_SYM(rel->r_info)) {
ptrace_read(pid, symtab + ELF32_R_SYM(rel->r_info) *
sizeof(Elf32_Sym), sym, sizeof(Elf32_Sym));
str = ptrace_readstr(pid, strtab + sym->st_name);
if (strcmp(str, sym_name) == 0) {
if(sym->st_value != 0){
free(str);
continue;
}
modifyflag = 2;
free(str);
break;
}
free(str);
}
}
if(modifyflag == 2)
break;
}while(lm);
if (modifyflag == 0)
ret = 0;
else
ret = link_addr + rel->r_offset;
free(rel);
free(sym);
return ret;
} void call__libc_dlopen_mode(int pid, unsigned long addr, char *libname)
{
void *plibnameaddr;
plibnameaddr = ptrace_push(pid, libname, strlen(libname) + 1);
ptrace_push(pid,&mode,sizeof(int));
ptrace_push(pid,&plibnameaddr,sizeof(plibnameaddr));
ptrace_call(pid, addr);
} void call_puts(int pid, unsigned long addr, char *string)
{
void *paddr;
paddr = ptrace_push(pid, string, strlen(string) + 1);
ptrace_push(pid,&paddr,sizeof(paddr));
ptrace_call(pid, addr);
} int main(int argc, char *argv[])
{
int pid;
struct link_map *map;
char sym_name[256];
unsigned long sym_addr;
unsigned long new_addr,old_addr,rel_addr;
int status = 0;
char libpath[1024];
char oldfunname[128];
char newfunname[128];
if(argc < 5){
printf("usage : ./ptrace_patch pid libpath oldfunname newfunname\n");
exit(-1);
} pid = atoi(argv[1]);
memset(libpath,0,sizeof(libpath));
memcpy(libpath,argv[2],strlen(argv[2]));
memset(oldfunname,0,sizeof(oldfunname));
memcpy(oldfunname,argv[3],strlen(argv[3]));
memset(newfunname,0,sizeof(newfunname));
memcpy(newfunname,argv[4],strlen(argv[4]));
printf("main pid = %d\n",pid);
printf("main libpath : %s\n",libpath);
printf("main oldfunname : %s\n",oldfunname);
printf("main newfunname : %s\n",newfunname); ptrace_attach(pid); map = get_linkmap(pid); sym_addr = find_symbol(pid, map, "puts");
printf("found puts at addr %p\n", sym_addr);
if(sym_addr == 0)
goto detach;
call_puts(pid,sym_addr,"patch successed\n");
waitpid(pid,&status,0);
printf("status = %x\n",status); sym_addr = find_symbol(pid, map, "__libc_dlopen_mode");
printf("found __libc_dlopen_mode at addr %p\n", sym_addr);
if(sym_addr == 0)
goto detach;
call__libc_dlopen_mode(pid, sym_addr,libpath);
waitpid(pid,&status,0); strcpy(sym_name, newfunname);
sym_addr = find_symbol(pid, map, sym_name);
printf("%s addr\t %p\n", sym_name, sym_addr);
if(sym_addr == 0)
goto detach;
strcpy(sym_name, oldfunname);
rel_addr = find_sym_in_rel(pid, sym_name);
printf("%s rel addr\t %p\n", sym_name, rel_addr);
if(rel_addr == 0)
goto detach; puts("intercept...");
if(modifyflag == 2)
sym_addr = sym_addr - rel_addr - 4;
printf("main modify sym_addr = %x\n",sym_addr);
ptrace_write(pid, rel_addr, &sym_addr, sizeof(sym_addr)); puts("patch ok");
detach:
printf("prepare to detach\n");
ptrace_detach(pid);
return 0;
}

上述代码基本上是实验指导书给出的代码,还有博客的代码。不过本作业需要Hook的函数是puts,不是printf,所以尝试通过查找putsymbol、调用call_puts来实现patch successed的输出。

编译ptrace_patch.c

gcc -m32 ptrace_patch.c -o ptrace_patch
3.3 热补丁的结果

完成编译后,先在一个终端运行repeat

./repeat

再另开一个终端,先ps -a查看repeat对应的pid,再运行:

sudo ./ptrace_patch <pid> ./patch.so puts newputs

结果如下图所示:

可以看到,上图左边终端运行着repeat程序,中途被修改成我的输出;右边终端运行着打热补丁的程序。

显然,热补丁打成功了,不过有一个小缺陷,call_puts没有调用成功,在ptrace_read的时候返回了错误。

我查找了原因,应该还是因为64位系统问题:

当您使用 64 位操作系统时,您必须使用兼容 64 位的寄存器。即,RAX,ORIG_EAX,EBX,EBX …如果不是,您将获得垃圾值。

——ptrace 寄存器中返回的异常值(ptrace abnormal values returned in the registers)答案 - 爱码网 (likecs.com)

我认为不需要修改这个问题,因为热补丁已经打入成功,满足实验要求。

【HUST】网络攻防实践|5_二进制文件补丁技术|实验三 热补丁的更多相关文章

  1. 20169214 2016-2017-2 《网络攻防实践》第十一周实验 SQL注入

    20169214 2016-2017-2 <网络攻防实践>SQL注入实验 SQL注入技术是利用web应用程序和数据库服务器之间的接口来篡改网站内容的攻击技术.通过把SQL命令插入到Web表 ...

  2. 2017-2018-2 20179215《网络攻防实践》seed缓冲区溢出实验

    seed缓冲区溢出实验 有漏洞的程序: /* stack.c */ /* This program has a buffer overflow vulnerability. */ /* Our tas ...

  3. <网络攻防实践> 课程总结20169216

    课程总结20169216 每周作业链接汇总 第一周作业:Linux基础入门(1-5).基本概念及操作 第二周作业:linux基础入门(6-11).网络攻防技术概述网络攻防试验环境搭构.Kali教学视频 ...

  4. 2017-2018-2 20179204《网络攻防实践》第十一周学习总结 SQL注入攻击与实践

    第1节 研究缓冲区溢出的原理,至少针对两种数据库进行差异化研究 1.1 原理 在计算机内部,输入数据通常被存放在一个临时空间内,这个临时存放的空间就被称为缓冲区,缓冲区的长度事先已经被程序或者操作系统 ...

  5. 2017-2018-2 20179204《网络攻防实践》linux基础

    我在实验楼中学习了Linux基础入门课程,这里做一个学习小结. 第一节 linux系统简介 本节主要介绍了linux是什么.发展历史.重要人物.linux与window的区别以及如何学习linux. ...

  6. 20169206 2016-2017-2 《网络攻防实践》 nmap的使用

    Part I 使用nmap扫描ubuntu靶机 先给出nmap的官方中文操作手册https://nmap.org/man/zh/,其实并不太好用,而且有时候会打不开,但至少是官方手册. 探查操作系统 ...

  7. 2017-2018-2 20179204《网络攻防实践》第十三周学习总结 python实现国密算法

    国密商用算法是指国密SM系列算法,包括基于椭圆曲线的非对称公钥密码SM2算法.密码杂凑SM3算法.分组密码SM4算法,还有只以IP核形式提供的非公开算法流程的对称密码SM1算法等. 第1节 SM2非对 ...

  8. [芯片] 3、接口技术·实验三·可编程并行接口8255A

    目录 一.实验目的和要求 二.实验原理与背景 2-1.8255A简介 2-2.8255A编程 三.实验具体的内容 3-1.8255方式0实验1 3-2.8255方式0实验2 3-3.8255方式1输出 ...

  9. 【腾讯bugly干货分享】微信Android热补丁实践演进之路

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=1264& ...

  10. 微信Android热补丁实践演进之路

    版权声明:本文由张绍文原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/81 来源:腾云阁 https://www.qclou ...

随机推荐

  1. Luogu P4425 转盘 题解 [ 黑 ] [ 线段树 ] [ 贪心 ] [ 递归 ]

    转盘:蒟蒻的第一道黑,这题是贪心和线段树递归合并的综合题. 贪心 破环成链的 trick 自然不用多说. 首先观察题目,很容易发现一个性质:只走一圈的方案一定最优.这个很容易证,因为再绕一圈回来标记前 ...

  2. AI之Ollama

    介绍 什么是llama LLaMA(Large Language Model Meta AI)是Meta开发的大规模预训练语言模型,基于Transformer架构,具有强大的自然语言处理能力.它在文本 ...

  3. [BZOJ3514] [Codechef MARCH14] GERALD07加强版 题解

    名字感觉挺奇怪的. 考虑离线算法.首先答案就是用 \(n\) 减去连完边后的生成树森林边数.生成树当然就可以用 \(lct\) 求解了.我是不会告诉你这个时候我已经开始想回滚莫队了的. 考虑当我们倒序 ...

  4. Typecho博客网站底部添加网站已运行时间教程

    样式一: 1. 将代码放入 functions.php 一般在主题根目录:网站 /usr/themes/ 主题 修改一下你自己的网站时间 // 设置时区 date_default_timezone_s ...

  5. /proc的相关知识

    /proc的相关知识 /proc 介绍 /proc 是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可 ...

  6. el-cascader 最后一级不显示出来

    1.业务背景 业务需要做一个父级查询,父级查询的级联组件不显示最后一级,其他层级均显示 2.解决办法 1.页面设计见上文 TypeError: Cannot read properties of nu ...

  7. Elasticsearch搜索引擎学习笔记(四)

    分词器 内置分词器 standard:默认分词,单词会被拆分,大小会转换为小写. simple:按照非字母分词.大写转为小写. whitespace:按照空格分词.忽略大小写. stop:去除无意义单 ...

  8. Zookeeper Java客户端连接慢、超时问题Ad-Hoc检查清单

    TL;DR 排查思路: 首先确认你的设备到zookeeper的连通性是OK的,可通过命令echo srvr | nc HOST 2181,检查是否可以正常打印节点信息.windows用户可以在命令行输 ...

  9. Cython与CUDA之Add

    技术背景 在前一篇文章中,我们介绍过使用Cython结合CUDA实现了一个Gather算子以及一个BatchGather算子.这里我们继续使用这一套方案,实现一个简单的求和函数,通过CUDA来计算数组 ...

  10. 【ABAQUS Material】density 行为

    1.overview 进行eigenfrequency . transient dynamic analysis. transient heat transfer analysis. adiabati ...