Reversing.kr 刷题笔记 - 1

0. 简介

这里将记录一些笔者学习 reversing.kr 中的逆向题所留下的笔记。

这篇笔记所记录的题目分值为 100-120。

1. Easy Crack

IDA 32 位打开,通过交叉引用:

image-20211209225604780

image-20211209225615397

很容易找到目标函数,并定位关键判断语句:

image-20211209225657036

因此可以很容易得出 flag 为: Ea5yR3versing

image-20211209230836059

2. Easy Keygen

下下来一个压缩包,ReadMe.txt 中写道:

1
Find the Name when the Serial is 5B134977135E7D13

同时打开程序,窗口提示输入 name:

image-20211209231245555

看来这题应该是要我们根据 Serial 来反推输入的 Name。

IDA 打开,发现一个简易的映射算法:

image-20211209231510613

于是我们可以根据该算法来编写一个简易的解密算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>

using namespace std;

int main() {
string serial = "5B134977135E7D13";
char key[3] = { 16, 32, 48 };
int hex_val;
for (int i = 0; i < serial.size(); i += 2) {
sscanf(serial.substr(i, 2).c_str(), "%x", &hex_val);
cout << (char)(hex_val ^ key[(i/2) % 3]);
}

return 0;
}

解出 flag:K3yg3nm3

image-20211209233454523

3. Easy_ELF

确实很 Easy。程序首先会读取一个字符串,之后对字符串进行以下判断:

image-20211209234452851

将该判断逆向一下,就可得到 flag:L1NUX

4. Easy Unpack

看上去这是一个需要脱壳的程序,但根据 IDA 反编译结果来看,应该是一个压缩壳。直接从 main 函数的反汇编列表往下拉到最底下,最后的那个 jmp 指令跳转的位置就是 OEP。

跳转前:

image-20211210001019055

跳转后(该部分代码是 _start 函数的反汇编代码):

image-20211210001149346

因此 OEP 为 00401150,而这也正是要提交的 flag。

5. ImagePrc

首先查看 WinMain 逻辑:

image-20211212115326564

我们可以很容易的找到事件处理例程,并通过字符串交叉对比,找到真正的校验位置:

image-20211212121036289

位图大小为 200 x 150,若有 90000 个像素相同则正确。而 *v13v13[v14] 应该指向的是两块不同的位图,要是能dump下来看看,估计就能看出结果。

位图大小总像素点个数:200x150x3 = 90000,RGB 格式。

别的也看不出什么了,在调试时先随手画个 A 留个标记,之后尝试用 ida dump 内存出来看看。

dump 脚本:

1
2
3
4
5
6
7
import idaapi
start_address = 0x2FA0048
data_length = 200*150*3
data = idaapi.dbg_read_memory(start_address , data_length)
fp = open('./dump.bin', 'wb')
fp.write(data)
fp.close()

用 Python 处理一下 dump 出来的内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#! python3
# `pip3 install pillow` to enable PIL
from PIL import Image, ImageDraw
import sys

im = Image.new("RGB", (200, 150))

with open("./dump.bin", "rb") as f:
for j in range(150):
for i in range(200):
r = int.from_bytes(f.read(1), 'little')
g = int.from_bytes(f.read(1), 'little')
b = int.from_bytes(f.read(1), 'little')
im.putpixel((i, j), (r,g,b))

# im.save("dump.png")
im.show()

v13 对应的图像如下:

image-20211212145026054

可以看到刚好 dump 出来的图片是上下倒置的。

接着我们就如法炮制,将 v13[v14] 的图片也 dump 出来:

image-20211212145155253

即 flag 为 GOT

6. Ransomware

压缩包解压,根据 readme 的描述,可以看到要求我们解密 file 文件。

把目标文件拖到 Exeinfo 里一看,加了个 UPX压缩壳:

image-20211212155306762s

因此直接用脱壳机脱壳:

image-20211212155515369

之后用 IDA 打开看看:

image-20211212155709684

可以发现在 main 函数中存在超大量无用指令,使得 IDA 无法进行反汇编,提高分析难度,因此我们需要尝试去掉这些指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data = None
with open("./run.exe", "rb") as f:
data = f.read()
'''
UPX0:0044A73D 60 pusha
UPX0:0044A73E 61 popa
UPX0:0044A73F 90 nop
UPX0:0044A740 50 push eax
UPX0:0044A741 58 pop eax
UPX0:0044A742 53 push ebx
UPX0:0044A743 5B pop ebx
'''
new_data = data.replace(b"\x60\x61\x90\x50\x58\x53\x5b", b"\x90" * 7)
with open("./run_dump_patch.exe", "wb") as f:
f.write(new_data)

而且在 sub_401000 函数中,整个函数体全部充斥着这类指令,即该函数是一个空函数体。为了防止混淆,我们将 sub_401000 函数名称修改为 nop_func

接着非常悲剧的发现,main 函数还是因为函数太大无法被反汇编…

莫得办法了,只能将函数头的

1
2
3
4
5
6
UPX0:004135E0 55                                      push    ebp
UPX0:004135E1 8B EC mov ebp, esp
UPX0:004135E3 83 EC 24 sub esp, 24h
UPX0:004135E6 53 push ebx
UPX0:004135E7 56 push esi
UPX0:004135E8 57 push edi

移动到末尾:

image-20211212154417401

然后修改一下函数的起始位置:

image-20211212154503604

之后就可以照常反编译了,以下是经过简化的反汇编代码。

不过需要注意的是,这里修改函数的起始地址,指的是IDA 静态分析的起始地址。实际上函数调用 main 时仍然会跳转回原先的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int __cdecl main(int argc, const char **argv, const char **envp)
{
[...]
printf("Key : ");
scanf("%s", key);
v3 = strlen(key);
v7 = 0;
Stream = fopen("file", "rb");
if (!Stream)
;/* exit */
fseek(Stream, 0, 2);
v6 = ftell(Stream); // 获取文件长度
rewind(Stream);
while (!feof(Stream)) // 将文件数据读入 buf
{
buf[v7] = fgetc(Stream);
++v7;
}
for ( i = 0; i < v6; ++i ) // 尝试加密
{
buf[i] ^= key[i % v3];
buf[i] = ~buf[i];
}
fclose(Stream);
v5 = fopen("file", "wb");
for ( j = 0; j < v6; ++j )
{
fputc(buf[j], v5);
}
printf(asc_44C1E8);
return getch();
}

最核心的就是这部分加密算法:

1
2
3
4
5
6
7
// v6 为文件数据长度
for ( i = 0; i < v6; ++i ) // 尝试加密
{
// v3 为 key 长度
buf[i] ^= key[i % v3];
buf[i] = ~buf[i];
}

而 readme.txt 的描述是这样的:Decrypt File (EXE)。也就是说那个 file 文件实际上就是加密后的 exe 文件。

在已有明文、密文并了解加密算法的情况下,我们便可以很容易的将密钥解出来。需要注意的是这里选取的是两个 exe 文件(一个加密前一个加密后)的前30 字节,因为应该所有 exe 文件的前30个字节都相同。

以下是暴力枚举密钥的算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

int main() {
// hexdump -n 30 run.exe.bak -e '30/1 "\\x%x"'
const char* origin_text = "\x4d\x5a\x90\x0\x3\x0\x0\x0\x4\x0\x0\x0\xff\xff\x0\x0\xb8\x0\x0\x0\x0\x0\x0\x0\x40\x0\x0\x0\x0\x0";
// hexdump -n 30 file -e '30/1 "\\x%x"'
const char* cipher_text = "\xde\xc0\x1b\x8c\x8c\x93\x9e\x86\x98\x97\x9a\x8c\x73\x6c\x9a\x8b\x34\x8f\x93\x9e\x86\x9c\x97\x9a\xcc\x8c\x93\x9a\x8b\x8c";
for (size_t i = 0; i < strlen(cipher_text); i++) {
int key;
for (key = 0; key < 255; key++) {
if ((origin_text[i] ^ key) == (~cipher_text[i])) {
cout << (char)key;
break;
}
}
if (key == 0xff)
abort();
}

return 0;
}

输出密钥为 letsplaychess

image-20211212164923948

因此再解密一下 file 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

int main() {
FILE* cipher_file = fopen("C:\\Users\\Kiprey\\Desktop\\rever\\ransomware\\file", "rb");
FILE* output_file = fopen("C:\\Users\\Kiprey\\Desktop\\rever\\ransomware\\flag.exe", "wb");

const char* key = "letsplaychess";

fseek(cipher_file, 0, SEEK_END);
size_t cipher_len = ftell(cipher_file); // 获取文件长度
rewind(cipher_file);

for (size_t i = 0; i < cipher_len; ++i) // 尝试加密
{
unsigned char buf = fgetc(cipher_file);
buf = ~buf;
buf ^= key[i % strlen(key)];
fputc(buf, output_file);
}

fclose(cipher_file);
fclose(output_file);
return 0;
}

解出一个 flag.exe,运行下看看:

image-20211212165915247

晕,从别处拷了一些 DLL 过来,终于解出来了…

image-20211212170048738

flag为 Colle System

7. CSHOP

程序打开后没有任何可交互部分。拖进 IDA 发现是个 .NET 程序,直接用 dnSpy x86 打开(不得不说 dnSpy 的界面是真的好看):

image-20211212173934346

简单通读了一下代码,该窗口有 10 个 Label 和 1 个 Button,当 Button 被按下后,这10个 label 将显示出对应的文字(应该是 flag)。但问题是, Button 的 size 为 (0,0),因此正常情况下我们无法点击该 Button。

不过我们可以尝试修改这个 IL:

image-20211212174305594

这两个值改大一点,然后 File -> Save Module

image-20211212174327671

重新打开被 patch 后的程序,并点击按钮,即可出现 flag:

image-20211212175001051

flag 为 P4W6RP6SES

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2020-2022 Kiprey
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~