虽说是新生赛的题,但是这题要手撕汇编,学到还是蛮多的。
直接IDA打开题目给的可执行文件.exe,很轻易的找到了主函数,如图
第一个关键信息是第一个if语句的判断
if ( &Str1[strlen(Str1) + 1] - v11 == 26 && !strncmp(Str1, "ACTF{", 5u) && v12 == 125 )
#Ascii值为125的字符为125,即v12=="}"
所以我们输入的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)
#这里必须要多加个括号确保先进行异或运算,不然ord(b[i])会先加35,再进行异或
print(flag)
#输出结果为yOu0y*
然后再看后面
后面又有个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
#xor edi,edi可以清空edi的数据,这个我也是刚知道,手撕汇编太少了,没有这样的感知,清空edi是为了在for循环当中计数。
001D511C 51 push ecx
001D511D 53 push ebx
001D511E 83FF 06 cmp edi,0x6
#for循环开始的标志,用cmp,操作数一般是循环的次数,这里进行了6次循环,应该flag的第二部分刚好长度为6.但这里不知道为什么识别成0x6
001D5121 7D 20 jge short Splendid.001D5143
001D5123 33C9 xor ecx,ecx
001D5125 8A0C3E mov cl,byte ptr ds:[esi+edi]
#这里跟踪右边寄存器发现,每次循环都往ecx寄存器里面存入一个我们flag第二部分的输入
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
#每执行一次循环,edi的值会自增1,直到6就跳出循环
所以这一部分汇编应该就是将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]
#跑到这OD的中间窗口会显示该地址处的内容,是0x61,也就是a,是我们第二部分的输入
001D5162 80E1 FF and cl,0xFF
#这里的运算不影响cl的值,ecx存着我们的输入的第二部分
001D5165 2D 00010000 sub eax,0x100
001D516A 33DB xor ebx,ebx
#清空ebx寄存器的值
001D516C 8AD9 mov bl,cl
#将我们的输入放入ebx寄存器
001D516E 8BCF mov ecx,edi
#edi的值是循环执行的次数,也就是我们常写代码的i
001D5170 81C1 83000000 add ecx,0x83
#ecx的值为0x83+i,
001D5176 33D9 xor ebx,ecx
#进行input2[i]^(0x83+i)
001D5178 8A1C18 mov bl,byte ptr ds:[eax+ebx]
#让上面的结果作为一个数组的下标,赋值该数组对于下标的值给ebx寄存器,类似于bl=data[input2[i]^(0x83+i)]
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)
#输出knowo3
所以flag就是ACTF{yOu0y*_knowo3_5mcsM<}
这题学到蛮多的,对汇编又有了更深的理解。
后面一大部分内容都是跟着这个视频做的
【BUUCTF】ACTF新生赛2020-Splendid_MineCraft 手把手带你跟汇编!_哔哩哔哩_bilibili
偶然发现的一个宝藏up主,但是更新频率不高,看他的博客,应该是一个大佬。