之前遇到过Dart的逆向,但是关键逻辑不在libapp.so。这次做的Fluffy算是真的要去恩看Dart的逻辑,Dart的代码放入ida中的非常难看,很多native操作,通过这篇文章来总结我对Dart语言的一些特性的思考。
Dart中的对象中存入的指针,为了节省空间,并不是将所有地址都存入内存,而是只是将低32位的偏移存入内存,高32位的地址由x28保存。如果你打开ida,发现很多 var << 32 + var 之类的表达式,please just relax,这只是在拼接堆指针罢了。
在blutter还原的汇编注释也可见一斑:
首先Dart中一切皆为Object,但是实际Dart在也是有底层优化的。在Dart中最最常见的就是int类型。
共有两种类型:
举个例子:
smi与bigint的区别就在于,右移一位后是否为1

Dart的栈使用X15,而不是X29,
返回值使用的X0,但是寄存器传参是从X1开始的
Dart中的特殊寄存器如下:
X21用于多态调用,基于cid(class id)进行寻址
举个例子:
v29-1 指向object头部, 头部高20为class id,低12位为一些标志位用于GC等。v5指向X21
然后根据cid进行寻址。进行多态调用。

然后使用如下的函数签名,重新set call type将函数还原成原本的样子:
还原后:

好看很多,函数参数也清晰了。
还有一些细枝末节的东西,比如很多的内存0xb, 0xf偏移的内存访问,还有new 一个数组要经过 Arraystub, GrowArraystub等等,只要知道这些底层操作对用的高级操作就可以了。
blutter预处理后
接下来,就是摁看加调试(确定多态调用)
最后还原出加密逻辑
所以接下来要爆破4位pin和时间戳爆破。
接下来请欣赏,河豚鱼大人艺术般的爆破脚本。
(引用名人名句:爆,爆,爆,爆tmd —河豚鱼)
// 0x358708: DecompressPointer r2
// 0x358708: add x2, x2, HEAP, lsl
// 0x358708: DecompressPointer r2
// 0x358708: add x2, x2, HEAP, lsl
| 寄存器 |
用途 |
说明 |
X21 |
类型表指针(TypeTable / GDT) |
用于类型跳转(vtable)调用、类 ID 映射等 |
X26 |
当前线程指针(Thread* thr) |
GC、栈检查等使用 |
X27 |
常量池指针(ObjectPool* pp) |
用于加载常量、内联缓存等 |
X28 |
Heap 基地址 |
解压压缩指针 |
X15 |
虚拟栈指针 |
传递更多参数 / spill slot |
_QWORD (__usercall *)@<X0>(DartObjectPool *pool@<X27>, DartThread *thread@<X26>, _QWORD *stack@<X15>, _QWORD@<X1>, _QWORD@<X2>, _QWORD@<X4>)
_QWORD (__usercall *)@<X0>(DartObjectPool *pool@<X27>, DartThread *thread@<X26>, _QWORD *stack@<X15>, _QWORD@<X1>, _QWORD@<X2>, _QWORD@<X4>)
static int saveSecret(unsigned char* res, const void* token, int token_len, int pin, const void* secret, int secret_len) {
unsigned char* token_bytes = malloc(token_len);
int token_bytes_len = b62decode(token_bytes, token, token_len);
assert(token_bytes_len == 8);
memcpy(res, secret, secret_len);
for (int i = 0; i < pin; i++) {
for (int j = 0; j < secret_len; j++) {
res[j] = rol1(res[j] + token_bytes[j % token_bytes_len], j & 7);
}
unsigned char tmp = res[secret_len - 1];
memmove(res + 1, res, secret_len - 1);
res[0] = tmp;
update_token_bytes(token_bytes, token_bytes_len, pin, i);
}
free(token_bytes);
return b62encode(res, res, secret_len);
}
static void update_token_bytes(unsigned char* token_bytes, int token_bytes_len, int pin, int round) {
unsigned char tmp = token_bytes[0];
memmove(token_bytes, token_bytes + 1, token_bytes_len - 1);
token_bytes[token_bytes_len - 1] = tmp;
for (int j = 0; j < token_bytes_len; j++) {
token_bytes[j] = ror1(token_bytes[j], (pin ^ ((round & 3) + 1)) & 7);
}
}
static int saveSecret(unsigned char* res, const void* token, int token_len, int pin, const void* secret, int secret_len) {
unsigned char* token_bytes = malloc(token_len);
int token_bytes_len = b62decode(token_bytes, token, token_len);
assert(token_bytes_len == 8);
memcpy(res, secret, secret_len);
for (int i = 0; i < pin; i++) {
for (int j = 0; j < secret_len; j++) {
res[j] = rol1(res[j] + token_bytes[j % token_bytes_len], j & 7);
}
unsigned char tmp = res[secret_len - 1];
memmove(res + 1, res, secret_len - 1);
res[0] = tmp;
update_token_bytes(token_bytes, token_bytes_len, pin, i);
}
free(token_bytes);
return b62encode(res, res, secret_len);
}
static void update_token_bytes(unsigned char* token_bytes, int token_bytes_len, int pin, int round) {
unsigned char tmp = token_bytes[0];
memmove(token_bytes, token_bytes + 1, token_bytes_len - 1);
token_bytes[token_bytes_len - 1] = tmp;
for (int j = 0; j < token_bytes_len; j++) {
token_bytes[j] = ror1(token_bytes[j], (pin ^ ((round & 3) + 1)) & 7);
}
}
static unsigned char rol1(unsigned char x, int n) {
return (x << n) | (x >> (8 - n));
}
static unsigned char ror1(unsigned char x, int n) {
return (x >> n) | (x << (8 - n));
}
static void reverse_str(unsigned char* s, int n) {
for (int i = 0, j = n - 1; i < j; i++, j--) {
unsigned char tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
static void bytes_to_long(mpz_t* res, const unsigned char* s, int n) {
mpz_set_ui(*res, 0);
for (int i = 0; i < n; i++) {
mpz_mul_ui(*res, *res, 256);
mpz_add_ui(*res, *res, s[i]);
}
}
static int long_to_bytes(unsigned char* res, const mpz_t* v) {
mpz_t t;
mpz_init(t);
mpz_set(t, *v);
int l = 0;
while (mpz_cmp_ui(t, 0)) {
res[l++] = mpz_get_ui(t) & 0xff;
mpz_fdiv_q_ui(t, t, 256);
}
mpz_clear(t);
reverse_str(res, l);
return l;
}
static const char* b62table = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static unsigned char b62table_r[256];
static void init_b62table() {
for (int i = 0; i < strlen(b62table); i++) {
b62table_r[b62table[i] & 0xff] = i;
}
}
static int b62encode(unsigned char* res, const unsigned char* s, int n) {
if (!b62table_r['z']) {
init_b62table();
}
mpz_t v;
mpz_init(v);
bytes_to_long(&v, s, n);
int l = 0;
mpz_t mod;
mpz_init(mod);
while (mpz_cmp_ui(v, 0)) {
mpz_mod_ui(mod, v, 62);
res[l++] = b62table[mpz_get_ui(mod)];
mpz_fdiv_q_ui(v, v, 62);
}
mpz_clear(mod);
mpz_clear(v);
reverse_str(res, l);
res[l] = 0;
return l;
}
static int b62decode(unsigned char* res, const unsigned char* s, int n) {
if (!b62table_r['z']) {
init_b62table();
}
mpz_t v;
mpz_init(v);
for (int i = 0; i < n; i++) {
mpz_mul_ui(v, v, 62);
mpz_add_ui(v, v, b62table_r[s[i]]);
}
int l = long_to_bytes(res, &v);
mpz_clear(v);
res[l] = 0;
return l;
}
static int generate_token(char* res, time_t ts) {
if (!ts) {
ts = time(NULL);
}
char buffer[256];
sprintf(buffer, "gctf25_%ld", ts);
unsigned char hash[20];
SHA1((unsigned char*) buffer, strlen(buffer), hash);
return b62encode((unsigned char*) res, hash, 8);
}
static void update_token_bytes(unsigned char* token_bytes, int token_bytes_len, int pin, int round) {
unsigned char tmp = token_bytes[0];
memmove(token_bytes, token_bytes + 1, token_bytes_len - 1);
token_bytes[token_bytes_len - 1] = tmp;
for (int j = 0; j < token_bytes_len; j++) {
token_bytes[j] = ror1(token_bytes[j], (pin ^ ((round & 3) + 1)) & 7);
}
}
static int saveSecret(unsigned char* res, const void* token, int token_len, int pin, const void* secret, int secret_len) {
unsigned char* token_bytes = malloc(token_len);
int token_bytes_len = b62decode(token_bytes, token, token_len);
assert(token_bytes_len == 8);
memcpy(res, secret, secret_len);
for (int i = 0; i < pin; i++) {
for (int j = 0; j < secret_len; j++) {
res[j] = rol1(res[j] + token_bytes[j % token_bytes_len], j & 7);
}
unsigned char tmp = res[secret_len - 1];
memmove(res + 1, res, secret_len - 1);
res[0] = tmp;
update_token_bytes(token_bytes, token_bytes_len, pin, i);
}
free(token_bytes);
return b62encode(res, res, secret_len);
}
static int restoreSecret(unsigned char* res, const void* token, int token_len, int pin, const void* encrypted, int encrypted_len) {
unsigned char* token_bytes = malloc(0x18);
int token_bytes_len = b62decode(token_bytes, token, token_len);
int res_len = b62decode(res, encrypted, encrypted_len);
unsigned char* token_bytes_list = malloc(pin * token_bytes_len);
for (int i = 0; i < pin; i++) {
memcpy(token_bytes_list + i * token_bytes_len, token_bytes, token_bytes_len);
update_token_bytes(token_bytes, token_bytes_len, pin, i);
}
free(token_bytes);
for (int i = pin - 1; i >= 0; i--) {
unsigned char* _token_bytes = token_bytes_list + i * token_bytes_len;
unsigned char tmp = res[0];
for (int j = 0; j < res_len - 1; j++) {
res[j] = res[j + 1];
}
res[res_len - 1] = tmp;
for (int j = 0; j < res_len; j++) {
res[j] = ror1(res[j], j & 7) - _token_bytes[j % token_bytes_len];
}
}
free(token_bytes_list);
return res_len;
}
static unsigned char restoreSecretWithPrecomputedAt(int index, const unsigned char* token_bytes_list, int token_bytes_len, int pin, const unsigned char* res_start, int res_len) {
assert(index >= 0 && index < res_len);
unsigned char c = res_start[(index + pin) % res_len];
for (int i = pin - 1; i >= 0; i--) {
const unsigned char* token_bytes = token_bytes_list + i * token_bytes_len;
const int c_index = (index + i) % res_len;
c = ror1(c, c_index & 7) - token_bytes[c_index % token_bytes_len];
}
return c;
}
static int restoreSecretWithPrecomputed(unsigned char* res, const unsigned char* token_bytes_list, int token_bytes_len, int pin, const unsigned char* res_start, int res_len) {
memcpy(res, res_start, res_len);
for (int i = pin - 1; i >= 0; i--) {
const unsigned char* token_bytes = token_bytes_list + i * token_bytes_len;
unsigned char tmp = res[0];
memmove(res, res + 1, res_len - 1);
res[res_len - 1] = tmp;
for (int j = 0; j < res_len; j++) {
// const int res_index = (j + pin - i) % res_len;
// res[res_index] = ror1(res[res_index], j & 7) - token_bytes[j % token_bytes_len];
res[j] = ror1(res[j], j & 7) - token_bytes[j % token_bytes_len];
}
}
return res_len;
}
static int is_printable(const unsigned char* s, int n) {
for (int i = 0; i < n; i++) {
if (s[i] < 0x20 || s[i] >= 0x7f) {
return 0;
}
}
return 1;
}
static void bf_secret(int ts_lower_bound, int ts_upper_bound, const void* encrypted, int encrypted_len) {
/*
char token[0x100];
unsigned char* decrypted = malloc(encrypted_len);
for (int ts = ts_upper_bound - 1; ts >= ts_lower_bound; ts--) {
int token_len = generate_token(token, ts);
for (int pin = 1; pin < 10000; pin++) {
printf("\r%d, %d: ", ts, pin);
int decrypted_len = restoreSecret(decrypted, token, token_len, pin, encrypted, encrypted_len);
if (is_printable(decrypted, decrypted_len)) {
printf("\r%d, %d: %s\n", ts, pin, (char*) decrypted);
}
}
}
free(decrypted);
*/
unsigned char* res_start = malloc(encrypted_len);
unsigned char* decrypted = malloc(encrypted_len);
memset(decrypted, 0, encrypted_len);
unsigned char* token_bytes = malloc(0x18);
unsigned char* token_bytes_list = malloc(10000 * 0x10);
int res_len = b62decode(res_start, encrypted, encrypted_len);
char token[0x100];
for (int ts = ts_upper_bound - 1; ts >= ts_lower_bound; ts--) {
int token_len = generate_token(token, ts);
for (int pin_start = 1; pin_start < 9; pin_start++) {
// printf("\r%d, %d: ", ts, pin_start);
int token_bytes_len = b62decode(token_bytes, (unsigned char*) token, token_len);
for (int i = 0; i < 10000; i++) {
memcpy(token_bytes_list + i * token_bytes_len, token_bytes, token_bytes_len);
update_token_bytes(token_bytes, token_bytes_len, pin_start, i);
}
for (int pin = pin_start; pin < 10000; pin += 8) {
if (restoreSecretWithPrecomputedAt(res_len - 1, token_bytes_list, token_bytes_len, pin, res_start, res_len) == '}') {
int decrypted_len = restoreSecretWithPrecomputed(decrypted, token_bytes_list, token_bytes_len, pin, res_start, res_len);
if (is_printable(decrypted, decrypted_len)) {
printf("\r%d, %d: %s\n", ts, pin, (char*) decrypted);
// exit(0);
}
}
}
}
}
free(token_bytes_list);
free(token_bytes);
free(decrypted);
free(res_start);
}
struct bf_secret_thread_struct {
int id;
int ts_lower_bound;
int ts_upper_bound;
const char* encrypted;
};
static void bf_secret_thread(struct bf_secret_thread_struct* arg) {
printf("thread %d started (from %d to %d)\n", arg->id, arg->ts_lower_bound, arg->ts_upper_bound);
// while (1) {
const int ts_count = arg->ts_upper_bound - arg->ts_lower_bound;
assert(ts_count % SECONDS_PRE_LOG == 0);
const int log_count = ts_count / SECONDS_PRE_LOG;
for (int i = 0; i < log_count; i++) {
printf("thread %2d: %d / %d\n", arg->id, i, log_count);
bf_secret(arg->ts_lower_bound + SECONDS_PRE_LOG * i, arg->ts_lower_bound + SECONDS_PRE_LOG * (i + 1), arg->encrypted, strlen(arg->encrypted));
}
printf("thread %d finished\n", arg->id);
// prepare_next_arg(arg);
// }
free(arg);
}
int main() {
/*
unsigned char res[100];
unsigned char res2[100];
int l1 = b62encode(res, (unsigned char*) "0123456789abcdef", 16);
printf("%d: %s\n", l1, (char*) res);
int l2 = b62decode(res2, res, l1);
printf("%d: %s\n", l2, (char*) res2);
unsigned char res[100];
generate_token(res, 1000);
puts((char*) res);
const int pin = 1000;
const char* token = "aaaaaaaaaaa";
const int token_len = strlen(token);
unsigned char res[0x100];
unsigned char res2[0x100];
int l1 = saveSecret(res, token, token_len, pin, "0123456789abcdef", 16);
printf("%d: %s\n", l1, (char*) res);
int l2 = restoreSecret(res2, token, token_len, pin, res, l1);
printf("%d: %s\n", l2, (char*) res2);
*/
setbuf(stdout, NULL);
/*
const int lower_bound = 1691127420 + 60 + 8 * 60 * 60 - 12 * 60 * 60;
const char* encrypted = "fmMf7mIMbHcPoQmLGx1CO0XVGBmhjTaYhB0";
pthread_t threads[24];
for (int i = 0; i < 24; i++) {
struct bf_secret_thread_struct* s = malloc(sizeof(struct bf_secret_thread_struct));
s->id = i;
s->ts_upper_bound = lower_bound + i * 60 * 60;
s->ts_lower_bound = s->ts_upper_bound - 10 * 60;
s->encrypted = encrypted;
assert(!pthread_create(threads + i,NULL, (void*) bf_secret_thread, s));
}
for (int i = 0; i < 24; i++) {
assert(!pthread_join(threads[i], NULL));
}
puts("done");
*/
/*
const int upper_bound[] = {
1691127420 + 60 + 6 * 60 * 60,
1725706320 + 60 + 6 * 60 * 60,
1741010820 + 60 + 6 * 60 * 60
};
const char* encrypted[] = {
"fmMf7mIMbHcPoQmLGx1CO0XVGBmhjTaYhB0",
"5O6WRgCajs3QSTyohnu2hldds18mjkx",
"fgv99dOvazsvEESh7DPKbb3k0I3RW"
};
const int idx = 2;
bf_secret(upper_bound[idx] - 10 * 60, upper_bound[idx], encrypted[idx], strlen(encrypted[idx]));
*/
// const int upper_bound = 1741010820 + 60 + 8 * 60 * 60; // - THREAD_COUNT * SECONDS_PER_THREAD;
// const char* encrypted = "fgv99dOvazsvEESh7DPKbb3k0I3RW";
// pthread_t threads[THREAD_COUNT];
// for (int i = 0; i < THREAD_COUNT; i++) {
// struct bf_secret_thread_struct* s = malloc(sizeof(struct bf_secret_thread_struct));
// s->id = i;
// s->ts_upper_bound = upper_bound - i * SECONDS_PER_THREAD;
// s->ts_lower_bound = s->ts_upper_bound - SECONDS_PER_THREAD;
// s->encrypted = encrypted;
// assert(!pthread_create(threads + i,NULL, (void*) bf_secret_thread, s));
// }
// for (int i = 0; i < THREAD_COUNT; i++) {
// assert(!pthread_join(threads[i], NULL));
// }
// puts("done");
// bf_secret_string(1691127420 + 60 + 8 * 60 * 60, "fmMf7mIMbHcPoQmLGx1CO0XVGBmhjTaYhB0");
// 1691149047, 8126: CTF{Ok4y_h4v3_u_0ptim1zed_
// bf_secret_string(1725706320 + 60 + 8 * 60 * 60, "5O6WRgCajs3QSTyohnu2hldds18mjkx");
// 1725727951, 5178: brUt3_f0rcE_0R_y0u_jUst
// bf_secret_string(1741010820 + 60 + 8 * 60 * 60, "fgv99dOvazsvEESh7DPKbb3k0I3RW");
//
return 0;
}
// gcc ./main.c -o main -s -O3 -Wall -Wno-unused-function -lgmp -lcrypto && ./main
// CTF{Ok4y_h4v3_u_0ptim1zed_brUt3_f0rcE_0R_y0u_jUst
static unsigned char rol1(unsigned char x, int n) {
return (x << n) | (x >> (8 - n));
}
static unsigned char ror1(unsigned char x, int n) {
return (x >> n) | (x << (8 - n));
}
static void reverse_str(unsigned char* s, int n) {
for (int i = 0, j = n - 1; i < j; i++, j--) {
unsigned char tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
static void bytes_to_long(mpz_t* res, const unsigned char* s, int n) {
mpz_set_ui(*res, 0);
for (int i = 0; i < n; i++) {
mpz_mul_ui(*res, *res, 256);
mpz_add_ui(*res, *res, s[i]);
}
}
static int long_to_bytes(unsigned char* res, const mpz_t* v) {
mpz_t t;
mpz_init(t);
mpz_set(t, *v);
int l = 0;
while (mpz_cmp_ui(t, 0)) {
res[l++] = mpz_get_ui(t) & 0xff;
mpz_fdiv_q_ui(t, t, 256);
}
mpz_clear(t);
reverse_str(res, l);
return l;
}
static const char* b62table = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static unsigned char b62table_r[256];
static void init_b62table() {
for (int i = 0; i < strlen(b62table); i++) {
b62table_r[b62table[i] & 0xff] = i;
}
}
static int b62encode(unsigned char* res, const unsigned char* s, int n) {
if (!b62table_r['z']) {
init_b62table();
}
mpz_t v;
mpz_init(v);
bytes_to_long(&v, s, n);
int l = 0;
mpz_t mod;
mpz_init(mod);
while (mpz_cmp_ui(v, 0)) {
mpz_mod_ui(mod, v, 62);
res[l++] = b62table[mpz_get_ui(mod)];
mpz_fdiv_q_ui(v, v, 62);
}
mpz_clear(mod);
mpz_clear(v);
reverse_str(res, l);
res[l] = 0;
return l;
}
static int b62decode(unsigned char* res, const unsigned char* s, int n) {
if (!b62table_r['z']) {
init_b62table();
}
mpz_t v;
mpz_init(v);
for (int i = 0; i < n; i++) {
mpz_mul_ui(v, v, 62);
mpz_add_ui(v, v, b62table_r[s[i]]);
}
int l = long_to_bytes(res, &v);
mpz_clear(v);
res[l] = 0;
return l;
}
static int generate_token(char* res, time_t ts) {
if (!ts) {
ts = time(NULL);
}
char buffer[256];
sprintf(buffer, "gctf25_%ld", ts);
unsigned char hash[20];
SHA1((unsigned char*) buffer, strlen(buffer), hash);
return b62encode((unsigned char*) res, hash, 8);
}
static void update_token_bytes(unsigned char* token_bytes, int token_bytes_len, int pin, int round) {
unsigned char tmp = token_bytes[0];
memmove(token_bytes, token_bytes + 1, token_bytes_len - 1);
token_bytes[token_bytes_len - 1] = tmp;
for (int j = 0; j < token_bytes_len; j++) {
token_bytes[j] = ror1(token_bytes[j], (pin ^ ((round & 3) + 1)) & 7);
}
}
static int saveSecret(unsigned char* res, const void* token, int token_len, int pin, const void* secret, int secret_len) {
unsigned char* token_bytes = malloc(token_len);
int token_bytes_len = b62decode(token_bytes, token, token_len);
assert(token_bytes_len == 8);
memcpy(res, secret, secret_len);
for (int i = 0; i < pin; i++) {
for (int j = 0; j < secret_len; j++) {
res[j] = rol1(res[j] + token_bytes[j % token_bytes_len], j & 7);
}
unsigned char tmp = res[secret_len - 1];
memmove(res + 1, res, secret_len - 1);
res[0] = tmp;
update_token_bytes(token_bytes, token_bytes_len, pin, i);
}
free(token_bytes);
return b62encode(res, res, secret_len);
}
static int restoreSecret(unsigned char* res, const void* token, int token_len, int pin, const void* encrypted, int encrypted_len) {
unsigned char* token_bytes = malloc(0x18);
int token_bytes_len = b62decode(token_bytes, token, token_len);
int res_len = b62decode(res, encrypted, encrypted_len);
unsigned char* token_bytes_list = malloc(pin * token_bytes_len);
for (int i = 0; i < pin; i++) {
memcpy(token_bytes_list + i * token_bytes_len, token_bytes, token_bytes_len);
update_token_bytes(token_bytes, token_bytes_len, pin, i);
}
free(token_bytes);
for (int i = pin - 1; i >= 0; i--) {
unsigned char* _token_bytes = token_bytes_list + i * token_bytes_len;
unsigned char tmp = res[0];
for (int j = 0; j < res_len - 1; j++) {
res[j] = res[j + 1];
}
res[res_len - 1] = tmp;
for (int j = 0; j < res_len; j++) {
res[j] = ror1(res[j], j & 7) - _token_bytes[j % token_bytes_len];
}
}
free(token_bytes_list);
return res_len;
}
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
最后于 2025-7-8 00:41
被SleepAlone编辑
,原因: