比赛信息
题目详解
拿到题目之后,从题目名字中的”asm”可以看出,这是一道考察
那么接下来就是 找到右上方的(x)用小手点击一下鼠标左键 哐哐一顿分析啦,详细的分解步骤就不多说了,这里涉及到汇编语言的知识,我简单解释一下它的逻辑:
这段汇编代码的功能是对一个字符串(flag)进行加密处理,然后输出加密后的结果。具体的操作步骤如下:
- 首先,定义一个变量var_4,用来存储字符串的索引,初始值为0。
- 然后,进入一个循环,对字符串的每个字符进行两次异或和减法操作,具体如下:
- 将var_4作为偏移量,从flag中取出一个字符,与16进制数0x1E异或,得到新的字符。
- 将新的字符存回flag中覆盖原来的字符。
- 再次从flag中取出同一个字符,减去16进制数0x0A,得到最终的加密字符。
- 将最终的加密字符存回flag中覆盖原来的字符。
- 将var_4加一,准备处理下一个字符。
- 循环结束的条件是var_4等于16进制数0x27,也就是字符串的长度。
- 最后,调用printf函数,输出加密后的字符串。
下面是我们对这段ASM加密算法翻译后的代码:
#include<string>
#include<iostream>
using namespace std;
string encrypt(string s) {
string result = "";
for (char c : s) {
unsigned int x = (unsigned int)c;
x ^= 0x1E;
x -= 0x0A;
result += (char)x;
}
return result;
}
int main() {
string flag = "";
string crypt_str = encrypt(flag);
cout << crypt_str << endl;
return 0;
}
那么知道了加密的原理,下面就是解密啦,我们只需要把这段加密过程逆过来运算即可:
- 逐一遍历flag的每一个字符,对每一个字符进行逆运算操作。
- 将每一个字符都加上16进制数0x0A
- 再将每一个字符都与16进制数0x1E进行异或操作
- 最后逐一储存在result中并返回原始字符串
代码如下:
#include<string>
#include<iostream>
using namespace std;
string decrypt(string s) {
string result = "";
for (char c : s) {
unsigned int x = (unsigned int)c;
x += 0x0A;
x ^= 0x1E;
result += (char)x;
}
return result;
}
int main() {
string flag = "";
string crypt_str = decrypt(flag);
cout << crypt_str << endl;
return 0;
}
我们将题目提供的加密后的字符串:nhuo[M
将题目下载下来,发现这是一个可执行程序,那么我们要做的第一步就是查壳,确定程序的PE类型:(查壳图片略),查壳过后我们发现这是一个x64可执行程序,打开之后我们会发现他是直接让我们输入flag的:
那根据我们的直觉或者经验,可以看出,他肯定有一套验证flag是否正确的算法,我们直接拖进IDA看看之后发现,所有的验证代码全都在Main函数里,而且没有任何混淆:
从伪代码的这一部分分析可以知道,处理过后的字符串通过与
我们直接从HEX进制窗口中复制这一段字节:
66 C6 16 76 B7 45 27 97 F5 47 03 F5 37 03 C6 67 33 F5 47 86 56 F5 26 96 E6 16 27 97 F5 07 27 03 26 C6 33 D6 D7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
代码段Str[i] = (Str[i] >> 4) | (16 * Str[i]);其实是位操作中的典型算法:位交换,即把每一个字节的高低位互换位置,那么如果我们想要得到原始数据,就需要把des的高低位互换回去,我们只需要重新调用一次这个算法即可,代码以及运行结果如下:
#include<string>
#include<iostream>
#include<stdio.h>
#include<windows.h>
using namespace std;
int main() {
BYTE des[64] = {
0x66, 0xC6 ,0x16 ,0x76 ,0xB7 ,0x45 ,0x27 ,0x97 ,
0xF5 ,0x47 ,0x03 ,0xF5 ,0x37 ,0x03 ,0xC6 ,0x67 ,
0x33 ,0xF5 ,0x47 ,0x86 ,0x56 ,0xF5 ,0x26 ,0x96 ,
0xE6 ,0x16 ,0x27 ,0x97 ,0xF5 ,0x07 ,0x27 ,0x03 ,
0x26 ,0xC6 ,0x33 ,0xD6 ,0xD7 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00
};
for (auto i = 0; i < 64; ++i) {
des[i] = (des[i] >> 4) | (16 * des[i]);
printf("%X ", des[i]);
}
return 0;
}
将右图字节集转换为字符串即为flag:
下载题目,发现是一个可执行程序,查壳发现是一个64位的可执行程序,运行,啥玩意都没有,我们直接拖进IDA查看Main函数,好了,flag就在眼前:flag{flag1sinarray}
下载题目,发现是一个可执行程序,查壳发现是一个64位的可执行程序,运行,直接让我们输入falg,我们直接拖进IDA查看Main函数,可以看到这里与[WEEK1]easy_re是大同小异的,都是异或操作之后与内存中的一串字节des对比,我们直接逆运算即可得出来原本的输入数据,要注意这里是与取随机数之后的v5数组进行异或,我们要先模拟v5,再进行异或。
解密代码以及运行结果如下:
#include<string>
#include<iostream>
#include<stdio.h>
#include<windows.h>
using namespace std;
int main() {
DWORD des[48] = {
0x00000040 ,0x00000029 ,0x00000028 ,0x000000E9 ,0x000000C2 ,0x00000004 ,0x000000A4 ,0x000000ED ,
0x0000009F ,0x00000053 ,0x0000005F ,0x00000075 ,0x0000003C ,0x000000D1 ,0x000000CD ,0x0000002B ,
0x000000A8 ,0x000000C4 ,0x00000089 ,0x00000069 ,0x00000015 ,0x00000021 ,0x00000016 ,0x000000EF ,
0x000000D7 ,0x00000027 ,0x00000092 ,0x000000DF ,0x000000CA ,0x00000053 ,0x0000005F ,0x0000002A ,
0x0000003C ,0x000000D1 ,0x000000CE ,0x00000003 ,0x000000A3 ,0x000000EF ,0x000000A5 ,0x00000078 ,
0x00000016 ,0x0000001A ,0x0000002D ,0x000000E1 ,0x000000C4 ,0x00000000 ,0x00000000 ,0x00000000
};
BYTE v5[10];
srand(0);
for (auto i = 0; i <= 9; ++i)
{
v5[i] = rand() % 255;
}
for (size_t i = 0; i <= 44; i++)
{
auto v1 = des[i] ^ v5[i % 10];
printf("%X ", v1);
}
return 0;
}
执行代码,并输出数据即可得到flag的字节集,转换为字符串即可得到flag:
下载题目,发现是一个py文件,里面是关于对flag进行加密的算法,分析一下算法可知:
- 要求输入flag,并且程序会检查输入的字符串是否是42个字符长。如果不是,程序将打印出”Check your length!”并且退出程序。
- 如果输入的字符串长度符合要求,程序将进入一个循环。在这个循环中,输入的字符串被分成6个部分,每个部分包含7个字符。然后将每个部分中的字符转换成对应的ASCII码,再转换成16进制的形式。这些16进制数值会被存储在列表l中。
- 接下来的部分是一系列的条件判断。程序会检查列表l中的值是否满足一组复杂的方程式,其中每个方程式的右边都是一个十六进制数。如果所有的条件都满足,程序会打印”Good job!”,否则打印”Wrong\nTry again!!!”并退出程序。
题目源代码如下:
print("Please input flag:")
flag = input()
if len(flag)!=42:
print("Check your length!")
exit()
l=[]
for i in range(6):
s=""
for j in flag[i*7:i*7+7]:
s+=hex(ord(j))[2:]
l.append(int(s,16))
if (
(593*l[0] + 997*l[1] + 811*l[2] + 258*l[3] + 829*l[4] + 532*l[5])== 0x5b8e0aef71d34ff43 and \
(605*l[0] + 686*l[1] + 328*l[2] + 602*l[3] + 695*l[4] + 576*l[5])== 0x551a262360964ef7f and \
(373*l[0] + 512*l[1] + 449*l[2] + 756*l[3] + 448*l[4] + 580*l[5])== 0x49d158a5657d6931c and \
(560*l[0] + 635*l[1] + 422*l[2] + 971*l[3] + 855*l[4] + 597*l[5])== 0x625568d5abbabf4f3 and \
(717*l[0] + 507*l[1] + 388*l[2] + 925*l[3] + 324*l[4] + 524*l[5])== 0x50ee0c025e70e3c23 and \
(312*l[0] + 368*l[1] + 884*l[2] + 518*l[3] + 495*l[4] + 414*l[5])== 0x40e735f8aa2815f65):
print("Good job!")
else:
print("Wrong\nTry again!!!")
exit()
其实到了这一步,看到了解方程这里,这道题目就很简单了,我们直接调用python的sympy库对方程进行求解,即可得到原始flag的数据,下面是简单的python代码:
运行后即可得到原始的flag字符串,即为:
下载题目,发现是一个可执行程序,查壳发现是一个32位的可执行程序,运行,随便输入一个密钥,会输出给我们一份加密后的字符串,我们直接拖进IDA查看Main函数:
进入IDA后,我们发现,程序开始对ArgList已经赋值了,紧接着又对其部分位置的字符进行了修改,此处的代码编译器进行了优化,我们很难看出来他的执行逻辑,那么我们可以另辟蹊径,通过动态调试来拿到修改后的ArgList的数值,此处动态调试的过程不再展示。
紧接着他又利用我们输入的那个密钥对ArgList进行了进一步的加密,然后输出了加密后的字符串。此处出题人其实是在迷惑我们,其实第一次处理后的ArgList就是用Base64编码后的字符串,我们直接动态调试出第一次处理后ArgList的字节集,然后用伪代码中出题人给的异或算法输出Base64编码后的字节集,将输出的字节集转换为字符串,并将字符串用Base64解码即可得到flag:
下面是解密代码以及运行结果:
拿到题目我们可以发现是.bc文件,经过搜索我们可以知道,bc文件为LLVM IR bitcode文件,所谓LLVM IR bitcode文件 ,就是代码到可执行文件的中间码。我们可以使用命令将其继续编译下去 ,生成可执行文件。
在linux下 ,我们使用命令clang not_gcc.bc -o not_gcc(这里文件需要改名 ,题目文件有空格 ,将其改为_)
生成可执行文件,我们放入ida中进行分析:
各种跳转 ,有点花 ,不好厘清逻辑,动态调试 ,厘清一下思路:
先判断字符长度 ,81位:
判断第一个输入的字符是否为0:
判断输入 ,输入字符的都必须满足1~9:
进入关键逻辑,进入函数内部 ,分析逻辑可以知道 ,是生成了9*9的数独盘 ,以0为标志 ,作为未填充:
因此需要我们写脚本将数独解出:
我们可以解出数独为:497513268538426917612987354759164823261839475843275196986351742125748639374692581
这个就是我们的解出来的数独,但是由于程序有第⼀位检测,所以我们需要将第⼀位的4改为0
因此:097513268538426917612987354759164823261839475843275196986351742125748639374692581
就是我们最终的数独,我们将其输入到脚本中可得到:
将flag按照出题人要求的,将数独取MD5哈希值,即为flag。
但此处解出来flag之后,出题人并未指明flag的具体格式,在比赛中途我没有再去尝试这一题。
下载题目,发现是一个可执行文件,打开之后需要我们输入Key,Key的范围是0-100,我直接用爆破的方式了,解出来key是23,输入之后,下面让我们输入flag,我们将其拖入DIE查看,会发现他的Packer是PyInstaller,从这里可以判断这个是用Py打包的方式生成的可执行文件:
那么我们直接调用
反汇编出来python源代码之后,我们要注意的是:我们需要配置与他的源代码一致的python环境,从反汇编的信息上来看,这个脚本是python3.8的版本,我们配置完环境之后直接调试这段代码即可,我们在图中指向的地方下断点,这里的主要目的是为了拿到他处理后的执行代码,flag的字节就在里面。
断下来之后,我们查看输出窗口,找到
运行以上代码,即可得到flag:
尾言
以上解析部分代码不能提供文本,主要原因在于我自己,因为写每一题的时候,都直接把上一题的代码覆盖掉了,然后昨天手贱把这次CTF的所有代码都删了,真无语。
暂无评论内容