第一届OpenHarmony专题赛

本文最后更新于 2025年6月9日 晚上

第一届OpenHarmony专题赛

牢了两天,收获良多。分享一下这次解出的题目。

”任由世界下坠暗夜裹挟,请你聆听一座山的哽咽。在平缓心绪后依旧选择向命运吟唱高歌,在无论是晴是雨的明天里,我还是我,青山依旧巍峨。

Reverse

easyre

反编译abc看到跟flag有关的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public Object #~@0>@1*^2*#(Object functionObject, Object newTarget, Flag this) {
ldlexvar = _lexenv_0_1_;
ldlexvar.count = tonumer(ldlexvar.count) + 1;
router = import { default as router } from "@ohos:router";
r14 = router.getParams().hint1;
if ((1000000 == _lexenv_0_1_.count ? 1 : 0) == 0) {
promptAction = import { default as promptAction } from "@ohos:promptAction";
obj = promptAction.showToast;
obj2 = createobjectwithbuffer(["message", 0, "duration", 2000]);
obj2.message = _lexenv_0_1_.count + "";
obj(obj2);
return null;

}

ldlexvar2 = _lexenv_0_1_;
getH2 = r14 + ldlexvar2.getH2(_lexenv_0_1_.magic);
promptAction2 = import { default as promptAction } from "@ohos:promptAction";
obj3 = promptAction2.showToast;
obj4 = createobjectwithbuffer(["message", 0, "duration", 2000]);
obj4.message = "The flag is flag{" + getH2 + "}";
obj3(obj4);
return null;
}

flag由两部分构成,一部分是router.getParams().hint1,另一部分是ldlexvar2.getH2(lexenv_0_1.magic)

第二部分就是标准的base64,在类coder里,但是decodeToString里面有个对字符串的reverse反转操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public Object #*#decodeToString(Object functionObject, Object newTarget, Coder this, Object arg0) {
return _lexenv_0_6_(_lexenv_0_7_(arg0));
}
public Object #*#convertToString(Object functionObject, Object newTarget, Coder this, Object arg0) {
obj = arg0;
if (isfalse(instanceof(obj, ArrayBuffer)) == null) {
obj = _lexenv_0_0_(Uint8Array(obj));
}
if (isfalse(instanceof(obj, Uint8Array)) == null) {
obj = _lexenv_0_0_(obj);
}
if (("string" == typeof(obj) ? 1 : 0) == 0) {
throw(Error("Unsupported type"));
return null;
}
obj2 = "";
for (i = obj.length - 1; (i >= 0 ? 1 : 0) != 0; i = tonumer(i) - 1) {
obj2 = (obj2 == true ? 1 : 0) + obj[i];
}
return obj2;
}

密文找到是ODg0ZjMxNWYxMDJiMGI4ZGI1NjgwNWYzNGJkYzgxY2ZlYzI,赛博厨子一下得到2cefc18cdb43f50865bd8b0b201f513f488

image-20250609171327504

再看对于hint1的处理

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
public Object #~@1>@2*^2*#(Object functionObject, Object newTarget, Index this) {
i = "";
for (i2 = 0; (i2 < _lexenv_0_1_.hint1.length ? 1 : 0) != 0; i2 = tonumer(i2) + 1) {
obj = String.fromCharCode;
obj2 = _lexenv_0_1_.hint1;
i += obj(obj2.charCodeAt(i2) + _lexenv_0_1_.hint1.length);
}
ldlexvar = _lexenv_0_1_;
reverseStr = ldlexvar.reverseStr(i);
i3 = "";
for (i4 = 0; (i4 < _lexenv_0_1_.hint1.length ? 1 : 0) != 0; i4 = tonumer(i4) + 1) {
i3 += String.fromCharCode(reverseStr.charCodeAt(i4) - i4);
}
ldlexvar2 = _lexenv_0_1_;
reverseStr2 = ldlexvar2.reverseStr(i3);
obj3 = createobjectwithbuffer(["hint1", 0]);
obj3.hint1 = reverseStr2;
router = import { default as router } from "@ohos:router";
obj4 = router.pushUrl;
obj5 = createobjectwithbuffer(["url", "pages/Flag", "params", 0]);
obj5.params = obj3;
callthisN = obj4(obj5);
then = callthisN.then();
then.catch(#~@1>@2*^2**#);
return null;
}

对字符串先反转再加i再反转再减长度,逆向脚本如下:

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 <string.h>

void reverseStr(char *str)
{
int len = strlen(str);
for (int i = 0; i < len / 2; i++)
{
char tmp = str[i];
str[i] = str[len - i - 1];
str[len - i - 1] = tmp;
}
}

int main()
{
char hint1[] = "opfj^_mgekc]iWccXbf";
int len = strlen(hint1);
char step1[100] = {0};
char step2[100] = {0};
for (int i = 0; i < len; i++)
{
step1[i] = hint1[i] + len;
}
step1[len] = '\0';
reverseStr(step1);
for(int i = 0; i < len; i++)
{
step2[i] = step1[i] - i;
}
step2[len] = '\0';
reverseStr(step2);
printf("%s\n", step2);
return 0;
}

得到第一部分的结果:princetonuniversity

两部分合起来:flag{princetonuniversity2cefc18cdb43f50865bd8b0b201f513f488}

oh~baby

鸿蒙内核题,qemu启动脚本多了个空格,删了然后正常跑起来,挂载共享文件夹方便拷贝文件

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
OHOS_IMG="images"
qemu-system-x86_64 \
-machine pc \
-smp 6 \
-m 4096M \
-boot c \
-nographic \
-vga none \
-virtfs local,path=/mnt/shared,mount_tag=host0,security_model=passthrough,id=host0 \
-device virtio-vga-gl,xres=360,yres=720 \
-display sdl,gl=on \
-rtc base=utc,clock=host \
-device es1370 \
-initrd ${OHOS_IMG}/ramdisk.img \
-kernel ${OHOS_IMG}/bzImage \
-usb \
-device usb-mouse \
-device usb-ehci,id=ehci \
-drive file=${OHOS_IMG}/updater.img,if=virtio,media=disk,format=raw,index=0 \
-drive file=${OHOS_IMG}/system.img,if=virtio,media=disk,format=raw,index=1 \
-drive file=${OHOS_IMG}/vendor.img,if=virtio,media=disk,format=raw,index=2 \
-drive file=${OHOS_IMG}/sys_prod.img,if=virtio,media=disk,format=raw,index=3 \
-drive file=${OHOS_IMG}/chip_prod.img,if=virtio,media=disk,format=raw,index=4 \
-drive file=${OHOS_IMG}/userdata.img,if=virtio,media=disk,format=raw,index=5 \
-snapshot \
-append " \
ip=dhcp \
loglevel=7 \
console=ttyS0,115200 \
init=init root=/dev/ram0 rw \
ohos.boot.hardware=virt \
default_boot_device=10007000.virtio_mmio \
ohos.boot.sn=01234567890 \
ohos.required_mount.system=/dev/block/vdb@/usr@ext4@ro,barrier=1@wait,required \
ohos.required_mount.vendor=/dev/block/vdc@/vendor@ext4@ro,barrier=1@wait,required"

给了提示:hcs客户端在/vendor/下

那么去翻/vendor下的目录,在/vendor/bin找到一个chall

image-20250609171650397

chall是本题的客户端主程序,命令行传参然后会去跟chall_driver驱动交互来进行aes加密,输入长度必须是16的倍数

使用命令cp /vendor/bin/chall /mnt/shared将chall拉下来进行分析

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
int __fastcall main(int argc, const char **argv, const char **envp)
{
*(_OWORD *)dest = 0;
if ( argc >= 2 )
{
strncpy(dest, argv[1], 0xFFu);
HIBYTE(v38) = 0;
HiLogPrint(218113296, 4, 218113296, "chall_test", "Using input from command line argument: %s", dest);
if ( dest[0] )
goto LABEL_3;
LABEL_8:
HiLogPrint(218113296, 6, 218113296, "chall_test", "Input cannot be empty.");
return -1;
}
HiLogPrint(218113296, 4, 218113296, "chall_test", "Please enter your guess for the flag:");
if ( !fgets(dest, 256, stdin) )
{
HiLogPrint(218113296, 6, 218113296, "chall_test", "Failed to read input.");
return -1;
}
dest[strcspn(dest, "\n")] = 0;
if ( !dest[0] )
goto LABEL_8;
LABEL_3:
v3 = HdfIoServiceBind("chall_service");
if ( !v3 )
{
HiLogPrint(218113296, 6, 218113296, "chall_test", "fail to get service %s", "chall_service");
return -1;
}
v4 = v3;
if ( (unsigned int)HdfDeviceRegisterEventListener(v3, &main_listener) )
{
HiLogPrint(218113296, 6, 218113296, "chall_test", "fail to register event listener");
HdfIoServiceRecycle(v4);
return -1;
}
dword_4B80 = 0;
memset(&v20, 0, 0x200u);
memset(::dest, 0, 0x200u);
v6 = HdfSbufObtainDefaultSize();
if ( !v6 )
{
HiLogPrint(218113296, 6, 218113296, "chall_test", "SendAndGetEncryptedReply: fail to obtain sbuf dataSbuf");
v11 = -1;
goto LABEL_29;
}
v7 = v6;
v8 = HdfSbufObtainDefaultSize();
if ( !v8 )
{
HiLogPrint(218113296, 6, 218113296, "chall_test", "SendAndGetEncryptedReply: fail to obtain sbuf replySbuf");
HdfSbufRecycle(v7);
v11 = -201;
goto LABEL_29;
}
v9 = v8;
if ( !(unsigned __int8)HdfSbufWriteString(v7, dest) )
{
HiLogPrint(
218113296,
6,
218113296,
"chall_test",
"SendAndGetEncryptedReply: fail to write input string to dataSbuf");
goto LABEL_22;
}
HiLogPrint(218113296, 4, 218113296, "chall_test", "Sending input to driver: \"%{public}s\"", dest);
v10 = (**(__int64 (__fastcall ***)(__int64, __int64, __int64, __int64))(v4 + 16))(v4, 123, v7, v9);
if ( !v10 )
{
String = HdfSbufReadString(v9);
if ( !String )
{
HiLogPrint(
218113296,
6,
218113296,
"chall_test",
"SendAndGetEncryptedReply: fail to read reply string from replySbuf");
strncpy((char *)&v20, "ERROR: Failed to read reply string", 0x1FFu);
v22 = 0;
v11 = -4;
goto LABEL_28;
}
v16 = (const char *)String;
v11 = 0;
HiLogPrint(218113296, 4, 218113296, "chall_test", "Got direct reply string from service: \"%{public}s\"", String);
strncpy((char *)&v20, v16, 0x1FFu);
v22 = 0;
if ( v20 ^ 0x4F525245 | v21 ^ 0x3A52 )
goto LABEL_28;
HiLogPrint(
218113296,
6,
218113296,
"chall_test",
"SendAndGetEncryptedReply: Received error message from driver: %s",
(const char *)&v20);
LABEL_22:
v11 = -1;
goto LABEL_28;
}
v11 = v10;
HiLogPrint(218113296, 6, 218113296, "chall_test", "SendAndGetEncryptedReply: Dispatch failed, ret = %d", v10);
v12 = (const char *)HdfSbufReadString(v9);
if ( v12 )
{
v13 = v12;
HiLogPrint(218113296, 6, 218113296, "chall_test", "SendAndGetEncryptedReply: Error from driver dispatch: %s", v12);
v14 = v13;
}
else
{
v14 = "ERROR: Dispatch failed, no specific message";
}
strncpy((char *)&v20, v14, 0x1FFu);
v22 = 0;
LABEL_28:
HdfSbufRecycle(v7);
HdfSbufRecycle(v9);
if ( !v11 )
{
printf("Driver direct reply (Ciphertext or Status): %s\n", (const char *)&v20);
v17 = 1;
if ( !(v20 ^ 'ORRE' | v21 ^ 0x3A52) )
HiLogPrint(218113296, 6, 218113296, "chall_test", "Operation failed. Driver returned an error in direct reply.");
goto LABEL_31;
}
LABEL_29:
v17 = 0;
HiLogPrint(218113296, 6, 218113296, "chall_test", "Communication Dispatch Error with HDF service. Code: %d", v11);
puts("Communication Dispatch Error with HDF service. Check logs.");
if ( (_BYTE)v20 )
{
v17 = 0;
printf("Details: %s\n", (const char *)&v20);
}
LABEL_31:
v18 = 0;
HiLogPrint(218113296, 4, 218113296, "chall_test", "Waiting for event from driver...");
if ( dword_4B80 )
goto LABEL_36;
do
{
sleep(1u);
if ( dword_4B80 )
break;
}
while ( v18++ < 4 );
if ( dword_4B80 )
LABEL_36:
HiLogPrint(218113296, 4, 218113296, "chall_test", "Event received from driver: %s", ::dest);
else
HiLogPrint(218113296, 5, 218113296, "chall_test", "Timed out waiting for event from driver.");
if ( (unsigned int)HdfDeviceUnregisterEventListener(v4, &main_listener) )
HiLogPrint(218113296, 6, 218113296, "chall_test", "fail to unregister listener");
HdfIoServiceRecycle(v4);
result = 1;
if ( v17 )
return (v20 ^ 0x4F525245 | v21 ^ 0x3A52) == 0;
return result;
}

v3 = HdfIoServiceBind(“chall_service”);绑定了chall_service这个驱动,但是具体实现逻辑没有体现

尝试利用驱动名find找到了一些信息,比如

/sys/class/hdf/chall_service

/sys/devices/virtual/hdf/chall_service

但均没有驱动的真正逻辑

关注到imgs文件夹里的bzImage,并没有像其他img一样可以解包出系统的文件夹,binwalk -e提取到一个elf 52C4

image-20250609171927880

ida分析该elf

image-20250609172000463

搜驱动名发现有Congratulate一系列的描述,定位到比较函数的位置

1
2
3
4
5
6
7
8
if ( !strcmp(v46, a8be07936adbb87) )
{
memset(v102, 0, sizeof(v102));
v101 = 0LL;
sub_FFFFFFFF81B559E0(byte_FFFFFFFF8281AAE0, (__int64)&v101, byte_100, a5, a6, a7, a8, v47, v48, a11, a12);// congratulate
if ( v67 )
sub_FFFFFFFF810F19B0((__int64)&v101, a5, a6, a7, a8, v65, v66, a11, a12);
}

找到密文是8BE07936ADBB8728D93BB1E0AB715353

image-20250609172043113

继续分析找到aes的加密逻辑

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
36
37
38
39
40
41
42
43
44
45
46
void __fastcall sub_FFFFFFFF81BEAA50(
v10 = a1 + 16;
v29 = __readgsqword(0x28u);
v26[0] = 0LL;
v26[1] = 0LL;
Key_Schedule(v26, v27);
v11 = v27;
do
{
sub_FFFFFFFF81BEA580(a1);
sub_FFFFFFFF81BEA580(v11);
for ( i = 0LL; i != 16; ++i )
a1[i] ^= v11[i];
v13 = a1;
v14 = a1;
do
{
v15 = *v14++;
*(v14 - 1) = Sbox[v15];
}
while ( v10 != v14 );
v11 += 16;
MixColumns(a1);
}
while ( v28 != v11 );
sub_FFFFFFFF81BEA580(a1);
sub_FFFFFFFF81BEA580(v28);
for ( j = 0LL; j != 16; ++j )
a1[j] ^= v28[j];
do
{
v22 = *v13++;
*(v13 - 1) = Sbox[v22];
}
while ( v10 != v13 );
for ( k = 0LL; k != 16; ++k )
a1[k] ^= v28[k + 16];
for ( m = 0LL; m != 16; ++m )
{
v25 = a1[m];
*(_BYTE *)(a2 + m) = v25;
}
if ( __readgsqword(0x28u) != v29 )
sub_FFFFFFFF81EE2180(v28, v27, v25, v16, v17, v18, a3, a4, a5, a6, v19, v20, a9, a10);
JUMPOUT(0xFFFFFFFF81EEF360LL);
}

​ 其中

1
2
3
v26[0] = 0LL;
v26[1] = 0LL;
Key_Schedule(v26, v27);

其中把密钥v26赋值为0,然后通过字符串确定AES-128-ECB模式,尝试全0密钥解密,得到

image-20250609172150562故flag是flag{Wh1T3BoxA4s1noho}

Hardware

easy_designer

第一次在ctf里感受到了专业对口()

题目是电路板,找到使灯亮的输入,开关从(SW01-SW48),如输入01011000,flag为flag{01011000}

使用kicad看整个pcb板子的走线

image-20250609172505562

`键可以高亮走线,看一下从LED引出的线

image-20250609172528829

尝试分析整体的走线,发现所有开关都被调用,那么我们来看pcb板子上的这两种芯片类型

第一种是74LS08 ,这是一款常见的 四路与非门(Quad 2-input AND gate) 芯片,内部有 4 个独立的 2 输入 AND 门

原理图如下:

image-20250609172607063

封装:14 脚双列直插(DIP-14)

引脚号 名称 说明
1 A1 第1个门输入 A
2 B1 第1个门输入 B
3 Y1 第1个门输出 Y
4 A2 第2个门输入 A
5 B2 第2个门输入 B
6 Y2 第2个门输出 Y
7 GND
8 Y3 第3个门输出 Y
9 A3 第3个门输入 A
10 B3 第3个门输入 B
11 Y4 第4个门输出 Y
12 A4 第4个门输入 A
13 B4 第4个门输入 B
14 VCC 电源正(+5V)

与门的特性是门输入都是1的时候才会输出1

第二种是74AHC04 ,这是一款 六路反相器(Hex Inverter),即它内部包含 6 个独立的 NOT 门(反相器)。每个门有一个输入和一个输出

原理图如下:

image-20250609173111315 image-20250609173122702

74AHC04 引脚定义(DIP-14 封装)

引脚号 名称 功能说明
1 A1 第1个反相器输入
2 Y1 第1个反相器输出
3 A2 第2个反相器输入
4 Y2 第2个反相器输出
5 A3 第3个反相器输入
6 Y3 第3个反相器输出
7 GND 地(0V)
8 Y4 第4个反相器输出
9 A4 第4个反相器输入
10 Y5 第5个反相器输出
11 A5 第5个反相器输入
12 Y6 第6个反相器输出
13 A6 第6个反相器输入
14 VCC 电源(+5V)

非门的特性是输入为0的时候输出为1

那我们可以推测得到位于与门输入的开关对应引脚都需置1,非门输入的开关对应引脚都需置0

image-20250609173238302

一共有6个74AHC04芯片,位于这些芯片的非门输入端都需置0,如下图的SW25,29,31,32

image-20250609173311579

同理 ,74LS08的芯片上面的输入都要置1,如下图的SW23,24

image-20250609173341131

那么统计一下需要置0的开关:

1 4

9

12 14 15

16 17 20

21 22 25

29

31 32 33

36 37 40

41 43

所以整体的序列为011011110110100001100011011101000110011001011111

包上flag{}提交即为答案

写在最后

一直觉得自己没啥进步总在踯躅不前,但是好像每次打比赛都有新的收获和新的肯定出现,仍在路上,仍在努力。

“我想再无风雪滂沱围困生命长河。且看枯木燃成烈火,用青山诠释我,生生不息的磅礴。”


第一届OpenHarmony专题赛
http://example.com/2025/06/09/第一届OpenHarmony专题赛/
作者
p3cd0wn
发布于
2025年6月9日
许可协议