June 18, 2022

miniL2022-NotRC4

我是懒狗,懒狗是我,拖到今天。–6.18

0x00 日常查壳

拖进IDA直接闪退,好!

image-20220618193604956

linux下file查一下,可以发知道是RISC-V架构

image-20220618193637229

0x01 Ghidra Time

详见

https://ppppz.net/2022/06/15/Ghidra-learning/

稍加分析可知,符号执行题,那么一个个分析即可

image-20220618194159597

0x02 Opcode

1
2
F3,00,  F4,E1,  F4,E2,  F2,04,0B,  F5,
F3,00, F4,E1, F4,E2, F2,04,0B, F5, F1,FF

分析可知,这里就是每个FX对应一个操作,这些FX的后面就是所需的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

void init_opcode(undefined4 *param_1)

{
*param_1 = 0;
*(undefined *)(param_1 + 6) = 0xf1;
*(undefined **)(param_1 + 8) = &Check;
*(undefined *)(param_1 + 10) = 0xf2;
*(undefined **)(param_1 + 0xc) = &for_count;
*(undefined *)(param_1 + 0xe) = 0xf3;
*(undefined **)(param_1 + 0x10) = &Add_Key;
*(undefined *)(param_1 + 0x12) = 0xf4;
*(undefined **)(param_1 + 0x14) = &ShiftXor;
*(undefined *)(param_1 + 0x16) = 0xf5;
*(undefined **)(param_1 + 0x18) = &NextRound;
return;
}

逐个分析,分析之前要清楚!

param_1是一个结构体(然而还没找到Ghidra处理结构体比较好的办法)

F1 | Check

这里爆个wrong基本可以猜测另一个奇怪数据是加密后的flag

之后F5函数可以确定DAT_001020c8就是加密后的input

1
2
3
4
5
6
7
8
9
10
11
12
13
void UndefinedFunction_00100b7e(int *param_1)
{
int iStack20;

for (iStack20 = 0; iStack20 < 4; iStack20 = iStack20 + 1) {
if (*(qword *)(&DAT_001020c8 + (longlong)iStack20 * 8) != (&encFlag)[iStack20]) {
printf("Wrong!");
exit(0);
}
}
*param_1 = *param_1 + 1;
return;
}

F2 | for_count

1
2
3
4
5
6
7
8
9
10
11
12
void UndefinedFunction_00100bfe(int *param_1)
{
if (count < (int)(uint)(byte)(&opcode)[*param_1 + 2]) {
*param_1 = *param_1 - (uint)(byte)(&opcode)[*param_1 + 1];
count = count + 1;
}
else {
count = 0;
*param_1 = *param_1 + 3;
}
return;
}

那么其实是什么意思呢,整理可知

1
2
3
4
5
6
for ( count = 0; count < 0xB; count++ )
{
f4,e1, f4,e2 函数操作
}
count = 0
opcode += 3

count小于的是当前F2 opcode后两位也就是0xB

而为了实现循环操作,用了当前opcode - 4的操作,也就是减去的F2 opcode后的第一位0x4

F3 | Add_Key

注意开始我说过要把param_1当成结构体

1
2
3
4
5
6
7
8
9
10
11
void UndefinedFunction_00100974(int *param_1)
{
*(longlong *)(param_1 + 2) =
*(longlong *)(&input + (longlong)(int)(uint)(byte)(&opcode)[*param_1 + 1] * 8) +
_DWORD_00102008;
*(longlong *)(param_1 + 4) =
*(longlong *)(&input + (longlong)(int)((byte)(&opcode)[*param_1 + 1] + 1) * 8) +
_DWORD_00102010;
*param_1 = *param_1 + 2;
return;
}

(param_1 + 2) 和 (param_1 + 4)可以当成所使用的临时数 X 和 Y

(&input + (longlong)(int)((byte)(&opcode)[*param_1 + 1] + 1) * 8) 分别根据F3后的第一个操作数,把我们的32位输入划分为64位

(可以想象成我们的32个字节输入是个 64位无符号整形的数组 长度为4)

整理可知

1
2
X = input[opcode] + key1
Y = input[opcode + 1] + key2

F4 | ShfitXor

Ghidra的代码亿点点怪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void UndefinedFunction_00100a10(int *param_1)
{
if ((&opcode)[*param_1 + 1] == -0x1f) {
*(ulonglong *)(param_1 + 2) =
_DWORD_00102008 +
((*(ulonglong *)(param_1 + 4) ^ *(ulonglong *)(param_1 + 2)) >>
((longlong)-(int)*(undefined8 *)(param_1 + 4) & 0x3fU) |
(*(ulonglong *)(param_1 + 4) ^ *(ulonglong *)(param_1 + 2)) <<
((longlong)(int)*(undefined8 *)(param_1 + 4) & 0x3fU));
}
if ((&opcode)[*param_1 + 1] == -0x1e) {
*(ulonglong *)(param_1 + 4) =
_DWORD_00102010 +
((*(ulonglong *)(param_1 + 4) ^ *(ulonglong *)(param_1 + 2)) >>
((longlong)-(int)*(undefined8 *)(param_1 + 2) & 0x3fU) |
(*(ulonglong *)(param_1 + 4) ^ *(ulonglong *)(param_1 + 2)) <<
((longlong)(int)*(undefined8 *)(param_1 + 2) & 0x3fU));
}
*param_1 = *param_1 + 2;
return;
}

整理可得

1
2
-0x1F: X = ((X ^ Y) >> (64 - Y & 0x3F) | (X ^ Y) << (Y & 0x3F)) + Key1
-0x1E: Y = ((X ^ Y) >> (64 - X & 0x3F) | (X ^ Y) << (X & 0x3F)) + Key2

一句话,逻辑左移加key

美其名曰,RC5

F5 | NextRound

世界线收束,注意DAT_001020c8,可以回去看了F1,可以发现这是同一个

我们X和Y赋值了进去,再清除,进行下轮input加密

1
2
3
4
5
6
7
8
9
10
void UndefinedFunction_00100af0(int *param_1)
{
*(undefined8 *)(&DAT_001020c8 + (longlong)DAT_0010210c * 8) = *(undefined8 *)(param_1 + 2);
*(undefined8 *)(&DAT_001020c8 + (longlong)(DAT_0010210c + 1) * 8) = *(undefined8 *)(param_1 + 4) ;
*(undefined8 *)(param_1 + 2) = 0;
*(undefined8 *)(param_1 + 4) = 0;
DAT_0010210c = DAT_0010210c + 2;
*param_1 = *param_1 + 1;
return;
}

0x03 GetFlag

那么整理下流量

  1. F3,00 (将input变成 X 和 Y,再加上key)
  2. F4,E1, F4,E2, F2,04,0B (循环操作,RC5加密不过是省去了初始化密钥流的操作)
  3. F5 (赋值入加密后的input数据区)
  4. F1 (与加密后的input数据区与encFlag进行比较)

关于RC5详解

好康的维基百科:https://en.wikipedia.org/wiki/RC5

EXP

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
35
#include <stdio.h>
#include <stdint.h>

#define ROTR(x, y) ((x >> (y & 0x3F)) | (x << (64 - y & 0x3F)))

int main(void)
{
uint64_t encFlag[] = { 0x4BC21DBB95EF82CA, 0xF57BECAE71B547BE, 0x80A1BDAB15E7F6CD, 0xA3C793D7E1776385 };
uint64_t key[] = { 0x64627421, 0x79796473 };
int i, j, z;

for ( i = 0; i < 4; i += 2)
{
for ( j = 0; j < 12; j++ )
{
encFlag[i + 1] = ROTR(encFlag[i + 1] - key[1], encFlag[i]) ^ encFlag[i];
encFlag[i] = ROTR(encFlag[i] - key[0], encFlag[i + 1]) ^ encFlag[i + 1];
}
encFlag[i] -= key[0];
encFlag[i + 1] -= key[1];
// printf("%p, %p, ", encFlag[i], encFlag[i + 1]);
for ( z = 0; z < 8; z++ )
{
printf("%c", encFlag[i] & 0xFF);
encFlag[i] >>= 8;
}
for ( z = 0; z < 8; z++ )
{
printf("%c", encFlag[i + 1] & 0xFF);
encFlag[i + 1] >>= 8;
}
}

return 0;
}

GetFlag!

image-20220618204557983

DASCTF X SU
🍬
HFCTF2022
🍪

About this Post

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