; ===== main 函数开始 =====00007FF632F710A0 | 48:895424 10 | mov qword ptr ss:[rsp+10], rdx; 你的理解:保存 argv; 正确:保存第二个参数 argv(char**)到栈上; |*| x64 调用约定:RCX=argc, RDX=argv, R8=envp 00007FF632F710A5 | 894C24 08 | mov dword ptr ss:[rsp+8], ecx; 你的理解:保存 argc; 正确:保存第一个参数 argc(int)到栈上 00007FF632F710A9 | 57 | push rdi; 你的理解:(无); 正确:保存 RDI(非易失寄存器); |*| RDI、RBX、RBP、R12-R15 属于非易失寄存器,函数返回前必须恢复原值。RAX、RCX、RDX、R8-R11 属于易失寄存器,可以随意修改。 00007FF632F710AA | 48:83EC 60 | sub rsp, 60; 你的理解:存一下 rsp; 正确:开辟 0x60 字节栈空间; |*| 汇编里的数字默认都是十六进制,0x60 = 十进制 96file:///C:/Users/11/AppData/Local/Temp/ksohtml22852/wps11.jpg ; ----- 栈保护 cookie(可跳过) -----00007FF632F710AE | 48:8B05 CB0F0200 | mov rax, qword ptr ds:[7FF632F92080]; 你的理解:把全局变量放到 rax; 正确:读取 __security_cookie; |*| 编译器安全机制,如果看到 mov rax, [全局地址] 然后 xor rax, rsp,直接判断为栈保护,不用深入分析。 00007FF632F710B5 | 48:33C4 | xor rax, rsp; 你的理解:异或 rsp 干嘛; 正确:生成栈保护值,与 rsp 异或 00007FF632F710B8 | 48:894424 50 | mov qword ptr ss:[rsp+50], rax; 你的理解:(无); 正确:保存栈保护值到栈上 ; ----- 初始化硬编码字节数组(14 字节)-----00007FF632F710BD | C64424 30 18 | mov byte ptr ss:[rsp+30], 18; 数组[0] = 0x1800007FF632F710C2 | C64424 31 1A | mov byte ptr ss:[rsp+31], 1A; 数组[1] = 0x1A00007FF632F710C7 | C64424 32 0B | mov byte ptr ss:[rsp+32], 0B; 数组[2] = 0x0B00007FF632F710CC | C64424 33 5F | mov byte ptr ss:[rsp+33], 5F; 数组[3] = 0x5F00007FF632F710D1 | C64424 34 1D | mov byte ptr ss:[rsp+34], 1D; 数组[4] = 0x1D ← 后面会用到这个00007FF632F710D6 | C64424 35 1E | mov byte ptr ss:[rsp+35], 1E; 数组[5] = 0x1E00007FF632F710DB | C64424 36 16 | mov byte ptr ss:[rsp+36], 16; 数组[6] = 0x1600007FF632F710E0 | C64424 37 0B | mov byte ptr ss:[rsp+37], 0B; 数组[7] = 0x0B00007FF632F710E5 | C64424 38 1A | mov byte ptr ss:[rsp+38], 1A; 数组[8] = 0x1A00007FF632F710EA | C64424 39 1B | mov byte ptr ss:[rsp+39], 1B; 数组[9] = 0x1B00007FF632F710EF | C64424 3A 5F | mov byte ptr ss:[rsp+3A], 5F; 数组[10] = 0x5F00007FF632F710F4 | C64424 3B 13 | mov byte ptr ss:[rsp+3B], 13; 数组[11] = 0x1300007FF632F710F9 | C64424 3C 10 | mov byte ptr ss:[rsp+3C], 10; 数组[12] = 0x1000007FF632F710FE | C64424 3D 13 | mov byte ptr ss:[rsp+3D], 13; 数组[13] = 0x13; 补充:这些是硬编码的加密字节,只有 [4] 参与了密钥计算。; |*| 看到连续的 mov byte ptr [rsp+xx], imm 就是在栈上初始化字节数组。file:///C:/Users/11/AppData/Local/Temp/ksohtml22852/wps12.jpg ; ----- 清零局部缓冲区 -----00007FF632F71103 | 48:8D4424 40 | lea rax, qword ptr ss:[rsp+40]; 你的理解:一个参数,先认为 rsp+40=argv[1] 参数1; 正确:取局部缓冲区地址(用于清零),不是 argv[1]; 补充:argv[1] 在 [rsp+78] 附近,后面会从那里取。; |*| 判断是否是指针参数:看它是从 [rsp+78] 取出 argv 基址,再加 8/16 的倍数偏移。不要看到 rsp+xx 就以为是 argv。 00007FF632F71108 | 48:8BF8 | mov rdi, rax; 你的理解:rdi:"fJ檶,~"; 正确:RDI = 缓冲区地址 00007FF632F7110B | 33C0 | xor eax, eax; eax = 0 00007FF632F7110D | B9 10000000 | mov ecx, 10; ecx = 16 00007FF632F71112 | F3:AA | rep stosb; 你的理解:不知道是什么,起到关键作用了么; 正确:把从 [rsp+40] 开始的 16 字节清零; |*| rep stosb = 重复执行 stosb,用 AL 填充 [RDI],RCX 是次数。遇到清空缓冲区不需要细看。file:///C:/Users/11/AppData/Local/Temp/ksohtml22852/wps13.jpg ; ----- 计算 v5 = argc*42 + argc/3 -----00007FF632F71114 | 6B4424 70 2A | imul eax, dword ptr ss:[rsp+70], 2A; 你的理解:eax = 参数*2a; 正确:eax = argc * 0x2A = 3 * 42 = 126 = 0x7E; 补充:此处 [rsp+70] 是 argc,值为 3。; |*| 三操作数 imul 指令格式:imul 目标, 源, 立即数。0x2A = 42。 00007FF632F71119 | 894424 20 | mov dword ptr ss:[rsp+20], eax; 你的理解:把 eax 的值 7e 放给另外一个参数 argv[2]; 正确:暂存 0x7E 到 [rsp+20]; |*| [rsp+20] 是临时栈变量,不是 argv[2]。argv[2] 在 [rsp+78]+16 的位置。 00007FF632F7111D | 8B4424 70 | mov eax, dword ptr ss:[rsp+70]; 你的理解:eax=rsp+70; 正确:eax = argc = 3 00007FF632F71121 | 99 | cdq; 你的理解:不知道是什么,rax变成了3,cdq是简写么; 正确:cdq 把 EAX 的符号位扩展到 EDX,为有符号除法做准备; |*| cdq = Convert Doubleword to Quadword(EDX:EAX 合并为被除数)。 00007FF632F71122 | B9 03000000 | mov ecx, 3; ecx = 3 00007FF632F71127 | F7F9 | idiv ecx; 你的理解:应该是 rax=ecx/eax=1,因为步过变1; 正确:eax = eax / ecx = 3 / 3 = 1,余数在 edx; |*| idiv 是有符号除法,被除数 EDX:EAX,除数 ECX。 00007FF632F71129 | 8B4C24 20 | mov ecx, dword ptr ss:[rsp+20]; 你的理解:ecx=rsp+20; 正确:ecx = 暂存的 0x7E 00007FF632F7112D | 03C8 | add ecx, eax; 你的理解:ecx=ecx+eax=7E+1=7F; 正确:ecx = 0x7E + 1 = 0x7F 00007FF632F7112F | 8BC1 | mov eax, ecx; eax = 0x7F 00007FF632F71131 | 894424 24 | mov dword ptr ss:[rsp+24], eax; 你的理解:rsp+24=7F; 正确:v5 = 0x7F,存入 [rsp+24]; |*| 核心变量 v5 = argc * 42 + argc / 3。当 argc=3 时,v5 固定为 0x7F。file:///C:/Users/11/AppData/Local/Temp/ksohtml22852/wps14.jpg ; ----- 检查 argc 是否为 3 -----00007FF632F71135 | 837C24 70 03 | cmp dword ptr ss:[rsp+70], 3; 你的理解:rsp+70 看了一下=3,这个就是 3-3; 正确:比较 argc 和 3; |*| cmp 不修改操作数,只影响标志位。ZF=1 表示相等。 00007FF632F7113A | 74 28 | je crack.7FF632F71164; 你的理解:算的值=0,zf 变 1,跳转 164; 正确:如果 argc == 3,则跳转到 1164 正常执行 ; ----- 如果 argc != 3,打印 Usage 并退出 -----00007FF632F7113C | B8 08000000 | mov eax, 8; eax = 800007FF632F71141 | 48:6BC0 00 | imul rax, rax, 0; rax = 000007FF632F71145 | 48:8B4C24 78 | mov rcx, qword ptr ss:[rsp+78]; rcx = argv 基地址00007FF632F7114A | 48:8B1401 | mov rdx, qword ptr ds:[rcx+rax]; rdx = argv[0](程序路径)00007FF632F7114E | 48:8D0D AB0E0200 | lea rcx, qword ptr ds:[7FF632F92000]; rcx = "Usage: %s [text] [key for the text]"00007FF632F71155 | E8 46010000 | call crack.7FF632F712A0; 调用 printf00007FF632F7115A | B8 01000000 | mov eax, 1; 返回值 = 100007FF632F7115F | E9 BA000000 | jmp crack.7FF632F7121E; 跳到函数结尾file:///C:/Users/11/AppData/Local/Temp/ksohtml22852/wps15.jpg ; ----- argc == 3,正常执行 -----00007FF632F71164 | 8B4424 70 | mov eax, dword ptr ss:[rsp+70]; eax = argc = 300007FF632F71168 | 8D04C5 48000000 | lea eax, qword ptr ds:[rax*8+48]; 你的理解:eax=rax*8+48=72, 算错了,16进制 3*8=24+48=60; 正确:eax = 3*8 + 0x48 = 0x18 + 0x48 = 0x60; |*| 十六进制计算的陷阱:3*8=0x18,加上0x48等于0x60,十进制是96。不要算成十进制然后直接加。00007FF632F7116F | 8BC8 | mov ecx, eax; ecx = 0x6000007FF632F71171 | E8 8AFEFFFF | call crack.7FF632F71000; 你的理解:跟进去看一下吧,进去没看懂,出来了,但是好像不影响 test eax,eax; 正确:调用检查函数 sub_7FF632F71000,该函数不修改 EAX,直接返回; 补充:这个函数内部比较传入值和 0x52D1,不相等就返回,不会改变 EAX。 00007FF632F71176 | 85C0 | test eax, eax; 你的理解:进去没看懂,出来了,但是好像不影响 teat eax,eax 值是固定的 0,zf=1,下面必跳转; 正确:EAX 仍然是 0x60(非零),所以 ZF=0,不会跳转; |*| test eax, eax 是判断 EAX 是否为 0,等价于 eax & eax。结果非零则 ZF=0。不要理解成减法。 00007FF632F71178 | 0F84 9E000000 | je crack.7FF632F7121C; 你的理解:实际是 zf=0,不跳转,又记错了?; 正确:ZF=0,所以不跳转,继续执行file:///C:/Users/11/AppData/Local/Temp/ksohtml22852/wps16.jpg ; ----- 调用 strtol(argv[2], NULL, 10) -----00007FF632F7117E | B8 08000000 | mov eax, 8; eax = 800007FF632F71183 | 48:6BC0 02 | imul rax, rax, 2; 你的理解:eax=8*2=10 和 3*8=18 一个道理,算错的自己罚20蹲起; 正确:rax = 8 * 2 = 16 = 0x10; |*| 这里 8 和 2 都是十六进制,8*2=0x10,不是十进制 10。写多了会形成肌肉记忆。 00007FF632F71187 | 41:B8 0A000000 | mov r8d, 10; r8d = 10(十进制)00007FF632F7118D | 33D2 | xor edx, edx; edx = 000007FF632F7118F | 48:8B4C24 78 | mov rcx, qword ptr ss:[rsp+78]; rcx = argv 基地址00007FF632F71194 | 48:8B0C01 | mov rcx, qword ptr ds:[rcx+rax]; 你的理解:rcx看不懂,是 j? 但是步过之后发现 rcx 存的是第二个参数值; 正确:rcx = argv[2](第二个参数,即你输入的密钥字符串); 补充:rax = 16,所以 rcx+rax = argv[2]; |*| 看 argv[x]:第一个参数偏移8,第二个偏移16,第三个偏移24。因为 argv 是指针数组,每个指针 8 字节。 00007FF632F71198 | E8 235E0000 | call crack.7FF632F76FC0; 调用 strtol(argv[2], NULL, 10)00007FF632F7119D | 894424 28 | mov dword ptr ss:[rsp+28], eax; 你的理解:rsp+28 存入 eax; 正确:把 strtol 的返回值(你输入的密钥数字)存入 [rsp+28]; |*| strtol 特征:3个参数,RCX=字符串, RDX=0, R8=10。返回整数在 EAX。是字符串转整数的库函数。 ; ----- 调用 sum(argv[1]) -----00007FF632F711A1 | B9 08000000 | mov ecx, 8; ecx = 800007FF632F711A6 | 48:6BC9 01 | imul rcx, rcx, 1; 你的理解:ecx=8; 正确:rcx = 8 * 1 = 8(argv[1] 的偏移量)00007FF632F711AA | 48:8B5424 78 | mov rdx, qword ptr ss:[rsp+78]; rdx = argv 基地址00007FF632F711AF | 48:8B0C0A | mov rcx, qword ptr ds:[rdx+rcx]; 你的理解:rcx:"1111111111111"; 正确:rcx = argv[1](第一个参数,文本)00007FF632F711B3 | E8 68FEFFFF | call crack.7FF632F71020; 你的理解:计算 sum(argv[1]); 正确:调用 sum 函数,返回字符 ASCII 之和00007FF632F711B8 | 83F0 03 | xor eax, 3; 你的理解:eax 是不是就是每次 call 出来的值,这次输入是 1111111111111,sum 后是 27D,和 3 做异或; 正确:eax = sum ^ 3file:///C:/Users/11/AppData/Local/Temp/ksohtml22852/wps17.jpg ; ----- 计算核心公式 -----00007FF632F711BB | B9 08000000 | mov ecx, 8; ecx = 800007FF632F711C0 | 48:6BC9 01 | imul rcx, rcx, 1; rcx = 800007FF632F711C4 | BA 01000000 | mov edx, 1; edx = 100007FF632F711C9 | 48:6BD2 00 | imul rdx, rdx, 0; 你的理解:edx=0; 正确:rdx = 1 * 0 = 000007FF632F711CD | 48:8B7C24 78 | mov rdi, qword ptr ss:[rsp+78]; rdi = argv 基地址00007FF632F711D2 | 48:8B0C0F | mov rcx, qword ptr ds:[rdi+rcx]; rcx = argv[1]00007FF632F711D6 | 0FBE0C11 | movsx ecx, byte ptr ds:[rcx+rdx]; 你的理解:rcx+rdx*1:"1111111111111"; 步过之后其实就是 rcx 放置参数1,这个是 ecx=参数1; 正确:ecx = argv[1][0](第一个字符),因为 rdx=0; |*| movsx 是符号扩展,把 1 字节扩展到 32 位。常用于取有符号字符。00007FF632F711DA | 0FAFC1 | imul eax, ecx; 你的理解:eax=sum(参数1)^3*参数1第一个字符的ascii值,就是 eax=27d^3*31=27e*31=7a1e; 正确:eax = (sum ^ 3) * argv[1][0]; |*| 双操作数 imul 指令:eax = eax * ecx。00007FF632F711DD | B9 01000000 | mov ecx, 1; ecx = 100007FF632F711E2 | 48:6BC9 04 | imul rcx, rcx, 4; rcx = 400007FF632F711E6 | 0FB64C0C 30 | movzx ecx, byte ptr ss:[rsp+rcx+30]; 你的理解:ecx=1d; 正确:ecx = 硬编码数组[4] = 0x1D; |*| movzx 是零扩展,把 1 字节扩展到 32 位。常用于取无符号字节。00007FF632F711EB | 0FB6C9 | movzx ecx, cl; ecx = 0x1D00007FF632F711EE | 8B5424 24 | mov edx, dword ptr ss:[rsp+24]; 你的理解:edx=7f, 这个是之前算的吧,00007FF632F71131 这一步算的。; 正确:edx = v5 = 0x7F00007FF632F711F2 | 33CA | xor ecx, edx; 你的理解:xor 1d,7f; 正确:ecx = 0x1D ^ 0x7F = 0x62; |*| 0x1D ^ 0x7F = 0x62(十进制 98)。这是固定的异或运算,直接算出结果即可。00007FF632F711F4 | 8D4408 9E | lea eax, qword ptr ds:[rax+rcx-62]; 你的理解:eax 又把 7a1e 取出来了,就是前面说的那个; 正确:eax = eax + ecx - 0x62 = eax + 0x62 - 0x62 = eax; |*| 核心算法简化:因为 0x1D ^ 0x7F = 0x62,所以这一长串加减可以相互抵消。最终 eax = (sum^3) * argv[1][0]。这是还原算法的关键突破口。 ; ----- 最终比较 -----00007FF632F711F8 | 8B4C24 28 | mov ecx, dword ptr ss:[rsp+28]; 你的理解:ecx 刚刚是 b 的值,现在变成 7FFFFfF 前面好像也出现过; 正确:ecx = 你输入的密钥数字(strtol 的结果)00007FF632F711FC | 3BC8 | cmp ecx, eax; 你的理解:这就是最后的比较了,后面就是打印 good 和 bad,我就不比了,应该是让 ecx=7a1e 就行。那么说 7FFFFFFF 就是和之前输入的第二个值有关系,就是 Key.; 正确:比较你输入的密钥和程序算出的正确密钥; |*| 最终判定:你需要输入的密钥 = EAX 的值。也就是 (sum ^ 3) * argv[1][0]。00007FF632F711FE | 75 0F | jne crack.7FF632F7120F; 不相等则跳去打印 "bad kitty"00007FF632F71200 | 48:8D0D 210E0200 | lea rcx, qword ptr ds:[7FF632F92028]; rcx = "Good job"00007FF632F71207 | E8 94000000 | call crack.7FF632F712A0; printf("Good job")00007FF632F7120C | 90 | nop00007FF632F7120D | EB 0D | jmp crack.7FF632F7121C; 跳到结尾00007FF632F7120F | 48:8D0D 220E0200 | lea rcx, qword ptr ds:[7FF632F92038]; rcx = "bad kitty"00007FF632F71216 | E8 85000000 | call crack.7FF632F712A0; printf("bad kitty")file:///C:/Users/11/AppData/Local/Temp/ksohtml22852/wps18.jpg //下面不重要00007FF632F7121B | 90 | nop00007FF632F7121C | 33C0 | xor eax, eax; eax = 0(返回 0)00007FF632F7121E | 48:8B4C24 50 | mov rcx, qword ptr ss:[rsp+50]; 读取栈保护值00007FF632F71223 | 48:33CC | xor rcx, rsp; 校验00007FF632F71226 | E8 65010000 | call crack.7FF632F71390; 检查栈是否被破坏00007FF632F7122B | 48:83C4 60 | add rsp, 60; 恢复栈00007FF632F7122F | 5F | pop rdi; 恢复 RDI00007FF632F71230 | C3 | ret; 返回 |