标签:actually linux open argv copy 管理员 garbage hat tag
这是虚拟内存系列文章的第二篇。
这次我们要做的事情和《虚拟内存探究 – 第一篇:C strings & /proc》类似,不同的是我们将访问Python 3 脚本的虚拟内存。这会比较费劲, 所以我们需要了解Pyhton3 内部的一些机制。
本文基于上一篇文章《虚拟内存探究 – 第一篇:C strings & /proc》中所讲的知识, 所以,在继续阅读本文前,请确保阅读并理解上一篇文章。
为了方便理解本文,你需要具备以下知识:
/proc
文件系统的基本知识(可参阅《虚拟内存探究 – 第一篇:C strings & /proc》中的相关介绍)所有的脚本和程序都在下面的环境中测试过:
下面是我们将要使用的Python脚本(main.py
)。我们将尝试修改运行该脚本的进程虚拟内存中的“字符串” Holberton
。
#!/usr/bin/env python3
‘‘‘
Prints a b"string" (bytes object), reads a char from stdin
and prints the same (or not :)) string again
‘‘‘
import sys
s = b"Holberton"
print(s)
sys.stdin.read(1)
print(s)
译者注:bytes在这里翻译成字节, 并非指单个字符。
如上面代码所示,我们使用一个字节对象(字符串Holberton
前面的b
说明这是个字节对象)来存储字符串Holberton
。字节对象会把字符串中的字符以字节的形式(相对于每个字符占多个字节的字符串编码方式而言,也就是宽字符编码,具体可参阅unicodeobject.h
)存下来。这样可以保证字符串在虚拟内存中是连续的ASCII码。
从技术上来讲, 上面代码中的s
并不是一个Python字符串。如下所示, 它是一个字节对象(不过没关系, 这不影响我们的后续讨论):
julien@holberton:~/holberton/w/hackthevm1$ python3
Python 3.4.3 (default, Nov 17 2016, 01:08:31)
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> s = "Betty"
>>> type(s)
<class ‘str‘>
>>> s = b"Betty"
>>> type(s)
<class ‘bytes‘>
>>> quit()
Pyhton中的整数、字符串、字节、函数等等, 都是对象。所以, 语句s = b"Holberton"
将创建一个字节对象,并将字符串存在内存中某处。字符串Holberton
很可能在堆上,因为Python必须为字节对象s
以及s
指向的字符串分配内存(字符串可能直接存在对象s
中,也可能s
只维护了一个指向字符串的索引,目前我们并不确定具体的实现)。
提示:read_write_heap.py
是《虚拟内存探究 – 第一篇:C strings & /proc》中的脚本,用来查找并替换内存中的字符串。
我们首先执行前面的脚本main.py
:
julien@holberton:~/holberton/w/hackthevm1$ ./main.py
b‘Holberton‘
这时main.py
阻塞在语句sys.stdin.read(1)
上,一直在等待用户输入。
接下来我们用管理员权限执行脚本read_write_heap.py
:
julien@holberton:~/holberton/w/hackthevm1$ ps aux | grep main.py | grep -v grep
julien 3929 0.0 0.7 31412 7848 pts/0 S+ 15:10 0:00 python3 ./main.py
julien@holberton:~/holberton/w/hackthevm1$ sudo ./read_write_heap.py 3929 Holberton "~ Betty ~"
[*] maps: /proc/3929/maps
[*] mem: /proc/3929/mem
[*] Found [heap]:
pathname = [heap]
addresses = 022dc000-023c6000
permisions = rw-p
offset = 00000000
inode = 0
Addr start [22dc000] | end [23c6000]
[*] Found ‘Holberton‘ at 8e192
[*] Writing ‘~ Betty ~‘ at 236a192
julien@holberton:~/holberton/w/hackthevm1$
不出所料,我们在堆上找到了字符串Holberton
并且将之替换成’~ Betty ~’。
现在我们按下回车键让脚本main.py
继续执行,它应该会输出b‘~ Betty ~‘
:
b‘Holberton‘
julien@holberton:~/holberton/w/hackthevm1$
什么???
我们找到字符串Holberton
并且替换了它,但是这不是我们要找的字符串?继续深入探究之前,我们需要再确认一件事情。我们的脚本read_write_heap.py
在目标字符串首次出现之后就退出了,如果堆中有多个字符串Holberton
呢?为了避免遗漏,我们将脚本read_write_heap.py
执行多次。
还是先启动脚本main.py
:
julien@holberton:~/holberton/w/hackthevm1$ ./main.py
b‘Holberton‘
然后多次执行脚本read_write_heap.py
:
julien@holberton:~/holberton/w/hackthevm1$ ps aux | grep main.py | grep -v grep
julien 4051 0.1 0.7 31412 7832 pts/0 S+ 15:53 0:00 python3 ./main.py
julien@holberton:~/holberton/w/hackthevm1$ sudo ./read_write_heap.py 4051 Holberton "~ Betty ~"
[*] maps: /proc/4051/maps
[*] mem: /proc/4051/mem
[*] Found [heap]:
pathname = [heap]
addresses = 00bf4000-00cde000
permisions = rw-p
offset = 00000000
inode = 0
Addr start [bf4000] | end [cde000]
[*] Found ‘Holberton‘ at 8e162
[*] Writing ‘~ Betty ~‘ at c82162
julien@holberton:~/holberton/w/hackthevm1$ sudo ./read_write_heap.py 4051 Holberton "~ Betty ~"
[*] maps: /proc/4051/maps
[*] mem: /proc/4051/mem
[*] Found [heap]:
pathname = [heap]
addresses = 00bf4000-00cde000
permisions = rw-p
offset = 00000000
inode = 0
Addr start [bf4000] | end [cde000]
Can‘t find ‘Holberton‘
julien@holberton:~/holberton/w/hackthevm1$
字符串’Holberton’在堆上只出现了一次。那么脚本main.py
所使用的字符串’Holberton’到底在哪里呢?Python的字节对象又是在内存的哪部分呢?有没有可能在栈上?我们可以把脚本read_write_heap.py
中的[heap]
改成[stack]
试试看。
提示:文件/proc/[pid]/maps
中标记为[stack]
的部分就是栈, 具体可参阅上一篇文件《虚拟内存探究 – 第一篇:C strings & /proc》。
改写栈的脚本read_write_stack.py
如下, 它所做的和之前的脚本read_write_heap.py
一样,唯一的不同是它访问进程的栈:
#!/usr/bin/env python3
‘‘‘
Locates and replaces the first occurrence of a string in the stack
of a process
Usage: ./read_write_stack.py PID search_string replace_by_string
Where:
- PID is the pid of the target process
- search_string is the ASCII string you are looking to overwrite
- replace_by_string is the ASCII string you want to replace
search_string with
‘‘‘
import sys
def print_usage_and_exit():
print(‘Usage: {} pid search write‘.format(sys.argv[0]))
sys.exit(1)
# check usage
if len(sys.argv) != 4:
print_usage_and_exit()
# get the pid from args
pid = int(sys.argv[1])
if pid <= 0:
print_usage_and_exit()
search_string = str(sys.argv[2])
if search_string == "":
print_usage_and_exit()
write_string = str(sys.argv[3])
if search_string == "":
print_usage_and_exit()
# open the maps and mem files of the process
maps_filename = "/proc/{}/maps".format(pid)
print("[*] maps: {}".format(maps_filename))
mem_filename = "/proc/{}/mem".format(pid)
print("[*] mem: {}".format(mem_filename))
# try opening the maps file
try:
maps_file = open(‘/proc/{}/maps‘.format(pid), ‘r‘)
except IOError as e:
print("[ERROR] Can not open file {}:".format(maps_filename))
print(" I/O error({}): {}".format(e.errno, e.strerror))
sys.exit(1)
for line in maps_file:
sline = line.split(‘ ‘)
# check if we found the stack
if sline[-1][:-1] != "[stack]":
continue
print("[*] Found [stack]:")
# parse line
addr = sline[0]
perm = sline[1]
offset = sline[2]
device = sline[3]
inode = sline[4]
pathname = sline[-1][:-1]
print("