这是一场人类与超智能AI的“生死”较量
请立刻集结,搭乘SpaceX,前往AI控制空间站
智慧博弈 谁能问鼎
看雪·2023 KCTF 年度赛于9月1日中午12点正式开赛!比赛基本延续往届模式,设置了难度值、火力值和精致度积分。由此来引导竞赛的难度和趣味度,使其更具挑战性和吸引力,同时也为参赛选手提供了更加公平、有趣的竞赛平台。
*注意:签到题持续开放,整个比赛期间均可提交答案获得积分
今日中午12:00第十二题《深入内核》已截止答题,该题仅有xxx支战队成功提交flag,一起来看下该题的设计思路和解析吧。
出题团队简介
出题战队:中午吃什么
战队成员:wx_孤城、瑞皇、hmfzy、charon561
设计思路
设计说明:
一个Windows的CrakeMe,难度中上。设计了一个简单的脚本解释器
脚本明文经过加密然后bin2hex转成数组存入全局变量。
赛题解析
程序中的一些关键函数加了混淆,直接把 0x49A0E8 处 patch 为 double 类型的 100.0 ,再声明其类型为 const double 这样 ida F5 就能直接去除无关的逻辑。
题目实现了一个虚拟机,程序启动后会先解密得到虚拟机执行的命令,之后来到函数 0x460D0E 执行:
char __thiscall global_vtable_2_func_18(C *this)
{
C *v1; // ebx
int _pc; // esi
B *ins_start; // edi MAPDST
char *v6; // esi
_DWORD *v7; // eax
const char *v8; // ecx
unsigned int v9; // edx
unsigned int v10; // edi
_BYTE *v11; // ebx
char v12; // al
int v14; // [esp+8h] [ebp-6Ch]
char v16[88]; // [esp+1Ch] [ebp-58h] BYREF
v1 = this;
if ( v1->instructions.start == v1->instructions.finish )
return 0;
this->pc = 0;
ins_start = this->instructions.start;
if ( v1->instructions.finish - ins_start > 0 )
{
_pc = 0;
ins_start = v1->instructions.start;
do
{
if ( _pc == -1 )
{
v1->pc = 0;
_pc = 0;
}
if ( !(*((unsigned __int8 (__thiscall **)(C *, B *))v1->vtable + 15))(v1, &ins_start[_pc]) )
{
// show error message
}
_pc = v1->pc + 1;
v1->pc = _pc;
ins_start = v1->instructions.start;
}
while ( _pc < v1->instructions.finish - ins_start );
}
return 1;
}
v1->vtable + 15 就是虚表中的第 15 项,地址为 0x4525EB 。
相关数据结构:
struct A
{
int field_0;
std::string s0;
int i1;
int i2;
int i3;
std::string s4;
std::string s5;
std::string s6;
};
struct std::vector$A$
{
A *start;
A *finish;
A *end_of_storage;
};
struct B
{
int is_label;
int field_4;
std::string s1;
std::string s2;
std::vector$A$ vector;
};
struct std::vector$B$
{
B *start;
B *finish;
B *end_of_storage;
};
struct RBTree // std::map<std::string, std::string>
{
struct RBTree *left;
struct RBTree *parent;
struct RBTree *right;
char color;
char isnil;
char padding[2];
std::string key;
std::string value;
};
struct C
{
int **vtable;
RBTree *tree;
int node_count;
std::vector$B$ instructions;
int pc;
char valid_data;
};
将解密之后的指令所在的内存 dump 出来,写脚本恢复原始指令:
def vm(ins):
opcode = int(ins[2])
operands = ins[3]
if opcode == 0x21344D4938CE0640:
print('system({0})'.format(repr(operands[0][0].decode())))
elif opcode == 0x3820EA1739C3E154:
print('map[{0}] = hex(vigenere(map[{0}], "恭喜发财".encode("gbk")))'.format(repr(operands[0][4])))
elif opcode == 0x4B0134D06B40680:
print('if (len(map[{0}]) {1} {2}) goto {3}'.format(repr(operands[0][4]), operands[1][5].decode(), operands[2][1], operands[3][6].decode()))
elif opcode == 0x5304FD305CA8C22A:
print('sleep({})'.format(operands[0][1]))
elif opcode == 0x5AC009C0F14B76E8:
print('map[{0}] = input()'.format(repr(operands[0][4])))
elif opcode == 0x6975C7A3C07CD226:
print('map[{0}] += {1}'.format(repr(operands[0][4]), repr(operands[1][0])))
elif opcode == 0x7929CBF0A1496FB0:
print('map[{0}] = base64encode(map[{0}])'.format(repr(operands[0][4])))
elif opcode == 0x885F75A1461ECEBB:
print('if (map[{0}] == map[{1}]) goto {2}'.format(repr(operands[0][4]), repr(operands[1][4]), operands[2][6].decode()))
elif opcode == 0x8DB9D83D80004137:
print('print({})'.format(repr(operands[0][0].replace(b'\\n', b'\n').decode('gbk'))))
elif opcode == 0xA43CBF9D015186F1:
if len(operands): code = operands[0][1]
else: code = 0
print('exit({0})'.format(code))
elif opcode == 0xAA9C8E70F01F8D61:
print('map[{0}] = map[{1}]'.format(repr(operands[0][4]), repr(operands[1][4])))
elif opcode == 0xDDEEFF2200112233:
print('nop')
elif opcode == 0xE56D33B21C50A892:
print('goto {0}'.format(operands[0][6].decode()))
elif opcode == 0xF4CC06C2E34200F0:
print('nop 0')
elif opcode == 0xF4CC06F2E3420459:
print('nop 1')
elif opcode == 0xFD1D1DFB19850CA1:
print('map[{0}] = md5(map[{0}])'.format(repr(operands[0][4])))
else:
assert False, hex(opcode)
mem_offset = 0xea0000
mem_size = 0x20000
mem_dump = open('MEM_00EA0000_00020000.mem', 'rb').read()
ins_start = 0xeb4b00
ins_end = 0xeb62a4
def read_dword(addr):
assert mem_offset <= addr < mem_offset + mem_size
return int.from_bytes(mem_dump[addr - mem_offset: addr - mem_offset + 4], 'little')
def read_data(addr, size):
assert mem_offset <= addr < mem_offset + mem_size - size
return mem_dump[addr - mem_offset: addr - mem_offset + size]
def read_str(addr):
size = read_dword(addr + 0x10)
cap = read_dword(addr + 0x14)
if cap > 0x10: addr = read_dword(addr)
return read_data(addr, size)
def read_A(addr):
return (read_str(addr + 4), read_dword(addr + 0x1c), read_dword(addr + 0x20), read_dword(addr + 0x24), read_str(addr + 0x28), read_str(addr + 0x40), read_str(addr + 0x58))
def read_B(addr):
B = (read_dword(addr), read_dword(addr + 4), read_str(addr + 0x20), [])
A_start = read_dword(addr + 0x38)
A_end = read_dword(addr + 0x3c)
for addr in range(A_start, A_end, 0x70):
B[-1].append(read_A(addr))
return B
ins = []
for addr in range(ins_start, ins_end, 0x44):
ins.append(read_B(addr))
for i in range(len(ins)):
t = ins[i]
if t[0]:
print(t[3][0][6].decode() + ':')
else:
# print(i, end=': ')
print(' ', end='')
vm(ins[i])
得到输出:
sleep(25)
print(' \n')
sleep(25)
print(' _oo0oo_ \n')
sleep(25)
print(' o8888888o \n')
sleep(25)
print(' 88" . "88 \n')
sleep(25)
print(' (| -_- |) \n')
sleep(25)
print(' 0\\ = /0 \n')
sleep(25)
print(" ___/`---'\\___ \n")
sleep(25)
print(" .'\\.\\| |/./'. \n")
sleep(25)
print(' / \\.\\|| : ||/./ \\ \n')
sleep(25)
print(' / _||||| -:- |||||- \\ \n')
sleep(25)
print(' | | \\.\\.\\ - /././ | | \n')
sleep(25)
print(" | \\_| ''\\---/'' |_/ | \n")
sleep(25)
print(" \\ .-\\__ '-' ___/-. / \n")
sleep(25)
print(" ___'. .' /--.--\\ `. .'___ \n")
sleep(25)
print(' ."" \'< `.___\\_<|>_/___.\' >\' "". \n')
sleep(25)
print(' | | : `- \\`.;`\\ _ /`;.`/ - ` : | | \n')
sleep(25)
print(' \\ \\ `_. \\_ __\\ /__ _/ .-` / / \n')
sleep(25)
print("=====`-.____`.___ \\_____/___.-`___.-'===== \n")
sleep(25)
print(" `=---=' \n")
sleep(25)
print(' \n')
sleep(25)
print(' 佛祖保佑 永无BUG \n')
sleep(25)
print(' 看雪 KCTF2023年度赛 \n')
sleep(25)
print(' 出题战队:中午吃什么 \n')
sleep(25)
print(' \n')
sleep(25)
print(' \n')
nop
sleep(25)
print('请输入用户名:\n--> ')
map[b'2569430338759937617'] = input()
nop
sleep(25)
print('请输入序列号:\n--> ')
map[b'1509181994979340817'] = input()
nop
nop 0
nop 1
if (len(map[b'1509181994979340817']) != 32) goto 10547232137042693405
nop
map[b'18097274335226857185'] = map[b'2569430338759937617']
map[b'18097274335226857185'] += b'KCTF2023'
map[b'18097274335226857185'] = hex(vigenere(map[b'18097274335226857185'], "恭喜发财".encode("gbk")))
map[b'18097274335226857185'] = base64encode(map[b'18097274335226857185'])
map[b'18097274335226857185'] = md5(map[b'18097274335226857185'])
nop
if (map[b'18097274335226857185'] == map[b'1509181994979340817']) goto 15317636321340901566
goto 15315557398280875957
nop
10547232137042693405:
sleep(25)
print('error\n\n\n')
system('pause')
exit(0)
nop
15317636321340901566:
sleep(25)
print('Success GoodJob!\n\n\n')
system('pause')
exit(0)
nop
15315557398280875957:
sleep(25)
print('error\n\n\n')
system('pause')
exit(0)
求解:
#!/usr/bin/env python3
from base64 import b64encode
from hashlib import md5
def vigenere(x, y):
return bytes(x[i] ^ y[i % len(y)] for i in range(len(x)))
def solve(username):
return md5(b64encode(vigenere(username + b'KCTF2023', "恭喜发财".encode("gbk")).hex().upper().encode())).hexdigest()
'''
User-Name : 4070382B95A4F0ED
Serial-Number : 47a62c6eb8a72031a27b89abc3d976f7
'''
assert solve(b'4070382B95A4F0ED') == '47a62c6eb8a72031a27b89abc3d976f7'
print(solve(b'KCTF')) # 35090e1336f1d1e872ba798256db1bfb
2、然后hook 算法相关函数。sub_403CFE、sub_403BA0、sub_4011C2,并打印参数,会发现:OEQ5N0Y4ODI4NDlBODA4NDgwOTI4RTg2RjE5MkY3ODJGMkU0OUJGNDg1OTI4MEY1
043998a8 4f 45 51 35 4e 30 59 34 4f 44 49 34 4e 44 6c 42 OEQ5N0Y4ODI4NDlB
043998b8 4f 44 41 34 4e 44 67 77 4f 54 49 34 52 54 67 32 ODA4NDgwOTI4RTg2
043998c8 52 6a 45 35 4d 6b 59 33 4f 44 4a 47 4d 6b 55 30 RjE5MkY3ODJGMkU0
043998d8 4f 55 4a 47 4e 44 67 31 4f 54 49 34 4d 45 59 31 OUJGNDg1OTI4MEY1
35090e1336f1d1e872ba798256db1bfb
截至发文,本题已有2支战队成功提交flag,他们分别是:
球分享
球点赞
球在看
点击阅读原文进入比赛