如果纯粹用文字来描述什么是动态注入,可能还是不太容易理解,所以本篇文章从如下一段代码开始:
// who.c
#include
#include
int main()
{
while (1) {
printf("who are you ?n");
sleep(2);
}
return 0;
}
这段代码本身没有什么意思,执行”gcc who.c -o who -g -Wall”完成编译并启动who程序,每隔2s会向终端打印一句”who are you ?”动态ip修改方法,但有意思的是,可能会出现一种”诡异”的现象,比如屏幕上突然冒出一句”it’s me ~”。
1. 为什么可以出现这种“诡异”的现象?
2. 怎么获取打印”it’s me ~”的机器码?
// isme32.c
int main()
{
__asm__(
"jmp forwardnt"
"backward:popl %esint"
"movl $4, %eaxnt"
"movl $2, %ebxnt"
"movl %esi, %ecxnt"
"movl $12, %edxnt"
"int $0x80nt"
"int3nt"
"forward:call backwardnt"
".string "it's me ~\n""
);
return 0;
}
// isme64.c
int main()
{
__asm__(
"jmp forwardnt"
"backward:popq %rsint"
"movq $1, %raxnt"
"movq $2, %rdint"
"movq $12, %rdxnt"
"syscallnt"
"int3nt"
"forward:call backwardnt"
".string "it's me ~\n""
);
return 0;
}
用蓝色、绿色标记出来的16进制内容,即为打印”it’s me ~”的机器码动态ip修改方法,但有2点需要说明:
3. 怎么实现注入?
本篇文章仅仅用于学习目的,注入的场景和方法都比较简单,思路在文章第1节已经介绍过了,就是”暂停被注入进程 -> 替换即将执行的指令块 -> 通知被注入进程继续执行”,由于有些场合还需要保证注入操作的隐蔽性,所以以下程序还在注入指令块完整执行后,将被注入进程恢复到了注入前的状态:
(理解这段代码,至少需要学习ptrace()系统调用的作用,如果有内核基础,也可以更深入的学习一下ptrace()的内部原理,另外,CODE宏对应的内容,即为利用文章第2节描述的方法,提取的机器码。)
// inject.c
#include
#include
#include
#include
#include
#include
#include
#include
// 注入指令块(打印"it's me ~")
#ifdef ENV_I386
#define CODE
"xebx15x5exb8x04x00x00x00"
"xbbx02x00x00x00x89xf1xba"
"x0cx00x00x00xcdx80xccxe8"
"xe6xffxffxffx69x74x27x73"
"x20x6dx65x20x7ex0ax00"
#define REG_IP regs.eip
#else // X_64
#define CODE
"xebx19x5ex48xc7xc0x01x00"
"x00x00x48xc7xc7x02x00x00"
"x00x48xc7xc2x0cx00x00x00"
"x0fx05xccxe8xe2xffxffxff"
"x69x74x27x73x20x6dx65x20"
"x7ex0ax00"
#define REG_IP regs.rip
#endif
#define CODE_SIZE (sizeof(CODE)-1)
/* 往pid进程的addr地址处写数据 */
void putdata(pid_t pid, unsigned long addr, void *vptr, int len)
{
int count = 0;
long word;
while (count < len)
{
memcpy(&word, vptr+count, sizeof(word));
word = ptrace(PTRACE_POKEDATA, pid, addr+count, word);
count += sizeof(word);
if (errno != 0)
printf("putdata failed: %pn", (void *)(addr+count));
}
}
/* 读pid进程addr地址处的数据 */
void getdata(pid_t pid, unsigned long addr, void *vptr, int len)
{
int i = 0, count = 0;
long word;
unsigned long *ptr = (unsigned long*)vptr;
while (count < len)
{
word = ptrace(PTRACE_PEEKDATA, pid, addr+count, NULL);
count += sizeof(word);
ptr[i++] = word;
if (errno != 0)
printf("getdata failed: %pn", (void *)(addr+count));
}
}
int main(int argc, char *argv[])
{
pid_t pid;
struct user_regs_struct regs;
char backup[CODE_SIZE+1];
if (argc != 2) {
printf("Usage: %s {pid}n", argv[0]);
return -1;
}
// 通过启动参数,获取被注入进程号,并attach
pid = atoi(argv[1]);
ptrace(PTRACE_ATTACH, pid, NULL, NULL); // SIGTRAP
if (errno != 0)
printf("attach failed: %sn", strerror(errno));
wait(NULL); // 收到回复信号,保证后续过程在attach完成的情况下执行
// 获取被注入进程当前寄存器值
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
// 备份*ip寄存器指向的指令块
getdata(pid, REG_IP, backup, CODE_SIZE);
// 替换为CODE指令块
putdata(pid, REG_IP, CODE, CODE_SIZE);
// 恢复被注入进程的执行
ptrace(PTRACE_CONT, pid, NULL, NULL); // SIGCONT
// 注入程序最后一条指令为int3,此为即为等待注入指令块执行完毕
wait(NULL);
// 等待用户输入,方便查看结果
printf("continue to execute the orginal process, press any key ..n");
getchar();
// 将修改的指令块恢复为备份内容
putdata(pid, REG_IP, backup, CODE_SIZE);
// 恢复被注入进程寄存器值
ptrace(PTRACE_SETREGS, pid, NULL, ®s);
// detach,被注入进程恢复正常执行
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 0;
}
4. 视频演示
视频链接:
5. 参考
– End –
看雪ID:xinpoo
本文由看雪论坛xinpoo原创
———END———
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,一年会员只需98元,全站资源免费下载 点击网站首页每天更新
站 长 微 信: aiwo51889
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。