key的标准验证方案:
(0)key = input
(1)deckey1 = base64.b64decode(key)
(2)deckey2 = base64.b64decode(deckey1)
(3)deckey3 = morse_decode(deckey2)
(4)sm3_hash(deckey2,3)==tail of key
(5)deckey3 can step out game_map
即输出key讲过二次base64解密得到deckey2
deckey2讲过摩斯解码得到deckey3
其中需校验deckey2前三字符的"国密3"的杂凑(哈希)值是否与输出key尾部相等
其次deckey3表示在迷宫中行走,zlqp分别表示往下、往右、往上、往左行走,
此方式假定坐标从上到下,从左到右表示的迷宫图,最后" "空格表示停止。
能走的坐标必须是"0"标记值,而走过之后都会在原来坐标标记"4",
而迷宫又是全局静态变量,所以标准走法只能验证一次。
而又由于” “表示停止,而后续没有检测走过不数或目的坐标的检测,
导致其存在地图校验bug,bug可以知道导致不走迷宫而直接跳过迷宫校验,
同时也可以只完成部分迷宫路径走动,而不必全部走出迷宫就可以完成校验。
废话少说,清除反调试检测
下述函数都是反调试逆向的操作,需要返回0才能继续正常功能,否则退出。
它们是对后面所列地址函数的封装,这里我们批量修改为指令
xor eax,eax
ret
即机器码 "\x33\xC0\xC3",用pefile处理原pe程序另存为CTF03_dbg.exe即可。
Hi_check_reverse_info1_sub_42D681 .text 0042D681 00000005 R . . . . . .
Hi_check_reverse_info2_sub_42E26B .text 0042E26B 00000005 R . . . . . .
Hi_check_reverse_info3_sub_42D7F8 .text 0042D7F8 00000005 R . . . . . .
Hi_check_reverse_info4_sub_42D23F .text 0042D23F 00000005 R . . . . . .
Hi_check_reverse_info5_sub_42D334 .text 0042D334 00000005 R . . . . . .
Hi_check_reverse_info6_sub_42DD66 .text 0042DD66 00000005 R . . . . . .
Hi_check_reverse_info7_sub_42D92E .text 0042D92E 00000005 R . . . . . .
Hi_check_reverse_info8_sub_42DF7D .text 0042DF7D 00000005 R . . . . . .
Hi_check_reverse_info9_sub_42E31F .text 0042E31F 00000005 R . . . . . .
Hi_check_reverse_infoA_sub_42DA7D .text 0042DA7D 00000005 R . . . . . .
Hi_check_reverse_infoB_sub_42D389 .text 0042D389 00000005 R . . . . . .
Hi_check_reverse_infoC_sub_42D6BD .text 0042D6BD 00000005 R . . . . . .
Hi_check_reverse_infoD_sub_42D807 .text 0042D807 00000005 R . . . . . .
Hi_check_reverse_infoE_sub_42D39D .text 0042D39D 00000005 R . . . . . .
Hi_check_reverse_infoF_sub_42D1CC .text 0042D1CC 00000005 R . . . . . .
Hi_check_reverse_info10_sub_42DA96 .text 0042DA96 00000005 R . . . . . .
Hi_check_reverse_info11_sub_42D8CA .text 0042D8CA 00000005 R . . . . . .
Hi_check_reverse_info12_sub_42DD48 .text 0042DD48 00000005 R . . . . . .
Hi_check_reverse_info13_sub_42DBC7 .text 0042DBC7 00000005 R . . . . . .
Hi_check_reverse_info15_sub_42E428 .text 0042E428 00000005 R . . . . . .
Hi_check_reverse_info16_sub_42D825 .text 0042D825 00000005 R . . . . . .
Hi_check_reverse_info17_sub_42E27F .text 0042E27F 00000005 R . . . . . .
Hi_check_reverse_info18_sub_42E162 .text 0042E162 00000005 R . . . . . .
Hi_check_reverse_info19_sub_42D4F6 .text 0042D4F6 00000005 R . . . . . .
Hi_check_reverse_info1A_sub_42DA41 .text 0042DA41 00000005 R . . . . . .
Hi_check_reverse_info1B_sub_42D096 .text 0042D096 00000005 R . . . . . .
Hi_check_reverse_info1C_sub_42E45A .text 0042E45A 00000005 R . . . . . .
Hi_check_reverse_info1D_sub_42D203 .text 0042D203 00000005 R . . . . . .
#反反调试处理脚本如下
#------- ------- ------- ------- ------- ------- -------
rvas = '''430B10
431190
430E80
430030
430170
4302A0
4303D0
4304F0
430610
431070
4313C0
431420
4314F0
4317C0
431C40
431CE0
431D80
431F20
431FD0
4338D0
42D0B4
42FA20
42FAF0
42FC90
4323B0
4325D0
4326C0
432840
4329C0
432B30
432D20'''
import pefile
pe = pefile.PE(r'.\CTF03.exe')
def cpy_rpl_nk_fn(pe,fn):
xor_eax_eax_ret = "\x33\xC0\xC3"
for eastr in rvas.split('\n'):
rva = int(eastr,0x10)-0x400000
pe.set_bytes_at_rva(rva,xor_eax_eax_ret)
pe.write(r'.\CTF03\{}'.format(fn))
cpy_rpl_nk_fn(pe,"CTF03_dbg.exe")
#------- ------- ------- ------- ------- ------- -------
三个函数
base64 解码 Hi_base64decode_sub_42D267
莫斯码 解码 Hi_sgd_morse_xsym_decode_sub_42D96A
国密3 杂凑 Hi_sm3_hash_sub_42DA78
base64可用python的base64模块替换
国密3可以用网络开源测试,实际我找到的一份只有计算'/\x00\x00'三个字符时的杂凑值一样,
所以还是直接用样例程序的
莫斯码解码分三种,5字节长的0~9,4字节的a~z,7字节长的特殊符号,
4字节长的都是在原莫斯码基础上补*对齐,7字节长也是补*对齐7,然后再加上所代表的符合
莫斯码解码与编码功能模拟,mnstr,sgdstr,xsymstr可通过GetString获取,如
mnstr GetString(0x49B1E0,-1,ASCSTR_C)
sgdstr = GetString(0x49B218,-1,ASCSTR_C)
xsymstr = GetString(0x49B218,-1,ASCSTR_C)
mnstr = '-----.----..---...--....-.....-....--...---..----.'
morse_n = {}
for i in xrange(0,0xA):
tmnstr = mnstr[i*5:i*5+5]
morse_n[chr(0x30+i)] = tmnstr
morse_n[tmnstr] = chr(0x30+i)
sgdstr='.-**-...-.-.-..*.***..-.--.*......**.----.-*.-..--**-.**---*.--.--.-.-.*...*-***..-*...-.--*-..--.----..'
sgd_az = {}
for i in xrange(0,0x1A):
tsgdstr = sgdstr[i*4:i*4+4]
sgd_az[chr(0x61+i)] = tsgdstr
sgd_az[tsgdstr] = chr(0x61+i)
xsymstr='.-.-.-*.---...*:--..--*,-.-.-.*;..--..*?-...-**=.----.*\'-..-.**/-.-.--*!-....-*-..--.-*_.-..-.*"-.--.**(-.--.-*)...-..-$.-...**&.--.-.*@.-**-...-.-.-..*.***..-.--.*......**.----.-*.-..--**-.**---*.--.--.-.-.*...*-***..-*...-.--*-..--.----..'
xsym = {} #.:,;?='/!-_"()$&@
a = ""
for i in xrange(0,0x11):
txsymstr = xsymstr[i*8:i*8+8]
symch = txsymstr[7]
xstr = txsymstr[:7]
xsym[symch] = xstr
xsym[xstr] = symch
a += symch
def get_encodestr_of__sgd_az__morse_n__xsymstr(gmap_steps_key=''):
global sgd_az,morse_n,xsym
xstr = ''
for ch in gmap_steps_key:
if ch == ' ':
xstr += '/'
return xstr
else:
if ch in sgd_az:
xstr += (sgd_az[ch]+' ')
if ch in morse_n:
xstr += (morse_n[ch]+' ')
if ch in xsym:
xstr += (xsym[ch]+' ')
因为deckey3用于走迷宫,通过迷宫分析我们得到(参考后续分析)
deckey3 = gmap_steps_key = 'zlzllllzzzppqppzzzlllzlllzll '
对deceky3进行摩斯编码即可得到deckey2
deckey2 = '--.. .-.. --.. .-.. .-.. .-.. .-.. --.. --.. --.. .--. .--. --.- .--. .--. --.. --.. --.. .-.. .-.. .-.. --.. .-.. .-.. .-.. --.. .-.. .-.. /'
对deckey2进行二次base64编码即可得到key的前面部分
import base64
deckey1 = base64.b64encode(deckey2)
key = base64.b64encode(deckey1)
其中key='TFMwdUxpQXVMUzR1SUMwdExpNGdMaTB1TGlBdUxTNHVJQzR0TGk0Z0xpMHVMaUF0TFM0dUlDMHRMaTRnTFMwdUxpQXVMUzB1SUM0dExTNGdMUzB1TFNBdUxTMHVJQzR0TFM0Z0xTMHVMaUF0TFM0dUlDMHRMaTRnTGkwdUxpQXVMUzR1SUM0dExpNGdMUzB1TGlBdUxTNHVJQzR0TGk0Z0xpMHVMaUF0TFM0dUlDNHRMaTRnTGkwdUxpQXY='
再加上deckey2的前三个字符的国密3杂凑值sm3_hash('--.')即可,
我用网络的sm3代码编译得到的是b92a72497b685c31013347a7276f371f8cf91085ab8322
而样例得到的是 b92a72497b685c31013347a7276f371f8cf91085ab8322009bfed2df41d94f94
当然我们用样例的,可以在 00435191 出断点断下,然后复制eax字符串指针指向的hash值即可。
sm3_hash = 'b92a72497b685c31013347a7276f371f8cf91085ab8322009bfed2df41d94f94'
于是我们得到最终的key=key+sm3_hash,
这个验证肯定是没问题的,由于闲的是字母和数字,上述key中有”=“号,所以我们对
deckey3 = gmap_steps_key = 'zlzllllzzzppqppzzzlllzlllzll '修改一下,追加一个空格符,这会导致deckey2追加一个"/"
最终二次base64加密会对齐没有出现”=“号,即最终标准结果为
deckey2 = '--.. .-.. --.. .-.. .-.. .-.. .-.. --.. --.. --.. .--. .--. --.- .--. .--. --.. --.. --.. .-.. .-.. .-.. --.. .-.. .-.. .-.. --.. .-.. .-.. //'
deckey1 = base64.b64encode(deckey2)
key = base64.b64encode(deckey1)+sm3_hash
即为"TFMwdUxpQXVMUzR1SUMwdExpNGdMaTB1TGlBdUxTNHVJQzR0TGk0Z0xpMHVMaUF0TFM0dUlDMHRMaTRnTFMwdUxpQXVMUzB1SUM0dExTNGdMUzB1TFNBdUxTMHVJQzR0TFM0Z0xTMHVMaUF0TFM0dUlDMHRMaTRnTGkwdUxpQXVMUzR1SUM0dExpNGdMUzB1TGlBdUxTNHVJQzR0TGk0Z0xpMHVMaUF0TFM0dUlDNHRMaTRnTGkwdUxpQXZMdz09b92a72497b685c31013347a7276f371f8cf91085ab8322009bfed2df41d94f94"
迷宫的路径怎么来的,即如何得到
deckey3 = gmap_steps_key = 'zlzllllzzzppqppzzzlllzlllzll '
下述为迷宫校验部分,其中Hi_game_map_49B000为迷宫的数据
.text:004351DB lea eax, [ebp+loc_deckey3]
.text:004351E1 push eax
.text:004351E2 push offset Hi_game_map_49B000
.text:004351E7 call Hi_zlqp_script_run_sub_42D9AB
迷宫为10*10的方形,通过下属IDAPython脚本可以得到迷宫的直观图像
#------- ------- ------- ------- ------- ------- -------
lstr = ''
for l in xrange(0,10):
for c in xrange(0,10):
f = Byte(0x49B000+l*10*4+c*4)
if f == 0:
lstr+='0'
else:
lstr+='1'
print lstr
lstr = ''
#------- ------- ------- ------- ------- ------- -------
0111111110
0011111000
1000001011
1111101001
1000101001
1010001011
1011111001
1000011100
1111000010
1111111000
#------- ------- ------- ------- ------- ------- -------
上述迷宫,左上坐标为[0,0],作者原意应该是走到右下[9,9]走出迷宫之后停止,
只能往"0"处走,且走过之后会设置为"4",即不能走回头路,我们可以通过
如前言所提zlqp四个字符分别表示往下、往右、往上、往左行走,通过下面python
脚本,我们可以自动获取迷宫走过的路径
deckey3 = gmap_steps_key = get_gmap_steps() + ' '
得到#deckey3 = 'zlzllllzzzppqppzzzlllzlllzll '
#------- ------- ------- ------- ------- ------- -------
gmap = '''
0111111110
0011111000
1000001011
1111101001
1000101001
1010001011
1011111001
1000011100
1111000010
1111111000
'''
gmap = gmap.replace('\n','')
def get_gmap_steps():
LINE = 0
COLUMN = 1
steps = ''
START_LC = [0,0]
END_LC = [9,9]
cur_lc = START_LC
pre_lc = cur_lc
while True:
next_lc,cur2next_op = get_next_lc(cur_lc,pre_lc)
pre_lc = cur_lc
cur_lc = next_lc
steps += cur2next_op
if cur_lc == END_LC:
break
return steps
def get_next_lc(cur_lc = [0,0],pre_lc = [0,0]):
LINE = 0
COLUMN = 1
next_lc = [cur_lc[LINE],cur_lc[COLUMN]]
cur2next_op = None
if step_zlqp('z',cur_lc,pre_lc):
next_lc[LINE] += 1
cur2next_op = 'z'
elif step_zlqp('l',cur_lc,pre_lc):
next_lc[COLUMN] += 1
cur2next_op = 'l'
elif step_zlqp('q',cur_lc,pre_lc):
next_lc[LINE] -= 1
cur2next_op = 'q'
elif step_zlqp('p',cur_lc,pre_lc):
next_lc[COLUMN] -= 1
cur2next_op = 'p'
return [next_lc,cur2next_op]
def step_zlqp(op='',cur_lc = [0,0],pre_lc = [0,0]):
global gmap
LINE = 0
COLUMN = 1
if 'z' == op:
if [cur_lc[LINE]+1,cur_lc[COLUMN]]==pre_lc:
return False
if cur_lc[LINE]+1 < 10:
if '0' == gmap[(cur_lc[LINE]+1)*10+cur_lc[COLUMN]]:
return True
return False
elif 'l' == op:
if [cur_lc[LINE],cur_lc[COLUMN]+1]==pre_lc:
return False
if cur_lc[COLUMN]+1 < 10:
if '0' == gmap[cur_lc[LINE]*10+cur_lc[COLUMN]+1]:
return True
return False
elif 'q' == op:
if [cur_lc[LINE]-1,cur_lc[COLUMN]]==pre_lc:
return False
if cur_lc[LINE]-1 > 0:
if '0' == gmap[(cur_lc[LINE]-1)*10+cur_lc[COLUMN]]:
return True
return False
elif 'p' == op:
if [cur_lc[LINE],cur_lc[COLUMN]-1]==pre_lc:
return False
if cur_lc[COLUMN]-1 > 0:
if '0' == gmap[cur_lc[LINE]*10+cur_lc[COLUMN]-1]:
return True
return False
#------- ------- ------- ------- ------- ------- -------
由于bug的存在,deckey3 = 'zlzllllzzzppqppzzzlllzlllzll '
最极致的情形就是deckey3=" "
此时deckey2 = "/"
sm3_hash("/\x00\x00")='2f725aaf8d9fa538554e9f3589ddc785364d52ab1a6760c12caa2ec01ae4ba9e'
key = base64.b64encode(base64.b64encode('/')) = 'THc9PQ=='
即key = 'THc9PQ==2f725aaf8d9fa538554e9f3589ddc785364d52ab1a6760c12caa2ec01ae4ba9e’
由于不能有"="号,根据base64解码特性,这里直接去掉两个”=“号就可以,即
‘THc9PQ2f725aaf8d9fa538554e9f3589ddc785364d52ab1a6760c12caa2ec01ae4ba9e’
其它的情形,如只走到中间,或后续追加不定个数的停止符空格” “,都会产生不同的验证key