一 从歧路开始 __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_}
这篇不好写,都是体力活,很多都是调试器里无数个断点反复观察出来的,实在没什么道理可讲
[培训]科锐逆向工程师培训班38期--远程教学预课班将于 2020年5月28日 正式开班!
最后于 1天前 被kanxue编辑 ,原因: