Welcome to C12AK's Re journal !
目录
- 题目传送门
- 前言
- 1. easyre
- 2. reverse1
- 3. reverse2
- 4. 内涵的软件
- 5. 新年快乐
- 6. xor
- 7. reverse3
- 8. helloworld
- 9. 不一样的flag
- 10. SimpleRev
- 11. luck_guy
- 12. Java逆向解密
- 13. JustRE
- 14. 刮开有奖
- 15. 简单注册器
- 结语
题目传送门
前言
现在是我大一的暑假,这篇文章将记录我在这个假期做的题。
我上大学之前,只对网安有一些模糊的认识,第一次听说“CTF”这个东西,还是和一起转专业的同学闲聊的时候听他说的。直到开始写这篇文章的前一周,我还:
· 没学过关于开发的任何知识(前后端、Web等)
· 有一本CTF书,完全看不懂
· 对各种所谓“入门课”里提到的概念和工具一无所知(不仅没用过,根本就没听说过)
· 上网搜索入门教程,除了“CTF”,不知道该搜什么关键词
· 不知道常用的练习平台(就比如BUUCTF)
完全零基础入门真是太难了。于是我写了这篇文章,供自己整理思路,也希望能帮大家轻松入门。
1. easyre
下载IDA,把题目文件拖进去,在左侧函数栏找到main函数,按F5反编译,直接拿到flag。
具体操作看这里,如果还是不会,再看这里
2. reverse1
进入main函数,发现嵌套了一层main_0。
双击进入main_0:
分析这段代码,意思是,首先有一个字符串Str2,把Str2中所有ascii值为111的位置改为ascii值为48,也就是把所有的'o'替换为'0'(第17到24行)。然后输入Str1,如果Str1和修改后的Str2相同,那么Str1就是flag。
双击Str2查看:
可以看到Str2是hello_world。把o换成0,变成hell0_w0rld。这就是flag。
3. reverse2
还是先查看main,发现代码逻辑和reverse1基本相同,也是一个字符串经过字符替换得到flag。于是采取相同操作直接通过。
4. 内涵的软件
进入main_0,眼前直接就是一个字符串v5,下面有一堆操作。
把这些sub函数挨个点进去看,好像与flag无关啊!由于v5形似flag,但大括号外并非“flag”,于是猜测大括号内就是base64加密过的flag。
使用base64加解码器将大括号内的内容解码:
这啥啊!于是把v5直接包上flag{}提交,还是不对。最后尝试把“DBAPP”换成“flag”直接提交。
过了。6。
[补充]:这道题可能是搬运的,“DBAPP”是原比赛的flag格式,所以要改成“flag”。
5. 新年快乐
首先查壳,发现文件带壳:
再查一次壳,果然没了。
好神奇
然后把文件拖入IDA并查看main函数:
显然Str2就是flag。
6. xor
查看main函数,发现,如果一个字符串v5经过“从前到后,每一位都异或上它的前一位”的操作之后,与golbal相同,那么v5就是flag。
由于异或的性质 a^b^b = a^0 = a,对global从后往前再跑一遍异或就能得到flag。
查看global:
此处还有一点:global的第一位是f,证明目前的判断没有错。
然后写脚本进行上述操作,得到flag:
7. reverse3
小蒟蒻被这题卡了一整天,后来才惊奇地发现,前面放那个视频的最后一段就是这个题……
在IDA里查看主函数:
依次检查每个sub函数,大致梳理代码逻辑如下:
1.清空Destination(19~24行);
2.对输入的Str进行一些操作,将操作后的字符串赋值给v4,然后又将v4赋值给Destination(25~29行);
3.Destination中每个字符的ascii值都加上它的下标(32~33行);
4.如果此时Destination与Str2相同,那么它就是flag。
然后查看Str2:
也就是说,将这个东西按上述过程逆向操作,就能得到flag。那就先把Str2的每个位置减去下标:
然后看看28行的函数是什么操作。点开:
int __fastcall sub_411AB0(int a1, int a2, _BYTE *a3, unsigned int a4, int *a5)
{
int v5; // edx
int v6; // ecx
int v8; // [esp+D4h] [ebp-38h]
int v9; // [esp+D4h] [ebp-38h]
int v10; // [esp+D4h] [ebp-38h]
int v11; // [esp+D4h] [ebp-38h]
int i; // [esp+E0h] [ebp-2Ch]
unsigned int v13; // [esp+ECh] [ebp-20h]
int v14; // [esp+ECh] [ebp-20h]
int v15; // [esp+ECh] [ebp-20h]
void *v16; // [esp+F8h] [ebp-14h]
_BYTE *v17; // [esp+104h] [ebp-8h]
if ( a3 && a4 )
{
v13 = a4 / 3;
if ( (int)(a4 / 3) % 3 )
++v13;
v14 = 4 * v13;
*a5 = v14;
malloc(v14 + 1);
v16 = (void *)sub_411127(v6, v5);
if ( v16 )
{
j_memset(v16, 0, v14 + 1);
v17 = a3;
v15 = a4;
v8 = 0;
while ( v15 > 0 )
{
byte_41A144[2] = 0;
a1 = 1;
byte_41A144[1] = 0;
a2 = 1;
byte_41A144[0] = 0;
for ( i = 0; i < 3 && v15 >= 1; ++i )
{
a1 = (int)v17;
LOBYTE(a2) = *v17;
byte_41A144[i] = *v17;
--v15;
++v17;
}
if ( !i )
break;
switch ( i )
{
case 1:
*((_BYTE *)v16 + v8) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
v9 = v8 + 1;
*((_BYTE *)v16 + v9) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
a2 = ++v9;
*((_BYTE *)v16 + v9) = aAbcdefghijklmn[64];
a1 = (int)v16 + ++v9;
LOBYTE(a2) = aAbcdefghijklmn[64];
*((_BYTE *)v16 + v9) = a2;
v8 = v9 + 1;
break;
case 2:
*((_BYTE *)v16 + v8) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
v10 = v8 + 1;
*((_BYTE *)v16 + v10) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
a2 = (int)v16 + ++v10;
*((_BYTE *)v16 + v10) = aAbcdefghijklmn[((byte_41A144[2] & 0xC0) >> 6) | (4 * (byte_41A144[1] & 0xF))];
a1 = (int)v16 + ++v10;
LOBYTE(a2) = aAbcdefghijklmn[64];
*((_BYTE *)v16 + v10) = a2;
v8 = v10 + 1;
break;
case 3:
*((_BYTE *)v16 + v8) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
v11 = v8 + 1;
*((_BYTE *)v16 + v11) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
*((_BYTE *)v16 + ++v11) = aAbcdefghijklmn[((byte_41A144[2] & 0xC0) >> 6) | (4 * (byte_41A144[1] & 0xF))];
a2 = (int)v16 + ++v11;
*((_BYTE *)v16 + v11) = aAbcdefghijklmn[byte_41A144[2] & 0x3F];
a1 = v11 + 1;
v8 = v11 + 1;
break;
}
}
*((_BYTE *)v16 + v8) = 0;
}
}
return sub_411127(a1, a2);
}
我去,这么多。不过它用了很多次 aAbcdefghijklmn 这个数组,那就看看它又是什么。点开:
原来是base64码表。看来把刚才得到的字符串进行base64解码就能拿到flag。
我也爱你!
8. helloworld
这是一道android逆向,可以用apkide或者apktool反编译。下图是用apkide打开题目文件后,输出框显示的内容:
正常来讲在apkide中直接可以查看.smali输出,但打开.smali时出现了错误,那就按照输出目录查看主函数,在这个位置:
就是这个MainActivity.smali,可以用各种方式打开,比如记事本,然后就看到了flag:
9. 不一样的flag
打开一看确实不太一样:
“选择一个方向,向上下左右行走”,合理猜测是一个东西在地图上行走,且v3[25]表示行数,v4表示列数。按照这个猜测,再看看具体的行走细节:
“如果v3[25] >= 5或者v3[29] >= 5,那就退出;如果v7[ 5*v3[25] - 41 + v4 ]是'1',就退出;如果v7[同上]是'#',就输出正确并退出。”
v3[29]是什么呢?查看前面声明变量的部分,可以看到v3是一个长为28的数组,所以v3[29]其实就是v4(“v4”这个名字就是IDA给命名的,之所以叫v4,是因为它在内存中的地址紧跟在v3后面)。如果上面的猜想成立,那么这部分意思就是“如果行数或列数不在0到4,那么就退出”。再加上v7下标中的“5*v3[25]”和“+v4”(尽管没看懂“v7”和“-41”),以及v3有值的长度正好是25,基本就可以确定 本题是在一个5*5的地图中行走,并且 flag是一个由1234组成的字符串,按照它可以从v3[0]开始,经过若干个非'1'的位置,走到一个值为'#'的位置。
那就将v3写成5*5的形式,从左上角走到'#',依此写出flag。
*1111
01000
01010
00010
1111#
flag{222441144222}
10. SimpleRev
进入主函数分析代码:
意思是,如果输入D或者d就执行Decry函数,否则退出程序。
然后分析Decry函数:
由于大小端存储问题[待补],伪代码窗口下显示的字符串都是实际的字符串经过反转得到的,而汇编窗口下显示的字符串与实际的相同,也就是说src和v9[0]要进行反转。这样我们就通过Decry的前半部分得到了两个字符串text和key。接下来看后半部分:
这部分就是输入一个字符串,它经过一系列处理得到str2,如果str2和text相同,那么它就是flag。既然已知text,我们可以直接反推出flag。
11. luck_guy
就不细说了,直接快进到能直接接触到flag的地方:
这是循环执行操作,每次执行一系列操作中的某一个,看来如果按顺序执行了某几个操作,就可以拿到flag。case1显然是拿到flag的最后一步,分析case1代码可知,flag是f1和f2拼接得到的。点击查看f1,它是“GXY{do_not_”,但f2看不到,看来需要通过其他操作得到。case2和case3显然没什么用,case4是在f2后面拼接一个s,这里s和上一题一样需要反转,是“icug`of0x7F”(长度为8)。再看case5,它是对f2进行一个操作,且操作的下标恰好是0到7。那就可以猜测case4应该是在case5之前进行的。对case4得到的f2(也就是s)进行case5的操作,得到:
很好,这很像flag的后半部分。所以把f1和这个东西拼起来,然后把原比赛格式的“GXY”改为此题要求的“flag”,提交,通过。
12. Java逆向解密
下载文件后发现后缀名是.class,是java文件,要用jd-gui反编译。
在jd-gui中查看java代码:
flag的每一位都加上64^0x20可以得到key。即可编写脚本解出此题:
13. JustRE
看到题名里大写的RE,我才意识到RE还可以表示Runtime Error来着……
我不要RE!!!qwq
言归正传,打开文件……
这啥啊?完全看不懂,要不先点开这个东西看看?
原来这里没有flag啊。那不做了。
再看看捷径?
6。那看看getflag?
6。要不看看Help能不能帮我?
铭记在心!
现在大概知道这软件是干啥的了,再来看代码……还是看不懂。要不查看一下字符串,看看有没有和flag相关的?
翻到最下面,发现一个疑似flag的字符串。双击定位,再按X跳转到引用位置,再按F5反编译包含这个字符串的函数:
没学过这些东西,但大致可以看出sprintf输出的就是flag。与printf同理,把两个“%d”分别换成19999和0,应该就是flag。当然,“BJD”也要换成“flag”,
尽管BJD万岁。
然后一提交就过了。
14. 刮开有奖
用IDA打开并进入WinMain函数。
再双击进入DialogFunc:
INT_PTR __stdcall DialogFunc(HWND hDlg, UINT a2, WPARAM a3, LPARAM a4)
{
const char *v4; // esi
const char *v5; // edi
int v7[2]; // [esp+8h] [ebp-20030h] BYREF
int v8; // [esp+10h] [ebp-20028h]
int v9; // [esp+14h] [ebp-20024h]
int v10; // [esp+18h] [ebp-20020h]
int v11; // [esp+1Ch] [ebp-2001Ch]
int v12; // [esp+20h] [ebp-20018h]
int v13; // [esp+24h] [ebp-20014h]
int v14; // [esp+28h] [ebp-20010h]
int v15; // [esp+2Ch] [ebp-2000Ch]
int v16; // [esp+30h] [ebp-20008h]
CHAR String[65536]; // [esp+34h] [ebp-20004h] BYREF
char v18[65536]; // [esp+10034h] [ebp-10004h] BYREF
if ( a2 == 272 )
return 1;
if ( a2 != 273 )
return 0;
if ( a3 == 1001 )
{
memset(String, 0, 0xFFFFu);
GetDlgItemTextA(hDlg, 1000, String, 0xFFFF);
if ( strlen(String) == 8 )
{
v7[0] = 90;
v7[1] = 74;
v8 = 83;
v9 = 69;
v10 = 67;
v11 = 97;
v12 = 78;
v13 = 72;
v14 = 51;
v15 = 110;
v16 = 103;
sub_4010F0(v7, 0, 10);
memset(v18, 0, 0xFFFFu);
v18[0] = String[5];
v18[2] = String[7];
v18[1] = String[6];
v4 = sub_401000(v18, strlen(v18));
memset(v18, 0, 0xFFFFu);
v18[1] = String[3];
v18[0] = String[2];
v18[2] = String[4];
v5 = sub_401000(v18, strlen(v18));
if ( String[0] == v7[0] + 34
&& String[1] == v10
&& 4 * String[2] - 141 == 3 * v8
&& String[3] / 4 == 2 * (v13 / 9)
&& !strcmp(v4, "ak1w")
&& !strcmp(v5, "V1Ax") )
{
MessageBoxA(hDlg, "U g3t 1T!", "@_@", 0);
}
}
return 0;
}
if ( a3 != 1 && a3 != 2 )
return 0;
EndDialog(hDlg, a3);
return 1;
}
发现其中有一行输出“You get it!”,往上看一点,又发现被比较的字符串是String,那么String就应该是flag了。而判断当前的String是否为flag,需要用到v7[0], v10, v8, v13, v4, v5,那就再往上寻找如何得到这些值。观察到两个函数sub_4010F0和sub_401000。点击查看sub_4010F0,发现这是对v7的前11个元素的加密,且加密方式比较难以分析。但由于v7[0]到v7[11]都已给出(根据IDA的变量命名规则,v8就是v7[2],v9就是v7[3],依此类推),我们可以直接在本地复现这个函数,得到加密后的v7。
这里要注意两点:
1.IDA对函数形参的类型判断有误,通过分析比较容易发现sub_4010F0的参数a1应该是int*类型。
2.IDA中*(a1+i)是指从a1开始偏移i个字节,所以IDA中的(a1 + 4i)实际上相当于a1[i],在本地复现时需要去掉函数中所有的“ 4 * ”(我在经历了无数次错误提交后,看了别人的题解才发现这一点)。
然后分析sub_401000,发现是base64加密,且返回值是v18的起始地址(关于这个返回值,本蒟蒻分析了好久……),所以,结合v18的赋值,可以确认v4和v5分别是经过base64加密的String[5 : 7]和String[2 : 4]。对“ak1w”和“V1Ax”进行解密,可得v4为“jMp”,v5为“WP1”。最后用v7求出String[0 : 1]即可。
本地总代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int sub_4010F0(int *a1, int a2, int a3)
{
int result; // eax
int i; // esi
int v5; // ecx
int v6; // edx
result = a3;
for ( i = a2; i <= a3; a2 = i )
{
v5 = i;
v6 = *(i + a1);
if ( a2 < result && i < result )
{
do
{
if ( v6 > *(a1 + result) )
{
if ( i >= result )
break;
++i;
*(v5 + a1) = *(a1 + result);
if ( i >= result )
break;
while ( *(a1 + i) <= v6 )
{
if ( ++i >= result )
goto LABEL_13;
}
if ( i >= result )
break;
v5 = i;
*(a1 + result) = *(i + a1);
}
--result;
}
while ( i < result );
}
LABEL_13:
*(a1 + result) = v6;
sub_4010F0(a1, a2, i - 1);
result = a3;
++i;
}
return result;
}
int main(){
//第一部分:解出v7
int v7[11] = {90,74,83,69,67,97,78,72,51,110,103};
sub_4010F0(v7, 0, 10);
for(int i = 0; i < 11; i++) cout << (char)v7[i];
cout << endl;
//第二部分:解出String
char str0 = v7[0] + 34;
char str1 = v7[4];
cout << str0 << str1 << "WP1" << "jMp" << endl;
return 0;
}
15. 简单注册器
进入主函数,发现第60行输出了一个“flag{*}”格式的字符串,可知“public void onClick”这部分应当是得到flag的关键。这部分有一个字符串xx,如果满足一定条件,就会对另一个字符串x进行一些操作,得到flag。由于flag的得出与xx无关,那我们就不考虑xx。又因为x的原始形式已经给出,所以可以直接在本地写脚本求得flag:
结语
标签:BUUCTF,Reverse,esp,int,题解,v16,a1,flag,ebp From: https://www.cnblogs.com/qjsswz/p/18392428我要开学了!啊啊啊啊啊啊啊啊啊啊啊我不想开学qwq
写博客毕竟要花费不少时间,以后的话,如果一道题让我学到了新东西,那还是会记录在博客里,但平常的题就不写题解了。
这篇文章有点长了,就先完结吧。敬请期待第二部分。