2024 N1CTF - Reverse
carbofish

2024 N1CTF Reverse 方向 Writes UP

ReFantazio 三血,嘻嘻
d83af494cb946b57c5e5333d2d53ff77

ezapk

image-20241110001711563

enc 注册在 libnative1 里面,加密逻辑在 libnative2 里面,在三处 call 寄存器的地方下断点

image-20241110001800606

image-20241110001805677

image-20241110001811360

得到加密逻辑

1
2
3
# iusp9aVAyoMI 异或 rand()
# SZ3pMtlDTA7Q RC4 密钥是 rand()出来的
# UqhYy0F049n5 base64编码

由 RC4 的对称性可以想到一个简便解法,用 frida 下断在 JNI 函数处,覆写 GetStringUTFChars 得到的内存地址

image-20241110001951632

然后再在 ret 的位置 hook 拿到解密后的内容就可得到 flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var inter = setInterval(function() {
try {
var module = Process.getModuleByName("libnative1.so");
var write_data = [0x89,0x1a,0xcb,0xeb,0x7b,0x6f,0x7b,0xe1,0xfb,0xdb,0x08,0xeb,0xfc,0x71,0xe2,0xc2,0x55,0x6e,0xe5,0x16,0x54,0xf5,0x70,0xdc,0x23,0xb0];
Interceptor.attach(module.base.add(0x1B188), {
onEnter: function () {
var reg = this.context.x0.and(0xFFFFFFFFFFFF)
console.log(hexdump(reg, {length: 100, ansi: true}))
ptr(reg).writeByteArray(write_data)
console.log(hexdump(reg, {length: 100, ansi: true}))
}
})
Interceptor.attach(module.base.add(0x1B458), {
onEnter: function() {
var reg = this.context.x0.and(0xFFFFFFFFFFFF)
console.log(hexdump(reg, {length: 100, ansi: true}))
}
})
console.log("Hook successful!")
clearInterval(inter)
} catch {}
}, 50)
image-20241110002048062 image-20241110002054945

ReFantazio

三血嘻嘻

到手发现一个很大的 so,ida 查看字符串发现 frida 字样,那么大概是个内置 frida-gum 的程序

第一步,先还原符号

去拉一份最新的 frida 源码,先编译一遍,生成 frida-android-arm64 后修改内容

image-20241110200309006

先关优化,然后去修改 subprojects\frida-core\tools 下的 post-process.py

image-20241110200443810

关闭 strip

然后就可以 bindiff 了

在还原大部分符号后,可以发现这个从 bytes 创建脚本,那么大胆猜测是加载明文脚本

image-20241110200630153

所以可以写一个脚本 dump 所有内存段,然后搜索 Java.use 定位即可

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
Java.perform(function() {
setTimeout(function() {

let allMemoryData = [];
let totalSize = 0;

const ranges = Process.enumerateRangesSync({
protection: '---',
coalesce: true
});

for (const range of ranges) {
try {
const current = range.base;
const size = range.size;
console.log(current, size)

const memoryData = Memory.readByteArray(current, size);
if (memoryData) {
allMemoryData.push(memoryData);
totalSize += size;
}
} catch {}
}

const finalBuffer = new ArrayBuffer(totalSize);
const finalArray = new Uint8Array(finalBuffer);
let offset = 0;

for (const data of allMemoryData) {
const tempArray = new Uint8Array(data);
finalArray.set(tempArray, offset);
offset += tempArray.length;
}

const timestamp = new Date().getTime();
const filename = `/data/data/com.android.refantazio/memory_dump_${timestamp}.bin`;

const file = new File(filename, "wb");
file.write(finalBuffer);
file.flush();
file.close();
console.log(`[+] Saved ${finalBuffer.byteLength} bytes to ${filename}`);
}, 3000)
})

// /data/data/com.android.refantazio/memory_dump_1731235293045.bin
// /data/data/com.android.refantazio/memory_dump_1731235318417.bin

得到转储文件后用 010 去搜

image-20241110200946965

就可以秒了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let t = [],
n = "n1cTfOwO".length;
for (let e = 0; e < 256; e++) t[e] = e;
let o = 0;
for (let e = 0; e < 256; e++) o = (o + t[e] + "n1cTfOwO".charCodeAt(e % n)) % 256,
[t[e], t[o]] = [t[o], t[e]];
let r = 0;
o = 0;
let i = [59, 67, 58, 32, 172, 94, 161, 232, 59, 225, 56, 210, 206, 94, 123, 253, 112, 252, 41, 136, 71, 102, 81, 80, 128, 39, 22, 44, 176, 41, 205, 197, 5, 247, 68, 151, 127, 29, 251, 58, 85];
var dt = [];
for (let n = 0; n < 41; n++) {
o = (o + t[r = (r + 1) % 256]) % 256,
[t[r], t[o]] = [t[o], t[r]];
let l = t[(t[r] + t[o]) % 256];
dt.push(String.fromCharCode(i[n] ^ l));
}

console.log(dt.join(""))

BrokenApp

一个简单鸿蒙题目,比赛的时候没想到 add xxx 是吧 栈顶取出然后在前面加上 xxx,导致 0 加到了字符串末尾,所以就一直解不出来。。。。。。
拿到题目后第一步先反 modules.abc,这里我用的是 abc-decompiler

image-20241112184348779

arkjs 层的代码是假的,关键去看 abc.def
abc.def 是被魔改了的,我们去 libark_runtime.so 分析一下
关键逻辑在这里,先把魔改的头部改回 PANDA,原本是 N1CTF

image-20241112184540939

然后把输入的 flag 填入 abc.def 以 0x1B5A80 开头的 40 个字节,

image-20241112184724196

然后继续加载,abc.def 没法反成 java 代码,所以我用了 abcde 反汇编,然后手动分析

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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278

newlexenv 2
tryldglobalbyname 0 "require"
sta v0
lda.str "@babel/core"
sta v1
lda v0
callarg1 1 v1
stconsttoglobalrecord 3 "babel"

tryldglobalbyname 4 "require"
sta v0
lda.str "@babel/parser"
sta v1
lda v0
callarg1 5 v1
stconsttoglobalrecord 7 "parser"

tryldglobalbyname 8 "require"
sta v0
lda.str "@babel/traverse"
sta v1
lda v0
callarg1 9 v1
ldobjbyname 11 "default"
stconsttoglobalrecord 13 "traverse"

tryldglobalbyname 14 "require"
sta v0
lda.str "@babel/types"
sta v1
lda v0
callarg1 15 v1
stconsttoglobalrecord 17 "t"

tryldglobalbyname 18 "require"
sta v0
lda.str "@babel/generator"
sta v1
lda v0
callarg1 19 v1
ldobjbyname 21 "default"
stconsttoglobalrecord 23 "generator"

/* 上面都在导库 翻译过来就是
const babel = require('@babel/core');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
const generator = require('@babel/generator').default;
const fs = require("fs");
*/

lda.str "N1CTF{padding_the_flag_here_XXXXXXXXXXX}"
stconsttoglobalrecord 24 "FLAG"

// const FLAG = "N1CTF{padding_the_flag_here_XXXXXXXXXXX}"

lda.str "
const u = [
"viKok6",
"xznLka",
... 这里省略
];
"
stconsttoglobalrecord 25 "code"

/*
code = "
const u = [
"viKok6",
"xznLka",
... 这里省略
];
"
*/

createemptyarray 26
stconsttoglobalrecord 27 "u_"

// const u_ = [];

tryldglobalbyname 28 "parser"
sta v0
ldobjbyname 29 "parse"
sta v1
tryldglobalbyname 31 "code"
sta v2
lda v1
callthis1 32 v0 v2
stconsttoglobalrecord 34 "ast"

// const ast = parser.parse(code);

tryldglobalbyname 35 "traverse"
sta v0
tryldglobalbyname 36 "ast"
sta v1
createobjectwithbuffer 37 { 3 [ str:"enter", Method:_GLOBAL.#*#enter, MethodAffiliate:1, ]}
sta v2
lda v0
callargs2 38 v1 v2

/*
这里是 调用 traverse, _GLOBAL.#*#enter之类的代码我已经提前翻译好了
traverse(ast, {
enter: function(path) {
if (t.isArrayExpression(path.node)) {
path.node.elements.forEach((arg0, arg1) => {
if (t.isStringLiteral(arg0)) {
if (arg1 > 150 && arg1 < 182) {
const value = arg0.value;
const index = (arg1 + 2) % 6;
const result = value[index];
u_.push(result);
}
}
});
}
}
});

*/

tryldglobalbyname 40 "FLAG"
sta v0
ldobjbyname 41 "startsWith"
sta v1
lda.str "N1CTF{"
sta v2
lda v1
callthis1 43 v0 v2
sta v0
callruntime.isfalse 45
jnez 27
tryldglobalbyname 46 "FLAG"
sta v0
ldobjbyname 47 "endsWith"
sta v1
lda.str "}"
sta v2
lda v1
callthis1 49 v0 v2
sta v0
lda v0
callruntime.isfalse 51
jnez 107

// if (FLAG.startsWith("N1CTF{") && FLAG.endsWith("}"))

tryldglobalbyname 52 "FLAG"
sta v0
ldobjbyname 53 "substring"
sta v1
tryldglobalbyname 55 "FLAG"
ldobjbyname 56 "length"
sta v2
ldai 1
sub2 58 v2
sta v2
ldai 6
sta v3
lda v1
callthis2 59 v0 v3 v2
sta v0

// const flagContent = FLAG.substring(6, FLAG.length - 1);

lda.str "0"
sta v1
tryldglobalbyname 61 "u_"
sta v2
ldobjbyname 62 "join"
sta v3
lda.str ""
sta v4
lda v3
callthis1 64 v2 v4
add2 66 v1

// 就是这里被坑惨了, arkts 的 add 是 操作数 + 栈顶值, 比赛的时候我是 u_.join("") + "0" 死活做不出。。。。。。

stricteq 67 v0
callruntime.isfalse 68

// flagContent === "0" + u_.join("")

jnez 20
tryldglobalbyname 69 "print"
sta v0
lda.str "You're awesome!"
sta v1
lda v0
callarg1 70 v1
jmp 18
tryldglobalbyname 72 "print"
sta v0
lda.str "What's wrong?"
sta v1
lda v0
callarg1 73 v1
ldundefined
returnundefined

/*
if (FLAG.startsWith("N1CTF{") && FLAG.endsWith("}")) {
const flagContent = FLAG.substring(6, FLAG.length - 1);
if (flagContent === "0" + u_.join("")) {
print("You're awesome!");
} else {
print("What's wrong?");
}
}
*/

# _GLOBAL.#*#enter
newlexenv 2
lda NewTarget
stlexvar 0 0
lda this
stlexvar 0 1
tryldglobalbyname 0 "t"
sta v0
ldobjbyname 1 "isArrayExpression"
sta v1
lda arg0
ldobjbyname 3 "node"
sta v2
lda v1
callthis1 5 v0 v2
callruntime.isfalse 7
jnez 33
lda arg0
ldobjbyname 8 "node"
ldobjbyname 10 "elements"
sta v0
ldobjbyname 12 "forEach"
sta v1
definefunc 14 this.#*@0*# 2
sta v2
lda v1
callthis1 15 v0 v2
ldundefined
returnundefined

# _GLOBAL.#*@0*#
tryldglobalbyname 0 "t"
sta v0
ldobjbyname 1 "isStringLiteral"
callthis1 3 v0 arg0
callruntime.isfalse 5
jnez 77
ldai 150
greater 6 arg1
callruntime.isfalse 7
jnez 64
ldai 182
less 8 arg1
callruntime.isfalse 9
jnez 51
tryldglobalbyname 10 "u_"
sta v0
ldobjbyname 11 "push"
sta v1
lda arg0
ldobjbyname 13 "value"
sta v2
ldai 2
add2 15 arg1
sta v3
ldai 6
mod2 16 v3
ldobjbyvalue 17 v2
sta v2
lda v1
callthis1 19 v0 v2
ldundefined
returnundefined
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
const babel = require('@babel/core');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
const generator = require('@babel/generator').default;
const fs = require("fs");

var code = `
const u = [
"viKok6",
"xznLka",
"DsA1Pd",
"55MYVz",
"DoUoja",
...
];
`;

const u_ = [];

const ast = parser.parse(code);

traverse(ast, {
enter: function (path) {
if (t.isArrayExpression(path.node)) {
path.node.elements.forEach((arg0, arg1) => {
if (t.isStringLiteral(arg0)) {
if (arg1 > 150 && arg1 < 182) {
// console.log(arg0, arg1);
const value = arg0.value;
const index = (arg1 + 2) % 6;
const result = value[index];

console.log(value, index)

u_.push(result);
}
}
});
}
}
});

if (FLAG.startsWith("N1CTF{") && FLAG.endsWith("}")) {
const flagContent = FLAG.substring(6, FLAG.length - 1);
if (flagContent === "0" + u_.join("")) {
print("You're awesome!");
} else {
print("What's wrong?");
}
}

// N1CTF{0KACtgWmvh3cMAHdUKeRbxfASt7CkBT}
 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
访客数 访问量