https://adworld.xctf.org.cn/
下载附件文件
使用exeinfope打开,32位无壳
发现是明文比较,并没有进行加密,然后看到最上面是赋值语句进入后看到字符串v5即为flag
ida使用小技巧,当光标移动到数据时,可以点击快捷键进行数据类型的转化,
a:将字符进行ascll编码,形成一个字符。
r:将一串数据转化为字符串,但要注意数据大小端的储存方式,这里形成的字符串时逆序的。
*:将数据组合形成数组。
u:消除对某段数据的分析,并呈现为十六进制数据。
c:将选中的数据分析为代码。
p:将选中的代码部分分析为一个函数。
d:转化选中的数据的数据类型,主要有Byte、Word、Dword
此处我们将转化好的数据放置到python中进行处理,直接倒序就可以得到flag
a = '0tem0c1eW{FTCTUD'[::-1]
b = '}FTCTUD'[::-1]
print(a+b)
使用exefope,32位无壳。
ida中分析,
主函数主要有三部分组成,此处主演展示了后两部分。
第一部分为输出程序出现的界面,然后获取我们输入的n。
第二部分为游戏运行的过程,并重新绘制游戏界面。
第三部分为判定所有行是否已经全部合并,如果是那将进入一个子函数。
所以我们可以明确的看出程序关键的解密并输出flag的函数就是sub_457ab4()
函数解密和输出部分如图示,其实就是一个简单的异或,但值得注意的是这是一种数组的写法:
其中的(&v2 + i)是表示地址偏移,是一种数组的写法,相当于v2[i],这是一种极为常见的写法。
所以我们可以直接根据这个函数写出脚本,注意将v2,v59写为数组就好。
但是这个函数负责将flag输出。
我们可以进行动态调试,并在此过程中控制if判定,最后让程序自己输出flag。
另外可以直接将游戏完成,输出flag。
还可以patch,使用ida将程序判定部分修改,然后输入一个值就可以判定为对,输出flag。
其中需要补充的函数:
sprintf:函数第一个参数为字符串,第二个是格式,第三个是数据,此函数的定义为:将某串数据以一种特定的格式形成一个字符串。
strcat:函数定义为字符串连接函数,将两个参量都是字符串,将参数二连接到参数一结尾。
所以我们可以简单的看出整个程序的算法就是将输入的flag转化为16进制,然后和与定义好的字符串v13比较,我们可以使用python2中的decode()
方法:
附件是一份c的源代码,使用编辑器打开分析。
if (argc != 4) {
printf("what?\n");
exit(1);
}
这是其中的一小段,是极为典型的if
配和!=
判定,结构内部是错误的情况。一般我们可以通过这种地方的判定线索来确定有关于flag的信息。这个题目就是极为典型的。
这里我们可以将出现的几个值都以这样的形式得到,然后最后得到变量hash的值,得到其十六进制。
使用exefope,upx壳64位。
使用upx进行脱壳:
然后使用ida64打开,
然后发现main函数是铭文比较,直接得到flag。
查看,无壳64位。
高亮的部分为错误,这里就再次出现了if
判断的反向,我们可以知道flag和v8长度相同,v8v7异或就可以得到,但是要注意的是,v7的长度只有7个注意%7 且注意数据类型,v7在此处是Byte
(相当于char
)我们将其化为字符串,发现正好是七个,但是要注意大小端导致的倒序问题。
于是我们就可以使用python脚本得到flag了。
v7 = 'ebmarah'[::-1]
v8 = ":\"AL_RT^L*.?+6/46"
for i in range(len(v8)):
print(chr(ord(v7[i % 7]) ^ ord(v8[i])),end='')
检查,无壳32位。
可以直接字符串搜索出来,ida中快捷键SHIFT
+F12
。
另外在Linux下直接运行也可以得到flag,./
+文件名。
检查,无壳32位。
main函数极为简单,其中观察后我们轻易的知道,关键函数应该是在authenticate()
中,
发现加密函数`decrypt()`
我们使用idapy脚本将传入decrypt()
的两个参数打印出来:
我们可以通过使用*指令的时候看到数组的大小,一般再根据数据进行调整。
addr=0x8048a90 #数组的地址
arr=[]
for i in range(6): #数组的个数
arr.append(Dword(addr+4*i))
print(arr)
然后我们打印出两个数组,舍掉每个数组最后出现的0,然后根据加密函数操作。
a2 = [5121, 5122, 5123, 5124, 5125]
dest = [5178, 5174, 5175, 5179, 5248, 5242, 5233, 5240, 5219, 5222, 5235, 5223, 5218, 5221, 5235, 5216, 5227, 5233, 5240, 5226, 5235, 5232, 5220, 5240, 5230, 5232, 5232, 5220, 5232, 5220, 5230, 5243, 5238, 5240, 5226, 5235, 5243, 5248]
i = j = 0
while i < 38:
j = 0
while j <5 and i < 38:
dest[i] -= a2[j]
i += 1
j += 1
for i in range(38):
print (chr(dest[i]),end='')
i += 1
将程序放置到linux环境下,使用gdb调试程序
gdb ./xxxxxx -q #使用gdb打开程序进行调试
start #开始进行调试
n #单步步过
s #单步步入
x/6sw $eax #显示六行数据/以字符串形式/word形式/查看eax中的值。
观察main函数,其中调用的子函数除了windowsapi,就只有两个函数,其中有一个还是最开始调用,所以关键的能使flag不乱码的函数应该是在sub_401000
中。
另外观察到函数前调用了apiIsDebuggerPresent
,这个函数用于检测该程序是否处于dedug状态,即是否被调试。
另外我们通过对apiMessageBoxA
的分析可知,我们显示乱码的字符串,即flag应该是局部变量`[ebp+lpMem]。
在动态调试过程中,控制程序执行到sub_401000
位置,然后注意前面的int 3干扰数据可以nop掉,然后关注mov指令是将局部变量的地址赋值给edx,此时就可以查到字符串的地址,进行call,调用完函数以后就可以看到字符串的值改变,成为了flag。
检查,无壳64位。
main函数后面主要为文件处理部分,前面是重要的flag处理函数。
可以得到s和t的值
然后写出脚本:
#SharifCTF{????????????????????????????????}
s = 'c61b68366edeb7bdce3c6820314b7498'
for v5 in range(len(s)):
v3 = -1
if v5 & 1:
v3 = 1
print(chr(ord(s[v5]) + v3),end='')
然后得到字符串替换掉?的内容即可。
得到一个.pyc文件,使用[在线编辑器][http://tools.bugscaner.com/decompyle/] 打开形成.py文件:
import base64
def encode(message):
s = ''
for i in message:
x = ord(i) ^ 32
x = x + 16
s += chr(x)
return base64.b64encode(s)
correct = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt'
flag = ''
print 'Input flag:'
flag = raw_input()
if encode(flag) == correct:
print 'correct'
else:
print 'wrong'
关键部分就是逆向出for
循环中的算法即可:
import base64
correct = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt'
a = base64.b64decode(correct)
flag = ''
for i in range(len(a)):
x = a[i]
x -= 16
y = x ^ 32
flag += chr(y)
print(flag)
检查,无壳64位。且题目提示这是迷宫题。
ida伪代码是一个多重跳转的复杂结构:
所以我们直接看汇编代码的结构图:
然后我们发现中间的判定是关于输入的字符,然后调整寄存器r14
和r15
最后回到大的区块上,使用sub_400690
函数处理,我们进入发现是迷宫的判定。
然后发现*8的就是上下,另一个是左右,可以定位到r14
,r15
然后我们可以确定四个字符分别代表的上下左右,而且也可以看出,是8行的迷宫。我们可以看到那个字符串,直接将他打印出来:
然后我们看到最开始是在(0,0)的位置,要走到#才算成功,就可以得到行走的字符串,然后再套上格式就是flag了。
现在刚整理完gdb那一段,所以完整的发博客了。
因为是之前整理的,其中大部分脚本使用sublime+cmd运行的python。后来才知道配置编辑器插件,sublime/vscode都可以跑程序还是很香。