总结经验:IDA Pro反编译的时候会把字符编译为ASCII码,需要注意!另外,也要多多关注循环和判断及其对应的条件。不要忽视变量类型以及数组长度!
逆向题难度不小,做了一个多小时才做了两题,还需要不断看WP。但是菜就多练,记录下做的题!
1.Reversing-x64Elf-100
发现是64位,直接丢IDA看看先。下代码经过分析,应该是当括号内容是false的时候,才会执行nice,估计就是正确的代码。接下来,我们点进去看看。有必要说明的是,sub_xxx一般指一个函数。
以下是函数
似乎实现了一个算法,我们目的是让他返回0,也就是让if不成立。那也就是让表达式里面的值等于1就行了。
首先获取它们代表的整数到底是什么。
这串代码用到了指针的概念,但是目前我还没咋掌握指针,参考了题解后大胆认为:它是获取减号前面表达式的字符,然后在获取减号后面的字符,估计后面的字符应该比前一个少一。a1就是我们输入的字符。因为a1原本存储的是字符数组首地址,因此*(char *)实现的是对a1每一位的访问。由此,写出c++脚本。
int main()
{
int strings[4];
char b[20];
strings[0] = (__int64)"Dufhbmf";
strings[1] = (__int64)"pG`imos";
strings[2] = (__int64)"ewUglpt";
for(int i = 0;i<=11;i++)
{
b[i]=char(*(char *)(strings[i % 3] + 2 * (i / 3))-1);
cout<<b[i];
}
return 0;
}
理清思路之后,也感觉是小小拿捏了一道题。
2.666
首先拿出die,发现是一个64位程序。放到IDA老婆肚子里看看会发生什么事:
f
他妈的,给了一个假的flag,害得我提交了一下。那么,关键应该在key了,if判断的是s和enflag是否相等。strcmp函数如果两个数相等就会返回0!
复制到下面:izwhroz""w"v.K".Ni
这段代码是汇编语言中的数据定义部分,使用的是 Intel 语法。下面是对这段代码的解释:
```
data:0000000000004060 enflag db 'izwhroz""w"v.K".Ni',0
```1. `data`:这是一个段(segment)的名称,通常用于汇编语言中,表示数据段。数据段是存储程序中所有数据的地方,包括变量、字符串常量等。
2. `0000000000004060`:这是数据段中 `enflag` 的内存地址。在实际的程序中,这个地址可能会有所不同,因为它取决于程序的加载地址和操作系统的内存管理。
3. `enflag`:这是一个标签(label),用于标识数据段中的特定位置。在这个例子中,`enflag` 指向一个字符串。
4. `db`:这是一个数据定义伪指令,用于定义一个字节(byte)的数据。`db` 后面的内容是要定义的数据。
5. `'izwhroz""w"v.K".Ni'`:这是一个字符串常量,包含了一系列的字符。在汇编语言中,字符串通常用单引号 `' '` 包围。
6. `,0`:这部分表示在字符串的末尾添加一个字节值为 0 的字符,即空字符(null terminator),用于表示字符串的结束。在 C 语言和许多其他编程语言中,字符串以空字符结尾。
综上所述,这段代码定义了一个名为 `enflag` 的字符串,内容为 `'izwhroz""w"v.K".Ni'`,并在字符串末尾添加了一个空字符。这个字符串可能用于程序中的某个功能,例如加密或解密。具体的用途取决于程序的上下文和设计。
上面的应该是enflag,我们当务之急是找到s,找到S就可以推回我们应该输入什么,进而确认flag。s就是enflag里面的东西。所以关键函数应该是encode,用来解码出s的,我们需要逆向解出我们应该输入什么key,使得key编码后是s。
(整体逻辑如上图)
还不知道key的长度,双击汇编代码中的key得到:
一定要注意!这里的key就是18,而不是说key的长度是18!
接下来,就是根据算法逆推了。为了练习python,本题解题脚本我选择python来编写。
要注意,异或运算优先级小于+-,所以要在异或运算外面包裹(),否则会出现算法层面问题
剩下的没什么需要注意的,就是不要越过a2界即可!附上代码:
a2='izwhroz""w"v.K".Ni'
i=0
r=[]
while(i<18):
r.append((chr((ord(a2[i])^18)-6)))
r.append((chr((ord(a2[i+1])^18)+6)))
r.append((chr(ord(a2[i+2])^18^6)))
i+=3
print("".join(r))
3.easyRE1
压缩包有个32和64,打开后是一样的,答案就在眼前。感觉这道题反而比前两题简单多了。
4.lucknum
查出是64位。
按它的算法,应该输入7后,出来flag。但是flag实际上已经被写出来,复制一下就行。
5.reverse_re3
64位,linux
错误思路(因未转换为字符 )
看main函数的意思,应该是v4不要等于±1. 才会结束do...while循环。从头看main函数,首先要跑一个这个函数,返回了v1异或这个值。__readfsqword
是一个函数或宏指令,用于从特定的内存地址读取数据。后面的参数的u代表unsigned int常量。
dword_202AB0 变量被赋值为了0,在这里需要注意一下。
shift+F12进入之后,点击ctrl+x定位到函数 。以下是代码
__int64 sub_940()
{
int v0; // eax
int v2; // [rsp+8h] [rbp-218h]
int v3; // [rsp+Ch] [rbp-214h]
char v4[520]; // [rsp+10h] [rbp-210h] BYREF
unsigned __int64 v5; // [rsp+218h] [rbp-8h]
v5 = __readfsqword(0x28u);
v3 = 0;
memset(v4, 0, 0x200uLL);
_isoc99_scanf(&unk_1278, v4, v4);//unk_1278 = 29477
while ( 1 )
{
do
{
v2 = 0;
sub_86C();
v0 = v4[v3];
if ( v0 == 100 )
{
v2 = sub_E23();
}
else if ( v0 > 100 )
{
if ( v0 == 115 )
{
v2 = sub_C5A();
}
else if ( v0 == 119 )
{
v2 = sub_A92();
}
}
else
{
if ( v0 == 27 )
return 0xFFFFFFFFLL;
if ( v0 == 97 )
v2 = sub_FEC();
}
++v3;
}
while ( v2 != 1 );
if ( dword_202AB0 == 2 )
break;
++dword_202AB0;
}
puts("success! the flag is flag{md5(your input)}");
return 1LL;
}
memset函数原型及作用
memset
是 C 和 C++ 标准库中的一个函数,其功能是将一段内存空间的内容按字节设置为特定的值。通常用于初始化数组、结构体等内存区域,把指定内存范围初始化为特定字节内容,以确保内存中的数据处于预期的初始状态。参数含义
v4
:这是memset
函数的第一个参数,它表示要进行内存设置操作的目标内存地址。一般来说,这个参数是一个指向某个内存区域起始位置的指针,比如可以是指向数组、结构体等的指针。例如,如果v4
是一个字符数组char arr[10];
,那么传递arr
(实际上就是数组首地址)给memset
时,操作就会从这个数组的开头位置开始对内存进行设置。0
:第二个参数表示要设置的字节值,在这里值为0
,意味着将内存中的每个字节都设置为0
这个数值对应的字节表示(在二进制下就是00000000
)。如果想把内存区域设置为其他固定的值(比如全设置为0xFF
等),可以相应地修改这个参数。0x200uLL
:第三个参数指定了要设置的内存区域的大小,单位是字节。这里0x200uLL
是一个无符号长整型常量(u
表示无符号,LL
表示长整型后缀,在 64 位系统下通常表示 64 位的无符号整数),意味着memset
函数会从v4
所指向的内存起始位置开始,连续设置0x200
(也就是十进制的512
)个字节的内存内容为0
。
此函数又执行了sub_86c函数,因此点进去看看sub_86c函数。
看样子主要是给这两个变量赋值的。 v4因为用了scanf函数,大概率就是咱们所要输入的。因此我们输入的应该就是v4。
真正思路
但是做到这里就没有思路了。后来查看题解发现,IDA在反编译的时候会把字符反编译成ASCII码,因此将其中的字符变回去,得到下面的真正代码。
__int64 sub_940()
{
int v0; // eax
int v2; // [rsp+8h] [rbp-218h]
int v3; // [rsp+Ch] [rbp-214h]
char v4[520]; // [rsp+10h] [rbp-210h] BYREF
unsigned __int64 v5; // [rsp+218h] [rbp-8h]
v5 = __readfsqword(0x28u);
v3 = 0;
memset(v4, 0, 0x200uLL);
_isoc99_scanf(&qword_1278, v4, v4);
while ( 1 )
{
do
{
v2 = 0;
sub_86C();
v0 = v4[v3];
if ( v0 == 'd' )
{
v2 = sub_E23();
}
else if ( v0 > 'd' )
{
if ( v0 == 's' )
{
v2 = sub_C5A();
}
else if ( v0 == 'w' )
{
v2 = sub_A92();
}
}
else
{
if ( v0 == '\x1B' )
return 0xFFFFFFFFLL;
if ( v0 == 'a' )
v2 = sub_FEC();
}
++v3;
}
while ( v2 != 1 );
if ( dword_202AB0 == 2 )
break;
++dword_202AB0;
}
puts("success! the flag is flag{md5(your input)}");
return 1LL;
}
每次循环都会执行下面的函数,发现就是来给B4和B8赋值的。20是一个数组应该。
接着写,20是下面的数组。B0已经在main函数之前赋值为0,结合i和j的边界是14,推测代码应该是实现:i是行数,j是列数,3是目前的地方。我们接着往下看函数,
这个数组按shift+e,导出为一整列的数字,用python转换为15列的数组,方便后续操作。以下是源码。
a = '1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,3,1,1,0,0,0,0,0,0,1,1,1,1,1,0,0,0,1,0,0,0,0,0,0,1,1,1,1,1,0,0,0,1,0,0,0,0,0,0,1,1,1,1,1,0,0,0,1,1,1,1,1,0,0,1,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,1,1,1,1,0,0,0,0,0,0,0,1,1,0,1,1,1,1,1,0,0,0,0,0,0,0,0,1,0,1,1,1,1,1,0,0,0,0,0,0,0,0,4,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,3,1,1,1,1,1,0,0,0,0,0,0,1,1,0,1,1,0,0,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,0,1,1,0,0,0,1,1,1,1,1,0,0,1,1,0,1,1,0,0,0,0,0,0,0,1,0,0,1,1,0,1,1,0,0,0,0,0,0,0,1,0,0,1,1,0,1,1,0,0,0,0,0,1,1,1,1,0,1,1,0,1,1,0,0,0,0,0,1,0,0,1,0,1,1,0,1,1,0,0,0,0,0,1,0,0,0,0,1,1,0,1,1,1,1,1,1,0,1,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,4,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0'
a1= list(a)
b=[]
for each in a1:
if each!= ',':
b.append(each)
i = 0
while i < len(b):
print(b[i],end='')
if((i+1)>=15 and (i+1)%15==0):
print("\n")
i += 1
再往下分析输入d时调用的函数。
首先这个大的do..while循环退出条件是v2=1看出来当20数组到达4的时候,就会退出循环,输入我们的flag。然后看第一个if语句。B8为列数,B8!=14说的是还在这一列之中,我们看到第一个if的意思是右边如果是1,就把右边变成3,本来的位置变成1.这就相当于3是我自己,d的操作是把3右移了一个单位长度。其实,经常玩游戏的人都知道,d本身就是向右移动的意思。假如移到4身上,游戏结束,获得flag。
AWS的原理和这个肯定一样,无非是方向的不同。我们需要从3出发,寻找到4的位置。接下来,就让我们尝试一下。
这个还有输入esc的检验,就是if ( v0 == '\x1B' )。\x1B的意思是esc。返回的0x好多 F其实就是-1,相当于退出游戏。
那么这个代码是什么意思呢。这个代码执行的时候已经 走到4的位置了,B0是乘以225的量,他要是变化了,不就溢出了?其实不是的,参考了CTF-reverse-reverse_re3(全网最详细wp,超4000字有效解析)_ctfreverse题目-优快云博客
师傅的题解,发现其实20这个数组是3个小迷宫 ,因为20的长度是675,刚好是225*3,而225=15*15。到这里一切都明白了。而且题目上说:不要走回头路,也给我们解出迷宫提示了部分信息。
最后效果大约是如此。
里面的1是什么情况?还记得我们分析d的时候嘛?假如向右移动,就把3左边的数字赋值为1.显而易见,题目上和3靠近的1肯定是移动后的1.题目也给了我们提示,就是找1.然后,我们把对应的wasd输进去,程序就会按照算法计算,如果最后确实到达4,会输出md5加密后的wasd。
检验之后发现思路是正确的。md5加密后就可以得到答案了
不过发现少数一个s也会提示这样的弹窗,现在还不知道为啥。以后填坑。