[{"data":1,"prerenderedAt":2367},["ShallowReactive",2],{"category-data-reverse":3},[4,1378],{"id":5,"title":6,"body":7,"description":1363,"extension":1364,"meta":1365,"navigation":1373,"ogImage":1367,"path":1374,"seo":1375,"stem":1376,"__hash__":1377},"content/blogs/10. ctf-reverse-overview.md","CTF 逆向入门：侦查、反汇编与 GDB 调试",{"type":8,"value":9,"toc":1353},"minimark",[10,15,19,22,84,87,91,94,122,129,133,140,146,151,154,167,170,178,189,194,198,200,212,214,220,230,248,251,256,280,284,296,302,314,319,324,328,333,350,354,356,377,380,420,423,486,489,549,556,577,581,614,617,637,644,660,664,684,686,692,702,715,719,722,785,788,811,814,841,844,848,851,856,865,877,880,884,894,896,902,906,915,917,923,926,930,939,942,946,955,961,970,973,997,1004,1017,1020,1026,1029,1035,1039,1120,1130,1134,1141,1145,1150,1163,1179,1185,1189,1194,1203,1209,1218,1222,1227,1236,1305,1307,1313,1317,1330,1334,1338,1349],[11,12,14],"h3",{"id":13},"一现实背景为什么需要逆向","一、现实背景：为什么需要逆向",[16,17,18],"p",{},"商业软件只发布编译后的二进制文件，不发布源码。恶意软件分析、漏洞研究、授权校验分析都依赖逆向。CTF 逆向题最常见的形式：程序读取一个输入，判断对不对，对就输出 flag；不给你源码，只给你编译好的文件。",[16,20,21],{},"目标程序示例：",[23,24,29],"pre",{"className":25,"code":26,"language":27,"meta":28,"style":28},"language-bash shiki shiki-themes dracula","$ ./checkflag\nUsage: ./checkflag \u003Cflag>\n$ ./checkflag test\nWrong!\n","bash","",[30,31,32,45,68,78],"code",{"__ignoreMap":28},[33,34,37,41],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"sAOxA","$",[33,42,44],{"class":43},"s-mGx"," ./checkflag\n",[33,46,48,51,54,58,61,65],{"class":35,"line":47},2,[33,49,50],{"class":39},"Usage:",[33,52,53],{"class":43}," ./checkflag",[33,55,57],{"class":56},"s0Tla"," \u003C",[33,59,60],{"class":43},"fla",[33,62,64],{"class":63},"sCdxs","g",[33,66,67],{"class":56},">\n",[33,69,71,73,75],{"class":35,"line":70},3,[33,72,40],{"class":39},[33,74,53],{"class":43},[33,76,77],{"class":43}," test\n",[33,79,81],{"class":35,"line":80},4,[33,82,83],{"class":39},"Wrong!\n",[16,85,86],{},"解题目标：通过分析二进制，找出正确输入。",[11,88,90],{"id":89},"二环境准备","二、环境准备",[16,92,93],{},"在 Linux（Kali/Ubuntu）上：",[23,95,97],{"className":25,"code":96,"language":27,"meta":28,"style":28},"apt install file binutils gdb ltrace strace\n",[30,98,99],{"__ignoreMap":28},[33,100,101,104,107,110,113,116,119],{"class":35,"line":36},[33,102,103],{"class":39},"apt",[33,105,106],{"class":43}," install",[33,108,109],{"class":43}," file",[33,111,112],{"class":43}," binutils",[33,114,115],{"class":43}," gdb",[33,117,118],{"class":43}," ltrace",[33,120,121],{"class":43}," strace\n",[16,123,124,125,128],{},"本文以编译后的 ",[30,126,127],{},"checkflag"," 为例，逐步演示。",[11,130,132],{"id":131},"三核心步骤-1侦查","三、核心步骤 1：侦查",[16,134,135,139],{},[136,137,138],"strong",{},"目的","：在不运行程序的情况下，先收集\"程序是什么、里面写了什么字符串\"。",[16,141,142,145],{},[136,143,144],{},"思路","：程序类型决定用什么工具（ELF 用 objdump，PE 用其他）；字符串往往直接暴露提示、密钥甚至 flag。先做最便宜的三件事，通常能直接定位简单题。",[147,148,150],"h4",{"id":149},"_31-file看文件类型","3.1 file：看文件类型",[16,152,153],{},"输入：",[23,155,157],{"className":25,"code":156,"language":27,"meta":28,"style":28},"file checkflag\n",[30,158,159],{"__ignoreMap":28},[33,160,161,164],{"class":35,"line":36},[33,162,163],{"class":39},"file",[33,165,166],{"class":43}," checkflag\n",[16,168,169],{},"预期输出：",[23,171,176],{"className":172,"code":174,"language":175,"meta":28},[173],"language-text","checkflag: ELF 64-bit LSB pie executable, x86-64, dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped\n","text",[30,177,174],{"__ignoreMap":28},[16,179,180,181,184,185,188],{},"重点看最后：",[30,182,183],{},"not stripped"," 表示符号表还在，函数名（main、check）可见；如果是 ",[30,186,187],{},"stripped","，函数名被删，难度更高。",[16,190,191,193],{},[136,192,138],{},"：确认我们面对的是 ELF 还是 PE、多少位、是否被 strip。这决定后续用 objdump 还是其他工具，以及能否直接看到函数名。",[147,195,197],{"id":196},"_32-strings提取字符串","3.2 strings：提取字符串",[16,199,153],{},[23,201,203],{"className":25,"code":202,"language":27,"meta":28,"style":28},"strings checkflag\n",[30,204,205],{"__ignoreMap":28},[33,206,207,210],{"class":35,"line":36},[33,208,209],{"class":39},"strings",[33,211,166],{"class":43},[16,213,169],{},[23,215,218],{"className":216,"code":217,"language":175,"meta":28},[173],"/lib64/ld-linux-x86-64.so.2\nUsage: ./checkflag \u003Cflag>\nCorrect!\nWrong!\n",[30,219,217],{"__ignoreMap":28},[16,221,222,223,225,226,229],{},"三个提示字符串都出来了，但还没有 flag。注意 ",[30,224,209],{}," 默认只显示长度 ≥ 4 的字符串，短字符串用 ",[30,227,228],{},"-n"," 指定：",[23,231,233],{"className":25,"code":232,"language":27,"meta":28,"style":28},"strings -n 3 checkflag\n",[30,234,235],{"__ignoreMap":28},[33,236,237,239,243,246],{"class":35,"line":36},[33,238,209],{"class":39},[33,240,242],{"class":241},"sIQBb"," -n",[33,244,245],{"class":241}," 3",[33,247,166],{"class":43},[16,249,250],{},"如果程序用 XOR 加密了 flag，strings 只能看到提示，看不到明文，需要继续分析。",[16,252,253,255],{},[136,254,138],{},"：把文件里所有可打印字符串列出来，找\"提示/密钥/硬编码密文\"。",[16,257,258,260,261,264,265,264,268,271,272,275,276,279],{},[136,259,144],{},"：输出里的 ",[30,262,263],{},"Usage: ./checkflag \u003Cflag>","、",[30,266,267],{},"Correct!",[30,269,270],{},"Wrong!"," 说明这是个接收参数的校验程序；如果看到可疑短串（如 ",[30,273,274],{},"k3y","），先用 ",[30,277,278],{},"-n 3"," 确认——可能正是加密密钥。",[147,281,283],{"id":282},"_33-运行程序观察行为","3.3 运行程序观察行为",[23,285,287],{"className":25,"code":286,"language":27,"meta":28,"style":28},"./checkflag test\n",[30,288,289],{"__ignoreMap":28},[33,290,291,294],{"class":35,"line":36},[33,292,293],{"class":39},"./checkflag",[33,295,77],{"class":43},[16,297,298,299,301],{},"输出：",[30,300,270],{},"。",[23,303,305],{"className":25,"code":304,"language":27,"meta":28,"style":28},"./checkflag AAAAAAAAAAAAAAAAAAAAAAAA\n",[30,306,307],{"__ignoreMap":28},[33,308,309,311],{"class":35,"line":36},[33,310,293],{"class":39},[33,312,313],{"class":43}," AAAAAAAAAAAAAAAAAAAAAAAA\n",[16,315,298,316,318],{},[30,317,270],{},"。行为确认：带一个参数，对就 Correct，错就 Wrong。",[16,320,321,323],{},[136,322,138],{},"：确认输入输出格式（参数个数、提示文字），为后续在反汇编里找\"打印 Correct 的分支\"做准备。",[11,325,327],{"id":326},"四核心步骤-2反汇编","四、核心步骤 2：反汇编",[16,329,330,332],{},[136,331,138],{},"：找到程序\"判断对错\"的那段代码，弄清校验逻辑。",[16,334,335,337,338,341,342,345,346,349],{},[136,336,144],{},"：程序要么比较字符串（",[30,339,340],{},"strcmp","），要么逐字节运算（XOR 等）。反汇编里找 ",[30,343,344],{},"call","（函数调用）和 ",[30,347,348],{},"je/jne","（条件跳转）就能定位判断点。",[147,351,353],{"id":352},"_41-objdump-看-main","4.1 objdump 看 main",[16,355,153],{},[23,357,359],{"className":25,"code":358,"language":27,"meta":28,"style":28},"objdump -d -M intel checkflag\n",[30,360,361],{"__ignoreMap":28},[33,362,363,366,369,372,375],{"class":35,"line":36},[33,364,365],{"class":39},"objdump",[33,367,368],{"class":241}," -d",[33,370,371],{"class":241}," -M",[33,373,374],{"class":43}," intel",[33,376,166],{"class":43},[16,378,379],{},"输出很长，用 grep 只看 main：",[23,381,383],{"className":25,"code":382,"language":27,"meta":28,"style":28},"objdump -d -M intel checkflag | grep -A 40 '\u003Cmain>'\n",[30,384,385],{"__ignoreMap":28},[33,386,387,389,391,393,395,398,401,404,407,410,414,417],{"class":35,"line":36},[33,388,365],{"class":39},[33,390,368],{"class":241},[33,392,371],{"class":241},[33,394,374],{"class":43},[33,396,397],{"class":43}," checkflag",[33,399,400],{"class":56}," |",[33,402,403],{"class":39}," grep",[33,405,406],{"class":241}," -A",[33,408,409],{"class":241}," 40",[33,411,413],{"class":412},"seVfx"," '",[33,415,416],{"class":43},"\u003Cmain>",[33,418,419],{"class":412},"'\n",[16,421,422],{},"预期输出（节选）：",[23,424,428],{"className":425,"code":426,"language":427,"meta":28,"style":28},"language-asm shiki shiki-themes dracula","0000000000001189 \u003Cmain>:\n    1189:  push   rbp\n    118a:  mov    rbp, rsp\n    1191:  cmp    edi, 0x2\n    1194:  jne    0x11a0\n    119e:  call   0x1149 \u003Ccheck>\n    11a3:  test   eax, eax\n    11a5:  je     0x11b5\n    11a7:  lea    rdi, [rip+0x2ea]\n    11ae:  call   puts@plt\n","asm",[30,429,430,435,440,445,450,456,462,468,474,480],{"__ignoreMap":28},[33,431,432],{"class":35,"line":36},[33,433,434],{},"0000000000001189 \u003Cmain>:\n",[33,436,437],{"class":35,"line":47},[33,438,439],{},"    1189:  push   rbp\n",[33,441,442],{"class":35,"line":70},[33,443,444],{},"    118a:  mov    rbp, rsp\n",[33,446,447],{"class":35,"line":80},[33,448,449],{},"    1191:  cmp    edi, 0x2\n",[33,451,453],{"class":35,"line":452},5,[33,454,455],{},"    1194:  jne    0x11a0\n",[33,457,459],{"class":35,"line":458},6,[33,460,461],{},"    119e:  call   0x1149 \u003Ccheck>\n",[33,463,465],{"class":35,"line":464},7,[33,466,467],{},"    11a3:  test   eax, eax\n",[33,469,471],{"class":35,"line":470},8,[33,472,473],{},"    11a5:  je     0x11b5\n",[33,475,477],{"class":35,"line":476},9,[33,478,479],{},"    11a7:  lea    rdi, [rip+0x2ea]\n",[33,481,483],{"class":35,"line":482},10,[33,484,485],{},"    11ae:  call   puts@plt\n",[16,487,488],{},"逐行解释：",[490,491,492,505],"table",{},[493,494,495],"thead",{},[496,497,498,502],"tr",{},[499,500,501],"th",{},"指令",[499,503,504],{},"含义",[506,507,508,519,529,539],"tbody",{},[496,509,510,516],{},[511,512,513],"td",{},[30,514,515],{},"cmp edi, 0x2",[511,517,518],{},"比较参数个数 argc 是否为 2",[496,520,521,526],{},[511,522,523],{},[30,524,525],{},"call check",[511,527,528],{},"调用校验函数 check",[496,530,531,536],{},[511,532,533],{},[30,534,535],{},"test eax, eax",[511,537,538],{},"检查返回值是否为 0",[496,540,541,546],{},[511,542,543],{},[30,544,545],{},"je 0x11b5",[511,547,548],{},"为 0 则跳到打印 Wrong 的地方",[16,550,551,552,555],{},"结论：逻辑在 ",[30,553,554],{},"check"," 函数里。",[16,557,558,561,562,564,565,567,568,571,572,574,575,301],{},[136,559,560],{},"怎么想到看 check","：",[30,563,525],{}," 之后紧跟 ",[30,566,535],{}," 和 ",[30,569,570],{},"je","，这是典型的\"函数返回结果决定分支\"结构——",[30,573,554],{}," 的返回值非 0 才打印 Correct。所以下一步只看 ",[30,576,554],{},[147,578,580],{"id":579},"_42-看-check","4.2 看 check",[23,582,584],{"className":25,"code":583,"language":27,"meta":28,"style":28},"objdump -d -M intel checkflag | grep -A 50 '\u003Ccheck>'\n",[30,585,586],{"__ignoreMap":28},[33,587,588,590,592,594,596,598,600,602,604,607,609,612],{"class":35,"line":36},[33,589,365],{"class":39},[33,591,368],{"class":241},[33,593,371],{"class":241},[33,595,374],{"class":43},[33,597,397],{"class":43},[33,599,400],{"class":56},[33,601,403],{"class":39},[33,603,406],{"class":241},[33,605,606],{"class":241}," 50",[33,608,413],{"class":412},[33,610,611],{"class":43},"\u003Ccheck>",[33,613,419],{"class":412},[16,615,616],{},"如果看到：",[23,618,620],{"className":425,"code":619,"language":427,"meta":28,"style":28},"call   strcmp@plt\ntest   eax, eax\nje     ...\n",[30,621,622,627,632],{"__ignoreMap":28},[33,623,624],{"class":35,"line":36},[33,625,626],{},"call   strcmp@plt\n",[33,628,629],{"class":35,"line":47},[33,630,631],{},"test   eax, eax\n",[33,633,634],{"class":35,"line":70},[33,635,636],{},"je     ...\n",[16,638,639,640,643],{},"说明是字符串比较，flag 就藏在 ",[30,641,642],{},".rodata"," 里。",[16,645,646,561,649,652,653,656,657,659],{},[136,647,648],{},"验证方式",[30,650,651],{},"call strcmp@plt"," 前面通常有一条 ",[30,654,655],{},"lea rsi, [rip+...]"," 把要比较的字符串地址装进 rsi。记下那个地址，去 ",[30,658,642],{}," 里找内容即可。",[147,661,663],{"id":662},"_43-看只读数据段","4.3 看只读数据段",[23,665,667],{"className":25,"code":666,"language":27,"meta":28,"style":28},"objdump -s -j .rodata checkflag\n",[30,668,669],{"__ignoreMap":28},[33,670,671,673,676,679,682],{"class":35,"line":36},[33,672,365],{"class":39},[33,674,675],{"class":241}," -s",[33,677,678],{"class":241}," -j",[33,680,681],{"class":43}," .rodata",[33,683,166],{"class":43},[16,685,169],{},[23,687,690],{"className":688,"code":689,"language":175,"meta":28},[173],"...\n 402000 636f6e67726174756c6174696f6e73 00   \"congratulations\"\n",[30,691,689],{"__ignoreMap":28},[16,693,694,695,698,699,301],{},"ASCII 十六进制 ",[30,696,697],{},"63 6f 6e 67..."," 解出来就是 ",[30,700,701],{},"congratulations",[16,703,704,707,708,710,711,714],{},[136,705,706],{},"为什么 flag 会在 .rodata","：字符串常量编译后存放在只读数据段 ",[30,709,642],{},"，",[30,712,713],{},"objdump -s"," 直接按字节显示，右侧还附了 ASCII 明文。",[147,716,718],{"id":717},"_44-用-ghidra-反编译更省力","4.4 用 Ghidra 反编译（更省力）",[16,720,721],{},"Ghidra 是 NSA 开源的免费反编译器。安装：",[23,723,725],{"className":25,"code":724,"language":27,"meta":28,"style":28},"apt install openjdk-21-jdk unzip\nwget https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_*.zip\nunzip ghidra_*.zip\ncd ghidra_*_PUBLIC && ./ghidraRun\n",[30,726,727,739,754,766],{"__ignoreMap":28},[33,728,729,731,733,736],{"class":35,"line":36},[33,730,103],{"class":39},[33,732,106],{"class":43},[33,734,735],{"class":43}," openjdk-21-jdk",[33,737,738],{"class":43}," unzip\n",[33,740,741,744,747,751],{"class":35,"line":47},[33,742,743],{"class":39},"wget",[33,745,746],{"class":43}," https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_",[33,748,750],{"class":749},"sqerP","*",[33,752,753],{"class":43},".zip\n",[33,755,756,759,762,764],{"class":35,"line":70},[33,757,758],{"class":39},"unzip",[33,760,761],{"class":43}," ghidra_",[33,763,750],{"class":749},[33,765,753],{"class":43},[33,767,768,772,774,776,779,782],{"class":35,"line":80},[33,769,771],{"class":770},"sLL85","cd",[33,773,761],{"class":43},[33,775,750],{"class":749},[33,777,778],{"class":43},"_PUBLIC",[33,780,781],{"class":63}," && ",[33,783,784],{"class":39},"./ghidraRun\n",[16,786,787],{},"操作步骤：",[789,790,791,795,801,804],"ol",{},[792,793,794],"li",{},"File → New Project → 命名。",[792,796,797,798,800],{},"把 ",[30,799,127],{}," 拖进项目，双击导入。",[792,802,803],{},"等待自动分析完成。",[792,805,806,807,810],{},"左侧 Symbol Tree 双击 ",[30,808,809],{},"main","，右侧 Decompiler 显示伪代码。",[16,812,813],{},"预期伪代码：",[23,815,819],{"className":816,"code":817,"language":818,"meta":28,"style":28},"language-c shiki shiki-themes dracula","if (strcmp(argv[1], \"congratulations\") == 0)\n    puts(\"Correct!\");\nelse\n    puts(\"Wrong!\");\n","c",[30,820,821,826,831,836],{"__ignoreMap":28},[33,822,823],{"class":35,"line":36},[33,824,825],{},"if (strcmp(argv[1], \"congratulations\") == 0)\n",[33,827,828],{"class":35,"line":47},[33,829,830],{},"    puts(\"Correct!\");\n",[33,832,833],{"class":35,"line":70},[33,834,835],{},"else\n",[33,837,838],{"class":35,"line":80},[33,839,840],{},"    puts(\"Wrong!\");\n",[16,842,843],{},"flag 直接可见。",[11,845,847],{"id":846},"五核心步骤-3gdb-动态调试","五、核心步骤 3：GDB 动态调试",[16,849,850],{},"静态分析看不懂时，让程序跑起来。",[16,852,853,855],{},[136,854,138],{},"：在真实执行现场观察\"比较发生时寄存器里到底装了什么\"。",[16,857,858,860,861,864],{},[136,859,144],{},"：下断点停在 check 入口 → 带参数运行 → 单步走到 ",[30,862,863],{},"call strcmp"," → 查看 rsi（第二个参数）指向的字符串，那就是程序期望的 flag。",[23,866,868],{"className":25,"code":867,"language":27,"meta":28,"style":28},"gdb ./checkflag\n",[30,869,870],{"__ignoreMap":28},[33,871,872,875],{"class":35,"line":36},[33,873,874],{"class":39},"gdb",[33,876,44],{"class":43},[16,878,879],{},"进入 GDB 交互后：",[147,881,883],{"id":882},"_51-下断点","5.1 下断点",[23,885,888],{"className":886,"code":887,"language":874,"meta":28,"style":28},"language-gdb shiki shiki-themes dracula","(gdb) break check\n",[30,889,890],{"__ignoreMap":28},[33,891,892],{"class":35,"line":36},[33,893,887],{},[16,895,169],{},[23,897,900],{"className":898,"code":899,"language":175,"meta":28},[173],"Breakpoint 1 at 0x1149\n",[30,901,899],{"__ignoreMap":28},[147,903,905],{"id":904},"_52-带参数运行","5.2 带参数运行",[23,907,909],{"className":886,"code":908,"language":874,"meta":28,"style":28},"(gdb) run test\n",[30,910,911],{"__ignoreMap":28},[33,912,913],{"class":35,"line":36},[33,914,908],{},[16,916,169],{},[23,918,921],{"className":919,"code":920,"language":175,"meta":28},[173],"Breakpoint 1, 0x0000555555555149 in check ()\n",[30,922,920],{"__ignoreMap":28},[16,924,925],{},"程序停在 check 入口。",[147,927,929],{"id":928},"_53-查看即将执行的指令","5.3 查看即将执行的指令",[23,931,933],{"className":886,"code":932,"language":874,"meta":28,"style":28},"(gdb) x/20i $rip\n",[30,934,935],{"__ignoreMap":28},[33,936,937],{"class":35,"line":36},[33,938,932],{},[16,940,941],{},"输出 check 函数开头的 20 条汇编。",[147,943,945],{"id":944},"_54-单步并观察比较","5.4 单步并观察比较",[23,947,949],{"className":886,"code":948,"language":874,"meta":28,"style":28},"(gdb) stepi\n",[30,950,951],{"__ignoreMap":28},[33,952,953],{"class":35,"line":36},[33,954,948],{},[16,956,957,958,960],{},"反复执行直到看到 ",[30,959,651],{},"。然后：",[23,962,964],{"className":886,"code":963,"language":874,"meta":28,"style":28},"(gdb) x/s $rsi\n",[30,965,966],{"__ignoreMap":28},[33,967,968],{"class":35,"line":36},[33,969,963],{},[16,971,972],{},"预期输出：程序正在比较的字符串，即 flag。",[16,974,975,561,978,981,982,985,986,989,990,981,993,996],{},[136,976,977],{},"补充说明",[30,979,980],{},"x/s"," 的 ",[30,983,984],{},"s"," 表示按字符串显示；",[30,987,988],{},"$rsi"," 是 x86-64 的第 2 个参数寄存器，",[30,991,992],{},"strcmp(a, b)",[30,994,995],{},"b"," 就存在这里。",[16,998,999,1000,1003],{},"补充：如果嫌 GDB 麻烦，",[30,1001,1002],{},"ltrace"," 直接跟踪库调用：",[23,1005,1007],{"className":25,"code":1006,"language":27,"meta":28,"style":28},"ltrace ./checkflag test\n",[30,1008,1009],{"__ignoreMap":28},[33,1010,1011,1013,1015],{"class":35,"line":36},[33,1012,1002],{"class":39},[33,1014,53],{"class":43},[33,1016,77],{"class":43},[16,1018,1019],{},"预期输出中会出现：",[23,1021,1024],{"className":1022,"code":1023,"language":175,"meta":28},[173],"strcmp(\"test\", \"congratulations\") = 1\n",[30,1025,1023],{"__ignoreMap":28},[16,1027,1028],{},"flag 一目了然。",[16,1030,1031,1034],{},[136,1032,1033],{},"ltrace 的原理","：它拦截并打印程序对共享库函数的调用，包括参数值。遇到 strcmp 类题目比 GDB 快得多，但不能用来调试自己写的代码。",[11,1036,1038],{"id":1037},"六必学工具","六、必学工具",[490,1040,1041,1054],{},[493,1042,1043],{},[496,1044,1045,1048,1051],{},[499,1046,1047],{},"工具",[499,1049,1050],{},"核心功能",[499,1052,1053],{},"命令示例",[506,1055,1056,1068,1086,1101],{},[496,1057,1058,1060,1063],{},[511,1059,163],{},[511,1061,1062],{},"看类型、位数、是否 stripped",[511,1064,1065],{},[30,1066,1067],{},"file checkflag",[496,1069,1070,1072,1075],{},[511,1071,209],{},[511,1073,1074],{},"提取字符串、指定最小长度、管道过滤",[511,1076,1077,264,1080,264,1083],{},[30,1078,1079],{},"strings checkflag",[30,1081,1082],{},"strings -n 3 a",[30,1084,1085],{},"strings a | grep flag",[496,1087,1088,1090,1093],{},[511,1089,365],{},[511,1091,1092],{},"反汇编、看数据段、Intel 语法",[511,1094,1095,264,1098],{},[30,1096,1097],{},"objdump -d -M intel a",[30,1099,1100],{},"objdump -s -j .rodata a",[496,1102,1103,1106,1109],{},[511,1104,1105],{},"GDB",[511,1107,1108],{},"断点、运行、看内存/寄存器",[511,1110,1111,264,1114,264,1117],{},[30,1112,1113],{},"break check",[30,1115,1116],{},"run",[30,1118,1119],{},"x/20i $rip",[16,1121,1122,1123,1125,1126,1129],{},"另：Ghidra 负责把汇编还原成伪代码；",[30,1124,1002],{},"/",[30,1127,1128],{},"strace"," 负责快速跟踪函数与系统调用。",[11,1131,1133],{"id":1132},"七小-ctf-实战一个-xor-加密的-crackme","七、小 CTF 实战：一个 XOR 加密的 CrackMe",[16,1135,1136,1137,1140],{},"题目：",[30,1138,1139],{},"crackme","，校验函数把输入和密钥异或后与密文比较。",[147,1142,1144],{"id":1143},"第一步侦查","第一步：侦查",[16,1146,1147,1149],{},[136,1148,138],{},"：先找密钥与提示。",[16,1151,1152,561,1154,1156,1157,1159,1160,1162],{},[136,1153,144],{},[30,1155,209],{}," 默认漏掉短串，所以用 ",[30,1158,278],{},"；看到 ",[30,1161,274],{}," 猜测是 XOR 密钥，接下来去确认校验函数。",[23,1164,1166],{"className":25,"code":1165,"language":27,"meta":28,"style":28},"strings -n 3 crackme\n",[30,1167,1168],{"__ignoreMap":28},[33,1169,1170,1172,1174,1176],{"class":35,"line":36},[33,1171,209],{"class":39},[33,1173,242],{"class":241},[33,1175,245],{"class":241},[33,1177,1178],{"class":43}," crackme\n",[16,1180,1181,1182,1184],{},"预期输出中有 ",[30,1183,274],{},"——疑似密钥。",[147,1186,1188],{"id":1187},"第二步反编译-check","第二步：反编译 check",[16,1190,1191,1193],{},[136,1192,138],{},"：确认密钥如何使用、密文数组是什么。",[16,1195,1196,1198,1199,1202],{},[136,1197,648],{},"：伪代码 ",[30,1200,1201],{},"input[i] ^ key[i%3] != enc[i]"," 说明每一位输入与 key 循环异或后要和 enc 相等——逆向就是再异或一次。",[16,1204,1205,1206,1208],{},"用 Ghidra 打开，",[30,1207,554],{}," 伪代码：",[23,1210,1212],{"className":816,"code":1211,"language":818,"meta":28,"style":28},"if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[30,1213,1214],{"__ignoreMap":28},[33,1215,1216],{"class":35,"line":36},[33,1217,1211],{},[147,1219,1221],{"id":1220},"第三步写脚本逆运算","第三步：写脚本逆运算",[16,1223,1224,1226],{},[136,1225,138],{},"：把\"加密\"反过来算，得到明文。",[16,1228,1229,1231,1232,1235],{},[136,1230,144],{},"：异或的自反性 ",[30,1233,1234],{},"A^B=C => C^B=A","。对密文数组的每一位，用 key 按位置循环异或，即还原明文。",[23,1237,1241],{"className":1238,"code":1239,"language":1240,"meta":28,"style":28},"language-python shiki shiki-themes dracula","enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\nkey = b\"k3y\"                                        # 从 strings 找到的密钥\nflag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n# 所以 flag[i] = enc[i] ^ key[i%3]。\nprint(flag.decode())\n# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n","python",[30,1242,1243,1248,1253,1258,1263,1268,1273,1278,1283,1288,1293,1299],{"__ignoreMap":28},[33,1244,1245],{"class":35,"line":36},[33,1246,1247],{},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[33,1249,1250],{"class":35,"line":47},[33,1251,1252],{},"       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[33,1254,1255],{"class":35,"line":70},[33,1256,1257],{},"       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\n",[33,1259,1260],{"class":35,"line":80},[33,1261,1262],{},"key = b\"k3y\"                                        # 从 strings 找到的密钥\n",[33,1264,1265],{"class":35,"line":452},[33,1266,1267],{},"flag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n",[33,1269,1270],{"class":35,"line":458},[33,1271,1272],{},"# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n",[33,1274,1275],{"class":35,"line":464},[33,1276,1277],{},"# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n",[33,1279,1280],{"class":35,"line":470},[33,1281,1282],{},"# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n",[33,1284,1285],{"class":35,"line":476},[33,1286,1287],{},"# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n",[33,1289,1290],{"class":35,"line":482},[33,1291,1292],{},"# 所以 flag[i] = enc[i] ^ key[i%3]。\n",[33,1294,1296],{"class":35,"line":1295},11,[33,1297,1298],{},"print(flag.decode())\n",[33,1300,1302],{"class":35,"line":1301},12,[33,1303,1304],{},"# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[16,1306,298],{},[23,1308,1311],{"className":1309,"code":1310,"language":175,"meta":28},[173],"ctf{reverse_engineering}\n",[30,1312,1310],{"__ignoreMap":28},[147,1314,1316],{"id":1315},"第四步验证","第四步：验证",[23,1318,1320],{"className":25,"code":1319,"language":27,"meta":28,"style":28},"./crackme ctf{reverse_engineering}\n",[30,1321,1322],{"__ignoreMap":28},[33,1323,1324,1327],{"class":35,"line":36},[33,1325,1326],{"class":39},"./crackme",[33,1328,1329],{"class":43}," ctf{reverse_engineering}\n",[16,1331,298,1332,301],{},[30,1333,267],{},[11,1335,1337],{"id":1336},"八小结","八、小结",[16,1339,1340,1341,1125,1343,1345,1346,1348],{},"逆向入门三步：",[30,1342,163],{},[30,1344,209],{}," 侦查 → ",[30,1347,365],{},"/Ghidra 定位校验函数 → GDB 验证。大部分入门题要么字符串直接可见，要么只是一个异或，逆运算一行脚本即可解决。",[1350,1351,1352],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .sqerP, html code.shiki .sqerP{--shiki-default:#BD93F9;--shiki-default-font-style:italic}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}",{"title":28,"searchDepth":47,"depth":47,"links":1354},[1355,1356,1357,1358,1359,1360,1361,1362],{"id":13,"depth":70,"text":14},{"id":89,"depth":70,"text":90},{"id":131,"depth":70,"text":132},{"id":326,"depth":70,"text":327},{"id":846,"depth":70,"text":847},{"id":1037,"depth":70,"text":1038},{"id":1132,"depth":70,"text":1133},{"id":1336,"depth":70,"text":1337},"完整演示逆向三板斧：file/strings 侦查、objdump 反汇编定位校验函数、GDB 动态调试，并举例一个CrackMe。","md",{"date":1366,"image":1367,"alt":1368,"tags":1369,"published":1373},"14th Aug 2026","/blogs-img/blog1.png","CTF 逆向入门",[1370,1371,1372],"ctf","reverse","example",true,"/blogs/ctf-reverse-overview",{"title":6,"description":1363},"blogs/10. ctf-reverse-overview","-PAtS7ArHlkJGQ0r9XrzjT8-num-kddi5E7M-GpdxHI",{"id":1379,"title":1380,"body":1381,"description":2358,"extension":1364,"meta":2359,"navigation":1373,"ogImage":2360,"path":2363,"seo":2364,"stem":2365,"__hash__":2366},"content/blogs/16. ctf-reverse-crackme-deep-dive.md","逆向一个 XOR CrackMe：从侦查到脚本还原",{"type":8,"value":1382,"toc":2347},[1383,1387,1390,1394,1400,1613,1616,1635,1638,1647,1652,1664,1668,1670,1675,1688,1692,1703,1706,1712,1717,1721,1732,1746,1748,1754,1763,1767,1772,1787,1789,1820,1822,1851,1858,1862,1894,1897,1922,1926,1929,1979,1984,1986,1994,1997,2003,2005,2017,2031,2033,2038,2047,2049,2054,2057,2072,2074,2080,2083,2087,2092,2109,2169,2172,2185,2188,2193,2196,2206,2210,2214,2298,2302,2333,2337,2344],[11,1384,1386],{"id":1385},"一现实背景注册码校验","一、现实背景：注册码校验",[16,1388,1389],{},"商业软件用\"输入正确序列号才放行\"的校验函数保护授权，逆向需要从二进制还原该校验逻辑。CrackMe 是这类校验的简化版，也是逆向入门最好的练习材料。",[11,1391,1393],{"id":1392},"二题目源码与编译","二、题目源码与编译",[16,1395,1396,1397,561],{},"出题人视角的 ",[30,1398,1399],{},"crackme.c",[23,1401,1403],{"className":816,"code":1402,"language":818,"meta":28,"style":28},"#include \u003Cstdio.h>\n#include \u003Cstring.h>\n\nint check(const char *input) {\n    char key[] = \"k3y\";\n    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n    unsigned char enc[] = {\n        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n    };\n    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n    int n = strlen(input);\n    // strlen：求输入长度（不含结尾 \\0）。\n    if (n != 24) return 0;\n    // 长度必须是 24，先卡掉大部分错误输入。\n    for (int i = 0; i \u003C n; i++)\n        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n    return 1;\n    // 全部通过才返回 1，main 据此打印 Correct!。\n}\n\nint main(int argc, char **argv) {\n    if (argc != 2) {\n        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n        puts(\"Usage: ./crackme \u003Cflag>\");\n        return 1;\n    }\n    if (check(argv[1]))\n        // argv[1]：命令行第二个参数，即用户输入的 flag。\n        puts(\"Correct!\");\n    else\n        puts(\"Wrong!\");\n    return 0;\n}\n",[30,1404,1405,1410,1415,1420,1425,1430,1435,1440,1445,1450,1455,1460,1465,1471,1477,1483,1489,1495,1501,1507,1513,1519,1525,1531,1536,1542,1548,1554,1560,1566,1572,1578,1584,1590,1596,1602,1608],{"__ignoreMap":28},[33,1406,1407],{"class":35,"line":36},[33,1408,1409],{},"#include \u003Cstdio.h>\n",[33,1411,1412],{"class":35,"line":47},[33,1413,1414],{},"#include \u003Cstring.h>\n",[33,1416,1417],{"class":35,"line":70},[33,1418,1419],{"emptyLinePlaceholder":1373},"\n",[33,1421,1422],{"class":35,"line":80},[33,1423,1424],{},"int check(const char *input) {\n",[33,1426,1427],{"class":35,"line":452},[33,1428,1429],{},"    char key[] = \"k3y\";\n",[33,1431,1432],{"class":35,"line":458},[33,1433,1434],{},"    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n",[33,1436,1437],{"class":35,"line":464},[33,1438,1439],{},"    unsigned char enc[] = {\n",[33,1441,1442],{"class":35,"line":470},[33,1443,1444],{},"        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[33,1446,1447],{"class":35,"line":476},[33,1448,1449],{},"        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[33,1451,1452],{"class":35,"line":482},[33,1453,1454],{},"        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n",[33,1456,1457],{"class":35,"line":1295},[33,1458,1459],{},"    };\n",[33,1461,1462],{"class":35,"line":1301},[33,1463,1464],{},"    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n",[33,1466,1468],{"class":35,"line":1467},13,[33,1469,1470],{},"    int n = strlen(input);\n",[33,1472,1474],{"class":35,"line":1473},14,[33,1475,1476],{},"    // strlen：求输入长度（不含结尾 \\0）。\n",[33,1478,1480],{"class":35,"line":1479},15,[33,1481,1482],{},"    if (n != 24) return 0;\n",[33,1484,1486],{"class":35,"line":1485},16,[33,1487,1488],{},"    // 长度必须是 24，先卡掉大部分错误输入。\n",[33,1490,1492],{"class":35,"line":1491},17,[33,1493,1494],{},"    for (int i = 0; i \u003C n; i++)\n",[33,1496,1498],{"class":35,"line":1497},18,[33,1499,1500],{},"        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[33,1502,1504],{"class":35,"line":1503},19,[33,1505,1506],{},"        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n",[33,1508,1510],{"class":35,"line":1509},20,[33,1511,1512],{},"        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n",[33,1514,1516],{"class":35,"line":1515},21,[33,1517,1518],{},"    return 1;\n",[33,1520,1522],{"class":35,"line":1521},22,[33,1523,1524],{},"    // 全部通过才返回 1，main 据此打印 Correct!。\n",[33,1526,1528],{"class":35,"line":1527},23,[33,1529,1530],{},"}\n",[33,1532,1534],{"class":35,"line":1533},24,[33,1535,1419],{"emptyLinePlaceholder":1373},[33,1537,1539],{"class":35,"line":1538},25,[33,1540,1541],{},"int main(int argc, char **argv) {\n",[33,1543,1545],{"class":35,"line":1544},26,[33,1546,1547],{},"    if (argc != 2) {\n",[33,1549,1551],{"class":35,"line":1550},27,[33,1552,1553],{},"        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n",[33,1555,1557],{"class":35,"line":1556},28,[33,1558,1559],{},"        puts(\"Usage: ./crackme \u003Cflag>\");\n",[33,1561,1563],{"class":35,"line":1562},29,[33,1564,1565],{},"        return 1;\n",[33,1567,1569],{"class":35,"line":1568},30,[33,1570,1571],{},"    }\n",[33,1573,1575],{"class":35,"line":1574},31,[33,1576,1577],{},"    if (check(argv[1]))\n",[33,1579,1581],{"class":35,"line":1580},32,[33,1582,1583],{},"        // argv[1]：命令行第二个参数，即用户输入的 flag。\n",[33,1585,1587],{"class":35,"line":1586},33,[33,1588,1589],{},"        puts(\"Correct!\");\n",[33,1591,1593],{"class":35,"line":1592},34,[33,1594,1595],{},"    else\n",[33,1597,1599],{"class":35,"line":1598},35,[33,1600,1601],{},"        puts(\"Wrong!\");\n",[33,1603,1605],{"class":35,"line":1604},36,[33,1606,1607],{},"    return 0;\n",[33,1609,1611],{"class":35,"line":1610},37,[33,1612,1530],{},[16,1614,1615],{},"编译（解题时只有二进制，没有源码）：",[23,1617,1619],{"className":25,"code":1618,"language":27,"meta":28,"style":28},"gcc -o crackme crackme.c\n",[30,1620,1621],{"__ignoreMap":28},[33,1622,1623,1626,1629,1632],{"class":35,"line":36},[33,1624,1625],{"class":39},"gcc",[33,1627,1628],{"class":241}," -o",[33,1630,1631],{"class":43}," crackme",[33,1633,1634],{"class":43}," crackme.c\n",[16,1636,1637],{},"运行确认行为：",[23,1639,1641],{"className":25,"code":1640,"language":27,"meta":28,"style":28},"./crackme\n",[30,1642,1643],{"__ignoreMap":28},[33,1644,1645],{"class":35,"line":36},[33,1646,1640],{"class":39},[16,1648,298,1649,301],{},[30,1650,1651],{},"Usage: ./crackme \u003Cflag>",[23,1653,1655],{"className":25,"code":1654,"language":27,"meta":28,"style":28},"./crackme wrongflag123456789012\n",[30,1656,1657],{"__ignoreMap":28},[33,1658,1659,1661],{"class":35,"line":36},[33,1660,1326],{"class":39},[33,1662,1663],{"class":43}," wrongflag123456789012\n",[16,1665,298,1666,301],{},[30,1667,270],{},[11,1669,132],{"id":131},[16,1671,1672,1674],{},[136,1673,138],{},"：先确认程序类型、收集可见字符串，缩小分析范围。",[16,1676,1677,561,1679,1681,1682,1684,1685,1687],{},[136,1678,144],{},[30,1680,209],{}," 能看到 ",[30,1683,274],{}," 这样的短密钥（需要 ",[30,1686,278],{},"），但看不到二进制密文数组。若直接看到 flag 明文就是送分题；看不到就继续反汇编。",[147,1689,1691],{"id":1690},"_31-file","3.1 file",[23,1693,1695],{"className":25,"code":1694,"language":27,"meta":28,"style":28},"file crackme\n",[30,1696,1697],{"__ignoreMap":28},[33,1698,1699,1701],{"class":35,"line":36},[33,1700,163],{"class":39},[33,1702,1178],{"class":43},[16,1704,1705],{},"预期输出（Linux ELF）：",[23,1707,1710],{"className":1708,"code":1709,"language":175,"meta":28},[173],"crackme: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ... not stripped\n",[30,1711,1709],{"__ignoreMap":28},[16,1713,1714,1716],{},[30,1715,183],{},"：函数名可见。",[147,1718,1720],{"id":1719},"_32-strings","3.2 strings",[16,1722,1723,1724,1726,1727,1729,1730,561],{},"注意：",[30,1725,209],{}," 默认只显示长度 ≥ 4 的字符串，密钥 ",[30,1728,274],{}," 只有 3 个字符，必须加 ",[30,1731,278],{},[23,1733,1734],{"className":25,"code":1165,"language":27,"meta":28,"style":28},[30,1735,1736],{"__ignoreMap":28},[33,1737,1738,1740,1742,1744],{"class":35,"line":36},[33,1739,209],{"class":39},[33,1741,242],{"class":241},[33,1743,245],{"class":241},[33,1745,1178],{"class":43},[16,1747,169],{},[23,1749,1752],{"className":1750,"code":1751,"language":175,"meta":28},[173],"Usage: ./crackme \u003Cflag>\nCorrect!\nWrong!\nk3y\n",[30,1753,1751],{"__ignoreMap":28},[16,1755,1756,1758,1759,1762],{},[30,1757,274],{}," 极可能是密钥。",[30,1760,1761],{},"enc"," 数组是二进制数据，strings 显示不出来。",[11,1764,1766],{"id":1765},"四核心步骤-2定位校验函数","四、核心步骤 2：定位校验函数",[16,1768,1769,1771],{},[136,1770,138],{},"：找到\"比较\"发生的代码，确认密钥如何参与运算。",[16,1773,1774,1776,1777,1779,1780,1783,1784,1786],{},[136,1775,144],{},"：main 里 ",[30,1778,525],{}," 后紧跟 ",[30,1781,1782],{},"test eax, eax; je","，说明返回值决定对错。于是把目光集中到 check；Ghidra 伪代码把汇编还原成 ",[30,1785,1201],{},"，逆向思路立刻清晰。",[147,1788,353],{"id":352},[23,1790,1792],{"className":25,"code":1791,"language":27,"meta":28,"style":28},"objdump -d -M intel crackme | grep -A 40 '\u003Cmain>'\n",[30,1793,1794],{"__ignoreMap":28},[33,1795,1796,1798,1800,1802,1804,1806,1808,1810,1812,1814,1816,1818],{"class":35,"line":36},[33,1797,365],{"class":39},[33,1799,368],{"class":241},[33,1801,371],{"class":241},[33,1803,374],{"class":43},[33,1805,1631],{"class":43},[33,1807,400],{"class":56},[33,1809,403],{"class":39},[33,1811,406],{"class":241},[33,1813,409],{"class":241},[33,1815,413],{"class":412},[33,1817,416],{"class":43},[33,1819,419],{"class":412},[16,1821,422],{},[23,1823,1825],{"className":425,"code":1824,"language":427,"meta":28,"style":28},"0000000000001189 \u003Cmain>:\n    1191:  cmp    edi, 0x2\n    1194:  jne    0x11a0\n    119e:  call   0x1149 \u003Ccheck>\n    11a3:  test   eax, eax\n    11a5:  je     0x11b5\n",[30,1826,1827,1831,1835,1839,1843,1847],{"__ignoreMap":28},[33,1828,1829],{"class":35,"line":36},[33,1830,434],{},[33,1832,1833],{"class":35,"line":47},[33,1834,449],{},[33,1836,1837],{"class":35,"line":70},[33,1838,455],{},[33,1840,1841],{"class":35,"line":80},[33,1842,461],{},[33,1844,1845],{"class":35,"line":452},[33,1846,467],{},[33,1848,1849],{"class":35,"line":458},[33,1850,473],{},[16,1852,1853,1855,1856,301],{},[30,1854,525],{}," 说明判断逻辑在 ",[30,1857,554],{},[147,1859,1861],{"id":1860},"_42-objdump-看-check","4.2 objdump 看 check",[23,1863,1865],{"className":25,"code":1864,"language":27,"meta":28,"style":28},"objdump -d -M intel crackme | grep -A 60 '\u003Ccheck>'\n",[30,1866,1867],{"__ignoreMap":28},[33,1868,1869,1871,1873,1875,1877,1879,1881,1883,1885,1888,1890,1892],{"class":35,"line":36},[33,1870,365],{"class":39},[33,1872,368],{"class":241},[33,1874,371],{"class":241},[33,1876,374],{"class":43},[33,1878,1631],{"class":43},[33,1880,400],{"class":56},[33,1882,403],{"class":39},[33,1884,406],{"class":241},[33,1886,1887],{"class":241}," 60",[33,1889,413],{"class":412},[33,1891,611],{"class":43},[33,1893,419],{"class":412},[16,1895,1896],{},"预期会看到循环比较：",[23,1898,1900],{"className":425,"code":1899,"language":427,"meta":28,"style":28},"movzx  eax, BYTE PTR [rax]        ; input[i]\nxor    eax, 0x...                 ; 与 key 异或\ncmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\nje     0x...\n",[30,1901,1902,1907,1912,1917],{"__ignoreMap":28},[33,1903,1904],{"class":35,"line":36},[33,1905,1906],{},"movzx  eax, BYTE PTR [rax]        ; input[i]\n",[33,1908,1909],{"class":35,"line":47},[33,1910,1911],{},"xor    eax, 0x...                 ; 与 key 异或\n",[33,1913,1914],{"class":35,"line":70},[33,1915,1916],{},"cmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\n",[33,1918,1919],{"class":35,"line":80},[33,1920,1921],{},"je     0x...\n",[147,1923,1925],{"id":1924},"_43-ghidra-反编译","4.3 Ghidra 反编译",[16,1927,1928],{},"安装并打开：",[23,1930,1932],{"className":25,"code":1931,"language":27,"meta":28,"style":28},"apt install openjdk-21-jdk unzip\nwget https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_*.zip\nunzip ghidra_*.zip && cd ghidra_*_PUBLIC && ./ghidraRun\n",[30,1933,1934,1944,1954],{"__ignoreMap":28},[33,1935,1936,1938,1940,1942],{"class":35,"line":36},[33,1937,103],{"class":39},[33,1939,106],{"class":43},[33,1941,735],{"class":43},[33,1943,738],{"class":43},[33,1945,1946,1948,1950,1952],{"class":35,"line":47},[33,1947,743],{"class":39},[33,1949,746],{"class":43},[33,1951,750],{"class":749},[33,1953,753],{"class":43},[33,1955,1956,1958,1960,1962,1965,1967,1969,1971,1973,1975,1977],{"class":35,"line":70},[33,1957,758],{"class":39},[33,1959,761],{"class":43},[33,1961,750],{"class":749},[33,1963,1964],{"class":43},".zip",[33,1966,781],{"class":63},[33,1968,771],{"class":770},[33,1970,761],{"class":43},[33,1972,750],{"class":749},[33,1974,778],{"class":43},[33,1976,781],{"class":63},[33,1978,784],{"class":39},[16,1980,1981,1982,301],{},"操作：新建项目 → 导入 crackme → 自动分析 → 双击 ",[30,1983,554],{},[16,1985,813],{},[23,1987,1988],{"className":816,"code":1211,"language":818,"meta":28,"style":28},[30,1989,1990],{"__ignoreMap":28},[33,1991,1992],{"class":35,"line":36},[33,1993,1211],{},[16,1995,1996],{},"得到逆运算关系：",[23,1998,2001],{"className":1999,"code":2000,"language":175,"meta":28},[173],"enc[i] = input[i] ^ key[i % 3]\ninput[i] = enc[i] ^ key[i % 3]\n",[30,2002,2000],{"__ignoreMap":28},[11,2004,847],{"id":846},[23,2006,2008],{"className":25,"code":2007,"language":27,"meta":28,"style":28},"gdb ./crackme\n",[30,2009,2010],{"__ignoreMap":28},[33,2011,2012,2014],{"class":35,"line":36},[33,2013,874],{"class":39},[33,2015,2016],{"class":43}," ./crackme\n",[23,2018,2020],{"className":886,"code":2019,"language":874,"meta":28,"style":28},"(gdb) break check\n# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[30,2021,2022,2026],{"__ignoreMap":28},[33,2023,2024],{"class":35,"line":36},[33,2025,887],{},[33,2027,2028],{"class":35,"line":47},[33,2029,2030],{},"# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[16,2032,169],{},[23,2034,2036],{"className":2035,"code":899,"language":175,"meta":28},[173],[30,2037,899],{"__ignoreMap":28},[23,2039,2041],{"className":886,"code":2040,"language":874,"meta":28,"style":28},"(gdb) run AAAAAAAAAAAAAAAAAAAAAAAA\n",[30,2042,2043],{"__ignoreMap":28},[33,2044,2045],{"class":35,"line":36},[33,2046,2040],{},[16,2048,169],{},[23,2050,2052],{"className":2051,"code":920,"language":175,"meta":28},[173],[30,2053,920],{"__ignoreMap":28},[16,2055,2056],{},"查看 enc 数组（地址在反汇编中确定）：",[23,2058,2060],{"className":886,"code":2059,"language":874,"meta":28,"style":28},"(gdb) x/24bx 0x5555555551e0\n# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[30,2061,2062,2067],{"__ignoreMap":28},[33,2063,2064],{"class":35,"line":36},[33,2065,2066],{},"(gdb) x/24bx 0x5555555551e0\n",[33,2068,2069],{"class":35,"line":47},[33,2070,2071],{},"# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[16,2073,169],{},[23,2075,2078],{"className":2076,"code":2077,"language":175,"meta":28},[173],"0x...: 0x08 0x47 0x1f 0x10 0x41 0x1c 0x1d 0x56 ...\n",[30,2079,2077],{"__ignoreMap":28},[16,2081,2082],{},"与源码中的 enc 数组一致，确认比较逻辑。",[11,2084,2086],{"id":2085},"六核心步骤-4写脚本还原","六、核心步骤 4：写脚本还原",[16,2088,2089,2091],{},[136,2090,138],{},"：把\"加密\"反过来算：密文与 key 再异或一次即明文。",[16,2093,2094,2096,2097,2100,2101,2104,2105,2108],{},[136,2095,144],{},"：因为 ",[30,2098,2099],{},"enc[i] = input[i] ^ key[i%3]","，两边同时异或 ",[30,2102,2103],{},"key[i%3]"," 得 ",[30,2106,2107],{},"input[i] = enc[i] ^ key[i%3]","。异或的自反性是本题全部关键。",[23,2110,2112],{"className":1238,"code":2111,"language":1240,"meta":28,"style":28},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\nkey = b\"k3y\"   # 从 strings -n 3 找到的密钥\n\nflag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n# c ^ key[...]：密文异或密钥 = 明文。\n# bytes([...])：把整数列表转成字节串。\nprint(flag.decode())\n# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[30,2113,2114,2118,2122,2126,2131,2135,2140,2145,2150,2155,2160,2164],{"__ignoreMap":28},[33,2115,2116],{"class":35,"line":36},[33,2117,1247],{},[33,2119,2120],{"class":35,"line":47},[33,2121,1252],{},[33,2123,2124],{"class":35,"line":70},[33,2125,1257],{},[33,2127,2128],{"class":35,"line":80},[33,2129,2130],{},"key = b\"k3y\"   # 从 strings -n 3 找到的密钥\n",[33,2132,2133],{"class":35,"line":452},[33,2134,1419],{"emptyLinePlaceholder":1373},[33,2136,2137],{"class":35,"line":458},[33,2138,2139],{},"flag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n",[33,2141,2142],{"class":35,"line":464},[33,2143,2144],{},"# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n",[33,2146,2147],{"class":35,"line":470},[33,2148,2149],{},"# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n",[33,2151,2152],{"class":35,"line":476},[33,2153,2154],{},"# c ^ key[...]：密文异或密钥 = 明文。\n",[33,2156,2157],{"class":35,"line":482},[33,2158,2159],{},"# bytes([...])：把整数列表转成字节串。\n",[33,2161,2162],{"class":35,"line":1295},[33,2163,1298],{},[33,2165,2166],{"class":35,"line":1301},[33,2167,2168],{},"# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[16,2170,2171],{},"运行：",[23,2173,2175],{"className":25,"code":2174,"language":27,"meta":28,"style":28},"python3 solve.py\n",[30,2176,2177],{"__ignoreMap":28},[33,2178,2179,2182],{"class":35,"line":36},[33,2180,2181],{"class":39},"python3",[33,2183,2184],{"class":43}," solve.py\n",[16,2186,2187],{},"实际输出：",[23,2189,2191],{"className":2190,"code":1310,"language":175,"meta":28},[173],[30,2192,1310],{"__ignoreMap":28},[16,2194,2195],{},"验证：",[23,2197,2198],{"className":25,"code":1319,"language":27,"meta":28,"style":28},[30,2199,2200],{"__ignoreMap":28},[33,2201,2202,2204],{"class":35,"line":36},[33,2203,1326],{"class":39},[33,2205,1329],{"class":43},[16,2207,2187,2208,301],{},[30,2209,267],{},[11,2211,2213],{"id":2212},"七必学工具","七、必学工具",[490,2215,2216,2227],{},[493,2217,2218],{},[496,2219,2220,2222,2224],{},[499,2221,1047],{},[499,2223,1050],{},[499,2225,2226],{},"命令",[506,2228,2229,2241,2253,2268,2285],{},[496,2230,2231,2233,2236],{},[511,2232,163],{},[511,2234,2235],{},"看类型/是否 stripped",[511,2237,2238],{},[30,2239,2240],{},"file crackme",[496,2242,2243,2245,2248],{},[511,2244,209],{},[511,2246,2247],{},"提取字符串、指定最小长度",[511,2249,2250],{},[30,2251,2252],{},"strings -n 3 crackme",[496,2254,2255,2257,2260],{},[511,2256,365],{},[511,2258,2259],{},"反汇编、看数据段",[511,2261,2262,264,2265],{},[30,2263,2264],{},"objdump -d -M intel crackme",[30,2266,2267],{},"objdump -s -j .rodata crackme",[496,2269,2270,2272,2275],{},[511,2271,1105],{},[511,2273,2274],{},"断点、运行、查看内存",[511,2276,2277,264,2279,264,2282],{},[30,2278,1113],{},[30,2280,2281],{},"run xxx",[30,2283,2284],{},"x/24bx addr",[496,2286,2287,2290,2293],{},[511,2288,2289],{},"Ghidra",[511,2291,2292],{},"反编译伪代码",[511,2294,2295],{},[30,2296,2297],{},"ghidraRun",[11,2299,2301],{"id":2300},"八小-ctf-实战流程复述","八、小 CTF 实战：流程复述",[789,2303,2304,2311,2322,2328],{},[792,2305,2306,2308,2309,301],{},[30,2307,2252],{}," → 找到密钥 ",[30,2310,274],{},[792,2312,2313,2315,2316,2318,2319,301],{},[30,2314,365],{},"/Ghidra → 确认 ",[30,2317,554],{}," 里是 ",[30,2320,2321],{},"input[i] ^ key[i%3] == enc[i]",[792,2323,2324,2325,301],{},"Python 一行异或还原 → ",[30,2326,2327],{},"ctf{reverse_engineering}",[792,2329,2330,2331,301],{},"运行验证 → ",[30,2332,267],{},[11,2334,2336],{"id":2335},"九小结","九、小结",[16,2338,2339,2340,2343],{},"XOR CrackMe 的通用解法：",[136,2341,2342],{},"找密钥、找密文、再异或一次","。逆向题的套路基本都是\"侦查 → 定位校验 → 逆运算\"三步；后续遇到的壳、混淆只是在中间增加干扰。",[1350,2345,2346],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .sqerP, html code.shiki .sqerP{--shiki-default:#BD93F9;--shiki-default-font-style:italic}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}",{"title":28,"searchDepth":47,"depth":47,"links":2348},[2349,2350,2351,2352,2353,2354,2355,2356,2357],{"id":1385,"depth":70,"text":1386},{"id":1392,"depth":70,"text":1393},{"id":131,"depth":70,"text":132},{"id":1765,"depth":70,"text":1766},{"id":846,"depth":70,"text":847},{"id":2085,"depth":70,"text":2086},{"id":2212,"depth":70,"text":2213},{"id":2300,"depth":70,"text":2301},{"id":2335,"depth":70,"text":2336},"完整的 CrackMe 逆向：编译源码、file/strings 侦查、objdump/Ghidra 定位校验、GDB 动态调试、Python 逆运算还原 flag。",{"date":1366,"image":2360,"alt":2361,"tags":2362,"published":1373},"/blogs-img/blog7.png","XOR CrackMe 完整实操",[1370,1371,1139,1372],"/blogs/ctf-reverse-crackme-deep-dive",{"title":1380,"description":2358},"blogs/16. ctf-reverse-crackme-deep-dive","xZZ1X760wPJNGRPAuGfcnpCTK_DSMHcI_gsMU8yxErg",1786796568388]