[SHCTF]新生赛 – 部分Reverse题目解析

比赛信息

比赛名称:SHCTF-“山河”网络安全技能挑战赛

比赛平台:山河CTF

参赛对象:齐鲁工业大学、西安邮电大学、广东海洋大学、长春工程学院、郑州轻工业大学、河南大学、齐鲁师范学院、陕西邮电职业技术学院、陕西工业职业技术学院、商丘师范学院、咸阳师范学院 (排名不分先后)等十余所高校参与

比赛时间:2023-10-02 ~ 2023.10.29

协办单位:山东汉任信息安全技术有限公司,吉林谛听信息技术有限公司

题目详解

拿到题目之后,从题目名字中的”asm”可以看出,这是一道考察汇编语言的题目,我们要有一定的汇编语言基础。下载下来可以看到给了我们一个txt文本,那么这里面一定是汇编块。打开之后确实是汇编块。

图片[1],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

那么接下来就是 找到右上方的(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[M7mc7uhc$7midgbTf7`$7%#ubf7 ci5Y填进去,并运行这段代码,即可输出正确的flag:flag{It_is_als0_impor@nt_t0_13arn_4sm!}

将题目下载下来,发现这是一个可执行程序,那么我们要做的第一步就是查壳,确定程序的PE类型:(查壳图片略),查壳过后我们发现这是一个x64可执行程序,打开之后我们会发现他是直接让我们输入flag的:

图片[2],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

那根据我们的直觉或者经验,可以看出,他肯定有一套验证flag是否正确的算法,我们直接拖进IDA看看之后发现,所有的验证代码全都在Main函数里,而且没有任何混淆:

图片[3],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

从伪代码的这一部分分析可以知道,处理过后的字符串通过与des字符数组对比,判断是否相等,从而得出是否正确,那么des的字节就是我们需要的,我们双击des可以跳转到des的数据块,从这里可以看到des的所有字节数据:

图片[4],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

我们直接从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;
}
图片[5],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

将右图字节集转换为字符串即为flag:flag{Try_t0_s0lv3_the_binary_pr0bl3m}

下载题目,发现是一个可执行程序,查壳发现是一个64位的可执行程序,运行,啥玩意都没有,我们直接拖进IDA查看Main函数,好了,flag就在眼前:flag{flag1sinarray}

图片[6],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

下载题目,发现是一个可执行程序,查壳发现是一个64位的可执行程序,运行,直接让我们输入falg,我们直接拖进IDA查看Main函数,可以看到这里与[WEEK1]easy_re是大同小异的,都是异或操作之后与内存中的一串字节des对比,我们直接逆运算即可得出来原本的输入数据,要注意这里是与取随机数之后的v5数组进行异或,我们要先模拟v5,再进行异或。

图片[7],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

解密代码以及运行结果如下:

#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;
}
图片[8],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

执行代码,并输出数据即可得到flag的字节集,转换为字符串即可得到flag:flag{Give_y0u_the_se3d_and_D0_you_w@nt_t0_do}

下载题目,发现是一个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代码:

图片[9],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

运行后即可得到原始的flag字符串,即为:flag{N0_One_kn0ws_m@th_B3tter_Th@n_me!!!!}

图片[10],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

下载题目,发现是一个可执行程序,查壳发现是一个32位的可执行程序,运行,随便输入一个密钥,会输出给我们一份加密后的字符串,我们直接拖进IDA查看Main函数:

图片[11],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

进入IDA后,我们发现,程序开始对ArgList已经赋值了,紧接着又对其部分位置的字符进行了修改,此处的代码编译器进行了优化,我们很难看出来他的执行逻辑,那么我们可以另辟蹊径,通过动态调试来拿到修改后的ArgList的数值,此处动态调试的过程不再展示。

紧接着他又利用我们输入的那个密钥对ArgList进行了进一步的加密,然后输出了加密后的字符串。此处出题人其实是在迷惑我们,其实第一次处理后的ArgList就是用Base64编码后的字符串,我们直接动态调试出第一次处理后ArgList的字节集,然后用伪代码中出题人给的异或算法输出Base64编码后的字节集,将输出的字节集转换为字符串,并将字符串用Base64解码即可得到flag:flag{a10e7ccc-b802-e3eb-c85940e226d}

下面是解密代码以及运行结果:

图片[12],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网
图片[13],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

拿到题目我们可以发现是.bc文件,经过搜索我们可以知道,bc文件为LLVM IR bitcode文件,所谓LLVM IR bitcode文件 ,就是代码到可执行文件的中间码。我们可以使用命令将其继续编译下去 ,生成可执行文件。

在linux下 ,我们使用命令clang not_gcc.bc -o not_gcc(这里文件需要改名 ,题目文件有空格 ,将其改为_)

图片[14],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

生成可执行文件,我们放入ida中进行分析:

图片[15],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

各种跳转 ,有点花 ,不好厘清逻辑,动态调试 ,厘清一下思路:

先判断字符长度 ,81位:

图片[16],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

判断第一个输入的字符是否为0:

图片[17],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

判断输入 ,输入字符的都必须满足1~9:

图片[18],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

进入关键逻辑,进入函数内部 ,分析逻辑可以知道 ,是生成了9*9的数独盘 ,以0为标志 ,作为未填充:

图片[19],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网
图片[20],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

因此需要我们写脚本将数独解出:

图片[21],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网
图片[22],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

我们可以解出数独为:497513268538426917612987354759164823261839475843275196986351742125748639374692581

这个就是我们的解出来的数独,但是由于程序有第⼀位检测,所以我们需要将第⼀位的4改为0
因此:097513268538426917612987354759164823261839475843275196986351742125748639374692581
就是我们最终的数独,我们将其输入到脚本中可得到:

图片[23],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

将flag按照出题人要求的,将数独取MD5哈希值,即为flag。

但此处解出来flag之后,出题人并未指明flag的具体格式,在比赛中途我没有再去尝试这一题。

下载题目,发现是一个可执行文件,打开之后需要我们输入Key,Key的范围是0-100,我直接用爆破的方式了,解出来key是23,输入之后,下面让我们输入flag,我们将其拖入DIE查看,会发现他的Packer是PyInstaller,从这里可以判断这个是用Py打包的方式生成的可执行文件:

图片[24],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

那么我们直接调用pyinstxtractor对其进行解包,拿到其中的python脚本文件(.pyc),此程序的主要脚本是main.pyc,然后再使用python字节码反汇编工具pycdc得到正常的python源代码:

图片[25],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网
图片[26],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

反汇编出来python源代码之后,我们要注意的是:我们需要配置与他的源代码一致的python环境,从反汇编的信息上来看,这个脚本是python3.8的版本,我们配置完环境之后直接调试这段代码即可,我们在图中指向的地方下断点,这里的主要目的是为了拿到他处理后的执行代码,flag的字节就在里面。

图片[27],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

断下来之后,我们查看输出窗口,找到co_consts,展开就可以看到与flag有关的信息,从中我们可以看到,flag是一个用rc4算法加密之后的字符串,并且加密时用的key也显示了出来。程序用rc4算法对你输入的flag进行加密,验证是否与加密后的flag相等,如果相等,就会输出yes,否则输出no,那么到了这里我们直接调用rc4的解密算法来对flag进行解密即可。

图片[28],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网
图片[29],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

运行以上代码,即可得到flag:flag{d8e8d9d0-b2b1-7304-74b760-90b11ab6a3}

图片[30],[SHCTF]新生赛 – 部分Reverse题目解析,网络安全爱好者中心-神域博客网

尾言

以上解析部分代码不能提供文本,主要原因在于我自己,因为写每一题的时候,都直接把上一题的代码覆盖掉了,然后昨天手贱把这次CTF的所有代码都删了,真无语。

如果有错误的地方,请及时指出,喵~~~!

------本文已结束,感谢您的阅读------
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发
头像
善语结善缘,恶语伤人心
提交
头像

昵称

取消
昵称常用语 夸夸
夸夸
还有吗!没看够!
表情图片

    暂无评论内容