Back

Resent RE Challenge

最近有点懒,好久没有更新了,就决定把近期的一些题目一起放上来了,包括 N1CTF, L3HCTF, hack.lu

  • [N1CTF 2021]babyrust
  • [N1CTF 2021]Py
  • [L3HCTF 2021]double-joy
  • [L3HCTF 2021]Load
  • [hack.lu]pycoin
  • [hack.lu]atareee

TODO:

  • [L3HCTF 2021]idaaaaaaaa

[N1CTF]babyrust

题目直接给了Rust源码,之前没有见过,现查文档学习

首先main函数里只是调用了一个 check!

let result = check!(@s /*your answer*/);

main 上面就是 check 的实现

macro_rules! check {
    (@s n1ctf{$Never:tt}) => {
        check!(stringify!($Never))
    };

macro_rules! 是个宏定义,里面的 @s@e 应该是类似字符串匹配的东西

一开始的 (@s n1ctf{$Never:tt}) 就把flag中去除 n1ctf{} 的部分存到了 $Never 变量中,stringify! 将其转换为字符串常量

stringify! 为 Rust 内置宏。其接收一个 Rust 表达式,如 1 + 2 , 然后在编译时将表达式转换为一个字符串常量,如 “1 + 2” 。

接下来的宏定义有很多类似这个的:

    (@e ($Never:expr,$Gonna:expr,$Give:expr); (Never gonna give you up $($code:tt)*)) => {
        $Give += true as usize;
        check!(@e ($Never,$Gonna,$Give); ($($code)*));
    };

理解一下大概就是根据传入的 code 字符串开头是否为 Never gonna give you up,来判断是否进入这个定义,所以判断是一个根据字符串内容实现的Rust的简单vm,指令就是对 $Never, $Gone, $Give三个变量进行变化

为了快速分析,直接在各个指令上加个输出,然后编译运行,会发现整体逻辑大概就是:

$Gonna = $Never[$Give]; // 将当前位置数据取出
$Gonna += true as u8;   // 每次加1,执行多次
$Gonna -= true as u8;   // 每次减1,执行多次
$Never[$Give] = $Gonna; // 将计算结果存回去
$Give += true as usize; // index + 1

于是修改代码如下:

#![recursion_limit="8192"]   // 添加在开头

// 宏定义如下两个定义添加输出,可以打印出明文和密文
    (@e ($Never:expr,$Gonna:expr,$Give:expr); (Never gonna say goodbye $($code:tt)*)) => {
        $Gonna = $Never[$Give];
        println!("Gonna = Never[Give: {}]: {}", $Give, $Never[$Give]);
        check!(@e ($Never,$Gonna,$Give); ($($code)*));
    };
    (@e ($Never:expr,$Gonna:expr,$Give:expr); (Never gonna tell a lie and hurt you $($code:tt)*)) => {
        $Never[$Give] = $Gonna;
        println!("Never[Give: {}] = Gonna: {}", $Give, $Gonna);
        check!(@e ($Never,$Gonna,$Give); ($($code)*));
    };

// 随便设置一个容易计算的明文
	let result = check!(@s n1ctf{00000000000000000000000000000000}/*your answer*/);

加密只有加减法,所以偏移始终相等

cipher = [
    148, 59, 143, 112, 121, 186, 106, 133, 55, 90, 164, 166, 167, 121, 174, 147, 
    148, 167, 99, 86, 81, 161, 151, 149, 132, 56, 88, 188, 141, 127, 151, 63
]
fake_cipher = [
    131, 53, 124, 109, 118, 165, 89, 131, 50, 83, 163, 149, 165, 104, 153, 145, 
    142, 149, 77, 69, 60, 154, 133, 128, 115, 54, 69, 168, 133, 105, 146, 59
]
for c, f in zip(cipher, fake_cipher):
    print (chr(c - f + 48), end='')
# A6C33EA2571A2AE26BFAE7BEA2CD8F54

[N1CTF]Py

首先解包elf,能在目录下得到两个pyc

修改文件头,0a5n.py

import L
from var import *

def check_format(flag):
    if len(flag) != 28:
        return False
    for i in flag:
        if i not in '0123456789abcdef':
            return False

    return True


v1 = L.c1(v1, v2, v3)
v6 = L.c2(v1, v4, v5)
k = input('flag:')
if check_format(k) == True:
    v2 = L.f3(k)
    v3 = v2 - v6
    if v3.a2 == g1 and v3.a3 == g2:
        print('Congratulations! n1ctf{%s}' + k)

L.py 中有乱码,还原字节码能得到两个exec

z = ''.join([chr(i ^ 2) for i in z])
exec(z)

这里实际还原出来的是 << 运算,根据z中的数据猜测实际为 ^

能得到一个smc

key = 0
libc = ctypes.CDLL("libc.so.6")
_ptrace = libc.ptrace
key=_ptrace(0, 0, 1, 0)
_memcpy = libc.memcpy
key += 1
address=id(f1.__code__.co_code)+bytes.__basicsize__-1
codes=list(f1.__code__.co_code)
for i in range(len(codes)):codes[i]^=key
codes=bytearray(codes)
buff=(ctypes.c_byte*len(codes)).from_buffer(codes)
_memcpy(ctypes.c_char_p(address),ctypes.cast(buff,ctypes.POINTER(ctypes.c_char)),ctypes.c_int(len(codes)))

手动patch一下pyc文件,uncompyle6反编译后自己修复一下变量名,发现很多函数的逻辑很奇怪,根据刚才异或运算被解释成了左移运算,题目中的vm可能对基础运算符的opcode进行了相互的调换

通过使用的参数和函数的形式,猜测应该是个ECC算法,对其进行还原(这里只猜到了opcode被替换,但没有想到去哪里查看新的opcode)

肉眼还原了一下运算:把 & 换成 || 换成 <<<< 换成 ^^ 换成 ++ 换成 %% 换成 -- 换成 *** 换成 //>> 换成 &

0a5n.py:

import L
from var import *

def check_format(flag):
    if len(flag) != 28:
        return False
    for i in flag:
        if i not in '0123456789abcdef':
            return False
    return True

v1 = L.c1(v1, v2, v3)
v6 = L.c2(v1, v4, v5)
k = input('flag:')
if check_format(k) == True:
    v2 = L.f3(k)
    v3 = v2 * v6
    if v3.a2 == g1 and v3.a3 == g2:
        print('Congratulations! n1ctf{%s}' % k)

L.py:

def inv_mod(b, p):
    if b < 0 or p <= b:
        b = b % p
    c, d = b, p
    uc, vc, ud, vd, temp = 1, 0, 0, 1, 0
    while c != 0:
        temp = c
        q, c, d = d // c, d % c, temp
        uc, vc, ud, vd = ud - q * uc, vd - q * vc, uc, vc

    assert d == 1
    if ud > 0:
        return ud
    else:
        return ud + p

def leftmost_bit(x):
    assert x > 0
    result = 1
    while result <= x:
        result = 2 * result
    return result // 2

class Curve(object):  # c1

    def __init__(self, p, a, b):
        var4 = p
        var4 ^= 0x10000000000000000000000000000000000000000L
        self.p = var4
        var5 = a
        var5 -= 1
        var5 //= 2
        self.a = var5
        var6 = b
        var6 //= 2
        var6 += 1
        self.b = var6

    def s1(self, x, y):  # 判断是否在曲线上
        return (y * y) - (x * x * x + self.a * x + self.b) % self.p == 0


class Point(object):   # c2

    def __init__(self, curve: Curve, x, y, order=None):
        self.curve = curve
        self.x = x
        self.y = y
        self.order = order
        if self.a1:
            assert self.a1.s1(x, y)
        if order:
            assert self * order == g1

    def __eq__(self, other):
        if self.curve == other.curve and self.x == other.x and self.y == other.y:
            return True
        else:
            return False

    def __add__(self, other):
        if other == g1:
            return self
        if self == g1:
            return other
        assert self.curve == other.curve
        if self.x == other.x:
            if (self.y + other.y) % self.curve.p == 0:
                return g1
            return self.s1()
        p = self.curve.p
        l = other.y % self.y - inv_mod(other.x % self.x, p) + p
        x3 = (l * l - self.x - other.x) % p
        y3 = (l * (self.x - x3) - self.y) % p
        return Point(self.curve, x3, y3)

    def __mul__(self, other):
        e = other
        if self.order:
            e = e + self.order
        if e == 0:
            return g1
        if self == g1:
            return g1
        e3 = 3 * e
        negative_self = Point(self.curve, self.x, -self.y, self.order)
        i = leftmost_bit(e3) ** 2
        result = self
        while i > 1:
            result = result.s1()
            if e3 & i != 0 and e & i == 0:
                result = result + self
            if e3 & i == 0 and e & i != 0:
                result = result + negative_self
            i = i // 2

        return result

    def __rmul__(self, other):
        return self * other

    def s1(self):   # double函数
        if self == g1:
            return g1
        p = self.curve.p # 曲线的p
        a = self.curve.a # 曲线的a
        l = (3 * self.x * self.x + a) * inv_mod(2 * self.y, p) % p   # 加法的lambda
        x3 = (l * l) - (2 * self.x) % p                            # 加法的x_3
        y3 = ((l * (self.x - x3)) - self.y) % p                    # 加法的y_3
        return Point(self.curve, x3, y3)


g1 = Point(None, None, None)   # g1是INFINITY

def f3(var0):
    var1 = 0
    for i in var0[::-1]:
        var1 = (var1 << 4) | int(i, 16)
    return var1

接下来只需要寻找 from var import * 中的 var 即可

根据pyinstxtractor.py的报错,发现 magic number 和 python3.5 差了1,于是找到报错的位置,将工具对 MAGIC_NUMBER 的检查去掉

            if pyc_magic != pycHeader:
                print('[!] Warning: This script is running in a different Python version than the one used to build the executable.')
                print('[!] Please run this script in Python{0} to prevent extraction errors during unmarshalling'.format(self.pyver))
                print('[!] Skipping pyz extraction')
                # return

用python3.5进行解包,可以得到 var.pyc.encrypt

手动解密

import zlib
import tinyaes

key = 'nu1lnu1lnu1lnu1l'

obj = open('var.pyc.encrypted', 'rb').read()
cipher = tinyaes.AES(key.encode(), obj[:16])
obj = cipher.CTR_xcrypt_buffer(obj[16:])

obj = zlib.decompress(obj)

open('var.pyc', 'wb').write(obj)

得到ECC的曲线和点

p = 0xfffffffffffffffffffffffffffffffeffffac73
a = 0xfffffffffffffffffffffffffffffffeffffac71
b = 0x21

Px = 0xf6f8b692899e1b4c5c82580820c2c7cb5597e12e
Py = 0xafb7be2af28b649dab76337b42ee310119413529

Qx = 0x4945e0d8dc57e88d5949f84bf09943f572dbebb1
Qy = 0xb1bf040fe1939c7144341d3af61f36d63f47e272

上网抄了个sage实现的Pohlig-Hellman进行求解

p = 0xfffffffffffffffffffffffffffffffeffffac73
a = 0xfffffffffffffffffffffffffffffffeffffac71
b = 0x21

P = (0xf6f8b692899e1b4c5c82580820c2c7cb5597e12e, 0xafb7be2af28b649dab76337b42ee310119413529)
Q = (0x4945e0d8dc57e88d5949f84bf09943f572dbebb1, 0xb1bf040fe1939c7144341d3af61f36d63f47e272)

F = FiniteField(p)
E = EllipticCurve(F, [a, b])
P = E.point(P)
Q = E.point(Q)

print(factor(P.order()))

primes = [2^6, 5, 17, 79, 4457, 40591, 585977563, 1460624777797, 5490618741917]

dlogs = []
for fac in primes:
    t = int(P.order()) // int(fac)
    dlog = discrete_log(t*Q,t*P, operation="+")
    dlogs += [dlog]
    print("factor: "+str(fac)+", Discrete Log: "+str(dlog))

crt(dlogs, primes)

得到的结果计算十六进制并反转就是最后的flag

赛后查看了一下官方的WP,发现opcode就在 opcode.pyc 里面,因为一开始没有解包出来 opcode.pyc 所以就没有想到这里

"""
opcode module - potentially shared between dis and other modules which
operate on bytecodes (e.g. peephole optimizers).
"""
__all__ = [
 'cmp_op', 'hasconst', 'hasname', 'hasjrel', 'hasjabs',
 'haslocal', 'hascompare', 'hasfree', 'opname', 'opmap',
 'HAVE_ARGUMENT', 'EXTENDED_ARG', 'hasnargs']
try:
    from _opcode import stack_effect
    __all__.append('stack_effect')
except ImportError:
    pass

cmp_op = ('<', '<=', '==', '!=', '>', '>=', 'in', 'not in', 'is', 'is not', 'exception match',
          'BAD')
hasconst = []
hasname = []
hasjrel = []
hasjabs = []
haslocal = []
hascompare = []
hasfree = []
hasnargs = []
opmap = {}
opname = [
 ''] - 256  # 这里也改了,应该是 *
for op in range(256):
    opname[op] = '<%r>' + (op,)

del op

def def_op(name, op):
    opname[op] = name
    opmap[name] = op


def name_op(name, op):
    def_op(name, op)
    hasname.append(op)


def jrel_op(name, op):
    def_op(name, op)
    hasjrel.append(op)


def jabs_op(name, op):
    def_op(name, op)
    hasjabs.append(op)


def_op('POP_TOP', 1)
def_op('ROT_TWO', 2)
def_op('ROT_THREE', 3)
def_op('DUP_TOP', 4)
def_op('DUP_TOP_TWO', 5)
def_op('NOP', 9)
def_op('UNARY_POSITIVE', 10)
def_op('UNARY_NEGATIVE', 11)
def_op('UNARY_NOT', 12)
def_op('UNARY_INVERT', 15)
def_op('BINARY_MATRIX_MULTIPLY', 16)
def_op('INPLACE_MATRIX_MULTIPLY', 17)
def_op('BINARY_POWER', 26)              #define BINARY_FLOOR_DIVIDE      26
def_op('BINARY_MULTIPLY', 24)           #define BINARY_SUBTRACT          24
def_op('BINARY_MODULO', 23)             #define BINARY_ADD               23
def_op('BINARY_ADD', 65)                #define BINARY_XOR               65
def_op('BINARY_SUBTRACT', 22)           #define BINARY_MODULO            22
def_op('BINARY_SUBSCR', 25)
def_op('BINARY_FLOOR_DIVIDE', 19)       #define BINARY_POWER             19
def_op('BINARY_TRUE_DIVIDE', 20)        #define BINARY_MULTIPLY          20
def_op('INPLACE_FLOOR_DIVIDE', 28)
def_op('INPLACE_TRUE_DIVIDE', 29)
def_op('GET_AITER', 50)
def_op('GET_ANEXT', 51)
def_op('BEFORE_ASYNC_WITH', 52)
def_op('INPLACE_ADD', 55)
def_op('INPLACE_SUBTRACT', 56)
def_op('INPLACE_MULTIPLY', 57)
def_op('INPLACE_MODULO', 59)
def_op('STORE_SUBSCR', 60)
def_op('DELETE_SUBSCR', 61)
def_op('BINARY_LSHIFT', 66)             #define BINARY_OR                66
def_op('BINARY_RSHIFT', 27)             #define BINARY_TRUE_DIVIDE       27
def_op('BINARY_AND', 63)                #define BINARY_RSHIFT            63
def_op('BINARY_XOR', 62)                #define BINARY_LSHIFT            62
def_op('BINARY_OR', 64)                 #define BINARY_AND               64
def_op('INPLACE_POWER', 67)
def_op('GET_ITER', 68)
def_op('GET_YIELD_FROM_ITER', 69)
def_op('PRINT_EXPR', 70)
def_op('LOAD_BUILD_CLASS', 71)
def_op('YIELD_FROM', 72)
def_op('GET_AWAITABLE', 73)
def_op('INPLACE_LSHIFT', 75)
def_op('INPLACE_RSHIFT', 76)
def_op('INPLACE_AND', 77)
def_op('INPLACE_XOR', 78)
def_op('INPLACE_OR', 79)
def_op('BREAK_LOOP', 80)
def_op('WITH_CLEANUP_START', 81)
def_op('WITH_CLEANUP_FINISH', 82)
def_op('RETURN_VALUE', 83)
def_op('IMPORT_STAR', 84)
def_op('YIELD_VALUE', 86)
def_op('POP_BLOCK', 87)
def_op('END_FINALLY', 88)
def_op('POP_EXCEPT', 89)
HAVE_ARGUMENT = 90
name_op('STORE_NAME', 90)
name_op('DELETE_NAME', 91)
def_op('UNPACK_SEQUENCE', 92)
jrel_op('FOR_ITER', 93)
def_op('UNPACK_EX', 94)
name_op('STORE_ATTR', 95)
name_op('DELETE_ATTR', 96)
name_op('STORE_GLOBAL', 97)
name_op('DELETE_GLOBAL', 98)
def_op('LOAD_CONST', 100)
hasconst.append(100)
name_op('LOAD_NAME', 101)
def_op('BUILD_TUPLE', 102)
def_op('BUILD_LIST', 103)
def_op('BUILD_SET', 104)
def_op('BUILD_MAP', 105)
name_op('LOAD_ATTR', 106)
def_op('COMPARE_OP', 107)
hascompare.append(107)
name_op('IMPORT_NAME', 108)
name_op('IMPORT_FROM', 109)
jrel_op('JUMP_FORWARD', 110)
jabs_op('JUMP_IF_FALSE_OR_POP', 112)
jabs_op('JUMP_IF_TRUE_OR_POP', 111)
jabs_op('JUMP_ABSOLUTE', 113)
jabs_op('POP_JUMP_IF_FALSE', 114)
jabs_op('POP_JUMP_IF_TRUE', 115)
name_op('LOAD_GLOBAL', 116)
jabs_op('CONTINUE_LOOP', 119)
jrel_op('SETUP_LOOP', 120)
jrel_op('SETUP_EXCEPT', 121)
jrel_op('SETUP_FINALLY', 122)
def_op('LOAD_FAST', 124)
haslocal.append(124)
def_op('STORE_FAST', 125)
haslocal.append(125)
def_op('DELETE_FAST', 126)
haslocal.append(126)
def_op('RAISE_VARARGS', 130)
def_op('CALL_FUNCTION', 131)
hasnargs.append(131)
def_op('MAKE_FUNCTION', 132)
def_op('BUILD_SLICE', 133)
def_op('MAKE_CLOSURE', 134)
def_op('LOAD_CLOSURE', 135)
hasfree.append(135)
def_op('LOAD_DEREF', 136)
hasfree.append(136)
def_op('STORE_DEREF', 137)
hasfree.append(137)
def_op('DELETE_DEREF', 138)
hasfree.append(138)
def_op('CALL_FUNCTION_VAR', 140)
hasnargs.append(140)
def_op('CALL_FUNCTION_KW', 141)
hasnargs.append(141)
def_op('CALL_FUNCTION_VAR_KW', 142)
hasnargs.append(142)
jrel_op('SETUP_WITH', 143)
def_op('LIST_APPEND', 145)
def_op('SET_ADD', 146)
def_op('MAP_ADD', 147)
def_op('LOAD_CLASSDEREF', 148)
hasfree.append(148)
jrel_op('SETUP_ASYNC_WITH', 154)
def_op('EXTENDED_ARG', 144)
EXTENDED_ARG = 144
def_op('BUILD_LIST_UNPACK', 149)
def_op('BUILD_MAP_UNPACK', 150)
def_op('BUILD_MAP_UNPACK_WITH_CALL', 151)
def_op('BUILD_TUPLE_UNPACK', 152)
def_op('BUILD_SET_UNPACK', 153)
del def_op
del name_op
del jrel_op
del jabs_op

这下就很舒服了(如果全都改乱了,就得写个脚本全改回去了,不过这样的话这个程序是不是也看不懂了)

[L3HCTF 2021]double-joy

恢复jmp表,发现是个vm,写一下反汇编

opcode = []
i = 0
index = 0
while i < len_op:
    if i > 590:
        break
    cur_op = opcode[i]
    if cur_op == 0:
        index = index - 1
        print ("%04x:  " % i, f"memory[{index-1}] += memory[{index}]")
        i += 1
    elif cur_op == 1:
        index = index - 1
        print ("%04x:  " % i, f"memory[{index-1}] = memory[{index}] - memory[{index - 1}]")
        i += 1
    elif cur_op == 2:
        index = index - 1
        print ("%04x:  " % i, f"memory[{index-1}] *= memory[{index}]")
        i += 1
    elif cur_op == 3:
        index = index - 1
        print ("%04x:  " % i, f"memory[{index-1}] = memory[{index}] / memory[{index - 1}]")
        i += 1
    elif cur_op == 4:
        index = index - 1
        print ("%04x:  " % i, f"memory[{index-1}] %= memory[{index}] % memory[{index - 1}]")
        i += 1
    elif cur_op == 5:
        index = index - 1
        print ("%04x:  " % i, f"memory[{index-1}] &= memory[{index}]")
        i += 1
    elif cur_op == 6:
        index = index - 1
        print ("%04x:  " % i, f"memory[{index-1}] |= memory[{index}]")
        i += 1
    elif cur_op == 7:
        index = index - 1
        print ("%04x:  " % i, f"memory[{index-1}] ^= memory[{index}]")
        i += 1
    elif cur_op == 8:
        print ("%04x:  " % i, f"memory[memory[{index-2}]] = memory[{index-1}]")
        # print ("%04x:  " % i, f"index -= 2")
        index = index - 2
        i += 1
    elif cur_op == 9:
        print ("%04x:  " % i, f"memory[{index - 1}] = memory[memory[{index - 1}]]")
        i += 1
    elif cur_op == 10:    #和case 11的情况一样
        print ("%04x:  " % i, f"memory[{index - 1}] = (1 if memory[{index - 1}] == 0 else 0)")
        i += 1
    elif cur_op == 11:
        print ("%04x:  " % i, f"memory[{index - 1}] = (1 if memory[{index - 1}] < 0 else 0)")
        i += 1
    elif cur_op == 12:    #这个再仔细看看,不确定性有点高,脑袋有点糊
        print ("%04x:  " % i, f"memory[{index - 1}], memory[{index - 2}] = memory[{index - 2}], memory[{index - 1}]")        
        i += 1    
    elif cur_op == 13:
        print("%04x:  " % i,  "index -= 1      # index = {index}")
        index -= 1
        i += 1    elif cur_op == 14:
        print ("%04x:  " % i, f"memory[{index}] = {int.from_bytes(opcode[i + 1: i + 5], 'little', signed=False)}")        # print ("%04x:  " % i, "i += 5")
        index += 1
        i += 5    
    elif cur_op == 15:
        print("%04x:  " % i, f"i = {hex((int.from_bytes(opcode[i + 1: i + 5], 'little', signed=False) + i + 5) & 0xffffffff)}")
        # i = int.from_bytes(opcode[i + 1: i + 5], 'little', signed=False) + i + 5
        i += 5    elif cur_op == 16:
        print ("%04x:  " % i, f"i += 5 + ({int.from_bytes(opcode[i + 1: i + 5], 'little', signed=False)} if memory[{index - 1}] != 0 else 0)")
        index = index - 1
        i += 5    elif cur_op == 17:
        print("%04x:  " % i, f"index += {int.from_bytes(opcode[i + 1: i + 5], 'little', signed=False)}")
        # print("%04x i += 5" % i)
        index += int.from_bytes(opcode[i + 1: i + 5], 'little', signed=False)
        i += 5
    elif cur_op == 18:
        print("%04x:  " % i, f"return (memory[{int.from_bytes(opcode[i + 1: i + 5], 'little', signed=False)}])")
        i += 5
    else:
        print ("Error @ %04x" % i)

动调发现,总共有两个vm,flag的内存被共享,其他的状态分别保留,两个vm依次执行(能看到中间某个循环末尾有个return)分别是xtea加密和tea加密,相当于同时进行xtea和tea加密了写个求解脚本

需要注意是程序中为int类型,运算需要和题目中的一致(不能用位运算)

#include <stdio.h>   
#include <stdint.h>     
void decipher(int32_t* v) {
    unsigned int i;
    int const xtea_k[4] = {18764, 28534, 25888, 17237};
    int v0, v1, xtea_delta=123456789, xtea_sum=987654321 + xtea_delta * 100;
    int tea_k[4] = {21332, 20301, 8308, 25953};
    int tea_delta=22334455;                     /* a key schedule constant */
    int tea_sum=1592647341 + tea_delta * 100;  /* set up */
    for (int j = 8; j >= 0; j -= 2){
        v0 = v[j]; v1 = v[j + 1];
        for (i = 0; i < 20; i++) {
            v1 -= ((v0<<4) + tea_k[2]) ^ (v0 + tea_sum) ^ ((v0 / 32) + tea_k[3]);
            v0 -= ((v1<<4) + tea_k[0]) ^ (v1 + tea_sum) ^ ((v1 / 32) + tea_k[1]);
            tea_sum -= tea_delta;
            if (i == 19 && j == 0){
                v0 ^= 0x1010101;
                v1 ^= 0x1010101;
            }
            v1 -= (((v0 << 4) ^ (v0 / 32)) + v0) ^ (xtea_sum + xtea_k[(xtea_sum / 2048) & 3]);
            xtea_sum -= xtea_delta;
            v0 -= (((v1 *16 ) ^ (v1 / 32)) + v1) ^ (xtea_sum + xtea_k[xtea_sum & 3]);
            if (i == 19 && j == 0){
                v0 ^= 0x1010101;
                v1 ^= 0x1010101;
            }
        }                                              /* end cycle */
        v[j]=v0; v[j + 1]=v1;
    }
}

int main(){
    int v[10] = {0xAEE0FAE8, 0xFC3E4101, 0x167CAD92, 0x51EA6CBE, 0x242A0100, 0x1511A1B, 0x514D6694, 0x2F5FBFEB, 0x46D36398, 0x79EEE3F0};
    decipher(v);
    for (int i = 0; i < 10; i+=2)
        printf("%x %x\n",v[i],v[i + 1]);
    return 0;
}
// [0x6c427530, 0x4d765f65, 0x7431575f, 0x4f645f68, 0x65496275, 0x4145545f, 0x7d] 
// L3HCTF{D0uBle_vM_W1th_dOubIe_TEA} // 第一组失败了,猜测是flag头和D

[L3HCTF 2021]Load

这题使用了进程镂空技术(找时间研究室一下这一系列的技术),需要先把PE文件提取出来

动调进入镂空的函数,会发现一个对PE的比较,网上找可以找到MZ头(name字符串下面),dump下来即可

分析PE

首先提取了flag的中间部分,随后将其两两一组转化成16进制,共13字节

401070函数是将13字节拆成9和4字节分别存入src和v27,分别是33和22的矩阵

401370函数较大,且调用了一个递归函数

      v11 = mul_matrix_4012A0(v17, v6) * a1[v5];
      if ( (v5 & 1) != 0 )
        v4 -= v11;
      else
        v4 += v11;

分析发现函数计算递归后与第一行的一个数字相乘,并根据奇偶进行加法和减法,重写该函数并验证后发现是求解矩阵行列式

再分析401370函数时发现,函数使用了一个数字除以行列式,猜测是用来求解逆矩阵的

调用完401370后就是验证部分,只需要满足所有等式即可

使用在线网站求解逆矩阵

flag = [-8, 18, -9, 6, -13, 6, -1, 2, -1, 13, -3, -30, 7]

for f in flag:
    print ('%02x' % (f & 0xff), end='')
# f812f706f306ff02ff0dfde207

[hack.lu]pycoin

先使用uncompyle6反编译,发现执行了一串marshal字节码

将该字节码输出到文件,然后根据题目给的pyc补全文件头

再次反编译发现有花指令,开头和中间各有一个 jump_forward,中间还有两个连续的 rot_tow

这里尝试了一下新的patch方法:将花指令全替换成nop,但该方法的问题在于只能使用pycdc进行反编译,并不支持uncompyle6

个人感觉uncompyle6反编译效果更好,但适用的版本范围更窄,3.7及之前的版本较为合适 pycdc一直更新最新版本,但反编译效果较差,而且在细节上容易出问题,新版本只能使用pycdc,但最好结合pycdas的结果进行分析

另一种传统的patch方法就行把opcode去掉,然后把所有jmp修改到正确位置

patch后的结果如下:

最后反编译的结果为

from hashlib import md5
k = str(input('please supply a valid key:')).encode()
correct = len(k) == 16 and k[0] == 102 and k[1] == k[0] + 6 and k[2] == k[1] - k[0] + 91 and k[3] == 103 and k[4] == k[11] * 3 - 42 and k[5] == sum(k) - 1322 and k[6] + k[7] + k[10] == 260 and int(chr(k[7]) * 2) + 1 == k[9] and k[8] % 17 == 16 and k[9] == k[8] * 2 and md5(k[10] * b'a').digest()[0] - 1 == k[3] and k[11] == 55 and k[12] == k[14] / 2 - 2 and k[13] == k[10] * k[8] % 32 * 2 - 1 and k[14] == (k[12] ^ k[9] ^ k[15]) * 3 - 23 and k[15] == 125
print(f"valid key! {k.decode()}" if correct else 'invalid key :(')

然后一个z3就完事(tm k[5]约束了个寂寞)

from z3 import *

s = Solver()

k = [BitVec('k%d' % i, 8) for i in range(16)]

s.add(k[0] == 102)
s.add(k[1] == k[0] + 6)
s.add(k[2] == (k[1] - k[0]) + 91)
s.add(k[3] == 103)
s.add(k[4] == k[11] * 3 - 42)
s.add(k[11] == 55)
s.add(k[10] == 101)
s.add(k[15] == 125)
s.add(k[5] == sum(k) - 1322)
s.add(k[6] + k[7] + k[10] == 260)
s.add(k[7] > 0x30)
s.add(k[7] < 0x40)
s.add((k[7] - 0x30) * 11 + 1 == k[9])
s.add(k[8] % 17 == 16)
s.add(k[9] == k[8] * 2)
s.add(k[12] == k[14] / 2 - 2)
s.add(k[13] == (k[10] * k[8] % 32) * 2 - 1)
s.add(k[14] == (k[12] ^ k[9] ^ k[15]) * 3 - 23)

if s.check():
    model = s.model()
    for i in range(16):
        if i == 5: # 这个位置根本没有约束,最后官方直接给出来了
            continue
        print (chr(model[k[i]].as_long()), end='')
else:
    print ("No result")

[hack.lu]atareee

atareee.atstate2 是内存状态,对其进行binwalk,可以得到 memory.binsavestate.json 文件

根据分析可以得知是 6052 架构,用Ghidra打开,根据题目中提示的 @061A 找到验证逻辑的地址

加密函数和验证函数如下:

void FUN_529e(void) {
  byte bVar1;
  byte bVar2;
  byte bVar3;
  byte in_C;
  
  bVar3 = 0;
  do {
    write_1(PORTA,bVar3);
    bVar1 = read_1(PORTA);
    if ((bVar1 & 1) == 0) {
      BYTE_ARRAY_5234[bVar3] = BYTE_ARRAY_50c2[bVar3] ^ BYTE_ARRAY_50c2[(byte)(bVar3 + 1)];
      bVar2 = read_1(PORTA);
      BYTE_ARRAY_5234[bVar3] = BYTE_ARRAY_5234[bVar3];
      bVar1 = BYTE_ARRAY_5234[bVar3];
      BYTE_ARRAY_5234[bVar3] = bVar1 << 1 | CARRY1(bVar2,BYTE_ARRAY_50c2[bVar3]);
    }
    else {
      BYTE_ARRAY_5234[bVar3] = BYTE_ARRAY_50c2[bVar3] ^ BYTE_ARRAY_5219[(byte)(bVar3 - 1) + 1];
      bVar1 = BYTE_ARRAY_5234[bVar3];
      BYTE_ARRAY_5234[bVar3] = bVar1 << 1 | in_C;
    }
    if ((bool)(bVar1 >> 7)) {
      BYTE_ARRAY_5234[bVar3] = BYTE_ARRAY_5234[bVar3] + '\x01';
    }
    bVar3 = bVar3 + 1;
    in_C = 0x19 < bVar3;
  } while (bVar3 != 0x1a);
  return;
}


undefined FUN_52e8(void) {
  byte bVar1;
  
  bVar1 = 0;
  do {
    if (BYTE_ARRAY_5200[bVar1] != BYTE_ARRAY_5234[bVar1]) {
      FUN_531d();
      bVar1 = 0;
      do {
        BYTE_ARRAY_509a[bVar1] = s__NICE_TRY,_MAYBE_NEXT_TIME_ITS_C_5276[bVar1];
        bVar1 = bVar1 + 1;
      } while (bVar1 != 0x28);
      return 0;
    }
    bVar1 = bVar1 + 1;
  } while (bVar1 != 0x1a);
  bVar1 = 0;
  do {
    BYTE_ARRAY_509a[bVar1] = s__GOOD_JOB!_ENJOY_THOSE_REVERSING_524e[bVar1];
    bVar1 = bVar1 + 1;
  } while (bVar1 != 0x28);
  return 0;
}

通过修改 savestate.json 的PC位置(EIP)即可进行动调,发现输入被存入 50c2 中,复现该函数并爆破即可得到flag

target = [
    0x14,  0x1E,   0xC,  0xE0,
    0x30,  0x5C,  0xCE,  0xF0,
    0x36,  0xAE,  0xFC,  0x39,
    0x1A,  0x91,  0xCE,  0xB4,
    0xC4,   0xE,  0x18,  0xF3,
    0xC8,  0x8E,   0xA,  0x85,
    0xF6, 0xbd
]


array_50c2 = [
    0xD9,  0x50,  0x48,  0xB9,
    0xD8,  0x50,  0x48,  0x60,
    0x46,  0x54,  0x43,  0x44,
    0x45,  0x49,  0x50,  0x55,
    0x52,  0x53,  0x4C,  0x47,
    0x58,  0x51,  0xF3,  0x50,
    0x8,  0x51, 0x10
]



array_5219 = [
    0xBD,  0x43,  0x11,  0x37,
    0xF2,  0x69,  0xAB,  0x2C,
    0x99,  0x13,  0x12,  0xD1,
    0x7E,  0x9A,  0x8F,   0xE,
    0x92,  0x37,  0xF4,  0xAA,
    0x4D,  0x77,   0x3,  0x89,
    0xCA,  0xFF,
]


array_5234 = [0 for _ in range(0x1a)]


in_C = 0


j = 0
for i in range(0x19, -1, -1):
    in_C = 1 if (0x19 < i + 1) else 0
    for j in range(0x30, 0x60):
        array_50c2[i] = j
        var1 = i
        
        var2 = 0
        if var1 & 1 == 0:
            array_5234[i] = array_50c2[i] ^ array_50c2[i + 1]
            var2 = i
            var1 = array_5234[i]
            array_5234[i] = ((var1 << 1) | ((array_50c2[i] + i) >> 7)) & 0xff
        else:
            array_5234[i] = array_50c2[i] ^ array_5219[i]
            var1 = array_5234[i]
            array_5234[i] = ((var1 << 1) | in_C) & 0xff
        if var1 >> 7 != 0:
            array_5234[i] = array_5234[i] + 1


        if array_5234[i] == target[i]:
            print (chr(j), end= '')
            break
    else:
        print ("no")
Built with Hugo
Theme Stack designed by Jimmy