RE1:
RE3:
1.IDA打开发现没有Main函数,shift + F12查看字符串交叉应用找到程序加密的主逻辑
2.上面的操作不重要最重要的是红框里面的三个函数,自己修改过函数名,分别是 魔改RC4 魔改BASE,以及比较函数
3.RC4加密函数分析,其实就是将S盒的大小从256魔改为了64,别的并没有啥变化
4.BASE加密魔改了码表以及位运算和标准的BASE加密有所不同,根据我的注释还原就好
5. exp
base_table = '4KBbSzwWClkZ2gsr1qA+Qu0FtxOm6/iVcJHPY9GNp7EaRoDf8UvIjnL5MydTX3eh'
enc = '6zviISn2McHsa4b108v29tbKMtQQXQHA+2+sTYLlg9v2Q2Pq8SP24Uw'
index = [base_table.index(i) for i in enc]
index.append(0)
base_decode = []
for i in range(0,len(enc), 4):
ans = index[i:i+4]
base_decode.append(ans[0] | ((ans[1] & 0b11) << 6))
base_decode.append(((ans[1] >> 2) & 0b1111) | ((ans[2] & 0b1111) << 4))
base_decode.append(((ans[2] >> 4) & 0b11) | (ans[3] << 2))
base_decode.pop()
def Init_RC4(key):
j = 0
S = [i for i in range(0x40)]
K = []
for k in range(64):
K.append(ord(key[k % len(key)]))
for t in range(64):
j = (j + S[t] + K[t]) % 64
temp = S[j]
S[j] = S[t]
S[t] = temp
return S
def Decode_RC4(S):
i = 0
j = 0
for t in range(len(base_decode)):
i = (i + 1) % 64
j = (j + S[i]) % 64
tmp = S[i]
S[i] = S[j]
S[j] = tmp
base_decode[t] ^= (i ^ j) & S[(((i ^ j) + S[i] + S[j]) % 64) & 0xff]
key = "the_key_"
S = Init_RC4(key)
Decode_RC4(S)
print(bytes(base_decode).decode())
RE4:
1.一直调试跟进到主函数加密的位置,我的主函数加密位置的地址是 0x1005F820,遇见啥DLL,等跳出就行,主函数的逻辑就是提示用户输入用户输入后计算用户输入的长度不为41退出程序,然后进入encrypt加密(修改过函数名)
2.encrypt函数分析,加密6轮每轮加密8字节,每轮调用enc函数加密加密64次,根据后四字节判断分支如果后四字节符号位为1也就是负数,进入if 否则 进入else分支,图片有注释
3.进入enc前将前四字节给eax,后四字节给edx,cl为1,a2也就是cl固定为1,这个函数只有else分支是有意义的
4.也就是这两条指令,shld指令会对edx也就是后四字节左移cl也就是左移1,左移高位溢出低位补0,而补的这个0其实会使用eax的最高位覆盖,然后将eax左移1,那么其实就是eax丢失最高位,edx丢失最高位,而eax丢失的最高位变为了edx的最低位,而edx是最高位其实可以通过判断条件得到因为如果走的是if分支代表edx小于0,也就是最高位是-1,else分支同理
5.还有一个问题就是我们如何判断每次加密走的是那个分支呢,在if分支enc函数结束后会对eax 异或一个54AA4A9h,edx 异或的是 0不用管,因为eax左移1那么最低为必定是0,if分支xor 54AA4A9h后eax最低为会变成1,因为异或 1 0为1嘛,而else分支不会xor只有移位操作也就是最低位为0,我们可以通过判断eax的最后一个字节从而知道每次加密所走的分支
6.exp
import libnum
enc = [695646285, 1503570421, 3299956309, 808198447, 393771443, 3690458536, 3689960417, 1582367721, 3741368585, 3254556400, 2506286773, 1]
for i in range(0,len(enc),2):
a = enc[i]
b = enc[i+1]
#这个判断条件是因为如果走的是大于0的分支那么 a << 1低位肯定是0,如果是另外一个分支那么 xor 0x54AA4A9 那么最低位就是 1
for t in range(64):
if a & 1 == 1:
a ^= 0x54AA4A9
#a左移1最高位丢失,但是丢失的最高位是b的最低位,因为shld指令回家b左移后的低位使用a的最高位补全
a = ((b & 1) << 31) | a >> 1
#因为根据进入分支的判断当b最高位为1时也就是b是负数是进入这个分支,左移b的最高位丢失但其实我们都知道每次的最高位是1,将最高位填成1,舍去最低位就好
b = 1 << 31 | b >> 1
else:
#与上同理
a = ((b & 1) << 31) | a >> 1
#这个分支 b最高位为0也就是正数,既然最高位为正数左移其实舍弃的最高位并没有什么影响,右移1 最高位的 0就回来了
b = b >> 1
enc[i] = a
enc[i+1] = b
for i in enc:
print(libnum.n2s(i)[::-1].decode(),end='')
RE5:
1.Unity逆向,在这个目录下有一个Assembly-CSharp.dll文件这是程序的关键代码,使用dnspy打开
2.按照我箭头指的打开,发现好几个模块,先点击第一个里面有很多TEA,根据TEA的特点找到最符合的一个,现在还缺KEY和密文
3.在这个模块中,翻到最下面在OnValueChanged这个类中找到key和密文
在这边双击BABBB...其实也可以找到正确的TEA,因为这里就是调用嘛
4.exp
from ctypes import *
import libnum
enc = [ 3363017039,
1247970816,
549943836,
445086378,
3606751618,
1624361316,
3112717362,
705210466,
3343515702,
2402214294,
4010321577,
2743404694]
key = [0x11111111] * 4
delate = 2654435769
flag = []
for i in range(0,len(enc),2):
a1 = c_uint32(enc[i])
a2 = c_uint32(enc[i+1])
num3 = c_uint32(delate * 32)
for t in range(32):
a2.value -= ((a1.value << 4) + key[2] ^ a1.value + num3.value ^ (a1.value >> 5) + key[3])
a1.value -= ((a2.value << 4) + key[0] ^ a2.value + num3.value ^ (a2.value >> 5) + key[1])
num3.value -= delate
flag.append(a1)
flag.append(a2)
for i in flag:
print(libnum.n2s(i.value)[::-1],end='')