-
-
[推荐]看雪·安恒 2020 KCTF 春季赛 | 第九题设计思路及解析
-
发表于: 2020-5-6 18:45 7459
-
这道题难度较大,一开始参赛选手们都找不到头绪,直到第三天,98K战队和金左手战队终于找到了突破口,成功破解。金左手战队也凭借此题登上攻击方总排名的第一位。
小编点评
出题团队简介
设计思路
前言
已经很久没有参加看雪 CTF 了,主要原因还是能力有限:想做攻击方却逆不出来题,想做防守方又没有什么好点子,还会被 riatre、ccfer 等诸多高手吊打,可以说是十分惨烈了。
这一年补了补代数数论的课,看了看密码学,奈何脑子不好,终究学了这个忘那个,逆向能力早就被别人甩了好几个秒差距。以前还能看到别人背影而兴叹,如今连影子都看不到了。
好在看雪论坛里面学习资料不少,把自己当萌新(划掉,已经变成萌新了)在论坛里面找了些基础题目做做。
发现自己对于 OLLVM 之类的混淆还是非常头疼,虽然这些年一直靠 bird 写的利用符号执行自动 patch 的脚本苟活,但是如果把混淆稍微改改就又一脸懵逼了。
玩了玩 IDA 的 microcode 只感觉一知半解,试了试 binary ninja 对于太大的函数又难以分析,每次做题都像是在"启发式逆向",闭着眼睛逆一点是一点,颇感力不从心。
所以本着 "打不过它就加入它" 的思路,想来看雪 CTF 里面出个 OLLVM 的题。但是 OLLVM 毕竟已是将近 7 年的老技术了,拿过来出题未免有点贻笑大方。
而且正好看到去年有一道 "叹息之墙" 似乎就是 OLLVM 的混淆,结果被各路高手轮番攻破,令在下胆战心惊。可能正如前人所言,要想在看雪 CTF 里面存活,最好的办法就是把这些大佬们拉到一起聚个餐,觥筹交错,大醉三日而还。
但后来转念一想,虽然 OLLVM 拦不住这些逆向机器,但是这个在编译阶段混淆的思路还是挺不错的。于是花了点时间研究了一下 LLVM,实现了几个新的 Obfuscation Pass,又写了一个 CrackMe,用这些 Pass 混淆过后,来参加今年看雪的春季赛,且看究竟能撑过几天。
设计说明
之前一直想做一个这样的 CrackMe:
验证算法本身非常简单, 不是密码学/数学题 使用某种通用的程序保护的方法对它进行混淆. 然后将混淆的方法开源, 并附详细说明(而不遮遮掩掩) 混淆后的程序在看雪 CTF 中存活
这一次我在看雪题库提交的题目,除了最后一点不敢保证,但是前两点均完全满足。
之前虽然也想这样做,但是因为题目设计思路和题目 flag 都在一个帖子内,无法在比赛期间公开设计帖。这次提交题目的通道是看雪题库,故而可以和本帖分离。所以希望管理员能在攻击方比赛开始的时候,把本帖公开。让大家可以提前了解题目中应用的混淆方法。
执行程序如同跋山涉水,行路时阡陌纵横,每一次分支都是一次歧路。古人曾感慨 "行路难,行路难,多歧路,今安在"。这一个程序所采用的混淆, 恰恰就是增加了许多歧路,彻底解决 "行路难" 的问题 23333。
但是歧路多了,走着走着可能就跟丢了,正巧这次 CTF 赛题皆以十二生肖为名。那我这个程序,不如就叫 "歧路亡羊" 吧。
YANSOllvm
YANSOllvm 是在下这几天编写的一个类 OLLVM 项目,它的全称是 Yet Another Not So Obfuscated LLVM,源代码已公开到 Github 上。
记得在 2018 年看雪论坛上似乎发生过有人抄袭 Hikari Obfuscator 的争论,当时觉得 OLLVM 的代码一定很高大上,自己写起来多半困难重重。
这几天粗略上手试了一下,感觉如果不考虑用户体验和各种实际代码的情况,简单写写自己玩好像还挺容易的。
再后来我又发现,除了 CFG Flattening、LLVM 上能做的变换实在是太多了,于是我又自己实现了几个新的混淆 Pass,抛砖引玉,供大家开阔思路。
编译与使用
原则上来讲,OLLVM 的代码树完全可以不用包含 LLVM 和 Clang 的部分,可是为什么 YANSOllvm 的编译需要 LLVM 呢?一个原因是 YANSOllvm 实现了对 Calling Convention 的混淆,而 Calling Convention 是 Backend 的约定,所以需要对 LLVM backend 进行修改。另外一个原因是 LLVM 有很多代码变换的 Utils,这些 Utils 稍作修改就是一种不错的代码混淆。
编译的方法在 Github 上已经很详细了,这里主要说一下本题中所使用的命令行参数:
clang -O1 -Wall -Wextra -c -emit-llvm pediy.c ../bin/opt -load ../lib/LLVMObf.so -vm -merge -bb2func -flattening -obfZero -connect -obfZero -obfCall pediy.bc -o pediy.obf.bc ../bin/llc -O3 --disable-block-placement --max-loads-per-memcmp=0 pediy.obf.bc cat pediy.obf.s | grep -v ud2 > pediy.s clang -Wall -Wextra -Wl,-nodefaultlib:libcmt -lmsvcrt pediy.s
Pass介绍
我们以如下源代码为例,后面加上每一个 pass 的效果图:
#include <stdio.h> short zero[2] = {0,1}; static short *d(void){ return zero; } static short c(int x){ if(x == 0) return (*(d()+1) ^ 12); return c(x-1)+1; } static int b(int x){ int sum = 0; for(int i = 0; i < x; i++){ sum += c(i); } return sum; } static void a(unsigned long long x){ for(int i = 0; i < x; i++){ int temp = b(i) + 1; printf("%d ", temp); } } int main(int argc, char *argv[]){ int i; if(argc > 1){ sscanf(argv[1], "%d", &i); a(i); } return 0; }
VM
VM Pass 非常简单,就是将 LLVM IR 中的一些简单二元操作替换成函数调用。比如将 a = b + c 替换成 a = Add(b, c)。上述代码混淆后的效果图:
其中 Add 函数大概长这样:
Merge
Merge 的主要作用是将所有 internal linkage 的函数 (比如 static 函数,和 VM Pass 添加的 vm 函数等) 合并成一个新的函数。这样做的性能开销并不大,但是会形成一个大型函数从而可以进一步混淆,并且大幅拖慢反编译器的速度。下图中把 abcd 四个函数合并成了同一个函数:
merge 的函数通过第一个参数 switch 来选择:
bb2func
这个 Pass 的作用正好和 Merge 相反,它可以从一个函数中把较大的 Basic Block 切分,并且强行提取出来作为新的函数。
因为一般而言,函数是某个独立的功能单位,而这样随机抽取的函数往往只是一个功能单位的某一部分,从而干扰逆向者与反编译器。并且配合文末介绍的 ObfCall Pass 可以进一步干扰 IDA 的分析。
其中某个单独的函数:
Flattening
这个 Flattening 是在 OLLVM 原有的 flattening pass 的基础上进行的修改。OLLVM 的 CFG 平坦化存在一个明显问题:每一个 basic block 都直接暴露了其后继的 basic block,抽象来看,Basic Block 都对应着一个 internal state,而 Switch Dispatcher 的分发则依赖 switchVar. 在 OLLVM 的平坦化算法中,internal state 就等同于 switchVar,并且 internal state 的转换则通过直接赋值而进行,这样做会使得静态分析变得尤其容易。
YANSOllvm 将 internal state 和 switchVar 分别看待,switchVar 由 internal state 的 hash 计算得出,而 internal state 之间的转移则通过 xor 来完成。
这样就能够避免攻击者直接通过静态分析从一个 basic block 获取到其后继的所有 basic block。
obfZero
这个 Pass 的作用很简单,利用复杂的恒等式 (如 MBA) 将 IR 中的常量 0 替换成几个变量的运算结果。从而干扰分析器,配合 Flattening 和 Connect Pass 可以产生很多虚假分支。
可以看到图中将原有的 if(x == 0) 中的 0 混淆成关于 x 的表达式。
Connect
Connect 将 Basic Block 切开,然后通过 switch 来链接他们,正确的分支自然只有一个,其他的虚假分支通过 obfZero 来混淆,并且会在其中一个虚假分支里插入一些简陋的花指令来干扰反汇编:
注意到 a 并没有被成功识别为函数:
patch 掉几个干扰分析的花指令后,CFG 比原来复杂很多,但是大部分分支是无用的:
obfCall
在编译时,genObfCC.py 会随机生成 10 个 Calling Convention,他们的返回寄存器和传参寄存器均不为 AMD64 ABI 中对应的常用寄存器,从而混淆反汇编器的分析。
保护全开
附件提供了上述示例源代码和其在 YANSOLLVM 打开所有混淆下编译的 DEMO (包括 PE 和 ELF),使用的混淆参数略有不同,但是可作为简单的参考。
尾声
写了一大篇帖子好累 orz。YANSOllvm 只是一个兴趣之作,甚至可以说仅为此次 CTF 而生,仅供学习参考。很多情况并没有考虑到,代码质量极差,也有许多 bug,希望大家多多谅解。
最后,祝大家看雪 CTF 玩得愉快~~
解析过程
一 从歧路开始 __int64 sub_140001000() { unsigned int v0; // edi const char *v1; // rcx int v2; // edx char v4; // [rsp+20h] [rbp-F8h] sub_140038630("Please input your flag: "); if ( (signed int)sub_140038680("%16s", &v4) >= 0 ) { sub_140038630("Checking... "); v0 = 0; sub_140001130(); v1 = "Wow, you've got the correct flag!!!!!"; if ( !v2 ) v1 = "Sorry, maybe you should try harder."; } else { v0 = 1; v1 = "Error read input"; } puts(v1); return v0; } 读取16个字符的输入,调用sub_140001130检查结果 通过memcmp可以看到输入检查,要求前5个字符是"KCTF{",末尾字符是"}" 000000014001D1DB | 4 | lea rdx,qword ptr ds:[14003A356] |"KCTF{" 000000014001D1E2 | E | call <JMP.&memcmp> | 000000014002471E | 4 | lea rdx,qword ptr ds:[14003A35C] |“}” 0000000140024725 | E | call <JMP.&memcmp> | 所以先是断定输入格式:KCTF{1234567890} 调试发现sub_140001130被很很多地方调用,是个复用函数入口,有4个参数:r13,r11,r15,r10,返回rdx r13是操作码id,r11和r15是操作数 0x1C7FA2AB : rdx = r11 >> r15 0x36E9DD2F : rdx = r11 - r15 0x51B457D2 : rdx = r11 ^ r15 0x788A4576 : rdx = r11 | r15 0x811608C6 : rdx = r11 << r15 0xCB5F003D : rdx = r11 >> r15 0xD28BBC22 : rdx = r11 & r15 0xF63646BD : rdx = r11 + r15 0x07DDDD59 : 是主检查入口 0xDE06D135 : 一个解密函数,后来发现是个假算法陷阱 0x32652A7E : 真正的检查从这里才算开始 0x8052C248 : 没发现什么用途,忽略 0xFB9FC71E : 用r10d做循环次数,对r11处16字节xor,偶数次次循环内容不变,后来知道是控制延时的 把sub_140001130入口hook以后,就基本能把所有运算逻辑log出来,分析算法了 这里是检查结果的地方,如果16字节匹配,就会ok了: 000000014000346C | | lea rcx,qword ptr ds:[14003C000] |解密结果 0000000140003473 | | lea rdx,qword ptr ds:[14003A2C0] |16字节常数 000000014000347A | | call <JMP.&memcmp> | 根据log出来的算法流程,整理出算法,运行结果也和上面解密结果完全匹配 只是怎么也无法写出逆算法,明显感觉功力不够,也怀疑可能是不可逆,只好找找其他路子,比如是否有多解漏洞 果然发现输入超过16个字符,也只读取出16字符,末尾任意附加不就是多解吗? 于是在群里告诉半神,存在多解可能,半神信誓旦旦且人品担保不会多解,多亏有这个提示,我相信了半神的人品 如果没有多解,那这里一定有机关了 二 大侠重新来过 队友KevinsBobo首先发现了问题,对qword_14003C0D8这个全局变量下了硬件断点,后来有访问: 0000000140038735 | | mov qword ptr ds:[14003C0D8],rcx |这个地方 000000014003873C | | call yanso.140038770 | 0000000140038741 | | mov rcx,qword ptr ds:[rax] | 0000000140038744 | | mov qword ptr ss:[rsp+20],rsi | 0000000140038749 | | mov rdx,rdi | 000000014003874C | | mov r8,rbx | 000000014003874F | | mov r9,r14 | 0000000140038752 | | call <JMP.&__stdio_common_vfscanf> | 看了一下这个是__acrt_iob_func返回的结构,通过它可以获取输入缓冲区中16字符以后未读取部分数据 通过断点定位到这里: 0000000140028C9C | | mov eax,dword ptr ss:[rsp+DB0] | 0000000140028CA3 | | movzx eax,word ptr ds:[rax+15] |0x15位置 0000000140028CA7 | | cmp eax,A7D |是"}\n" 现在修正输入格式:KCTF{1234567890}abcde} 有了这个输入格式可以更进一步走到这里了: 0000000140012940 | | mov rax,qword ptr ss:[rsp+DB0] | 0000000140012948 | | mov rcx,qword ptr ds:[rax+D] |x1 = 读取输入第14~21的8个字符 000000014001294C | | mov r15,rcx | 000000014001294F | | mov rax,DAE79EBC6E26E62B | 0000000140012959 | | imul r15,rax | 000000014001295D | | mov rax,qword ptr ss:[rsp+DB0] | 0000000140012965 | | mov r11,qword ptr ds:[rax+5] |x0 = 读取输入第6~13的8个字符 0000000140012969 | | mov rax,49BA1EEFBCFA13FF | 0000000140012973 | | imul r11,rax | 0000000140012977 | | mov r13d,32652A7E |貌似是个正确的验证入口了 000000014001297D | | xor r10d,r10d | 0000000140012980 | | call yanso.140001130 |真正的check流程要开始了 (x0 * 49BA1EEFBCFA13FF)和(x1 * DAE79EBC6E26E62B)作为参数去验证 运行到下面这个断点完成一段2个64轮的异或,根据输入每一位0或1分别从两个表里取值异或 0000000140005EAF | | mov r13d,8052C248 | 0000000140005EB5 | | xor r10d,r10d | 0000000140005EB8 | | xor r11d,r11d | 0000000140005EBB | | xor r15d,r15d | 0000000140005EBE | | call yanso.140001130 | 这部分算法相当于GF(2)的矩阵乘以输入向量,把整个完整的表抓出来就可以得到矩阵 接着有一轮加密,不长没循环,没什么好写的,就是对数据下硬件读取断点,一行一行把追踪到的算法记录下来 然后从下面开始是很长的过程: 000000014001BF9E | | movzx ebx,byte ptr ss:[rsp+20D] | 000000014001BFA6 | | lea r10,qword ptr ss:[rsp+258] | 000000014001BFAE | | lea r15,qword ptr ss:[rsp+48] | 000000014001BFB3 | | lea r12,qword ptr ss:[rsp+30] | 000000014001BFB8 | | call yanso.140037110 | 直到这里结束: 0000000140032AA4 | | lea r13,qword ptr ss:[rsp+2F8] | 0000000140032AAC | | lea r15,qword ptr ss:[rsp+128] | 0000000140032AB4 | | lea rdx,qword ptr ss:[rsp+58] | 0000000140032AB9 | | lea r11,qword ptr ss:[rsp+30] | 0000000140032ABE | | call yanso.140036CE0 | 上面这段循环算法对128位的输入数据,重新排列次序,把位置关系记录下来即可逆推 这段虽然没什难度,但是需要格外小心,这里利用了rdtsc时间检测,是不能patch的: 000000014002B3A3 | | rdtsc | 000000014002B3A5 | | mov rdi,rax | 000000014002B3A8 | | shl rdx,20 | 000000014002B3AC | | or rdx,rax | 000000014002B3AF | | mov r15,qword ptr ss:[rsp+E10] | 000000014002B3B7 | | mov r13d,hook09.36E9DD2F | 000000014002B3BD | | xor r10d,r10d | 000000014002B3C0 | | mov r11,rdx | 000000014002B3C3 | | call yanso.140001130 |减法 000000014002B3C8 | | cmp rdx,73375 |时间差判断 因为这个时间差是用前面128位的0或1来控制的,该超时的要超时,不该超时的不能超时,才能得到正确结果 bit是1的时候会执行到这里: 00000001400095A1 | | movsxd rdi,dword ptr ss:[rsp+128] | 00000001400095A9 | | movzx ecx,byte ptr ss:[rsp+rdi+38] | 00000001400095AE | | mov dword ptr ss:[rsp+B98],ecx | 00000001400095B5 | | mov eax,dword ptr ss:[rsp+B98] | 00000001400095BC | | xor ebp,ebp | 00000001400095BE | | cmp eax,7F | 00000001400095C1 | | setg bpl | 00000001400095C5 | | mov r10d,84 | 00000001400095CB | | mov eax,7E | 00000001400095D0 | | cmovg r10d,eax | 00000001400095D4 | | mov r13d,FB9FC71E |循环0x7E次或0x84次的16字节异或 00000001400095DA | | lea r11,qword ptr ss:[rsp+58] |故意产生延时超过0x73375 00000001400095DF | | xor r15d,r15d | 00000001400095E2 | | call yanso.140001130 | 所以这个0x73375阈值的时间差判断是由输入数据控制的,调试的时间干扰会影响运算结果 三 见到曙光 这里后面就是比较最终结果的时候了: 000000014002080F | | call yanso.1400374E0 |p[15] ^ [9] 000000014002BF50 | | call yanso.1400379B0 |== 0x08 000000014001234A | | movzx r11d,byte ptr ss:[rsp+61] |[9] 0000000140012350 | | movzx ecx,byte ptr ss:[rsp+64] |[12] 0000000140012355 | | mov r13d,51B457D2 | 000000014001235B | | xor r10d,r10d | 000000014001235E | | mov r15,rcx | 0000000140012361 | | call yanso.140001130 | 0000000140012366 | | xor eax,eax | 0000000140012368 | | cmp edx,83 |xor == 0x83 000000014001236E | | sete al | 000000014000D9C0 | | mov al,byte ptr ss:[rsp+64] |[12] 000000014000D9C4 | | mov byte ptr ss:[rsp+A1],al | 000000014000D9CB | | movzx eax,byte ptr ss:[rsp+A1] | 000000014000D9D3 | | mov dword ptr ss:[rsp+7A8],eax | 000000014000D9DA | | movzx r15d,byte ptr ss:[rsp+63] |[11] 000000014000D9E0 | | mov ecx,dword ptr ss:[rsp+7A8] | 000000014000D9E7 | | mov r13d,51B457D2 | 000000014000D9ED | | xor r10d,r10d | 000000014000D9F0 | | mov r11,rcx | 000000014000D9F3 | | call yanso.140001130 | 000000014000D9F8 | | xor eax,eax | 000000014000D9FA | | cmp edx,9B |xor == 0x9B 000000014000DA00 | | sete al | 00000001400238DF | | mov al,byte ptr ss:[rsp+63] |[11] 00000001400238E3 | | mov byte ptr ss:[rsp+B5],al | 00000001400238EA | | movzx ecx,byte ptr ss:[rsp+B5] | 00000001400238F2 | | movzx edi,byte ptr ss:[rsp+5A] |[2] 00000001400238F7 | | mov byte ptr ss:[rsp+B6],dil | 00000001400238FF | | movzx r15d,byte ptr ss:[rsp+B6] | 0000000140023908 | | mov r13d,51B457D2 | 000000014002390E | | xor r10d,r10d | 0000000140023911 | | mov r11,rcx | 0000000140023914 | | call yanso.140001130 | 0000000140023919 | | xor eax,eax | 000000014002391B | | cmp edx,73 |xor == 0x73 0000000140007BA5 | | movzx ecx,byte ptr ss:[rsp+5A] |[2] 0000000140007BAA | | movzx r15d,byte ptr ss:[rsp+62] |[10] 0000000140007BB0 | | mov r13d,51B457D2 | 0000000140007BB6 | | xor r10d,r10d | 0000000140007BB9 | | mov r11,rcx | 0000000140007BBC | | call yanso.140001130 | 0000000140007BC1 | | cmp edx,92 |xor == 0x92 0000000140017D4A | | movzx ecx,byte ptr ss:[rsp+62] |[10] 0000000140017D4F | | movzx ebp,byte ptr ss:[rsp+5D] |[5] 0000000140017D54 | | mov byte ptr ss:[rsp+A9],bpl | 0000000140017D5C | | movzx edi,byte ptr ss:[rsp+A9] | 0000000140017D64 | | mov r13d,51B457D2 | 0000000140017D6A | | xor r10d,r10d | 0000000140017D6D | | mov r11,rcx | 0000000140017D70 | | mov r15,rdi | 0000000140017D73 | | call yanso.140001130 | 0000000140017D78 | | mov dword ptr ss:[rsp+86C],edx | 0000000140017D7F | | mov eax,dword ptr ss:[rsp+86C] | 0000000140017D86 | | cmp eax,93 |xor == 0x93 0000000140016E54 | | movzx edi,byte ptr ss:[rsp+5D] |[5] 0000000140016E59 | | movzx ecx,byte ptr ss:[rsp+5B] |[3] 0000000140016E5E | | mov r13d,51B457D2 | 0000000140016E64 | | xor r10d,r10d | 0000000140016E67 | | mov r11,rdi | 0000000140016E6A | | mov r15,rcx | 0000000140016E6D | | call yanso.140001130 | 0000000140016E72 | | cmp edx,5F |xor == 0x5F 000000014001E7B7 | | movzx r11d,byte ptr ss:[rsp+5B] |[3] 000000014001E7BD | | mov al,byte ptr ss:[rsp+5E] |[6] 000000014001E7C1 | | mov byte ptr ss:[rsp+B1],al | 000000014001E7C8 | | movzx eax,byte ptr ss:[rsp+B1] | 000000014001E7D0 | | mov dword ptr ss:[rsp+CC0],eax | 000000014001E7D7 | | mov ecx,dword ptr ss:[rsp+CC0] | 000000014001E7DE | | mov r13d,51B457D2 | 000000014001E7E4 | | xor r10d,r10d | 000000014001E7E7 | | mov r15,rcx | 000000014001E7EA | | call yanso.140001130 | 000000014001E7EF | | mov rbp,rdx | 000000014001E7F2 | | xor eax,eax | 000000014001E7F4 | | cmp ebp,BA |xor == 0xBA 000000014001CE0F | | movzx r11d,byte ptr ss:[rsp+5E] |[6] 000000014001CE15 | | mov al,byte ptr ss:[rsp+5C] |[4] 000000014001CE19 | | mov byte ptr ss:[rsp+AF],al | 000000014001CE20 | | movzx ecx,byte ptr ss:[rsp+AF] | 000000014001CE28 | | mov dword ptr ss:[rsp+104],ecx | 000000014001CE2F | | mov r15d,dword ptr ss:[rsp+104] | 000000014001CE37 | | mov r13d,51B457D2 | 000000014001CE3D | | xor r10d,r10d | 000000014001CE40 | | call yanso.140001130 | 000000014001CE45 | | cmp edx,37 |xor == 0x37 00000001400133B4 | | movzx eax,byte ptr ss:[rsp+5C] |[4] 00000001400133B9 | | mov dword ptr ss:[rsp+800],eax | 00000001400133C0 | | movzx ecx,byte ptr ss:[rsp+66] |[14] 00000001400133C5 | | mov dword ptr ss:[rsp+804],ecx | 00000001400133CC | | mov edi,dword ptr ss:[rsp+800] | 00000001400133D3 | | mov r12d,dword ptr ss:[rsp+804] | 00000001400133DB | | mov r13d,51B457D2 | 00000001400133E1 | | xor r10d,r10d | 00000001400133E4 | | mov r11,rdi | 00000001400133E7 | | mov r15,r12 | 00000001400133EA | | call yanso.140001130 | 00000001400133EF | | cmp edx,A1 |xor == 0xA1 00000001400367DA | | movzx eax,byte ptr ss:[rsp+66] |[14] 00000001400367DF | | mov dword ptr ss:[rsp+D9C],eax | 00000001400367E6 | | movzx r12d,byte ptr ss:[rsp+5F] |[7] 00000001400367EC | | mov r11d,dword ptr ss:[rsp+D9C] | 00000001400367F4 | | mov r13d,51B457D2 | 00000001400367FA | | xor r10d,r10d | 00000001400367FD | | mov r15,r12 | 0000000140036800 | | call yanso.140001130 | 0000000140036805 | | mov qword ptr ss:[rsp+1F68],rdx | 000000014003680D | | mov rax,qword ptr ss:[rsp+1F68] | 0000000140036815 | | cmp eax,9B |xor == 0x9B 0000000140024895 | | movzx ecx,byte ptr ss:[rsp+5F] |[7] 000000014002489A | | mov al,byte ptr ss:[rsp+58] |[0] 000000014002489E | | mov byte ptr ss:[rsp+B7],al | 00000001400248A5 | | movzx r15d,byte ptr ss:[rsp+B7] | 00000001400248AE | | mov r13d,51B457D2 | 00000001400248B4 | | xor r10d,r10d | 00000001400248B7 | | mov r11,rcx | 00000001400248BA | | call yanso.140001130 | 00000001400248BF | | cmp edx,D8 |xor == 0xD8 000000014002064E | | movzx ecx,byte ptr ss:[rsp+58] |[0] 0000000140020653 | | movzx edi,byte ptr ss:[rsp+60] |[8] 0000000140020658 | | mov r13d,51B457D2 | 000000014002065E | | xor r10d,r10d | 0000000140020661 | | mov r11,rcx | 0000000140020664 | | mov r15,rdi | 0000000140020667 | | call yanso.140001130 | 000000014002066C | | mov qword ptr ss:[rsp+22F0],rdx | 0000000140014D9A | | mov rax,qword ptr ss:[rsp+22F0] | 0000000140014DA2 | | xor edx,edx | 0000000140014DA4 | | cmp eax,5F |xor == 0x5F 00000001400157A7 | | movzx ebx,byte ptr ss:[rsp+60] |[8] 00000001400157AC | | mov qword ptr ss:[rsp+B20],r11 | 00000001400157B4 | | movzx r12d,byte ptr ss:[rsp+65] |[13] 00000001400157BA | | mov r13d,51B457D2 | 00000001400157C0 | | xor r10d,r10d | 00000001400157C3 | | mov r15,r12 | 00000001400157C6 | | call yanso.140001130 | 00000001400157CB | | xor ebp,ebp | 00000001400157CD | | cmp edx,25 |xor == 0x25 00000001400325EE | | movzx ecx,byte ptr ss:[rsp+65] |[13] 00000001400325F3 | | mov byte ptr ss:[rsp+C3],cl | 00000001400325FA | | movzx r11d,byte ptr ss:[rsp+C3] | 0000000140032603 | | movzx edi,byte ptr ss:[rsp+59] |[1] 0000000140032608 | | mov dword ptr ss:[rsp+2E0],edi | 000000014003260F | | mov r15d,dword ptr ss:[rsp+2E0] | 0000000140032617 | | mov r13d,51B457D2 | 000000014003261D | | xor r10d,r10d | 0000000140032620 | | call yanso.140001130 | 0000000140032625 | | cmp edx,7C |xor == 0x7C 0000000140005D17 | | cmp byte ptr ss:[rsp+59],34 |[1] == 0x34 0000000140005D1C | | sete byte ptr ss:[rsp+9C] | 16个字节都比较通过就成功了 kg代码见附件文件 算出的flag结果:KCTF{BL4ckSH33p}WA11_}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!