一 从歧路开始
__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_}
这篇不好写,都是体力活,很多都是调试器里无数个断点反复观察出来的,实在没什么道理可讲
看雪学院推出的专业资质证书《看雪安卓应用安全能力认证 v1.0》(中级和高级)!
最后于 2020-5-5 09:49
被kanxue编辑
,原因: