修改任意Linux进程地址空间实施代码注入

# 代码就不展示了,很简单,就是把75 c7改成75 00即可。
[root@localhost test]# ./a.out 5446 4006a3 # base(0x400000)+offset(0x6a2+1)

看看psss的情况:

value:5446   address:0x7ffd4943cf18  pid:5446
value:5446   address:0x7ffd4943cf18  pid:5446
value:5446   address:0x7ffd4943cf18  pid:5446
break the loop!

成功跳出了死循环!

接下来玩一个简单的进程注入,来调用一个僵尸函数。

请看原始程序代码:

// pokestack.c
#include <stdio.h>
#include <stdlib.h>

void say\_hi()
{
	printf("skinshoe\n");
	exit(0);
}

int main(int argc, char \*\*argv)
{
	int tf = atoi(argv[1]);
	long a = 0x1122334455667788;
	while (tf) {
		printf("value:%lx address:%p pid:%d\n", a, &a, getpid());
		sleep(120); // 在120秒内完成手工操作。用getchar亦可。
	}

	printf("break the loop!\n");
}

请注意,say_hi函数没有任何地方调用它,我们接下来就通过修改其mem文件,让这个程序调用say_hi。

先把它跑起来:

[root@localhost test]# ./pokestack 1
value:1122334455667788   address:0x7fff75a4ba80  pid:5553

按照老样子,这次我们定位其stack中被压栈的sleep返回地址的位置,我们的目标就是改掉它。

如果你不想每次都去查stack的位置,你可以关闭一些ASLR的保护,比如:

sysctl -w kernel.randomize_va_space=0

但是这里为了让事情更加真实,不采用这个伎俩,在真实的环境中,也几乎没有关闭ASLR的。

所以我们依然要查stack的位置,毕竟它被随机化了,每次都不一样:

[root@localhost test]# cat /proc/5553/smaps |grep -E \\[stack\\]
7fff75a2c000-7fff75a4d000 rw-p 00000000 00:00 0                          [stack]
[root@localhost test]# dd if=/proc/5553/mem of=./pokestack.dd skip=140735166988288 bs=1 count=557056
记录了135168+0 的读入
记录了135168+0 的写出
135168字节(135 kB)已复制,0.165683 秒,816 kB/秒
[root@localhost test]# hexdump ./pokestack.dd >./pokestack.hex

我们开始寻找pokestack.hex里面sleep的return address的位置,找到了下面的行:

001fa60 bb70 75a4 7fff 0000 06ff 0040 0000 0000

4006ff就是了。

至于说是如何找到的,不是本文的内容,无非就是按照模式去匹配了:

  • 返回main函数肯定是400000附近…

让我们改掉它,改成say_hi的地址:

# 0x7FFF75A4BA68是stack基地址和0x001fa60+0x8的加和。
# 0x000000000040067d是say\_hi的地址。
# 将0x7FFF75A4BA68位置的值改成0x000000000040067d
[root@localhost test]# ./a.out 5553 0x7FFF75A4BA68 0x000000000040067d

等待sleep返回后,看效果:

value:1122334455667788   address:0x7fff75a4ba80  pid:5553

skinshoe
[root@localhost test]#

效果不错。

接下来让我们把一段独立的代码注入到一个已经运行的进程:

// pokestack.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char \*\*argv)
{
	\_\_asm("mov $123, %rdi\n"); // 这句是为了导出exit系统调用参数123的指令码。。。
	while (1) {
		printf("pid:%d\n", getpid());
		sleep(120);
	}
}

导出stack的信息:

[root@localhost test]# cat /proc/6033/smaps |grep -E -A2 \\[stack\\]
7ffe66868000-7ffe66889000 rw-p 00000000 00:00 0                          [stack]
Size:                136 kB
Rss:                  16 kB

注意,stack没有可执行权限,所以要在binary的text段进行注入。

如果stack可执行,那么事情就会简单的多,直接将要注入的代码覆盖stack的起始位置就好了(由于stack从高向低伸展,一般伸展不到这么远的地方),让stack变得可执行也不是那么困难,用下面的方式编译即可:

[root@localhost test]# gcc -O0 -z execstack pokestack.c -o pokestack

但是这里为了让事情更加真实,不采用这个伎俩,在真实的环境中,也几乎没有让stack可执行的。

现在我们看一下binary的text段信息:

[root@localhost test]# cat /proc/6033/smaps |grep -A2 r-xp.\*pokestack
00400000-00401000 r-xp 00000000 fd:00 75346995                           /usr/test/pokestack
Size:                  4 kB
Rss:                   4 kB

下面是实施注入的代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char \*\*argv)
{
	char name[32];
	int fd;
	int pid;
	unsigned long caddr, raddr;
	unsigned long ret_addr;
	// code 仅仅是一个exit(123)的系统调用
	char code[14] = {0x48, 0xc7, 0xc7, 0x7b, 0x00, 0x00, 0x00, 0xb8, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x05};

	pid = atoi(argv[1]);
	caddr = strtoul(argv[2], NULL, 16);
	raddr = strtoul(argv[3], NULL, 16);

	sprintf(name, "/proc/%d/mem", pid);
	fd = open(name, O_RDWR);

	lseek(fd, caddr, SEEK\_SET);
	write(fd, &code, sizeof(code));

	lseek(fd, raddr, SEEK\_SET);
	write(fd, &caddr, sizeof(caddr));
}

不再解释代码,我们直接看效果:

# 0x7FFE66887998依然是用dd方法导出的stack找到的return address地址位置。
[root@localhost test]# ./a.out 6033 0x00400000 0x7FFE66887998

看看6033号进程的结局:

[root@localhost test]# ./pokestack
pid:6033
[root@localhost test]# echo $?
123

成功以123退出。

下面,我们来个打印怎么样?来打印一堆a。

我们需要注入printf的调用,然而一般都是用相对偏移调用的call,校准的过程并不容易,因此我们采用下面的方法:

push printf@GLIBC
ret

然而printf何在?于是乎,先简单一点,我们直接调用write系统调用。

先看原始代码:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char \*\*argv)
{
	while (1) {
		printf("pid:%d\n", getpid());
		getchar();
	}
}

跑起来:

[root@localhost test]# ./pokestack
pid:14917

pid:14917
... # 继续敲任意键,依然会getchar,这就是个死循环

一步一步地获取信息:

[root@localhost test]# cat /proc/14917/smaps |grep -E \\[stack\\]
7ffc08ac1000-7ffc08ae2000 rw-p 00000000 00:00 0                          [stack]
[root@localhost test]# dd if=/proc/14917/mem of=./pokestack.dd skip=140720453980160 bs=1 count=557056
[root@localhost test]# hexdump ./pokestack.dd >./pokestack.hex
...

在pokestack.hex里找到了下面的行:

001f4a0 0000 0000 0000 0000 05dc 0040 0000 0000

拼接偏移:

0x7ffc08ac1000 + 0x001f4a0 + 0x08 = 0x7FFDF4F9E818

来制作我们的注入code并且实施注入,下面是代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char \*\*argv)
{
	char name[32];
	int fd;
	int pid;
	unsigned long caddr, raddr;
	unsigned long ret_addr;
	char code[58] = {/\*0x00\*/ 0x61, 0x61, 0x61, 0x0a,
					 /\*0x04\*/ 0x00, 0x00, 0x00, 0x00,
					 /\*0x08\*/ 0x00, 0x00, 0x00, 0x00,
					 /\*0x0c\*/ 0x00, 0x00, 0x00, 0x00,
					 /\*0x10\*/ 0x48, 0xc7, 0xc7, 0x01, 0x00, 0x00, 0x00, // mov 1, rdi
					 		  0x48, 0xc7, 0xc6, 0x00, 0x00, 0x40, 0x00, // mov 400000, rsi
					 		  0x48, 0xc7, 0xc2, 0x04, 0x00, 0x00, 0x00, // mov 4, rdx
							  0xb8, 0x01, 0x00, 0x00, 0x00,				// mov 1, eax
					 		  0x0f, 0x05,						// write syscall
							  0x48, 0xc7, 0xc7, 0x7b, 0x00, 0x00, 0x00, // mov 123, rdi
							  0xb8, 0x3c, 0x00, 0x00, 0x00, 			// mov 60, eax
							  0x0f, 0x05};						// exit syscall

	pid = atoi(argv[1]);
	caddr = strtoul(argv[2], NULL, 16);
	raddr = strtoul(argv[3], NULL, 16);

	sprintf(name, "/proc/%d/mem", pid);
	fd = open(name, O_RDWR);

	lseek(fd, caddr, SEEK\_SET);
	write(fd, &code[0], sizeof(code));

	caddr += 0x10; // 从mov 1, rdi开始执行
	lseek(fd, raddr, SEEK\_SET);
	write(fd, &caddr, sizeof(caddr));
}

来实施注入:

[root@localhost test]# ./a.out 14917 0x00400000 0x7FFC08AE04A8

看效果:

[root@localhost test]# ./pokestack
pid:14917

pid:14917

aaa
[root@localhost test]# echo $?
123
[root@localhost test]#

不错,跳出了死循环并以123退出。

是不是比ptrace好玩呢?

我们还是希望可以直接调用GLIBC的库函数,而不是直接调用系统调用,其实这并不难。

先看下面的代码:

#include <stdio.h>
char abc1[] = "aaaa\n";
int main()
{
	printf("\n\n"); // 为了让该程序加载时解析到printf函数
	\_\_asm("mov $0x000000000060102c, %rdi"); // abc1的地址给rdi寄存器作为参数
	\_\_asm("push $0x40053d"); // main函数\_asm("ret")后面的位置
	\_\_asm("push $0x0000000000400400"); // printf的位置
	\_\_asm("ret");
}

其中的数字都是从objdump里手工找到的。

执行一下试试看,看是不是打印出了aaaa呢。

我们就用此方法来实施注入,还是原来的那个程序,我再贴一遍:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char \*\*argv)
{
	while (1) {
		printf("pid:%d\n", getpid());
		getchar();
	}
}

跑起来:

[root@localhost test]# ./pokestack
![](https://img-blog.csdnimg.cn/img_convert/9a8cb5f8c0ec69e6499adead0da6e95b.png)



最全的Linux教程,Linux从入门到精通

======================

1.  **linux从入门到精通(第2版)**

2.  **Linux系统移植**

3.  **Linux驱动开发入门与实战**

4.  **LINUX 系统移植 第2版**

5.  **Linux开源网络全栈详解 从DPDK到OpenFlow**



![华为18级工程师呕心沥血撰写3000页Linux学习笔记教程](https://img-blog.csdnimg.cn/img_convert/59742364bb1338737fe2d315a9e2ec54.png)



第一份《Linux从入门到精通》466页

====================

内容简介

====

本书是获得了很多读者好评的Linux经典畅销书**《Linux从入门到精通》的第2版**。本书第1版出版后曾经多次印刷,并被51CTO读书频道评为“最受读者喜爱的原创IT技术图书奖”。本书第﹖版以最新的Ubuntu 12.04为版本,循序渐进地向读者介绍了Linux 的基础应用、系统管理、网络应用、娱乐和办公、程序开发、服务器配置、系统安全等。本书附带1张光盘,内容为本书配套多媒体教学视频。另外,本书还为读者提供了大量的Linux学习资料和Ubuntu安装镜像文件,供读者免费下载。



![华为18级工程师呕心沥血撰写3000页Linux学习笔记教程](https://img-blog.csdnimg.cn/img_convert/9d4aefb6a92edea27b825e59aa1f2c54.png)



**本书适合广大Linux初中级用户、开源软件爱好者和大专院校的学生阅读,同时也非常适合准备从事Linux平台开发的各类人员。**

> 需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论


  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值