首页
社区
课程
招聘
linux动态注入(含视频演示)
发表于: 2018-9-20 19:45 7853

linux动态注入(含视频演示)

2018-9-20 19:45
7853
    如果纯粹用文字来描述什么是动态注入,可能还是不太容易理解,所以本篇文章从如下一段代码开始
// who.c
#include <stdio.h>
#include <unistd.h>

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 ?",但有意思的是,可能会出现一种"诡异"的现象,比如屏幕上突然冒出一句"it's me ~"。。

    1.  为什么可以出现这种“诡异”的现象?
// who.c
#include <stdio.h>
#include <unistd.h>

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 ?",但有意思的是,可能会出现一种"诡异"的现象,比如屏幕上突然冒出一句"it's me ~"。。

    1.  为什么可以出现这种“诡异”的现象?
  • 通过反汇编who程序可以看出,程序最终会进入while(1)循环体,重复执行"bf24064000"、"e8c5feffff"、"bd0x000000"、"e8ebfeffff"、"ebea"这5条机器指令
        
  • 如果who进程执行到某条指令(比如"e8c5feffff")时暂停了,并且在恢复执行之前,0x400586处的指令块被替换成打印"it's me ~"的代码,那么who进程下次执行,自然就会打印"it's me ~"
        

    2.  怎么获取打印"it's me ~"的机器码?
  • 32位系统,编译如下代码并在反汇编结果中提取:
// isme32.c
int main()
{
__asm__(
	"jmp forward\n\t"
	"backward:popl %esi\n\t"
	"movl $4, %eax\n\t"
	"movl $2, %ebx\n\t"
	"movl %esi, %ecx\n\t"
	"movl $12, %edx\n\t"
	"int $0x80\n\t"
	"int3\n\t"
	"forward:call backward\n\t"
	".string \"it's me ~\\n\""
	);

	return 0;
}
  • 64位系统,编译如下代码并在反汇编结果中提取:
// isme32.c
int main()
{
__asm__(
	"jmp forward\n\t"
	"backward:popl %esi\n\t"
	"movl $4, %eax\n\t"
	"movl $2, %ebx\n\t"
	"movl %esi, %ecx\n\t"
	"movl $12, %edx\n\t"
	"int $0x80\n\t"
	"int3\n\t"
	"forward:call backward\n\t"
	".string \"it's me ~\\n\""
	);

	return 0;
}
// isme64.c
int main()
{
__asm__(
	"jmp forward\n\t"
	"backward:popq %rsi\n\t"
	"movq $1, %rax\n\t"
	"movq $2, %rdi\n\t"
	"movq $12, %rdx\n\t"
	"syscall\n\t"
	"int3\n\t"
	"forward:call backward\n\t"
	".string \"it's me ~\\n\""
	);

	return 0;
}
  • 本篇文章的实验环境是64位ubuntu系统,所以执行"gcc isme64.c -oisme64 -g -Wall"生成isme64可执行文件,并通过反汇编isme64文件提取机器码
// isme64.c
int main()
{
__asm__(
	"jmp forward\n\t"
	"backward:popq %rsi\n\t"
	"movq $1, %rax\n\t"
	"movq $2, %rdi\n\t"
	"movq $12, %rdx\n\t"
	"syscall\n\t"
	"int3\n\t"
	"forward:call backward\n\t"
	".string \"it's me ~\\n\""
	);

	return 0;
}
  • 本篇文章的实验环境是64位ubuntu系统,所以执行"gcc isme64.c -oisme64 -g -Wall"生成isme64可执行文件,并通过反汇编isme64文件提取机器码
        
        用蓝色、绿色标记出来的16进制内容,即为打印"it's me ~"的机器码,但有2点需要说明:
        a. 绿色部分其实是"it's me ~\n\0"这串字符的ascii码值,也被objdump解释成汇编指令了,这是反汇编工具很难避免的一个问题,用gdb/disassemble反汇编的结果也是一样的,因为它们选择的都是线性扫描算法,递归下降算法效果会好一些,但仍然不能保证得到精确的结果;
        b. C程序实现打印"it's me ~",写一条printf("it's me ~")语句不就可以了么,为什么要写成这种"奇奇怪怪"的样子?因为如果用C语法来实现的话,"it's me ~"这个字符串会被编译器安排到isme64程序的.data段,就没办法随着指令块一起注入到目标进程中,而且这段代码中取"it's me ~"地址的技巧,对于汇编初学者是很有意思的,代码不长,建议在大脑里执行一遍,并找到其中的奥妙。

    3.  怎么实现注入?
         本篇文章仅仅用于学习目的,注入的场景和方法都比较简单,思路在文章第1节已经介绍过了,就是"暂停被注入进程 -> 替换即将执行的指令块 -> 通知被注入进程继续执行",由于有些场合还需要保证注入操作的隐蔽性,所以以下程序还在注入指令块完整执行后,将被注入进程恢复到了注入前的状态:
         (理解这段代码,至少需要学习ptrace()系统调用的作用,如果有内核基础,也可以更深入的学习一下ptrace()的内部原理,另外,CODE宏对应的内容,即为利用文章第2节描述的方法,提取的机器码)
// inject.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <errno.h>


// 注入指令块(打印"it's me ~")
#ifdef ENV_I386
#define CODE \
	"\xeb\x15\x5e\xb8\x04\x00\x00\x00" \
	"\xbb\x02\x00\x00\x00\x89\xf1\xba" \
	"\x0c\x00\x00\x00\xcd\x80\xcc\xe8" \
	"\xe6\xff\xff\xff\x69\x74\x27\x73" \
	"\x20\x6d\x65\x20\x7e\x0a\x00"
#define REG_IP  regs.eip
#else   // X_64
#define CODE \
	"\xeb\x19\x5e\x48\xc7\xc0\x01\x00" \
	"\x00\x00\x48\xc7\xc7\x02\x00\x00" \
	"\x00\x48\xc7\xc2\x0c\x00\x00\x00" \
	"\x0f\x05\xcc\xe8\xe2\xff\xff\xff" \
	"\x69\x74\x27\x73\x20\x6d\x65\x20" \
	"\x7e\x0a\x00"
#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: %p\n", (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: %p\n", (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: %s\n", strerror(errno));
	wait(NULL);  // 收到回复信号,保证后续过程在attach完成的情况下执行

	// 获取被注入进程当前寄存器值
	ptrace(PTRACE_GETREGS, pid, NULL, &regs);
	// 备份*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, &regs);
	
	// detach,被注入进程恢复正常执行
	ptrace(PTRACE_DETACH, pid, NULL, NULL);

	return 0;
}

// inject.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <errno.h>


// 注入指令块(打印"it's me ~")
#ifdef ENV_I386
#define CODE \
	"\xeb\x15\x5e\xb8\x04\x00\x00\x00" \
	"\xbb\x02\x00\x00\x00\x89\xf1\xba" \
	"\x0c\x00\x00\x00\xcd\x80\xcc\xe8" \
	"\xe6\xff\xff\xff\x69\x74\x27\x73" \
	"\x20\x6d\x65\x20\x7e\x0a\x00"
#define REG_IP  regs.eip
#else   // X_64
#define CODE \
	"\xeb\x19\x5e\x48\xc7\xc0\x01\x00" \
	"\x00\x00\x48\xc7\xc7\x02\x00\x00" \
	"\x00\x48\xc7\xc2\x0c\x00\x00\x00" \
	"\x0f\x05\xcc\xe8\xe2\xff\xff\xff" \
	"\x69\x74\x27\x73\x20\x6d\x65\x20" \
	"\x7e\x0a\x00"
#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: %p\n", (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: %p\n", (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: %s\n", strerror(errno));
	wait(NULL);  // 收到回复信号,保证后续过程在attach完成的情况下执行

	// 获取被注入进程当前寄存器值
	ptrace(PTRACE_GETREGS, pid, NULL, &regs);
	// 备份*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, &regs);
	
	// detach,被注入进程恢复正常执行
	ptrace(PTRACE_DETACH, pid, NULL, NULL);

	return 0;
}

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2018-9-20 19:47 被jmpcall编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (4)
雪    币: 43
活跃值: (40)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
视频不清晰,不过还算能看
2018-9-22 16:25
0
雪    币: 3352
活跃值: (10987)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
3
hbcld 视频不清晰,不过还算能看
优酷转超清要等一段时间,现在清晰了
2018-9-26 17:21
0
雪    币: 441
活跃值: (149)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
4
LINUX下面的资料相对比较少。多谢分享。
2018-9-26 18:11
0
雪    币: 2359
活跃值: (528)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
谢lz,mark一下
2018-9-26 23:15
0
游客
登录 | 注册 方可回帖
返回
//