December 19, 2023

强网拟态2023线下 REVERSE

easy_firmware

描述:世界上最简单的固件逆向

0x00 Daily Shell Check

喜闻乐见的stm32,关于IDA怎么加载不再赘述,arm32小端序,根据开始几个函数地址判断基地址是 0x8000000,然后全选分析即可

https://bbs.kanxue.com/thread-247140.htm

image-20231220224307962

0x01 Find data

首先可以通过字符串比较容易的分析整个加密,只是对我们的输入进行了AES加密

image-20231221131302883

那么,密钥和密文呢…当时做这题的估计所有人都卡这个步骤了,这个内存 0x20000024 到底是什么数值,毕竟又运行不起来,因为qemu只对stm32的部分板子支持,所以该怎么拿到这个值呢?

image-20231221131734306

于是就整个文件乱翻,在结尾处找到神秘数据,就是这段了,可以从上图可知密钥和密文在内存中连续的,那么也可以得知肯定又哪个函数调用了这些数据,把他们加载到了内存,于是尝试对所有数据进行交叉引用,发现是引用不到的,因为ida没把一个关键函数分析出来,我们可以搜索整个文件看哪里引用了这个地址

1
2
3
4
5
6
7
start = 0x8000000
end = 0x8012408
while (start < end):
if (get_wide_byte(start) == 0xA4 and get_wide_byte(start + 1) == 0x24):
print(hex(start))
start += 1
print('OK!')

于是可以找到这个真正的main函数?在这里初始化了这些数据到内存,然后跳到了可以从字符串分析的sub_80000F4函数

image-20231221132010909

然后可以稍微审计一下就可以知道是怎么加载数据的了

image-20231221132140028

当然还有一个好用的插件,不过用unicorn来模拟一样,不过这个插件挺方便的

https://github.com/alexhude/uEmu

于是得到我们所需的内存数据

image-20231221132659056

0x02 GetFlag

于是数据有了,就是我们喜闻乐见的解密环节,然后AES ECB解密失败,因为魔改了,然后掏出祖传AES解密脚本

https://github.com/kokke/tiny-AES-c

而这次魔改的点其实就一处也很明显,但最恶心的就是用了强制转换成了 int 类型,这里用bn更好看

image-20231221133148179

注意这里的加密我本来以为只是个循环右移,但这里用了 int 类型,于是变得奇怪了起来,总结来说

就是需要注意 (arg1 >> 0x1f),这里进行算数右移31了一个int32的数据,于是只会出现两个值

也就是这样

1
2
3
4
5
int32_t num = 0xF2345678;
int32_t tmp = ((num << 1) ^ (num >> 0x1f));
printf("%X %X %X\n", tmp, (num << 1), (num >> 0x1f));
int32_t out = (((tmp >> 1)) ^ (-(tmp & 1)));
printf("%X %X %X\n", out, ((tmp >> 1)), (-(tmp & 1)));

那么搞清楚这个,就比较容易的写处逆向脚本

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
33
34
static void InvCipher(state_t* state, const uint8_t* RoundKey)
{
uint8_t round = 0;

// Add the First round key to the state before starting the rounds.
AddRoundKey(Nr, state, RoundKey);

// There will be Nr rounds.
// The first Nr-1 rounds are identical.
// These Nr rounds are executed in the loop below.
// Last one without InvMixColumn()

for (round = (Nr - 1); ; --round)
{
int i;
uint32_t* pp = (uint32_t*)state;
for (i = 0; i < 4; i++)
{
printf("%X, ", pp[i]);
pp[0] = ((pp[0] >> 1) ^ (-((int)pp[0] & 1)));
printf("%X, ", pp[i]);
//printf("%X, ", ((uint32_t*)state)[i]);
}
printf("\n\n");
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(round, state, RoundKey);
if (round == 0) {
break;
}
InvMixColumns(state);
}

}

Get Flag!

image-20231221134418060

Keygen

0x00 Daily Shell Check

apk逆向,无shell

0x01 Native Time

当然也可以审计分析流程

  1. Java_com_qwctf_keygen_MainActivity_start(pthread_create起了个线程函数sub_4D8C来处理我们的输入)
  2. sub_4D8C函数把输入分成了三部分,第一部分是flag{ 第二三个部分是输入的两个部分 第四个部分是}
    • 同时明文全都给加密了, sub_185C解密了我们所需要的数据第一个参数是key,第二个参数是key的长度,三四就是密文和长度

image-20231221135909197

  1. sub_55E4和另一个函数分别加密了我们明文的两个部分,不过都一样,无魔改RC4和无魔改AES,直接解密即可,拿到密钥直接加密即可

image-20231221140108117

至此整个流程分析完毕,其实这题就是个非常常见的加密流程,运行解密字符串,进行验证然后再把字符串解密回去,但由于ollvm不想分析加上出题人不知道怎么写的界面导致无法触发check函数,于是做的很慢,最后还是硬分析完了

0x02 GetFlag

1
2
key1:  0x21, 0xCF, 0xCC, 0x68, 0xEA, 0xC5, 0x0A, 0x87, 0xFA, 0x60, 0xDB, 0x52, 0x99, 0xD4, 0xDC, 0x1B
key2:  0xA9, 0x86, 0xC8, 0xC8, 0x5F, 0x57, 0xF8, 0x7E, 0x57, 0x30, 0x69, 0x27, 0xD1, 0x4C, 0x41, 0x5C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from Crypto.Cipher import AES       
flag = b''

enc = bytes.fromhex("C23093CDC44CAEB783372818757E4FA6")
key = bytes([0x21, 0xCF, 0xCC, 0x68, 0xEA, 0xC5, 0x0A, 0x87, 0xFA, 0x60, 0xDB, 0x52, 0x99, 0xD4, 0xDC, 0x1B])
cipher = AES.new(key, AES.MODE_ECB)
enc = cipher.decrypt(enc)
flag += enc

enc = bytes.fromhex("2CFF2945D7432A34D8E0A15F2F62BB62")
key = bytes([0xA9, 0x86, 0xC8, 0xC8, 0x5F, 0x57, 0xF8, 0x7E, 0x57, 0x30, 0x69, 0x27, 0xD1, 0x4C, 0x41, 0x5C])
cipher = AES.new(key, AES.MODE_ECB)
enc = cipher.decrypt(enc)
flag += enc

print(flag)

Get Flag!

image-20231221140524784

DASCTF X SU
🍬
HFCTF2022
🍪

About this Post

This post is written by P.Z, licensed under CC BY-NC 4.0.