看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析

WriteUp 3年前 (2021) admin
873 0 0
看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析

看雪·众安 2021 KCTF秋季赛的第八题《群狼环伺》已于今天中午12点截止答题!

本题共有3支战队成功破解,分别是辣鸡战队、金左手、mininep。

看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析

恭喜辣鸡战队用时100442秒拿下“一血”,接下来和我一起来看看该赛题的设计思路和相关解析吧~

出题团队简介


第八题《群狼环伺》出题方: 【ArmVMP】战队


看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析


赛题设计思路


本题目是安卓平台的crackme,算法简单,可玩性高,有兴趣可以随时交流。


规则2的demo为:KCTF2021-android-crackme.apk


其中规则2的两组序列号如下:


第一组序列号:
name:KCTF
serial:3633386636373733353933626439316437383865383931653663663432656661

 

第二组序列号:
name:4DD6F06301B04D13
serial:6461323135643835663832623133666234313635636637383266613665363961
(通过命令shasum -a 256 crackme.apk计算apk得文件hash:
4dd6f06301b04d13f76c513326be681de153ff743ce17f411a35e09a08a90d37)

 

设计思路:


1、对输入name字符串通过SHA1算法,计算得到16字节的hash值;
2、hash值做rc4加密运算,得到16字节value1值;
3、对部分代码计算SHA256得到16字节的密钥Key;
4、将输入64个字符password转为16字节的16进制表示(如:前四个字节64613231转换后为0xda21)和Key做3DES解密得到value2,当password不足64字节时提示错误;
5、比较value1 == value2时,则为正确的密钥对。

 

保护方法:


1、代码中有效隐藏了系统函数调用,仅保留了一个memset;
2、增加了常规的检测非法运行手段;
3、通过自研的保护方案将原so中的原始指令进行替换,转换后一条指令对应多条指令,并且部分指令经过编码生成数据,运行时对数据进行解析运行。原函数内部的字符串和函数访问通过模拟指令执行,能够有效隐藏相关调用。

 

解题思路:


由于对代码段的指令做了变形保护,通过相同功能的函数实现指令替代运行,所以反编译时,较难看出算法逻辑,需要对模拟代码标识出相应的指令来分析逻辑,当然也存在不需要标记的情况.


春季赛中4为选手均是通过对数据流分析,比对每种指令运行次数,以及数据输入及输出做观察跟踪出的序列号。当然也可以通过人肉找到关键比较处跟踪到序列号。

 

关于作者:喜欢阅读与运动,专注于互联网安全行业从业十多年之久。


主攻方向研究二进制文件保护技术,基于指令虚拟化保护技术,研发的安全产品覆盖诸多平台包括Android,Linux, IOS及物联网平台,致力于通过技术手段,以减少对用户的恶意攻击及破坏行为,让世界多一些友善。



赛题解析


本赛题解析由看雪论坛ThTsOd给出:

看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析

IDA上来一看,没啥函数:

看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析
找一下密码学常量,发现有个DES:
看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析
查找引用:
看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析
发现有部分函数没有识别,手动Create Function。
 
sub_788就是DES过程了,hook下打印三个参数:
Interceptor.attach(baseAddr.add(0x789),{    onEnter: function (args){        console.log((this.context as any).lr.sub(baseAddr));        console.log(JSON.stringify(this.context));         console.log(hexdump((this.context as any).r0,{            offset:0,            length:128*3,            header:true,            ansi:true        }));        //Enc Data        console.log(hexdump((this.context as any).r1,{            offset:0,            length:8,            header:true,            ansi:true        }));    },    onLeave: function (ret){        //After Enc        console.log(hexdump((this.context as any).r2,{            offset:0,            length:8,            header:true,            ansi:true        }));    }});

可以看到将输入转成hex再转hex后,加密了一次,随后是加密相同的内容:
 
看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析
这里可以将DES轮秘钥倒过来,解密。
function swapkey(addr:NativePointer){    var key = []    for(var i = 0;i<48;i+=1){        key.push(addr.add(i*8).readU64());    }    for(var i = 0;i<48;i+=1){        addr.add(i*8).writeU64(key[47-i]);    }}

当提交成功时,会出现”恭喜成功”,但是在java层代码和native层代码没有找到这个字符串,这里无意尝试把以下内容修改了。
看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析
程序崩溃,查看logcat:

看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析
 
UTF-8转换相关,hook下libart.so的CheckJNI::NewStringUTF(dump自己的so文件,ida找一下这个函数)
Interceptor.attach(baseAddr.add(0xae765),    {        onEnter: function (args){             //console.log(args[1].readCString(),args[1]);            if(args[1].readCString() == "恭喜成功" || args[1].readCString() == "输入错误"){                var mainAddr = Module.findBaseAddress("libcrackme.so");                console.log((this.context as any).lr.sub(mainAddr));                for(var i=0;i<64;i++){                    //console.log((this.context as any).sp.add(i*4).readPointer(),(this.context as any).sp.add(i*4).readPointer().sub(mainAddr));                }                 console.log(hexdump(args[1]));                console.log(JSON.stringify(this.context));                console.log(hexdump(this.context.sp.add(0xB0),{                    offset:0,                    length:192,                    header:true,                    ansi:true                }));                debugger;            }        },        onLeave: function (ret){         }    });

这样可以在判断完结果后将程序断下(frida的debugger命中会暂停当前线程)。
 
搜一下正确输入的加密结果,运行程序时保持name相同,serial不同。
 
bd 3a b0 69 39 40 f8 cd 42 0d e3 8a 79 db 52 bd
 
找到一个:
看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析
运行程序时保持name不同(这里输入KCTF),serial相同。
 
同样也找到一个:
看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析这时就能猜出DES加密结果应该为:
 
45 68 97 A3 29 2A 7F D4 F5 90 73 57 46 02 AE D5
 
就可以得到正确答案了。
if(true){    //DES Decrypt    swapkey((this.context as any).r0);    //Data 1    if((this.context as any).lr.sub(baseAddr) == 0xe8b5){        var encdata = [0x45,0x68,0x97,0xa3,0x29,0x2a,0x7f,0xd4];        (this.context as any).r1.writeByteArray(encdata);    }    //Data 2    if((this.context as any).lr.sub(baseAddr) == 0xe8e5){        var encdata = [0xf5,0x90,0x73,0x57,0x46,0x02,0xae,0xd5];        swapkey((this.context as any).r0);//Swap Again        (this.context as any).r1.writeByteArray(encdata);    }}

看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析

应该输入的hex 为 63 8f 67 73 59 3b d9 1d 78 8e 89 1e 6c f4 2e fa
 
再转换一次: 3633386636373733353933626439316437383865383931653663663432656661,即为正确答案。
 
部分代码如下:
function swapkey(addr:NativePointer){    var key = []    for(var i = 0;i<48;i+=1){        key.push(addr.add(i*8).readU64());    }    for(var i = 0;i<48;i+=1){        addr.add(i*8).writeU64(key[47-i]);    }} function hook(){    var baseAddr = Module.findBaseAddress("libcrackme.so");    Interceptor.attach(baseAddr.add(0x789),        {            onEnter: function (args){                console.log((this.context as any).lr.sub(baseAddr));                //console.log(JSON.stringify(this.context));                 // console.log(hexdump((this.context as any).r0,{                //     offset:0,                //     length:128*3,                //     header:true,                //     ansi:true                // }));                if(true){                    //DES Decrypt                    swapkey((this.context as any).r0);                    //Data 1                    if((this.context as any).lr.sub(baseAddr) == 0xe8b5){                        var encdata = [0x45,0x68,0x97,0xa3,0x29,0x2a,0x7f,0xd4];                        (this.context as any).r1.writeByteArray(encdata);                    }                    //Data 2                    if((this.context as any).lr.sub(baseAddr) == 0xe8e5){                        var encdata = [0xf5,0x90,0x73,0x57,0x46,0x02,0xae,0xd5];                        swapkey((this.context as any).r0);//Swap Again                        (this.context as any).r1.writeByteArray(encdata);                    }                }                //Enc Data                console.log(hexdump((this.context as any).r1,{                    offset:0,                    length:8,                    header:true,                    ansi:true                }));            },            onLeave: function (ret){                //After Enc                console.log(hexdump((this.context as any).r2,{                    offset:0,                    length:8,                    header:true,                    ansi:true                }));            }        }    );} function hookart(){    var baseAddr = Module.findBaseAddress("/system/lib/libart.so");    //var baseAddr = Module.findExportByName(null,"_ZN3art12_GLOBAL__N_18CheckJNI12NewStringUTFEP7_JNIEnvPKc");    console.log("Art",baseAddr)    Interceptor.attach(baseAddr.add(0xae765),        {            onEnter: function (args){                 //console.log(args[1].readCString(),args[1]);                if(args[1].readCString() == "恭喜成功" || args[1].readCString() == "输入错误"){                    var mainAddr = Module.findBaseAddress("libcrackme.so");                    console.log((this.context as any).lr.sub(mainAddr));                    for(var i=0;i<64;i++){                        //console.log((this.context as any).sp.add(i*4).readPointer(),(this.context as any).sp.add(i*4).readPointer().sub(mainAddr));                    }                    console.log(hexdump(args[1]));                    console.log(JSON.stringify(this.context));                    console.log(hexdump(this.context.sp.add(0xB0),{                        offset:0,                        length:192,                        header:true,                        ansi:true                    }));                    debugger;                }            },            onLeave: function (ret){             }        }    );}


往期解析


1、看雪·众安 2021 KCTF 秋季赛 | 第二题设计思路及解析


2、看雪·众安 2021 KCTF 秋季赛 | 第三题设计思路及解析


3、看雪·众安 2021 KCTF 秋季赛 | 第四题设计思路及解析


4、看雪·众安 2021 KCTF 秋季赛 | 第五题设计思路及解析


5、看雪·众安 2021 KCTF 秋季赛 | 第六题设计思路及解析


6、看雪·众安 2021 KCTF 秋季赛 | 第七题设计思路及解析



看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析
看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析

第九题《万事俱备》正在火热进行中,

?还在等什么,快来参赛吧!


看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析
– End –


看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]




看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析

球分享

看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析

球点赞

看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析

球在看



看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析
“阅读原文展开第9题的角逐!

原文始发于微信公众号(看雪学苑):看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析

版权声明:admin 发表于 2021年12月6日 上午9:59。
转载请注明:看雪·众安 2021 KCTF 秋季赛 | 第八题设计思路及解析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...