目录
0x00 初步分析程序
0x01 破解第一处验证
0x02 破解第二处验证
0x03 最终脚本
0x00 初步分析程序
由题目提示,应该是使用了一些加密算法,先使用PEID的一个插件看下有没有常见加密算法特征:
看到程序使用了Miracl大数库和S盒,可能是调用了AES算法,IDA打开,先shift+f5加载miracl大数库和常见加密函数的签名(签名文件放在https://github.com/sherlly/encryption了,需要自取~):
开始分析程序,发现程序中存在不少花指令,用于干扰IDA正常反编译:
通过加入一些无关的call指令,使堆栈不平衡,在IDA中体现为sp-analysis failed,也就是不能F5反编译,可以手动去花,但其实在这道题中,逻辑比较清楚,不能F5的关系也不大(好吧就是懒-。-)
根据字符串搜索,可以确定打印欢迎信息的位置:
xor_enc函数是将输入的字符串进行简单的异或操作得到解密的字符串:
接着定位到主函数中,可以看到输入的地方:
之后进入的应该就是验证逻辑,首先将输入的字符串进行分割,前3位进入验证函数2进行校验,后20位进入验证函数1校验:
具体验证函数的算法见下面分析。
0x01 破解第一处验证
进入第一处验证函数处,首先看到的是一些赋值的操作:
接下来同样调用了xor_enc函数对刚刚赋值的数据进行异或解密,动态调试记录下两个值为208CBB7CD6ECC64516D07D978F5F0681F534EAD235D5C49ADD72D2DB840D5304和7da39de66016477b1afc3dc8e309dc429b5de855f0d616d225b570b68b88a585,后面可以知道是校验用的密文和模数n:
接下来在函数中调用了大数库miracl,一些函数功能可以参考文章https://blog.csdn.net/shuilan0066/article/details/8520337
首先是调用mirvar函数进行赋值的操作,然后调用cinstr函数将大数字符串转换成大数:
看到调用了幂模函数powmod,猜想是使用了RSA算法:
在使用RSA进行加密后,将得到的密文和刚刚初始化的一段密文进行校验,相等则返回1:
尝试恢复下验证函数1的源码:
int RSA_ENC()
{
xor_enc(enc_data1,n_str); //生成模数n
// 7da39de66016477b1afc3dc8e309dc429b5de855f0d616d225b570b68b88a585
xor_enc(enc_data2,c_check); //生成校验用的密文
// 208CBB7CD6ECC64516D07D978F5F0681F534EAD235D5C49ADD72D2DB840D5304
hex_encode(input,hex_input); //input为输入字符串的后20位
mirsys(0x1f4,16); //初始化500位的16进制数
c=mirvar(0); //初始化为miracl大数类型的0
n=mirvar(0);
e=mirvar(0);
m=mirvar(0);
cinstr(m,hex_input);
cinstr(n,n_str); // 模n
cinstr(e,"3e9"); // e
powmod(m,e,n,c); // c = (m ^ e) mod n
big_to_bytes(0,c,c_str,0); //将大数转换为字符串
mirkill(c);
mirkill(n);
mirkill(e);
mirkill(m);
mirexit();
output_enc(c_str,output_hex,strlen(c_str));
if(strcmp(output_hex,c_check) == 0)
return 1;
else
return 0;
}
因此,验证函数1的破解可以转换为RSA攻击中的经典问题,已知模数n,指数e,密文c,如何求明文m。这里模数n可以通过大数分解网站(
http://factordb.com)分解,得到p,q。解密脚本见最后,解密得到的明文为iamahandsomeguyhaha1:
0x02 破解第二处验证
验证函数2对输入字符串的前3位进行校验,首先调用了check_digit函数校验是否满足都是纯数字:
可以进入该函数看下:
接下来进入验证函数2,在该函数中,首先也是调用了xor_enc函数解密了两个字符串0001314000000000和912CA2036A9A0656D17B6B552F157F8E,后面可以知道是原始的key和校验用的密文:
然后可以看到,函数将输入的前三位写入到了刚刚初始化的key的前三位中,并且第三位进行了加1的操作,得到最终的密钥key:
接下来调用了AES加密算法,动态调试可以知道加密的明文为pediy:
最后将加密后的密文和刚刚生成的校验的密文进行比较,相同则返回1:
因此,验证函数2的破解为爆破密钥,得到符合的密文,由于是纯数字,且未知的只有3位,破解速度很快,写脚本跑下得到密钥为521,因为在生成密钥时加了1,所以原始的输入应该是520:
输入完整flag验证下,成功:
flag:520iamahandsomeguyhaha1
0x03 最终脚本
# coding:utf-8
# author:sherllyyang00@gmail.com
flag_len = 23
serial="rgqmdj=" # i=0; xor i;i++ serial:
def get_part2():
c_check=0x208CBB7CD6ECC64516D07D978F5F0681F534EAD235D5C49ADD72D2DB840D5304
n=0x7da39de66016477b1afc3dc8e309dc429b5de855f0d616d225b570b68b88a585
e=0x3e9 #1001
p=208096057845685678782766058500526476379
q=273086345401562743300402731618892888991
def egcd(a, b):
if a==0:
return (b,0,1)
else:
g, y, x = egcd(b%a, a)
return (g, x-(b//a)*y, y)
def modinv(a, m):
g, x, y = egcd(a, m)
if g!=1:
raise Exception('modular inverse does not exist')
else:
return x%m
d=modinv(e,(p-1)*(q-1))
m=pow(c_check,d,n)
flag2 = hex(m)[2:-1].decode('hex')
print flag2
# iamahandsomeguyhaha1
return flag2
def get_part1():
from Crypto.Cipher import AES
import string
enc_s="831GD47;?K2M=8:&U#$V#T\"-+\\*)*X'e"
enc_key="123567389:;<=>?"
plain="pediy"
enc_check="912CA2036A9A0656D17B6B552F157F8E"
def encrypt(plain,key):
bs = AES.block_size
plain = plain+"\x00"*(bs-len(plain))
cipher = AES.new(key)
return cipher.encrypt(plain).encode('hex').upper()
table = string.digits
for i in table:
for j in table:
for k in table:
key="%s1314000000000"%(i+j+k)
enc = encrypt(plain,key)
if enc == enc_check:
print "key: "+i+j+k
flag1=i+j+chr(ord(k)-1)
return flag1
return 0
flag2=get_part2()
flag1=get_part1()
print flag1+flag2
# 520iamahandsomeguyhaha1
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。