请注意,本文编写于 1591 天前,最后修改于 1589 天前,其中某些信息可能已经过时。
首先庆祝一下,自己终于在XCTF比赛中,拿到了自己的第一个题目的分。有一说一这个题目除了样子比较新奇之外,其余的大多都是基本功,没错一个二进制选手的基本功(包括汇编,调试,数学等)。整道题目从8.1下午4点开始一直到8.2晚上9点半,除去吃饭睡觉,历时将近24个小时。并且由于大多数的逻辑,内存我都是采用手操的方式,导致速度下降(脚本编程能力需要提高)。所以本篇博客将分为四段,阐述我自己的做题思路。
前排警告:本博客只是记录做题过程,为各位师傅提供一个笨比的解法。
文章可能会涉及:
首先我们拿到题目,好家伙,这是个啥玩意。一堆让人无法理解的东西,点开txt,好像是一个计算机的配置,具体的细节也是完全看不懂的。看看题目的提示,题目的名字一点用都没有,就告诉你这是个软件。但是后面的./bochs -q就很关键,这很像是一个命令行。首先我们搜索一下bochs,发现这好像是个开源的虚拟机。这是大概就明白之前的的那堆文件是干啥的了,应该是一个虚拟机的配置文件,里面规定了内存等一系列的东西。
bochsrc.txt
在下载bochs的时候,一个逆向手一定会干的一件事情就是将他放到IDA里面看看。哦哟,我的老天爷,连七八糟的,就是一个大的虚拟机,函数乱七八糟的,并且伴随着大量没法反汇编的数据,这要是分析起来肯定要命(后面证明IDA确实没咋用到)。还有一个有意思的事情就是,我在逛bochs贴吧的时候,发现这个软件好像可以装在手机上,在手机上模拟PC环境。
在下载完成后我们进入,下载的根目录。我装的windows版的,因为我觉得在虚拟机里装虚拟机我可能会被卡死。
我们主要用的是用dbg文件对题目所给出的虚拟机进行调试。如何打开虚拟机?
bochsdbg.exe -f bochsrc.txt
这里不得不说,出题人并没有为难我们,配置文件的路径都是事先写好的,并且将需要用的环境(包括键盘等一些列资源)都统一的放在了.src下,直接使用虚拟机加载从给出的配置文件,就可以启动程序。不要看就是一行代码,我可是研究了好久,试了很多种情况的。还自己从头学着用bochs搭建了一个虚拟机,整个体验就是难用,VM真香。
PS:其实仔细看所给出的文件,在.src中,除了开辟的存储空间之外的的文件,你下载的bochs里面都有一模一样的文件只不过是放在不同的地方的。有点小贴心。
cmd输入指令,运行。开始界面直接点start就行了(程序看起来有点像微机接口实验的感觉)。这理就要稍微说一下bochsdbg的使用方法了。详情参见这里
这里主要说明几个我用的比较多的指令:
果不指定参数则反汇编当前EIP指向的代码。
整个调试程序与GDB很像(PWN的工具),我主要只用到了这些操作,至于为什仫不显示内存的原因是因为我微机学的太差啦,完全搞不清楚内存在哪里,按照调试打印出来的内存总是对不上。所以我最后采取了手动记录内存的方式。
然后再说一下整个调试器的构成:这也是摸了好久才摸出来的
好了现在我已经完全掌握调试了(压根不懂),我们要开始对程序开始分析了。首先回忆一下,OD里面我们怎么调试程序的,那一定是菜鸡三连:打开中文搜索,找到关键字符,下断运行。好的在这里面,我们啥也干不了,没有搜索,看不见字符。那我们就用最原始的方法找代码吧,启动程序next到底。碰见程序跑飞,记录跑飞位置,再次下断点,使用命令s步入。根据经验(运气),在输入前的循环大多数都是程序机器的初始化,和一些垃圾循环。想办法找到跳出点,断点跳出。不要怕断点打错或者其他什么问题。因为程序不会坏,可以再次加载。经过努力我们成功的。。。。。成功的执行到了硬件代码,为啥是硬件代码呢。来大佬们看这个。
首先感谢鼎哥的调试素材。你看这个代码,想不想咱们微机原理接口键盘实验的输入。从一个端口输入,每次检测是否有输入,然后判断是否跳出。那然后咋办,按照我们的断点一步步退回去。终于在0x6114处找到了我们想要的东西。
哈?你问我为啥代码这么整齐,那一定是用了指令u返汇编的代码,然后粘贴到了txt中,方便下次查看。好此我们已经找到了核心判断了,向上翻翻,判断函数,基本断定这里应该是主代码段了。将这段代码扣下来,就是我们需要分析的代码了。
00006000: ( ): jmp .+0 ; eb00
00006002: ( ): xor ecx, ecx ; 31c9
00006004: ( ): cmp ecx, 0x00000081 ; 81f981000000
0000600a: ( ): jz .+226 ; 0f84e2000000
00006010: ( ): xor esi, esi ; 31f6
00006012: ( ): cmp esi, 0x00000009 ; 83fe09
00006015: ( ): jz .+209 ; 0f84d1000000
0000601b: ( ): xor edx, edx ; 31d2
0000601d: ( ): mov eax, esi ; 89f0
0000601f: ( ): inc eax ; 40
00006020: ( ): mov ebx, 0x00000009 ; bb09000000
00006025: ( ): div eax, ebx ; f7f3
00006027: ( ): mov edi, edx ; 89d7
00006029: ( ): xor edx, edx ; 31d2
0000602b: ( ): mov eax, ecx ; 89c8
0000602d: ( ): mov ebx, 0x00000003 ; bb03000000
00006032: ( ): div eax, ebx ; f7f3
00006034: ( ): jz .+5 ; 7405
00006036: ( ): jnz .+3 ; 7503
00006038: ( ): ret 0x0099 ; c29900
0000603b: ( ): cmp edx, 0x00000000 ; 83fa00
0000603e: ( ): jz .+7 ; 7407
00006040: ( ): cmp edx, 0x00000001 ; 83fa01
00006043: ( ): jz .+56 ; 7438
00006045: ( ): jnz .+108 ; 756c
00006047: ( ): shl esi, 0x02 ; c1e602
0000604a: ( ): mov eax, dword ptr ds:[esi] ; 3e8b06
0000604d: ( ): shl edi, 0x02 ; c1e702
00006050: ( ): mov ebx, dword ptr ds:[edi] ; 3e8b1f
00006053: ( ): mov edx, eax ; 89c2
00006055: ( ): mov edi, ebx ; 89df
00006057: ( ): or eax, ebx ; 09d8
00006059: ( ): not edx ; f7d2
0000605b: ( ): not edi ; f7d7
0000605d: ( ): or edx, edi ; 09fa
0000605f: ( ): and eax, edx ; 21d0
00006061: ( ): mov edx, eax ; 89c2
00006063: ( ): mov ebx, 0x24114514 ; bb14451124
00006068: ( ): mov edi, ebx ; 89df
0000606a: ( ): not eax ; f7d0
0000606c: ( ): not ebx ; f7d3
0000606e: ( ): and eax, ebx ; 21d8
00006070: ( ): not eax ; f7d0
00006072: ( ): and edx, edi ; 21fa
00006074: ( ): not edx ; f7d2
00006076: ( ): and eax, edx ; 21d0
00006078: ( ): mov dword ptr ds:[esi], eax ; 3e8906
0000607b: ( ): jmp .+102 ; eb66
0000607d: ( ): shl esi, 0x02 ; c1e602
00006080: ( ): mov eax, dword ptr ds:[esi] ; 3e8b06
00006083: ( ): shl edi, 0x02 ; c1e702
00006086: ( ): mov ebx, dword ptr ds:[edi] ; 3e8b1f
00006089: ( ): mov edx, eax ; 89c2
0000608b: ( ): mov edi, ebx ; 89df
0000608d: ( ): not eax ; f7d0
0000608f: ( ): not ebx ; f7d3
00006091: ( ): and eax, ebx ; 21d8
00006093: ( ): not eax ; f7d0
00006095: ( ): and edx, edi ; 21fa
00006097: ( ): not edx ; f7d2
00006099: ( ): and eax, edx ; 21d0
0000609b: ( ): mov edx, eax ; 89c2
0000609d: ( ): mov ebx, 0x01919810 ; bb10989101
000060a2: ( ): mov edi, ebx ; 89df
000060a4: ( ): not ebx ; f7d3
000060a6: ( ): and eax, ebx ; 21d8
000060a8: ( ): not edx ; f7d2
000060aa: ( ): and edx, edi ; 21fa
000060ac: ( ): or eax, edx ; 09d0
000060ae: ( ): mov dword ptr ds:[esi], eax ; 3e8906
000060b1: ( ): jmp .+48 ; eb30
000060b3: ( ): shl esi, 0x02 ; c1e602
000060b6: ( ): mov eax, dword ptr ds:[esi] ; 3e8b06
000060b9: ( ): shl edi, 0x02 ; c1e702
000060bc: ( ): mov ebx, dword ptr ds:[edi] ; 3e8b1f
000060bf: ( ): mov edx, eax ; 89c2
000060c1: ( ): mov edi, ebx ; 89df
000060c3: ( ): not ebx ; f7d3
000060c5: ( ): and eax, ebx ; 21d8
000060c7: ( ): not edx ; f7d2
000060c9: ( ): and edx, edi ; 21fa
000060cb: ( ): or eax, edx ; 09d0
000060cd: ( ): mov edx, eax ; 89c2
000060cf: ( ): mov ebx, 0x19260817 ; bb17082619
000060d4: ( ): mov edi, ebx ; 89df
000060d6: ( ): or eax, ebx ; 09d8
000060d8: ( ): not edx ; f7d2
000060da: ( ): not edi ; f7d7
000060dc: ( ): or edx, edi ; 09fa
000060de: ( ): and eax, edx ; 21d0
000060e0: ( ): mov dword ptr ds:[esi], eax ; 3e8906
000060e3: ( ): shr esi, 0x02 ; c1ee02
000060e6: ( ): inc esi ; 46
000060e7: ( ): jmp .-218 ; e926ffffff
000060ec: ( ): inc ecx ; 41
000060ed: ( ): jmp .-238 ; e912ffffff
000060f2: ( ): xor ecx, ecx ; 31c9
000060f4: ( ): xor edx, edx ; 31d2
000060f6: ( ): cmp ecx, 0x00000012 ; 83f912
000060f9: ( ): jz .+25 ; 7419
000060fb: ( ): shl ecx, 1 ; d1e1
000060fd: ( ): mov ax, word ptr ds:[ecx-19092685] ; 3e668b8133abdcfe
00006105: ( ): mov bx, word ptr ds:[ecx] ; 3e668b19
00006109: ( ): cmp ax, bx ; 6639d8
0000610c: ( ): jz .+1 ; 7401
0000610e: ( ): inc edx ; 42
0000610f: ( ): shr ecx, 1 ; d1e9
00006111: ( ): inc ecx ; 41
00006112: ( ): jmp .-30 ; ebe2
00006114: ( ): cmp edx, 0x00000000 ; 83fa00
00006117: ( ): jnz .+98 ; 7562
00006119: ( ): mov byte ptr gs:0x00000320, 0x41 ; 65c6052003000041
00006121: ( ): mov byte ptr gs:0x00000321, 0x02 ; 65c6052103000002
00006129: ( ): mov byte ptr gs:0x00000322, 0x63 ; 65c6052203000063
00006131: ( ): mov byte ptr gs:0x00000323, 0x02 ; 65c6052303000002
00006139: ( ): mov byte ptr gs:0x00000324, 0x63 ; 65c6052403000063
00006141: ( ): mov byte ptr gs:0x00000325, 0x02 ; 65c6052503000002
00006149: ( ): mov byte ptr gs:0x00000326, 0x65 ; 65c6052603000065
00006151: ( ): mov byte ptr gs:0x00000327, 0x02 ; 65c6052703000002
00006159: ( ): mov byte ptr gs:0x00000328, 0x73 ; 65c6052803000073
00006161: ( ): mov byte ptr gs:0x00000329, 0x02 ; 65c6052903000002
00006169: ( ): mov byte ptr gs:0x0000032a, 0x73 ; 65c6052a03000073
00006171: ( ): mov byte ptr gs:0x0000032b, 0x02 ; 65c6052b03000002
00006179: ( ): jmp .+64 ; eb40
0000617b: ( ): mov byte ptr gs:0x00000320, 0x46 ; 65c6052003000046
00006183: ( ): mov byte ptr gs:0x00000321, 0x04 ; 65c6052103000004
0000618b: ( ): mov byte ptr gs:0x00000322, 0x61 ; 65c6052203000061
00006193: ( ): mov byte ptr gs:0x00000323, 0x04 ; 65c6052303000004
0000619b: ( ): mov byte ptr gs:0x00000324, 0x69 ; 65c6052403000069
000061a3: ( ): mov byte ptr gs:0x00000325, 0x04 ; 65c6052503000004
000061ab: ( ): mov byte ptr gs:0x00000326, 0x6c ; 65c605260300006c
000061b3: ( ): mov byte ptr gs:0x00000327, 0x04 ; 65c6052703000004
000061bb: ( ): jmp .-2 ; ebfe
000061bd: ( ): add byte ptr ds:[eax], al ; 0000
000061bf: ( ): add byte ptr ds:[eax], al ; 0000
000061c1: ( ): add byte ptr ds:[eax], al ; 0000
000061c3: ( ): add byte ptr ds:[eax], al ; 0000
000061c5: ( ): add byte ptr ds:[eax], al ; 0000
000061c7: ( ): add byte ptr ds:[eax], al ; 0000
000061c9: ( ): add byte ptr ds:[eax], al ; 0000
000061cb: ( ): add byte ptr ds:[eax], al ; 0000
000061cd: ( ): add byte ptr ds:[eax], al ; 0000
000061cf: ( ): add byte ptr ds:[eax], al ; 0000
000061d1: ( ): add byte ptr ds:[eax], al ; 0000
000061d3: ( ): add byte ptr ds:[eax], al ; 0000
000061d5: ( ): add byte ptr ds:[eax], al ; 0000
000061d7: ( ): add byte ptr ds:[eax], al ; 0000
000061d9: ( ): add byte ptr ds:[eax], al ; 0000
000061db: ( ): add byte ptr ds:[eax], al ; 0000
000061dd: ( ): add byte ptr ds:[eax], al ; 0000
ps:这一部分已经是8.2 号了,没错上面看起来简单的部分我分析了快12个小时,直到晚上的3点多。后面的部分是8.2号早上九点后的结果。
哇!你说这代码长吗,好像挺长的。但是若果放在OD里面分析,那一定一点问题都没有。毕竟OD功能太强大了,帮你追内存,划堆栈,告诉你哪个里面现在是啥状态,就差给你把flag找到了。但是这里面由于用的不是很熟(或者说没有),我不知道该从什么样子的地址开始查看内存,栈内也只有十六进制数,大多情况下也没什么帮助。所以这个汇编还是蛮痛苦的。
我们先来看看判断函数,我们要知道这个判断函数在干嘛。
000060f6: ( ): cmp ecx, 0x00000012 ; 83f912 #这里0x12,循环18次比较
000060f9: ( ): jz .+25 ; 7419
000060fb: ( ): shl ecx, 1 ; d1e1
000060fd: ( ): mov ax, word ptr ds:[ecx-19092685] ; 3e668b8133abdcfe
00006105: ( ): mov bx, word ptr ds:[ecx] ; 3e668b19
00006109: ( ): cmp ax, bx ; 6639d8
0000610c: ( ): jz .+1 ; 7401
0000610e: ( ): inc edx ; 42
0000610f: ( ): shr ecx, 1 ; d1e9
00006111: ( ): inc ecx ; 41
00006112: ( ): jmp .-30 ; ebe2
00006114: ( ): cmp edx, 0x00000000 ; 83fa00
00006117: ( ): jnz .+98 ; 7562
这里的逻辑大概就是比较18次,现将两块指定内存中的数据放入ax和bx注意是16位寄存器,为啥要注意这里,之后会提到。然后进行比较,假若不相等就将edx自加1,最后如果edx为0的话,那么就算校验成功。这里还是满人性化的,对于我这种不会看内存的人来说,它可以保证在你运行一次之后,看完所有的内存校验数据,而不会因为一位的错误而被中断。后面分析完算法,你会发现,这玩意只要错一位,整个校验数据基本上都是错的,而且是一点都不沾边的那种。
既然数据是被从内存中取出来的,那么一定有写入的过程,就算校验数据没有,但是加密数据一定有。于是在上面分析算法的时候,我们要注意内存的读写。
边调试边看代码,你会在代码中发现三处相同的地方,就是:
0000604a: ( ): mov eax, dword ptr ds:[esi] ; 3e8b06
0000604d: ( ): shl edi, 0x02 ; c1e702
00006050: ( ): mov ebx, dword ptr ds:[edi] ; 3e8b1f
00006053: ( ): mov edx, eax ; 89c2
这样的代码将数据写入寄存器,然后进行计算。于是我们在0x604a处下断点,观察是否能看见我们的输入。
我在程序中输入了很多小a大概有四五行吧。执行代码,寄存器中出现了整齐的0x73.
至于为啥是0x73,你多试几次其他的字符就好了,你会发现这是这个程序对我们键盘的译码。打个比方,你现在低头看自己的键盘,a:0x73,s:0x74,d:0x75依次类推。注意编码大小写敏感。
但当我们输入的是不同的字符的,栈内却不是我们想要的结果。
我为了得到整个键盘的字典我构造了如下flag:MWCTF{123456789asdfghjklqwzxcvbnmer}一共36位,为啥是36位,后面再说。
寄存器中的数据:
看见这个,相必做过子洋师傅出的那道题,应该瞬间就你能明白吧。他将输入的数据根据一个数组(也可能是个循环,因为在我构建字典的时候,发现之间还是有一定规律的)完成打乱,再存入内存。这里就直接上图吧,当当当当(记得有音调),我的人工内存。
这个是输入flag的位数,编码和字符的字典:
字典一
这个是被打乱后的字典,上面构造一次是:顺序,编码,翻译的字符,最下面两行对应在原字典中的顺序。
字典2
该大家一个清楚的数组吧:
add[19,13,7,1,8,2,31,25,32,26,20,14,21,15,9,3,10,4,33,27,34,28,22,16,23,17,11,5,12,6,35,29,36,30,24,18]
至此我们输入就分析完毕了,下面就是加密代码的分析了。
前排提示:测试flag依旧是MWCTF{123456789asdfghjklqwzxcvbnmer}
代码逻辑还是蛮轻松的,就是有点麻烦。这里我就叨叨我获得代码的过程了,直接说逻辑吧。有点不好说,我尽力而为吧。
首先整个加密逻辑是一个两层的循环,最外层是一个大循环一共循环43次(0x81/3),这个循环控制了三个小循环,每个小循环循环九次。
每个小循环中都是由not or and 三种运算这令构成的,用来对数据进行加密。每次循环都会从内存中取值,分别放入eax与ebx中。ebx总是取到eax后面的数据。每次函数生成一个新的8位数据,将其存在原有数据后面,等待下次被使用。
由于只有第一个循环用到了输入,并且在第一个循环的最后一次,已经使用到了第一次生成的数据,所以flag是36位的(9*8/2)
以上所有的都是一点一点调试出来的,就光抄每步寄存器里的关键值就抄了好几页。
这个是第一轮大循环中除去前九个输入后,生成的数据。
这里举个例子:
原始输入:1,2,3,4,5,6,7,8,9
第一层循环使用1,2进行加密,生成10
第一次结束内存:1,2,3,4,5,6,7,8,9,10
第二次使用:2,3,生成11
第二次结束内存:1,2,3,4,5,6,7,8,9,10,11
依次类推。
至于里面的算法很简单,基本上边跟边把它翻译成脚本,之后直接运行,将输入输出与程序进行对比,确保无误就可以了。来上我的垃圾脚本,未修改。把子洋师傅差点搞吐了233333。
import re
def first(A,B):
#A=0xc9f5f409
#B=0xe531241e
C=A | B
#print(hex(C))
C2=~A+0xffffffff+1
B2=~B+0xffffffff+1
#print(hex(C2))
#print(hex(B2))
D=C2 | B2
#print(hex(D))
H=C&D#下一个常数的参
#print(hex(H))
DATA=0x24114514
E=~H+0xffffffff+1
R=~DATA+0xffffffff+1
SS=E&R
NSS=~SS+0xffffffff+1
SSS=H&DATA
NSSS=~SSS+0xffffffff+1
last=NSSS&NSS
#print(hex(last))
return last
def sencond(Q,W):
#Q=0xfac0b72f
#W=0xda286bf9
NQ=~Q+0xffffffff+1
NW=~W+0xffffffff+1
P=NQ&NW
NP=~P+0xffffffff+1
L=Q&W
NL=~L+0xffffffff+1
last1=NL&NP
#print(hex(last1))
DATA2=0x01919810
NDATA2=~DATA2+0xffffffff+1
K=last1&NDATA2
Nlast1=~last1+0xffffffff+1
J=Nlast1&DATA2
last2=J|K
#print(hex(last2))
return last2
def thrid(T,Y):
#T=0xf1aab8d8
#Y=0x217944c6
NY=~Y+0xffffffff+1
V=T&NY
NT=~T+0xffffffff+1
V2=NT&Y
last3=V|V2
#print(hex(last3))
DATA3=0x19260817
LLL=last3|DATA3
Nlast3=~last3+0xffffffff+1
NDATA3=~DATA3+0xffffffff+1
#print(hex(Nlast3))
LLLL=Nlast3|NDATA3
last4=LLL&LLLL
#print(hex(last4))
return last4
if __name__ == '__main__':
add=[0x765d5796,0x58b78565,0x8666775e,0x785f59b3,0x5a998781,0x67827973,0x7a745ba6,0x5c6f6883,0x70847b75]
i=0
j=1
for m in range(0,43):
for Q in range(0,9):
add=add+[first(add[i],add[j])]
i=i+1
j=j+1
for Q in range(0,9):
add=add+[sencond(add[i],add[j])]
i=i+1
j=j+1
for Q in range(0,9):
add=add+[thrid(add[i],add[j])]
i=i+1
j=j+1
UU=1
for U in add:
print(hex(U))
其实我觉得直接看汇编,可能比我这个破脚本好理解一点。
对一下最后的校验数据:
这里我将8位全抄下来了,但是只用后四位。
对比我们手动生成的内存,发现就是最后九个数据。
0xe27157c9
0x7d312880
0x1c487924
0x5ff7f6b2
0x215387d6
0x4201d467
0x748b4f3
0x6eeac791
0x7d375861
至此,我们程序的逻辑也完全清楚了,现在就只剩下反向解密了。
啊!这个是子洋师傅给我说的,然后用了3分钟就帮我化简了加密函数∠( ᐛ 」∠)_。化简之后的加密函数只是三个异或。
这个来自于QQ聊天记录,在此感谢子洋,摸摸大。什么你问我有多感谢,来看一张我的化简图吧!
而且还不知道对不对。
好了我们写出反向脚本,将所给的九位为数据,逆向返还内存。
def FA3(A,B):
DATA3=0x19260817
return A^B^DATA3
def FA2(C,D):
DATA2=0x01919810
return C^D^DATA2
def FA1(E,F):
DATA=0x24114514
return E^F^DATA
if __name__ == '__main__':
add=[0x4537a864,0x71d63591,0x18a955e7,0x6ae00499,0xeda06c28,0x8105055f,0x02ba6d11,0x421a04b5,0xec5574d8]
i=0
j=8
for m in range(0,43):
for Q in range(0,9):
add=add+[FA3(add[i],add[j])]
i=i+1
j=j+1
for Q in range(0,9):
add=add+[FA2(add[i],add[j])]
i=i+1
j=j+1
for Q in range(0,9):
add=add+[FA1(add[i],add[j])]
i=i+1
j=j+1
for U in add:
print(hex(U))
在复现的时候,我有了一个想法,既然找到了主干代码,并且也有二进制流,是不是可以尝试将他们搞出来,使用IDA反编译,在F5呢。不晓得呢,应该也是种方法,不出意外的话。噶有!下次继续努力吧!
全部评论 (共 4 条评论)