样本在附件,这是我第一次接触wasm,不过还好,此样本为了兼容浏览器,将wasm转成了js,比较简单,适合新手小白
可以直接在本地运行调试,而且里面就一个方法 doAesCbc,所以就很简单了,直接整就完了
餐前甜品
很明显可以看出第一个参数是明文,第二个参数是模式?可能是用来区分加密解密,或者填充模式
进来全是初始化,直接跳过,不过一看这个memory, table啥的盲猜就是拿数组当内存使了
一直运行到最后才开始执行第一行代码
if (0 == parseInt(inMode)) {
return wbsk_AES_cbc_encrypt_base64(inInput);
} else {
return wbsk_AES_cbc_decrypt_base64(inInput);
}
这里对传入的模式进行了判断,如果是0就是加密,不是0就是解密,继续执行 进入 wbsk_AES_cbc_encrypt_base64
这里很明显可以看到 IV就出来了,后面经过跟代码,并没有发现在对IV进行额外的处理
直接单步执行,可以看到 outadd 这就是输出结果的内存地址了,然后将各种长度,传入了 CBCEncrypt,
然后进 CBCEncrypt 方法,这里直接进入了ccal
可以看到根据字符串获取了对应的方法,然后便利传进来的参数 args ,根据不同的类型进行不同方法的调用
stacksave就不说了,堆栈为0的时候,保存堆栈,主要是后面的调用,这里可以单步调试看这几个方法都干了啥
单步调试进来之后可以看到他只是将参数写入了内存,然后将内存中的参数回填到 cArgs 数组
然后就 cArgs 通过 func.apply 调用上面获取的_wbsk_AES_cbc_encrypt,欧克,接下来就是正餐了
小菜
单步调试可以发现进入的函数是 $5
传参:
$0_1是传入的明文,
$1_1是明文长度,
$2_1是输出地址,
$3_1是之前的lenadd,也不知道是啥,
$4_1 是iv,
$5_1是iv长度
还好方法体不长,一行一行看就行了
前面一堆变量的声明,然后可以发现 一堆抽象的赋值
HEAP32[($8_1 + 76 | 0) >> 2] = $0_1;
HEAP32[($8_1 + 72 | 0) >> 2] = $1_1;
HEAP32[($8_1 + 68 | 0) >> 2] = $2_1;
HEAP32[($8_1 + 64 | 0) >> 2] = $3_1;
HEAP32[($8_1 + 60 | 0) >> 2] = $4_1;
HEAP32[($8_1 + 56 | 0) >> 2] = $5_1;
HEAP32 就是参数数组,不同的偏移对应不同的变量
一直运行可以看到会进入第一个函数 $23,这个函数传入一个常数,传出是一个变量减去这个常数,而且中间运算过一遍并没有什么异常,应该是在获取某个变量值,直接跳过
然后跳出函数之后接下来就会走入 $35
一直单步调试,可以发现和上面差不多,没有做什么特别的操作,获取某个变量的值,也跳过跳过
跳出后会进入一个可疑的方法调用$40,传入整么多参数,还没有返回,单步进去看看
$40(HEAP32[($8_1 + 48 | 0) >> 2] | 0 | 0, 0 | 0, HEAP32[($8_1 + 52 | 0) >> 2] | 0 | 0) | 0;
HEAP32[($8_1 + 48 | 0) >> 2] 记得吗,是上面$35 的返回值,猜测是某个全局变量
0 | 0 是0
HEAP32[($8_1 + 52 | 0) >> 2] 是上面$23 的返回值,同理猜测是某个全局变量
仔细观察这个函数的方法体可以很清晰得看到,搞了一堆没用的到最后其实就是为了把上面获取到的两个变量初始化为0
然后进入下一个方法 $3
$3(402432 | 0, HEAP32[($8_1 + 48 | 0) >> 2] | 0 | 0) | 0;
将刚刚初始化得变量和一个常数传进这个方法,难不成是赋值key(狗头)
直接运行步过,然后看一下这个地址上的值是多少
正好有值!长得还很像,拿去测试一下
这里IV用前面发现的IV 结果发现不对,不对是正常的,进方法看看大概干了啥
这里有个小技巧,虽然这个函数没有变量接收返回值,但是我们可以看到这个方法实际上是有返回值的
返回值$84 但是$84 = HEAP32[($4_1 + 28 | 0) >> 2] | 0; 返回值实际上就是 HEAP32[($4_1 + 28 | 0) >> 2]
而在上面有很多个赋值,-1,-2,0,一般0才是i正常返回,复数都是错误情况,因此这些地方直接忽略,而正常的分支都是赋值,我们又验证过赋值的内容不是我们需要的key,那就直接跳过了
正餐
跳出 $3 之后可以发现后面全是赋值,然后就只调用了$10方法,整个加密就完成了
前面分析过,之前几乎都在做变量声明、变量初始化、变量赋值。那么这个$10几乎就是全部的加密计算过程了
参数比较长,有能力的可以分析还是分析一下,我是偷懒直接跳进去看参数地址再回来大海捞针
进入$10 方法,把参数地址挨个和前面初始化、赋值的变量进行对比,初步可以确定几个变量的内容
$0_1明文,$2_1输出地址,$4_1iv地址
同样的不难发现开头就是一大堆break,但是我们分析过全部计算流程都在这个方法里,你break掉那还算鸡毛
所以这部分必定全部是参数校验,全部忽略
到后面会进入一个 $12方法,这一堆break 和赋值,所以这一看也是校验参数,直接跳过
中间一直向下运行,单步调试,遇到方法就进去看看做了啥
直到进入一个 $39
$39(HEAP32[($11_1 + 28 | 0) >> 2] | 0 | 0, HEAP32[($11_1 + 92 | 0) >> 2] | 0 | 0, HEAP32[($11_1 + 88 | 0) >> 2] | 0 | 0) | 0;
手速快或者加粗心一点的看到一长串的break可能直接就跳过了,但其实他后面藏了一大堆的赋值,$1_1 赋值给了$2_1....
好吧,这玩意儿就是一个memcpy
填充模式
跳出$39之后,又进入了$14 方法
$14(HEAP32[($11_1 + 28 | 0) >> 2] | 0 | 0, HEAP32[($11_1 + 88 | 0) >> 2] | 0 | 0, HEAP32[($11_1 + 20 | 0) >> 2] | 0 | 0, HEAP32[((HEAP32[($11_1 + 60 | 0) >> 2] | 0) + 28 | 0) >> 2] | 0 | 0, HEAP32[((HEAP32[($11_1 + 60 | 0) >> 2] | 0) + 4 | 0) >> 2] | 0 | 0) | 0;
这里就比较明显了,很明显的填充明文动作,$0_1 明文,$1_1 长度,$2_1 长度,$3_1 模式标志
$3_1 的值固定为 1,不会执行后面的break,到后面把明文和0传入了$40,$40之前分析过是将变量初始化为传进来的常数
所以就是以0填充,填充模式就出来了pkcs7
到目前为止,iv 疑似确定,填充模式这里就确定了,那就只差key了
正式开始计算
后面繁琐的代码自己都看看,一直单步运行就行,不能把每一行代码干了啥都写下来
到 $19,有能力的参数都分析一下哪儿来的,怎么赋值的,毕竟到这儿就正式开始计算了
HEAP32[($11_1 + 56 | 0) >> 2] = $19(HEAP32[($11_1 + 28 | 0) >> 2] | 0 | 0, HEAP32[($11_1 + 84 | 0) >> 2] | 0 | 0, HEAP32[($11_1 + 24 | 0) >> 2] | 0 | 0, HEAP32[($11_1 + 76 | 0) >> 2] | 0 | 0, $11_1 + 32 | 0 | 0, HEAP32[($11_1 + 16 | 0) >> 2] | 0 | 0) | 0;
这是我分析出来的参考的参数
这里几个关键的参数都记录一下
HEAP32[($8_1 + 44 | 0) >> 2] = $0_1;//明文
HEAP32[($8_1 + 32 | 0) >> 2] = $3_1;// iv
HEAP32[($8_1 + 28 | 0) >> 2] = $4_1;// 输出
然后进入了一个大循环,循环里面还套了一个小循环
label$2: while (1) {
if (!((HEAP32[($8_1 + 36 | 0) >> 2] | 0 | 0) >= (16 | 0) & 1 | 0)) {
break label$1
}
HEAP32[($8_1 + 16 | 0) >> 2] = 0;
label$3: {
label$4: while (1) { // iv xor 明文
if (!((HEAP32[($8_1 + 16 | 0) >> 2] | 0 | 0) < (16 | 0) & 1 | 0)) {
break label$3
}
HEAP8[((HEAP32[($8_1 + 40 | 0) >> 2] | 0) + (HEAP32[($8_1 + 16 | 0) >> 2] | 0) | 0) >> 0] = ((HEAPU8[((HEAP32[($8_1 + 44 | 0) >> 2] | 0) + (HEAP32[($8_1 + 16 | 0) >> 2] | 0) | 0) >> 0] | 0) & 255 | 0) ^ ((HEAPU8[((HEAP32[($8_1 + 12 | 0) >> 2] | 0) + (HEAP32[($8_1 + 16 | 0) >> 2] | 0) | 0) >> 0] | 0) & 255 | 0) | 0;
HEAP32[($8_1 + 16 | 0) >> 2] = (HEAP32[($8_1 + 16 | 0) >> 2] | 0) + 1 | 0;
continue label$4;
}
;
}
FUNCTION_TABLE[HEAP32[($8_1 + 24 | 0) >> 2] | 0](HEAP32[($8_1 + 40 | 0) >> 2] | 0, HEAP32[($8_1 + 40 | 0) >> 2] | 0, HEAP32[($8_1 + 28 | 0) >> 2] | 0, $8_1 + 20 | 0);
HEAP32[($8_1 + 12 | 0) >> 2] = HEAP32[($8_1 + 40 | 0) >> 2] | 0;
HEAP32[($8_1 + 36 | 0) >> 2] = (HEAP32[($8_1 + 36 | 0) >> 2] | 0) - 16 | 0;
HEAP32[($8_1 + 44 | 0) >> 2] = (HEAP32[($8_1 + 44 | 0) >> 2] | 0) + 16 | 0;
HEAP32[($8_1 + 40 | 0) >> 2] = (HEAP32[($8_1 + 40 | 0) >> 2] | 0) + 16 | 0;
continue label$2;
}
;
里面的循环大家可以自己挨着看一下,具体动作就是把IV 和明文进行了xor
明文在里面的循环中被iv xor了之后又进行了加密计算并赋值
不要大意~不要大意~不要大意~
代码虽然比较多,但是他这个里面其实藏了一个方法调用
FUNCTION_TABLE[HEAP32[($8_1 + 24 | 0) >> 2] | 0](HEAP32[($8_1 + 40 | 0) >> 2] | 0, HEAP32[($8_1 + 40 | 0) >> 2] | 0, HEAP32[($8_1 + 28 | 0) >> 2] | 0, $8_1 + 20 | 0);
FUNCTION_TABLE 是在前面赋值,进步运行就可以发现进了 $21
$0_1和$1_1都是同一个值,就是前面明文和IV xor后的结果,其他俩值可以自行分析一下,没写注释我也给忘了
在赋值后面有个小循环 ,他的计算过程涉及了明文,这就不得不看一下了
label$1: {
label$2: while (1) {
if (!((HEAP32[($6_1 + 48 | 0) >> 2] | 0 | 0) < (16 | 0) & 1 | 0)) {
break label$1
}
HEAP8[(($6_1 + 32 | 0) + (HEAP32[($6_1 + 48 | 0) >> 2] | 0) | 0) >> 0] = HEAPU8[(((((HEAPU8[((HEAP32[($6_1 + 76 | 0) >> 2] | 0) + (HEAP32[($6_1 + 48 | 0) >> 2] | 0) | 0) >> 0] | 0) & 255 | 0) << 8 | 0) + ((HEAPU8[((HEAP32[($6_1 + 60 | 0) >> 2] | 0) + (HEAP32[($6_1 + 48 | 0) >> 2] | 0) | 0) >> 0] | 0) & 255 | 0) | 0) + 1024 | 0) >> 0] | 0;
HEAP32[($6_1 + 48 | 0) >> 2] = (HEAP32[($6_1 + 48 | 0) >> 2] | 0) + 1 | 0;
continue label$2;
}
;
}
他的逻辑很简单,就是每次循环就把明文替换成内存中的一个字节
(((((HEAPU8[((HEAP32[($6_1 + 76 | 0) >> 2] | 0) + (HEAP32[($6_1 + 48 | 0) >> 2] | 0) | 0) >> 0] | 0) & 255 | 0) << 8 | 0) + ((HEAPU8[((HEAP32[($6_1 + 60 | 0) >> 2] | 0) + (HEAP32[($6_1 + 48 | 0) >> 2] | 0) | 0) >> 0] | 0) & 255 | 0) | 0) + 1024 | 0) >> 0
这个的值是固定的,20288 我们可以去看下这个内存地址放的啥
这个数组 懂得都懂,sbox嘛,那你猜猜它魔改没有(手动狗头)不浪费大家宝贵的时间了,我一个个挨着核对了,只是sbox没有进行魔改,只是顺序不一样而已
向下运行
这就对味啦!走到了真正的加密计算过程
while大循环 判断条件 (HEAP32[($6_1 + 56 | 0) >> 2] | 0 | 0) & 1 | 0), 这个值恒定为10
这个计算过程我真不想写了,太长了而且没意义,管他咋算的,大家自己分辨哪里是列混淆,哪里是值替换
Over,然后在倒数第二轮加入DFA注入代码就行
const faultIndex = getRandomInt(0, 16);
const faultValue = getRandomInt(-128, 128);
HEAP8[$6_1 + 32 + faultIndex] = faultValue;
再跑一百次收集故障文
for (var i = 0; i < 100; i++) {
doAesCbc("123456", "0");
}
导入 phoenixAES,再用aes_keyschedule 跑一下就行
这个是网站的真实代码,附件中的iv已经进行了修改,就不放key出来了,目的是学习学习学习,自己动手,肯定能搞出来的,小case
分析过程中并没有发现对iv进行了额外处理,而且经过验证,确实没有魔改
下班下班,累死