KCTF从入门到实战(九)- 杯弓蛇影
简介
大家好! 我们又见面了! 继《传家之宝》和《南充茶坊》之后,这一次我们奉上实战系列作品——《杯弓蛇影》
故事背景简介
春夏秋冬,寒来暑往,日复一日,这人间循环往复空换时光流逝,似乎少了点乐趣。
无所事事便没有好事。
不知从哪儿传出的“巳蛇将出”的谣言,传说巳蛇亦蛇亦弓,入水为蛇,踪迹全无,出水为弓,毙敌百步。竟也有人捕风捉影、信以为真,一传十,十传百,事实变得更加扑朔迷离,人心惶惶。
遂智者言,地势坤、风入松、松如浪,一景一画皆有其意,善用者,良马也。
脱壳
使用的反调试:异常,硬件断点检测,内存检验,NtQueryInformationProcess,简单的反dump等。这些反调试有的会造成导入表加密,有的会使代码段错乱,造成异常,有的会修改算法,需要找出所有的并且绕过这些反调试。
内存校验数据选择放在了 紧跟在DOS头后得无效数据
清楚掉所有的反调试后
导入表保护:清除了源程序的导入表,使用了iat混淆,你需要修复导入表
VM识别解析
模拟存储识别:
1. 代码空间
Code用于模拟在一开始new出来
1. 代码标识 CD 8字节
2.代码sizeof 8字节
2.代码大小 8字节
进行拷贝赋值
2. 环境寄存器[9]
unsigned long long ER[9];//9个计算机环境寄存器 初始化的事后使用
#define TE 0 //Text End
#define HS 1 //Heap Start
#define HE 2 //Heap End
#define SS 3 //Stack Start
#define TOP 4 //Stack End
//3个指针寄存器
#define IP 5 //指令指针 (指令运行后必然使用 ER[5])
#define SP 6 //栈指针
#define FP 7 //过程调用帧栈指针
...
3. 寄存器[10];
类型 unsigned long long
寄存器[1] - 寄存器[9]
GR[1] - GR[9]
指令解析:
ps:(有些操作会随着SetProperty属性更改而进行修改)
指令操作
[默认编号0]:LEA(取地址)
[默认编号1]:SetProperty 寄存器,属性值(立即数)()更改不同属性的值
[默认编号2]:SetProperty 寄存器,属性值(立即数)()更改不同属性的值
[默认编号3]:LOADDI
[默认编号4]:LOADQI
[默认编号5]:LOADF4I
[默认编号6]:LOADF8I
[默认编号7]:LOADB
[默认编号8]:LOADW
[默认编号9]:LOADD [寄存器],[内存]
[默认编号10]:LOADQ
[默认编号11]:取地址
[默认编号12]:-
[默认编号13]:STOREBI
[默认编号14]:STOREWI
[默认编号15]:STOREDI[寄存器],立即数
[默认编号16]:STOREQI
[默认编号17]:STOREF4I
[默认编号18]:STOREF8I
[默认编号19]:STORERB [内存][内存] SetProperty 属性为准
[默认编号20]:STORERW
[默认编号21]:STORERD
[默认编号22]:STORERQ
[默认编号23]:-待扩展
[默认编号24]:-待扩展
[默认编号25]:MOVB [内存][内存]
[默认编号26]:MOVW
[默认编号27]:MOVD
[默认编号28]:MOVQ
[默认编号29]:-待扩展
[默认编号30]:-待扩展
[默认编号31]:MOVRR [寄存器][寄存器]
[默认编号32]:MOVRF4
[默认编号33]:MOVRF8
[默认编号34]:PUSHFP
[默认编号35]:PUSHB
[默认编号36]:PUSHW
[默认编号37]:PUSHD
[默认编号38]:PUSHQ
[默认编号39]:PUSHF4
[默认编号40]:PUSHF8
[默认编号41]:POPFP
[默认编号42]:POPB
[默认编号43]:POPW
[默认编号44]:POPD
[默认编号45]:POPQ
[默认编号46]:待扩展
[默认编号47]:待扩展
[默认编号48]:INCR
[默认编号49]:DECR
[默认编号50]:ADDR
[默认编号51]:SUBR
[默认编号52]:MULR
[默认编号53]:DIVR
[默认编号54]:--
[默认编号55]:--
[默认编号56]:--
[默认编号57]:--
[默认编号58]:--
[默认编号59]:--
[默认编号60]:--
[默认编号61]:--
[默认编号62]:AND
[默认编号63]:OR
[默认编号64]:XOR
[默认编号65]:NOT
[默认编号66]:SHRA
[默认编号67]:SHRL
[默认编号68]:SHL
[默认编号69]:CALL
[默认编号70]:RET
[默认编号71]:MOVSF
[默认编号72]:LDFPOB
[默认编号73]:LDFPOW
[默认编号74]:LDFPOD
[默认编号75]:LDFPOQ
[默认编号76]:JMPI
[默认编号77]:JNZ
[默认编号78]:JNS
[默认编号79]:JNL
[默认编号80]:JZ
[默认编号81]:JS
[默认编号82]:JL
[默认编号83]:COMP
[默认编号84]:COMPI
[默认编号85]:OUTO
[默认编号86]:OUTD
[默认编号87]:OUTH
[默认编号88]:OUTC
[默认编号89]:--
[默认编号90]:--
[默认编号91]:NOP
例子strlen:
LEA R1,HELLO
MOVRR R3,R1
L1:
LOADB R2,R1
JZ END
INCR R1
JMPI L1
END:
SUBR R1,R3
OUTD R1
HALT
; 字符串 AA AA AA 00
HELLO: DB AA AA AA 00
二进制解析CODE
unsigned AnsiChar data[70] = {
//标识符
0x43, 0x44, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
//code 大小
0x2A, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00,
//data 数据大小
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
//lea 寄存器[1],地址
0x00, 0x01, [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
//MOVRR 寄存器3,寄存器1
0x1F, 0x03, 0x01,
//loadb 寄存器2,寄存器1
0x07, 0x02, 0x01,
//jz 地址
0x50, 0x24,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00,
//INCR
0x30, 0x01,
//jmp跳转
0x4C, 0x0D,
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,]
//SUBR 寄存器1,寄存器3
0x33, 0x01, 0x03,
//OUTD
0x56,0x01,
//结束符
0x5C,
//数据字符串
0xAA, 0xAA, 0xAA, 0x00
};
指令混淆识别:
1.非混淆代码会 与外界交互
2.前面会执行多个pushd 指令 popd指令结束
算法破解
此题使用了一种面向多明文的数据对称加密算法
该算法能够对同一个密文,使用相同的计算过程,在不同的密钥作用下,得出既定的不同明文。
该算法能够为“必须在不同环境下表现出不同功能的程序设计”提供一种安全的实现方案。只有拥有合适密钥才能解出理想代码,否则不仅看不到理想代码,甚至不能确定所谓的理想代码是否真的存在。
为了符合参赛出题要求,我们将‘一批密钥’和‘每个密钥应该解出什么样的明文’固定在了CrackMe里面(我们认为,就算公开了),还将解密算法固定在了CrackMe里面(我们认为也算公开了),然后要求攻击者给出正确的密文,即:序列号(实际上是在挑战攻击者找出正确的加密算法)
密码算法描述
假设给定一组密钥masterkey[i]和一组对应的明文m[i]
明文预处理
0x01
对输入的n个明文m[i](i=1,2,...,n)均填充到等长长度且为L的倍数,令结果为m_p[i](L为分组密码中分组的长度,本题L为32bit)
即:填充到长度为ceil(maxlen(m[1],m[2],…,m[n])/L)*L;其中ceil()函数返回不小于给定数字的最小整数
0x02
对填充后的m_p[i]分别采用Hash算法(本题选用的是SHA-256),分别得到m_h[i];
0x03
m_p[i]分别循环地与m_h[i]异或,生成m_hp[i];
0x04
将m_hp[i]分别与m_h[i]拼接,得到m_hpd[i];
0x05
将m_hpd[i]分别以Lbit为分组长度进行分组(此题L为32bit)
m_hpd[i]=m_hpd[i][0] || m_hpd[i][1] || ... || m_hpd[i][length/L] (length为每个明文m_hpd[i]的长度)
密钥预处理
0x01
随机生成salt(每个salt的长度为Lbit)
0x02
将n个salt分别连接在n个密钥口令masterkey[i]后面得到k[i]
0x03
对k[i]进行Hash生成key[i](此题选用的是SHA-256)
0x04
对key[i]分别以Lbit为分组长度进行分组(此题L为32bit)
key[i]=key[i][0] || key[i][1] || ... || key[i][length/L] (length为每个分组key[i]的长度)
0x05
对分组后的不同的key[i]对应的相同位置的分组检查是否有相等,若有,则重复12345步
加密
0x01
随机产生多个长度为Lbit的数据offset(此题L为32bit)
0x02
生成密文的函数为:
c=Enc(key[1],...key[n],m_hpd[1],...mhpd[n])
=(R_11 || R_21 || ... || R_n1 || offset[1]) || (R_12 || R_22 || ... || R_n2 || offset[2])...(R_1m || R_2m || ... || R_nm || offset[m])
其中m为明文的分组组数
R为加密得到的密文数据块
n为输入的明文和密钥个数
offset为加密时生成的随机数
0x03
在每一个分组中:
在第i个分组:
c_i=Enc(key[1][i%t],key[2][i%t],...,key[n][i%t],m_hpd[1][i],m_hpd[2][i],...,m_hpd[n][i])
=R_1i || R_2i || ... || R_ni || offset[i]
其中n为输入的明文和密钥个数
t为密钥的分组组数
对n个坐标(key[1][i%t],m_hpd[1][i]^offset[i])
(key[2][i%t],m_hpd[2][i]^offset[i])
....
(key[n][i%t],m_hpd[n][i]^offset[i])
采用拉格朗日插值定理所生成的系数为R_1i,R_2i,...,R_ni
offset[i]为随机数,以确保坐标的第2个值都小于p
(其中所有的运算均为模p运算,p为小于2^L的最大素数)
0x04
对所有分组加密完成,产生的所有系数以及offset组成最终的密文
解密
0x01
对给定的n个密钥用加密时同样的方法对密钥进行预处理并分组
得到key[i]=key[i][0] || key[i][1] || ... || key[i][length/L](i=1,2,...,n)
0x02
设k为key[i]
解密函数为:m=Dec(k,c);
其中,第j个分组明文的解密方法为:
m[i]=Dec(k[j],c[j])
=Dec(k[j],R_1j,R_2j, ...,R_nj,offset[j])
=(R_1j*k[j]^n-1+R_2j*k[j]^n-2+...+R_nj)^offset[j]
其中,R_1j,R_2j,...,R_nj分别为第j分组的密文数据块,key[j]中的j需要对t取模, t为密钥的分组组数
*,+均为模p运算,p的取值与加密时相同
对所有分组进行解密后,将解密得到的明文分组拼接在一起得到完整的明文
0x04
对拼接在一起的明文进行分离,得到m_hp[i]与m_h[i]
0x05
将m_hp[i]循环与m_h[i]进行异或得到m_p[i]
0x06
将m_p[i]进行Hash运算(SHA256)得到的结果与m_hp[i]进行比较验证
如相等则表示第i个明文解密成功,如不相等则表示解密失败
0x07
以上步骤重复n次,便可成功解密得到所有的n对明文
出题
此题用Username作为种子,利用KDF函数(实际上是SHA-256的变种)生成salt和offset分别用于上述算法所需的对密钥进行预处理和加密过程。
对我们选取的3对明文和密钥利用以上方法进行加密(即n=3,L=32bit)
加密算法产生的系数(即以上算法产生的密文去掉offset)便为序列号
crackme运行过程
CM.EXE会对用户输入的Username作为种子,利用KDF函数生成salt和offset分别用于上述算法所需的对密钥进行预处理和解密过程。
对输入的序列号作为密文,用正确的3对密钥对密文进行解密,若能在上述算法中验证解密成功且解密得到的明文和我们公开的明文一致,则序列号正确。
PS.由于本人疏忽 在第一次出题时只按照上述算法的解密中的步骤0x06判断hash值是否相等验证序列号是否正确,遗漏了将解密得到的明文和公开的明文对比是否一致的判断,因此出现了bug,中途修改了题目 望各位大佬见谅!
用于加密产生密文的三对明文和密钥公开如下:(英文逗号为分割符,逗号前为密钥,逗号后为明文)
这杀软好多呀,好像是个VM,我是个任务管理器
这机器里文件修改时间分布广,找包含关键词的文件
伊娃找到了理想植物,赶紧带回去给船长
[培训]《安卓高级研修班(网课)》月薪三万计划
最后于 2020-4-28 00:50
被xh1998编辑
,原因: