原创 KCTF 看雪学院
出题团队简介
赛题设计思路
技术 • 要点
预期 • 破解思路
题目 • 小彩蛋
赛题解析
本赛题解析由看雪论坛 mb_mgodlfyn 给出:
loop:
find rip, "F348A5", 3
cmp $result, 0
je out
bphc
sti
sti
find rdi-0x60, "7D4DBA7A", 0xc0
bph $result+0xc, r, 1
run
jmp loop
out:
# https://github.com/unicorn-engine/unicorn/blob/master/bindings/python/sample_x86.py
# https://github.com/aquynh/capstone/blob/master/bindings/python/capstone/x86.py
# https://github.com/aquynh/capstone/blob/master/bindings/python/capstone/x86_const.py
from unicorn import *
from unicorn.x86_const import *
from capstone import *
from capstone.x86_const import *
import traceback
import ctypes
'''
// cl /O2 -c dodecryptblock.c
// link -DLL -out:libdodecryptblock.dll dodecryptblock.obj
__declspec(dllexport) void dodecryptblock(unsigned char *buf, int len, unsigned char table[256]) {
for (int i = 0; i < len; i++) {
buf[i] = table[buf[i]];
}
}
'''
libdodecryptblock = ctypes.CDLL("./libdodecryptblock.dll")
#global_serial_start = None
#global_serial_len = 32
global_tainted_memlist = set()
global_tainted_reglist = set()
global_last_inst = None
class LoopContext:
__slots__ = ["tracing", "tracecount", "operations", "direction", "address", "memreg"]
def __init__(self):
self.clear()
def clear(self):
self.tracing = False
self.tracecount = 0
self.operations = []
self.direction = None
self.address = None
self.memreg = None
class HoopPrintContext:
__slots__ = ["initial_rsp", "last_rsp", "last_address", "last_inst"]
def __init__(self):
self.initial_rsp = None
self.last_rsp = None
self.last_address = None
self.last_inst = None
def handlekeyboardinterupt(func):
def wrapper_func(*args, **kwargs):
try:
r = func(*args, **kwargs)
except KeyboardInterrupt:
import os
os._exit(0)
except Exception:
traceback.print_exc()
import os
import sys
sys.stdout.flush()
sys.stderr.flush()
os._exit(0)
return r
return wrapper_func
def capstone_reg_to_unicorn_reg(i):
d = {
X86_REG_RAX : UC_X86_REG_RAX,
X86_REG_RBX : UC_X86_REG_RBX,
X86_REG_RCX : UC_X86_REG_RCX,
X86_REG_RDX : UC_X86_REG_RDX,
X86_REG_RBP : UC_X86_REG_RBP,
X86_REG_RSP : UC_X86_REG_RSP,
X86_REG_RSI : UC_X86_REG_RSI,
X86_REG_RDI : UC_X86_REG_RDI,
X86_REG_R8 : UC_X86_REG_R8,
X86_REG_R9 : UC_X86_REG_R9,
X86_REG_R10 : UC_X86_REG_R10,
X86_REG_R11 : UC_X86_REG_R11,
X86_REG_R12 : UC_X86_REG_R12,
X86_REG_R13 : UC_X86_REG_R13,
X86_REG_R14 : UC_X86_REG_R14,
X86_REG_R15 : UC_X86_REG_R15,
X86_REG_AL : UC_X86_REG_AL,
X86_REG_BL : UC_X86_REG_BL,
X86_REG_CL : UC_X86_REG_CL,
X86_REG_DL : UC_X86_REG_DL,
X86_REG_SIL : UC_X86_REG_SIL,
X86_REG_DIL : UC_X86_REG_DIL,
X86_REG_RIP : UC_X86_REG_RIP,
}
return d[i]
def capstone_reg_to_normal_reg(i):
d = {
X86_REG_RAX : X86_REG_RAX,
X86_REG_RBX : X86_REG_RBX,
X86_REG_RCX : X86_REG_RCX,
X86_REG_RDX : X86_REG_RDX,
X86_REG_RBP : X86_REG_RBP,
X86_REG_RSP : X86_REG_RSP,
X86_REG_RSI : X86_REG_RSI,
X86_REG_RDI : X86_REG_RDI,
X86_REG_R8 : X86_REG_R8,
X86_REG_R9 : X86_REG_R9,
X86_REG_R10 : X86_REG_R10,
X86_REG_R11 : X86_REG_R11,
X86_REG_R12 : X86_REG_R12,
X86_REG_R13 : X86_REG_R13,
X86_REG_R14 : X86_REG_R14,
X86_REG_R15 : X86_REG_R15,
X86_REG_EAX : X86_REG_RAX,
X86_REG_EBX : X86_REG_RBX,
X86_REG_ECX : X86_REG_RCX,
X86_REG_EDX : X86_REG_RDX,
X86_REG_EBP : X86_REG_RBP,
X86_REG_ESP : X86_REG_RSP,
X86_REG_ESI : X86_REG_RSI,
X86_REG_EDI : X86_REG_RDI,
X86_REG_R8D : X86_REG_R8,
X86_REG_R9D : X86_REG_R9,
X86_REG_R10D : X86_REG_R10,
X86_REG_R11D : X86_REG_R11,
X86_REG_R12D : X86_REG_R12,
X86_REG_R13D : X86_REG_R13,
X86_REG_R14D : X86_REG_R14,
X86_REG_R15D : X86_REG_R15,
X86_REG_AX : X86_REG_RAX,
X86_REG_BX : X86_REG_RBX,
...完整代码请点击阅读原文
with open("t6.txt", "r") as f:
lines = f.readlines()
smallregs = [
"ax", "bx", "cx", "dx", "si", "di", "bp", "sp", "r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w",
# "al", "ah", "bl", "bh", "cl", "ch", "dl", "dh", "sil", "dil", "bpl", "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b",
]
for i in range(len(lines)-3):
#if lines[i].startswith("0x") and "rsp" not in lines[i] and "pop" not in lines[i] and "push" not in lines[i] and "cmov" not in lines[i]:
if lines[i].startswith("0x"):
line = lines[i]
#print(line)
inst_str = line.split('\t')[1]
mnemonic = inst_str[:inst_str.find(' ')]
op_str = inst_str[inst_str.find(' ')+1:]
tmp = op_str.split(",")
if len(tmp) >= 2:
leftop = tmp[0].strip()
rightop = tmp[1].strip()
else:
leftop = None
rightop = None
#if lines[i+1].startswith(" tainted"):
#if (lines[i+1].startswith(" tainted:") and leftop in smallregs) \
if (lines[i+1].startswith(" tainted:") and "rsp" not in op_str) \
or ("ptr [r13" in lines[i] and (lines[i+1].startswith(" tainted >>>") or lines[i+2].startswith(" tainted >>>"))):
print(lines[i], end="")
print(lines[i+1], end="")
if lines[i+2].startswith(" tainted"):
print(lines[i+2], end="")
1. serial的前16字节和后16字节分别做一次变换
2. 一个类似base64的解码过程,每4字节通过查表和移位得到3字节
def p16(n):
n &= 0xffff
return n.to_bytes(2, 'little')
def u16(s):
assert(len(s) == 2)
return int.from_bytes(s, 'little')
def p64(n):
n &= 0xffffffffffffffff
return n.to_bytes(8, 'little')
def u64(s):
assert(len(s) == 8)
return int.from_bytes(s, 'little')
def rol16(n, k):
n &= 0xffff
return ((n << k) | (n >> (16-k))) & 0xffff
def ror16(n, k):
n &= 0xffff
return ((n >> k) | (n << (16-k))) & 0xffff
def doencrypt1(buf, indextable):
assert(len(buf) == 16)
buf1 = bytearray(buf)
v1 = u16(buf1[0:2]) # s1 ^ k4
v2 = u16(buf1[2:4]) # s2 ^ k4
v3 = u16(buf1[4:6]) # s3 + k3
v4 = u16(buf1[6:8]) # s4 - k3
v5 = u16(buf1[8:0xa]) # s5 + k2
v6 = u16(buf1[0xa:0xc]) # s6 + k2
v7 = u16(buf1[0xc:0xe]) # s7 ^ k1
v8 = u16(buf1[0xe:0x10]) # s8 ^ k1
t1 = v2 ^ v1 # s2 ^ s1
t2 = (v4 + v3) & 0xffff # s4 + s3
t3 = (v5 - v6) & 0xffff # s5 - s6
cl = bin(v8 ^ v7).lstrip("0b").count('1') # s8 ^ s7# hamming distance
v7 = ror16(v7, cl)
v8 = ror16(v8, cl)
k1 = ( (t2 & t1) | ((~t1) & t3) ) & 0xffff
k2 = ( ((k1 * t1) >> cl) + 0x18) & 0xffff
k3 = ( k2 ^ t3 ) & 0xffff # 0x79c6
k4 = ( (k2 & k1) | (k3 & (k2 | k1)) ) & 0xffff
s1 = p16(v1 ^ k4)
s2 = p16(v2 ^ k4)
s3 = p16(v3 - k3)
s4 = p16(v4 + k3)
s5 = p16(v5 - k2)
s6 = p16(v6 - k2)
s7 = p16(v7 ^ k1)
s8 = p16(v8 ^ k1)
buf1[indextable[0]] = s1[1]
buf1[indextable[8]] = s1[0]
buf1[indextable[1]] = s2[1]
buf1[indextable[9]] = s2[0]
buf1[indextable[2]] = s3[1]
buf1[indextable[10]] = s3[0]
buf1[indextable[3]] = s4[1]
buf1[indextable[11]] = s4[0]
buf1[indextable[4]] = s5[1]
buf1[indextable[12]] = s5[0]
buf1[indextable[5]] = s6[1]
buf1[indextable[13]] = s6[0]
buf1[indextable[6]] = s7[1]
buf1[indextable[14]] = s7[0]
buf1[indextable[7]] = s8[1]
buf1[indextable[15]] = s8[0]
return buf1
def dodecrypt1(serial, indextable):
assert(len(serial) == 16)
buf1 = bytearray(serial)
s1 = (buf1[indextable[0]] << 8) | buf1[indextable[8]]
s2 = (buf1[indextable[1]] << 8) | buf1[indextable[9]]
s3 = (buf1[indextable[2]] << 8) | buf1[indextable[10]]
s4 = (buf1[indextable[3]] << 8) | buf1[indextable[11]]
s5 = (buf1[indextable[4]] << 8) | buf1[indextable[12]]
s6 = (buf1[indextable[5]] << 8) | buf1[indextable[13]]
s7 = (buf1[indextable[6]] << 8) | buf1[indextable[14]]
s8 = (buf1[indextable[7]] << 8) | buf1[indextable[15]]
'''
s1 = u16(buf1[0xb:0xd]) # 0x80c8
s2 = u16(buf1[9:0xb]) # 0x8b84
s3 = u16(buf1[7:9]) # 0xefa8
s4 = u16(buf1[5:7]) # 0xf12e
s5 = u16(buf1[3:5]) # 0x0x7a
s6 = u16(buf1[1:3]) # 0xba4d
s7 = (buf1[0] << 8) | buf1[0xf] # 0x7d70
s8 = u16(buf1[0xd:0xf]) # 0x31b8
'''
t1 = s2 ^ s1 # 0xb4c
t2 = (s4 + s3) & 0xffff # 0xe0d6
t3 = (s5 - s6) & 0xffff # 0xe22d
cl = bin(s8 ^ s7).lstrip("0b").count('1') # 6 # hamming distance
print(hex(s1), hex(s2), hex(s3), hex(s4), hex(s5), hex(s6), hex(s7), hex(s8))
print(hex(t1), hex(t2), hex(t3), hex(cl))
k1 = ( (t2 & t1) | ((~t1) & t3) ) & 0xffff # 0xe065
buf1[0xc:0xc+2] = p16(rol16(s7 ^ k1, cl))
buf1[0xe:0xe+2] = p16(rol16(s8 ^ k1, cl))
k2 = ( ((k1 * t1) >> cl) + 0x18) & 0xffff # 0x9beb
buf1[0x8:0x8+2] = p16(s5 + k2)
buf1[0xa:0xa+2] = p16(s6 + k2)
k3 = ( k2 ^ t3 ) & 0xffff # 0x79c6
buf1[0x6:0x6+2] = p16(s4 - k3)
buf1[0x4:0x4+2] = p16(s3 + k3)
k4 = ( (k2 & k1) | (k3 & (k2 | k1)) ) & 0xffff
buf1[0x0:0x0+2] = p16(s1 ^ k4)
buf1[0x2:0x2+2] = p16(s2 ^ k4)
print(hex(k1), hex(k2), hex(k3), hex(k4))
return buf1
def doencode2(s):
assert(len(s) % 3 == 0)
chartable = [149, 226, 128, 198, 234, 195, 213, 141, 158, 197, 179, 98, 100, 77, 118, 186, 146, 253, 222, 127, 66, 114, 129, 173, 121, 84, 115, 133, 134, 94, 241, 132, 106, 245, 99, 216, 254, 168, 192, 200, 79, 201, 199, 3, 123, 229, 223, 2, 0, 13, 31, 60, 19, 34, 37, 59, 43, 23, 170, 160, 246, 151, 89, 88, 109, 15, 12, 6, 61, 27, 14, 33, 20, 38, 45, 4, 18, 28, 9, 1, 46, 51, 53, 35, 32, 11, 56, 26, 50, 44, 57, 124, 209, 242, 92, 117, 161, 8, 36, 16, 40, 41, 63, 49, 7, 10, 47, 17, 25, 30, 62, 21, 29, 42, 58, 52, 22, 5, 54, 55, 39, 48, 24, 108, 74, 122, 68, 152, 150, 105, 196, 235, 204, 73, 190, 181, 72, 113, 148, 225, 163, 177, 120, 250, 83, 70, 64, 203, 188, 71, 131, 193, 238, 249, 232, 97, 169, 251, 194, 210, 76, 85, 218, 247, 126, 217, 143, 172, 227, 82, 96, 155, 233, 86, 156, 137, 87, 180, 81, 125, 176, 116, 142, 162, 157, 237, 182, 224, 95, 252, 75, 110, 165, 65, 208, 167, 187, 240, 140, 145, 101, 185, 212, 230, 135, 184, 191, 248, 236, 159, 154, 211, 111, 147, 93, 102, 136, 67, 91, 80, 243, 130, 183, 206, 103, 231, 244, 255, 175, 205, 214, 220, 171, 104, 90, 138, 221, 239, 228, 189, 78, 164, 119, 178, 69, 166, 153, 112, 219, 107, 215, 144, 207, 174, 139, 202]
rchartable = [None]*256
for i, c in enumerate(chartable):
rchartable[c] = i
r = b""
for i in range(0, len(s), 3):
x, y, z = s[i], s[i+1], s[i+2]
a = (x & 0x3) | ((z & 0xf) << 2)
b = (y & 0x3c) | ((x & 0xc) >> 2)
c = (x >> 4) | ((y & 0x3) << 4)
d = ((y & 0xc0) >> 2) | (z >> 4)
r += bytes([rchartable[a], rchartable[b], rchartable[c], rchartable[d]])
return r
def dodecode2(s):
assert(len(s) % 4 == 0)
chartable = [149, 226, 128, 198, 234, 195, 213, 141, 158, 197, 179, 98, 100, 77, 118, 186, 146, 253, 222, 127, 66, 114, 129, 173, 121, 84, 115, 133, 134, 94, 241, 132, 106, 245, 99, 216, 254, 168, 192, 200, 79, 201, 199, 3, 123, 229, 223, 2, 0, 13, 31, 60, 19, 34, 37, 59, 43, 23, 170, 160, 246, 151, 89, 88, 109, 15, 12, 6, 61, 27, 14, 33, 20, 38, 45, 4, 18, 28, 9, 1, 46, 51, 53, 35, 32, 11, 56, 26, 50, 44, 57, 124, 209, 242, 92, 117, 161, 8, 36, 16, 40, 41, 63, 49, 7, 10, 47, 17, 25, 30, 62, 21, 29, 42, 58, 52, 22, 5, 54, 55, 39, 48, 24, 108, 74, 122, 68, 152, 150, 105, 196, 235, 204, 73, 190, 181, 72, 113, 148, 225, 163, 177, 120, 250, 83, 70, 64, 203, 188, 71, 131, 193, 238, 249, 232, 97, 169, 251, 194, 210, 76, 85, 218, 247, 126, 217, 143, 172, 227, 82, 96, 155, 233, 86, 156, 137, 87, 180, 81, 125, 176, 116, 142, 162, 157, 237, 182, 224, 95, 252, 75, 110, 165, 65, 208, 167, 187, 240, 140, 145, 101, 185, 212, 230, 135, 184, 191, 248, 236, 159, 154, 211, 111, 147, 93, 102, 136, 67, 91, 80, 243, 130, 183, 206, 103, 231, 244, 255, 175, 205, 214, 220, 171, 104, 90, 138, 221, 239, 228, 189, 78, 164, 119, 178, 69, 166, 153, 112, 219, 107, 215, 144, 207, 174, 139, 202]
tmp = bytearray(len(s))
for i, c in enumerate(s):
tmp[i] = chartable[c]
for c in tmp:
assert(c < 0x40)
r = b""
for i in range(0, len(tmp), 4):
a, b, c, d = tmp[i], tmp[i+1], tmp[i+2], tmp[i+3]
x = ((c << 4) & 0xff) | ((b & 0x3) << 2) | (a & 0x3)
y = ((d & 0xf0) << 2) | (b & 0x3c) | ((c & 0x3f) >> 4)
z = ((d << 4) & 0xff) | ((a & 0x3f) >> 2)
r += bytes([x, y, z])
return r
def doencode3(r):
assert(len(r) == 8)
r = p64(u64(r)^0x8267f5d9b0ea143c)
s = bytearray(8)
s[3] = r[0]
s[2] = r[2] ^ s[3]
s[1] = r[1] ^ s[2]
s[0] = r[3] ^ s[1]
s[7] = r[4]
s[6] = r[6] ^ s[7]
s[5] = r[5] ^ s[6]
s[4] = r[7] ^ s[5]
return s
def dodecode3(s):
assert(len(s) == 8)
r = bytearray(8)
r[0] = s[3]
r[1] = s[1] ^ s[2]
r[2] = s[2] ^ s[3]
r[3] = s[0] ^ s[1]
r[4] = s[7]
r[5] = s[5] ^ s[6]
r[6] = s[6] ^ s[7]
r[7] = s[4] ^ s[5]
return p64(u64(r)^0x8267f5d9b0ea143c)
def name_to_serial(name):
assert(len(name) <= 16)
buf5 = bytearray(b"\x9a\x1e\x1d\x1c\x1b\x1a\x19\x18\x17\x16\x15\x14\x13\x12\x11\x10")
buf5[:len(name)] = name
if len(name) < 16:
buf5[len(name)] = 0
print(buf5.hex())
# stage4
indextable3 = [0xb, 0xd, 0xf, 0x1, 0x3, 0x5, 0x7, 0x9, 0xc, 0xe, 0x0, 0x2, 0x4, 0x6, 0x8, 0xa]
buf3_2 = doencrypt1(buf5, indextable3)
print(buf3_2.hex())
# stage3
print("buf5", buf5[:8])
buf3_1 = doencode3(buf5[:8])
print(buf3_1.hex())
buf3 = buf3_1 + buf3_2
# stage2
buf2 = doencode2(buf3)
print(buf2.hex())
# stage1
indextable1 = [0xc, 0xa, 0x8, 0x6, 0x4, 0x2, 0x0, 0xe, 0xb, 0x9, 0x7, 0x5, 0x3, 0x1, 0xf, 0xd]
indextable2 = [0xb, 0x9, 0x7, 0x5, 0x3, 0x1, 0xf, 0xd, 0x8, 0x6, 0x4, 0x2, 0x0, 0xe, 0xc, 0xa]
serial = doencrypt1(buf2[:16], indextable1) + doencrypt1(buf2[16:], indextable2)
print("serial")
print(serial.hex())
return serial
def serial_to_name(serial):
assert(len(serial) == 32)
# stage1
indextable1 = [0xc, 0xa, 0x8, 0x6, 0x4, 0x2, 0x0, 0xe, 0xb, 0x9, 0x7, 0x5, 0x3, 0x1, 0xf, 0xd]
indextable2 = [0xb, 0x9, 0x7, 0x5, 0x3, 0x1, 0xf, 0xd, 0x8, 0x6, 0x4, 0x2, 0x0, 0xe, 0xc, 0xa]
buf2 = dodecrypt1(serial[:16], indextable1) + dodecrypt1(serial[16:], indextable2)
print("buf2:", buf2.hex(), buf2)
# stage2
buf3 = dodecode2(buf2)
print("buf3", buf3.hex())
# stage3
print(buf3[:8].hex())
buf4 = dodecode3(buf3[:8])
# stage4
indextable3 = [0xb, 0xd, 0xf, 0x1, 0x3, 0x5, 0x7, 0x9, 0xc, 0xe, 0x0, 0x2, 0x4, 0x6, 0x8, 0xa]
buf5 = dodecrypt1(buf3[8:], indextable3)
print(buf5)
# stage5
assert(buf4 == buf5[:8])
return bytes(buf5)
test_serial = bytes.fromhex(("7D 4D BA 7A 9C 2E F1 A8 EF 84 8B C8 80 B8 31 70" + "43 1F 37 E5 99 04 8D BB 88 C3 06 BC 40 35 79 D1").replace(" ", ""))
#print(name_to_serial(b"FE0C37052AED0E33"))
#serial_to_name(test_serial)
serial = name_to_serial(b"KCTF")
print(serial_to_name(serial))
print(serial.hex().upper()) # B91AE5FCDA57D87406968CBDB8829799790A77302D7E8754B705894489B37A10
往期解析
1. 看雪·深信服 2021 KCTF 春季赛 | 第二题设计思路及解析
球分享
球点赞
球在看