本文最后更新于 375 天前,其中的信息可能已经过时,如有错误可以直接在文章下留言
虽说是新生赛的题,但是这题要手撕汇编,学到还是蛮多的。
直接 IDA 打开题目给的可执行文件.exe,很轻易的找到了主函数,如图
第一个关键信息是第一个 if 语句的判断
| if ( &Str1[strlen(Str1) + 1] - v11 == 26 && !strncmp(Str1, "ACTF{", 5u) && v12 == 125 ) |
| |
所以我们输入的 flag 形式为 ACTF {XXXXX},再看第一条判断 Str1 [strlen (Str1) + 1] – v11 == 26,v11 前面没有定义值,我们进入动态调试之后,这个 v11 就变了,如图
点进去确实存的是我们的输入,看上面 v11 的定义也变成了 char Str1 [25]。那是不是说明 flag 的长度为 25 呢?Str1 [1] 的为 C,其 Ascii 值为 99,而我们注意到 125-99 的值刚好为 26,而 Ascii 值为 125 的字符刚好就是 flag 的最后一个字符’}’。讲了这么多,好像也判断不了,好吧,其实 flag 的长度应该是 26,我们可以动态调试,发现只有长度为 26 的时候可以进入 if 判断里面执行的语句。
当我们动态调试输入 ACTF {12345678901234567890} 的时候,执行 if 代码块里面的语句时,出现了以下报错,
报错的原因是这里的 strtok 函数,这里运用三个这个函数,我们去搜索一下该函数的用途,发现 strtok 函数是 C 语言中用于分割字符串的函数,它可以将一个字符串按照指定的分隔符进行分割。它要求输入两个参数,第一个参数是要被分割的字符串,第二个参数是分隔符字符串,用于指定分割的分隔符,题目这里就是’_’,返回分割后的第一个子字符串的指针。如果无法继续分割,则返回 NULL。
但是我在 Visual Studio 2022 使用不了这个函数,只能用 strtok_s,和 scanf 一样的毛病,我也不知道为啥,strtok_s 需要传入三个参数,前两个和 strtok 一样,第三个参数具体是干啥的,说是指向一个字符指针的指针,用于保存当前分割位置的上下文信息,它的用法是第一次调用时,传入要分割的字符串 str
和分隔符 delim
,同时将 context
设置为 NULL,之后每次调用 strtok_s 函数,传入 NULL 作为第一个参数,并将 context
设置为上一次调用返回的子字符串指针。用了一下,如图
很多 Writeup 都是直接推测将 flag 用分隔符分成了三个部分,每个部分的长度为 6,但是怎么看出来的?靠猜吗?有的 Writeup 说根据 + 5 和 + 9 差 4 个,再加上 + 9 强转为 WORD,得到每组字符是 4+2=6 个,DWORD 是 4 个字节,WORD 是 2 个字节,加起来刚好 6 个字节,是这个意思吗?我也不太清楚。
然后看第二个 if 语句判断有个 unk_4051D8,后面有个括号传入了参数 v17,大概是 flag 的第一部分,这应该是一个函数,猜测是经过了 SMC 处理,不能直接反汇编成伪代码。听说题目的标题也暗示了是 SMC,我们直接动态调试到函数快结束处打断点,然后对 unk_4051D8 处的数据 U+C+P,然后反编译成伪代码,成功得到了原函数,如图。
脚本代码如下
| a="3@1b;b" |
| b="elcome " |
| flag='' |
| for i in range(len(a)): |
| flag+=chr((ord(a[i])^ord(b[i]))+35) |
| |
| print(flag) |
| |
然后再看后面
后面又有个 for 循环,数字很大,估计又是一个 SMC,和 v9 异或,而这个 v9 明显来源于第一部分的 flag。
在 String Windows 里面找到了一个长度为 6 的字符 5mcsM<。
再跟踪,来到汇编处
这里是直接 strncmp,简单的比较,说明这个是 flag 的一部分,而且我们注意到这里有个 Congratulations,应该是另外一个函数,但是反汇编还是回到主函数,不知道是怎么回事,怎么有个 You win,猜测这个应该是 flag 的最后一部分。
最后只要找到 flag 的第二部分,第二部分的 flag 有点复炸,最好是用 OD 来跑程序,要手撕汇编。
我们注意到主函数中有一段内联汇编 jmp eax,我们直接用 OllyDbg 打开,然后右键,查找命令 jmp eax,来到此处汇编,打上断点,使程序运行
输入 ACTF {yOu0y*_abcdef_5mcsM<} 来分析,F7 步入
汇编分析,注意标红的汇编代码
| 001D511A 33FF xor edi,edi |
| |
| 001D511C 51 push ecx |
| 001D511D 53 push ebx |
| 001D511E 83FF 06 cmp edi,0x6 |
| |
| 001D5121 7D 20 jge short Splendid.001D5143 |
| 001D5123 33C9 xor ecx,ecx |
| 001D5125 8A0C3E mov cl,byte ptr ds:[esi+edi] |
| |
| 001D5128 EB 0C jmp short Splendid.001D5136 |
| 001D512A 123456 adc dh,byte ptr ds:[esi+edx*2] |
| 001D512D ^ 78 9A js short Splendid.001D50C9 |
| 001D512F 05 61626364 add eax,0x64636261 |
| 001D5134 65:66:33db xor bx,bx |
| 001D5138 8A5C38 12 mov bl,byte ptr ds:[eax+edi+0x12] |
| 001D513C 884C38 18 mov byte ptr ds:[eax+edi+0x18],cl |
| |
| 001D5140 47 inc edi |
| |
所以这一部分汇编应该就是将 flag 的第二部分放入内存当中。后面又有一段相同作用的汇编指令,如图
不管它,往下走,进入最关键的一段加密指令.
指令如下
| 001D5156 33FF xor edi,edi |
| 001D5158 83FF 06 cmp edi,0x6 |
| 001D515B 7D 3B jge short Splendid.001D5198 |
| 001D515D 33C9 xor ecx,ecx |
| 001D515F 8A0C3E mov cl,byte ptr ds:[esi+edi] |
| |
| 001D5162 80E1 FF and cl,0xFF |
| |
| 001D5165 2D 00010000 sub eax,0x100 |
| 001D516A 33DB xor ebx,ebx |
| |
| 001D516C 8AD9 mov bl,cl |
| |
| 001D516E 8BCF mov ecx,edi |
| |
| 001D5170 81C1 83000000 add ecx,0x83 |
| |
| 001D5176 33D9 xor ebx,ecx |
| |
| 001D5178 8A1C18 mov bl,byte ptr ds:[eax+ebx] |
| |
| 001D517B EB 08 jmp short Splendid.001D5185 |
我跟着最后的跳转指令。
这里将某内存地址的值取出来放到 cl 里面,然后让 bl 和 cl 对比,这里应该就是最后的 flag 对比,bl 是我们要的 flag 的第二部分加密后的值,然后不对的话,就会跳出程序了。
我们进入这里的内存地址,取出 6 位的数据,
我们还需要的就是 bl 里面的值,上面分析指令说了,赋给 bl 的是从一个数组里面的值,flag 的第二部分处理后作为索引,而这个数组其实就在 eax 寄存器当中。
这里有个 sub eax,0x100 的指令其实就暗示了,这个数组在 eax 里面,后面对 bl 的赋值也是 [eax+ebx],并且长度为 0x100,我们取出里面的值。
整理一下思路,解题脚本如下
| data=[ 0xF6, 0xA3, 0x5B, 0x9D, 0xE0, 0x95, 0x98, 0x68, 0x8C, 0x65, 0xBB, 0x76, 0x89, 0xD4, 0x09, 0xFD, |
| 0xF3, 0x5C, 0x3C, 0x4C, 0x36, 0x8E, 0x4D, 0xC4, 0x80, 0x44, 0xD6, 0xA9, 0x01, 0x32, 0x77, 0x29, |
| 0x90, 0xBC, 0xC0, 0xA8, 0xD8, 0xF9, 0xE1, 0x1D, 0xE4, 0x67, 0x7D, 0x2A, 0x2C, 0x59, 0x9E, 0x3D, |
| 0x7A, 0x34, 0x11, 0x43, 0x74, 0xD1, 0x62, 0x60, 0x02, 0x4B, 0xAE, 0x99, 0x57, 0xC6, 0x73, 0xB0, |
| 0x33, 0x18, 0x2B, 0xFE, 0xB9, 0x85, 0xB6, 0xD9, 0xDE, 0x7B, 0xCF, 0x4F, 0xB3, 0xD5, 0x08, 0x7C, |
| 0x0A, 0x71, 0x12, 0x06, 0x37, 0xFF, 0x7F, 0xB7, 0x46, 0x42, 0x25, 0xC9, 0xD0, 0x50, 0x52, 0xCE, |
| 0xBD, 0x6C, 0xE5, 0x6F, 0xA5, 0x15, 0xED, 0x64, 0xF0, 0x23, 0x35, 0xE7, 0x0C, 0x61, 0xA4, 0xD7, |
| 0x51, 0x75, 0x9A, 0xF2, 0x1E, 0xEB, 0x58, 0xF1, 0x94, 0xC3, 0x2F, 0x56, 0xF7, 0xE6, 0x86, 0x47, |
| 0xFB, 0x83, 0x5E, 0xCC, 0x21, 0x4A, 0x24, 0x07, 0x1C, 0x8A, 0x5A, 0x17, 0x1B, 0xDA, 0xEC, 0x38, |
| 0x0E, 0x7E, 0xB4, 0x48, 0x88, 0xF4, 0xB8, 0x27, 0x91, 0x00, 0x13, 0x97, 0xBE, 0x53, 0xC2, 0xE8, |
| 0xEA, 0x1A, 0xE9, 0x2D, 0x14, 0x0B, 0xBF, 0xB5, 0x40, 0x79, 0xD2, 0x3E, 0x19, 0x5D, 0xF8, 0x69, |
| 0x39, 0x5F, 0xDB, 0xFA, 0xB2, 0x8B, 0x6E, 0xA2, 0xDF, 0x16, 0xE2, 0x63, 0xB1, 0x20, 0xCB, 0xBA, |
| 0xEE, 0x8D, 0xAA, 0xC8, 0xC7, 0xC5, 0x05, 0x66, 0x6D, 0x3A, 0x45, 0x72, 0x0D, 0xCA, 0x84, 0x4E, |
| 0xF5, 0x31, 0x6B, 0x92, 0xDC, 0xDD, 0x9C, 0x3F, 0x55, 0x96, 0xA1, 0x9F, 0xCD, 0x9B, 0xE3, 0xA0, |
| 0xA7, 0xFC, 0xC1, 0x78, 0x10, 0x2E, 0x82, 0x8F, 0x30, 0x54, 0x04, 0xAC, 0x41, 0x93, 0xD3, 0x3B, |
| 0xEF, 0x03, 0x81, 0x70, 0xA6, 0x1F, 0x22, 0x26, 0x28, 0x6A, 0xAB, 0x87, 0xAD, 0x49, 0x0F, 0xAF] |
| part2=[0x30, 4, 4, 3, 0x30, 0x63] |
| for i in range(0,6): |
| for j in range(len(data)): |
| if part2[i]==data[j]: |
| part2[i]=j^(i+0x83) |
| result = ''.join(chr(h) for h in part2) |
| print(result) |
| |
所以 flag 就是 ACTF {yOu0y*_knowo3_5mcsM<}
这题学到蛮多的,对汇编又有了更深的理解。
后面一大部分内容都是跟着这个视频做的
【BUUCTF】ACTF 新生赛 2020-Splendid_MineCraft 手把手带你跟汇编!_哔哩哔哩_bilibili
偶然发现的一个宝藏 up 主,但是更新频率不高,看他的博客,应该是一个大佬。