攻防世界新手练习题_REVERSE(逆向)
攻防世界新手练习题_REVERSE(逆向)
re1
题目描述:菜鸡开始学习逆向工程,首先是最简单的题目
做题实录
双击点开,让我输入flag
随便输一个:
看样子是要比较字符串的,放到OD里面看看,找到了比较字符串的地方,下断点
随便输入什么字符之后继续往下走,发现flag已经进入到了ESP中
分析总结
这个题非常简单,没什么好说的,其实放到OD里还可以直接搜索字符串,一步到位……
当然了,还可以用IDA直接看源码
看到strcmp()
函数,输入的是v9,和v5比较,所以数据就应该在v5里面,再看第10行,把xmmword_413E34
的值赋给v5,所以双击xmmword_413E34
看它的数据。把qword_413E44
和xmmword_413E34
的数据拼起来
你可以把qword_413E44
和xmmword_413E34
的数据拼起来用python把16进制的数转化成文本然后反向写出flag就可以了。
print ('7D4654435455443074656D30633165577B465443545544'.decode('hex'))
|
|
# 解码结果: }FTCTUD0tem0c1eW{FTCTUD
|
或者光标点击xmmword
的数据,按下a键,
确认就可以直接把它转化成字符串。(我也是后来才知道的,看来ida还是要好好学)
game
题目描述:菜鸡最近迷上了玩游戏,但它总是赢不了,你可以帮他获胜吗
做题实录
一个exe文件,打开如下:
让你玩游戏,赢了就有flag,于是玩三个小时玩通关拿到flag。放到OD里动态调试
一路走到这里让你输入n,估计接下来就是字符串比较了,随便输一个继续调试
这部分一整段都在判断灯的情况,调试的过程中发现只要判断出灯没有全亮,就会跳转回003EF4FB,没有进入game.003E7AB4,所以猜测是成功了就进入这里,于是进去看看
进来之后,果不其然,是成功的函数
接下来就运行这个函数,就可以得到flag
分析总结
这个题还是比较简单的,没啥好说的。再用IDA来试一下
进入main_0()
函数,看到判断语句,逻辑很简单
进入sub_457AB4()
这个程序就疯狂跳转,不知道要干啥
进去之后就是这样
可以直接写个脚本跑,在这里我选择改程序的逻辑,让它跳过判断直接执行这个函数
这一部分就是判断语句,只要把cmp edx, 1
改成cmp edx edx
那么条件就恒为真。
特别注意的是:
cmp edx, 1
占3个字节而cmp edx edx
占两个字节,所以最后一个字节用nop
填充。否则会出错
修改后如下:
保存之后双击运行,随便输入一个n,就可以得出flag
Hello,CTF
题目描述:菜鸡发现Flag似乎并不一定是明文比较的
做题实录
一个exe,运行结果如下:
要找序列号,IDA看一下:
看来flag就是这个绿色的字符串了,看到有字母有数字且字母不超过f,所以16进制解码试一下,得到flag:
CrackMeJustForFun
分析总结
这个题比较简单,我开始是用动态调试做的,都差不多,只要看到这个字符串基本上就可以解决了。
open-source
题目描述:菜鸡学逆向学得头皮发麻,终于它拿到了一段源代码
做题实录
源代码如下:
|
|
|
|
int main(int argc, char *argv[]) {
|
|
if (argc != 4) {
|
|
printf("what?\n");
|
|
exit(1);
|
|
}
|
|
unsigned int first = atoi(argv[1]);
|
|
if (first != 0xcafe) {
|
|
printf("you are wrong, sorry.\n");
|
|
exit(2);
|
|
}
|
|
unsigned int second = atoi(argv[2]);
|
|
if (second % 5 == 3 || second % 17 != 8) {
|
|
printf("ha, you won't get it!\n");
|
|
exit(3);
|
|
}
|
|
if (strcmp("h4cky0u", argv[3])) {
|
|
printf("so close, dude!\n");
|
|
exit(4);
|
|
}
|
|
printf("Brr wrrr grr\n");
|
|
unsigned int hash = first * 31337 + (second % 17) * 11 + strlen(argv[3]) - 1615810207;
|
|
printf("Get your key: ");
|
|
printf("%x\n", hash);
|
|
return 0;
|
|
}
|
看到就是要输入规定的参数运行就可以了,注意这个参数要在命令行中输入。第一个参数直接跑个脚本:
print(str(0xcafe))
|
|
#51966
|
第二个参数最小的是25,第三个参数h4cky0u
编译之后cmd运行,得到flag:
分析总结
分析源码,没啥可说的。
simple-unpack
题目描述:菜鸡拿到了一个被加壳的二进制文件
做题实录
UltraEdit打开看一下,搜索flag:
去掉中间的乱码,得到flag:
flag{Upx_1s_n0t_a_d3liv3r_c0mp4ny}
分析总结
直接查找确实是歪门邪道,毕竟这题是关于加壳的,所以UE看一下:
看到这是个upx的壳,那直接用upx脱壳就行了:
脱完壳查找flag即可:
logmein
题目描述:菜鸡开始接触一些基本的算法逆向了
做题实录
IDA里看一下:
第26行就是加密算法,写个解密算法就行了:
|
|
|
|
|
|
void main()
|
|
{
|
|
int v6;
|
|
int v9;
|
|
long long v7;
|
|
int i;
|
|
v9 = 0;
|
|
char * str = ":\"AL_RT^L*.?+6/46";
|
|
v7 = 28537194573619560LL;
|
|
v6 = 7;
|
|
for ( i = 0;i < 17; ++i )
|
|
{
|
|
printf("%c",*((char*)&v7 + i % v6)^str[i]);
|
|
}
|
|
}
|
|
// RC3-2016-XORISGUD
|
分析总结
这个题就是简单的算法逆向,没啥可说的。
insanity
题目描述:菜鸡觉得前面的题目太难了,来个简单的缓一下
做题实录
IDA打开,查找flag,得到flag:
分析总结
实际上这个题就直接运行,等上几秒它会随机给你输出一些字符串,我第一次运行就直接得到了flag……
no-strings-attached
题目描述:菜鸡听说有的程序运行就能拿Flag?
做题实录
这是个32位的elf文件,ubuntu里跑一下,出来一堆不知道是啥的东西。
IDA打开
这个authenticate()
看着很可疑,进去看看:
看到一个decrypt(...)
,估计就是它了,进去看看
看样子是一个解密函数,在decrypt(...)
下断点进行动态 调试,进入while循环Hex View跟踪EAX(因为函数返回值就存在EAX里),可以看到flag正在被解密出来,继续往下走
解密完成,得出flag,去掉0x00就是最终的flag,由于题目来源9447CTF,所以没有前面的1。
分析总结
我看了一下,基本上都差不多,不过很多都是用gdb调试的,没用过gdb,正好趁这个机会学习一下,写一下gdb调试的方法。
进入gdb后输入 file no-strings-attached 开始调试程序
然后在decrypt()
下断点,用命令 break decrypt
按n是单步步过,s是单步步进,啥也不输直接按回车默认执行上一条指令
单步执行到这里发现EAX里面存了9,是flag的开头
记下此时EAX的位置0x804cff0,输入 finish 让程序执行完当前的函数并返回
查看EAX的内容,输入命令 x/100u $eax (该命令具体使用方式参见GDB下查看内存命令(x命令))
写个脚本跑出来就行了
s=[57,52,52,55,123,121,111,117,95,97,114,101,95,97,110,95,
|
|
105,110,116,101,114,110,97,116,105,111,110,97,108,95,109,121,
|
|
115,116,101,114,121,125]
|
|
flag=''
|
|
for i in range(len(s)):
|
|
flag += chr(s[i])
|
|
print (flag)
|
|
#得出结果:9447{you_are_an_international_mystery}
|
getit
题目描述:菜鸡发现这个程序偷偷摸摸在自己的机器上搞事情,它决定一探究竟
做题实录
这是个elf文件,在ubuntu里跑了一下发现没反应,放到IDA里看一下
看样子估计是个解密flag的,看了看数据段发现了t是flag(IDA里面按a把ASCII码变成字符串),但是内容不知道
我猜while循环应该就是解密的,于是动态调试一下。
可以看到在循环的过程中下面flag就已经出现了,继续调试得出flag
分析总结
这个题其实也可以不用动态调试,因为while循环里转换算法都写好了,自己按照源码写个脚本跑就行了。
s = 'c61b68366edeb7bdce3c6820314b7498'
|
|
flag = ''
|
|
for i in range(len(s)):
|
|
if i & 1:
|
|
t = 1
|
|
else:
|
|
t = -1
|
|
flag += chr(ord(s[i]) + t)
|
|
print (flag)
|
|
#得出结果: b70c59275fcfa8aebf2d5911223c6589
|
python-trade
题目描述:菜鸡和菜猫进行了一场Py交易
做题实录
下下来是pyc文件,放到ubuntu里面,用uncompyle反编译(下载过程也踩了很多坑……)并保存到tes.py
uncompyle6 Py.pyc > tes.py
得到源码:
# uncompyle6 version 3.3.5
|
|
# Python bytecode 2.7 (62211)
|
|
# Decompiled from: Python 2.7.15+ (default, Nov 27 2018, 23:36:35)
|
|
# [GCC 7.3.0]
|
|
# Embedded file name: 1.py
|
|
# Compiled at: 2017-06-02 19:20:43
|
|
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'
|
|
# okay decompiling Py.pyc
|
是一个加密的程序,那按照它的步骤解密就行了,是一个算法逆向的题。
解密代码如下:
import base64
|
|
def decode(message):
|
|
message = base64.b64decode(message)
|
|
s = ''
|
|
for i in message:
|
|
x = ord(i)-16
|
|
x = x ^ 32
|
|
s += chr(x)
|
|
return s
|
|
message = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt'
|
|
print (decode(message))
|
|
#得出结果: nctf{d3c0mpil1n9_PyC}
|
分析总结
这题没啥好说的,就是算法逆向。但是安装使用uncompyle遇到点问题,也记下来吧。
pip install uncompyle
然后使用的时候需要输compyle6,我在输了之后一直提示我“command not found”,后来发现需要执行这两条命令安装环境变量
export PATH=PATH
|
|
PATH=PATH
|
csaw2013reversing2
题目描述:听说运行就能拿到Flag,不过菜鸡运行的结果不知道为什么是乱码
做题实录
拿到的是个exe文件,双击运行结果如图:
正如描述所说,是一堆乱码,放到OD里看一下。
发现这个位置就是弹出flag的位置。
在这儿设个断点,然后进去仔细看。
进来之后看这个函数,发现有两个MessageBox函数,觉得有蹊跷,于是在00B01094处取消跳转,让它往下走看看有什么反应。
在走的时候把int3用nop填充,00B010A3处的jmp跳转也用nop填充
走到第一个MessageBox,弹出了一个flag,但是什么也没有
于是点击忽略继续往下走,又遇到了jmp跳转,同样nop填充
走到这flag已经加载进内存了。
分析总结
方法一:直接修改程序逻辑
参考【XCTF 攻防世界】 Reverse —— csaw2013reversing2
由于是个32位的PE文件(linux下用file命令查看),所以用ida pro打开,F5反编译,生成类C语言的伪代码如下:
lpMem就是flag的内容。看第10行if语句,如果sub_40102A()
或IsDebuggerPresent()
返回值为真则执行__debugbreak()
调试断点函数、子函数sub_401000(...)
和退出进程函数ExitProcess(...)
函数,否则直接执行MessageBoxA(...)
函数弹出flag框。
打开sub_40102A()
函数看一下:
发现返回值恒等于零,那就是说只有在调试状态下才会执行if里面的语句。由于直接双击运行没有在调试环境下,弹出乱码的flag,所以可以肯定if里面的语句是关键点,应该包含解密flag的函数。
打开sub_401000(...)
看一下:
看样子是个解密函数,那应该就是它了。那么思路就很清晰了,我们要让程序跳过if判断,直接执行sub_401000(...)
函数,然后再跳过ExitProcess(...)
函数执行MessageBoxA(...)
函数弹出解密后的flag框。
既然思路已经清晰那么就开始修改程序吧。
这是原本的逻辑:
使用IDA修改指令的方法是将光标放在要修改的指令上,依次点击Edit -> Patch program -> Assemble,弹出指令修改框进行修改。
修改之后逻辑如下:
红色标注的就是修改过的地方。修改完成后要保存到文件,依次点击 Edit -> Patch program -> Apply patches to input file…,弹出设置框,选择待打补丁程序进行修改。
最后双击运行修改后的程序,直接弹出已解密的flag框。
方法二:DLL注入
maze
题目描述:菜鸡想要走出菜狗设计的迷宫
做题实录
打开IDA看了半天源码,不知道和迷宫有啥关系,以前没做过这种题,还以为是代码不停跳转什么的,还是最后看了wp才明白,真的是个迷宫(吐血)。还是要做题,提高自己的姿势水平,不然就太naive了。
分析总结
这是个64位的elf文件,放IDA里看一下main函数
(请无视我刚开始看的时候做的注释)首先看第13行,判断输入的字符串,从这里可以看出要求输入的flag长度为24,且以 'nctf{' 开头,以 '}' 结尾(最后的 '}' 本来是ASCII码,在IDA里把光标选择ASCII码按R键就可以把ASCII码变成字符了)。
由于是迷宫,一般是二维的,所以应该有一个记录坐标的变量,看到v9,在判断中有一个&v9
和(&v9)+1
(也就是v9跟着的的下一个字节的地址),那么可以猜想v9就是一个记录位置坐标信息的二维数组。那么while循环就是在判断输入的字符来改变坐标信息,也就是用户输入字符来走迷宫。
正常来说,v9和v9+1对应着迷宫的行和列(别问我为啥不是列和行,我觉得正常讲话就是行在前,所以自然而然想到行和列。就算不对那到时候再改吗,反正就两种情况。而且下面的分析可以确定就是对应的行和列)。
既然是走迷宫,当然就得判断走没走到终点,看到了第72行的“Congratulations!”,那看一下它前面第70行的判断,判断坐标所在,如果是“#”,就到终点了,说明“#”号就代表终点。而且看一下asc_601060
有个“#”,看来这应该就是迷宫的地图了。我们的目标就是要走到“#”处。再回过头来看8*v9+SHIDWORD(V9),既然是坐标,说明v9就代表行,而v9+1代表列,而且可以知道迷宫的地图应该是8个字符一行的(因为行增加一,数就增加8)。
至于这个LABLE_15,每走一步都要进入这里,应该就是判断有没有越界什么的函数。
现在基本上就已经理清楚了这个迷宫,只要知道怎么走就行了。while循环里的函数如下:
//因为是&v9+1,所以是判断列,也就是左右走
|
|
bool __fastcall sub_400650(_DWORD *a1)//(_DWORD *)&v9 + 1
|
|
{
|
|
//为'O'
|
|
int v1; // eax
|
|
v1 = (*a1)--; //往左走
|
|
return v1 > 0;
|
|
}
|
|
bool __fastcall sub_400660(int *a1)//(int *)&v9 + 1
|
|
{
|
|
//为'o'
|
|
int v1; // eax
|
|
v1 = *a1 + 1; //往右走
|
|
*a1 = v1;
|
|
return v1 < 8;
|
|
}
|
|
//上下走
|
|
bool __fastcall sub_400670(_DWORD *a1)//&v9
|
|
{
|
|
//为'.'
|
|
int v1; // eax
|
|
v1 = (*a1)--; //往上走
|
|
return v1 > 0;
|
|
}
|
|
bool __fastcall sub_400680(int *a1)//(int *)&v9
|
|
{
|
|
//为'0'
|
|
int v1; // eax
|
|
v1 = *a1 + 1; //往下走
|
|
*a1 = v1;
|
|
return v1 < 8;
|
|
}
|
总结一下:“.”往上走、“0”往下走、“O”往左走、“o”往右走
拿个脚本把地图写出来(python是真的好用)
map = ' ******* * **** * **** * *** *# *** *** *** *********'
|
|
for i in range(0, len(map), 8):
|
|
print (map[i: i + 8])
|
|
#得出结果:
|
|
'''
|
|
******
|
|
* * *
|
|
*** * **
|
|
** * **
|
|
* *# *
|
|
** *** *
|
|
** *
|
|
********
|
|
'''
|
要求步长为18,从左上角开始走,走到“#”的位置,走就行了,最后结果为
nctf{o0oo00O000oooo..OO}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)