几道rust逆向

以前做了一两次rs题,唯一感觉就是这是什么大粪语言

今天好好练一下,两个简单题和一个又臭又长的题

checkin-rs

用来入门rust逆向感觉挺不错的

首先得找main函数

但是这玩意不是main,在汇编里

这里的checkin_rs4main才是真正的main函数

main里的v0存了42个数据,很像enflag

中间都是一些报错处理,然后下面是重点

thread::spawn是创建线程,然后thread::JoinHandle是将线程加入处理队列

mpsc::Receiver就是主线程用于接收子线程数据的,mpsc是Multiple Producer Single Consumer,简单来说就是多个线程发送数据,然后单个线程接收数据

ref: https://skyao.io/learning-rust/std/sync/mpsc.html

v14那里就是输出Failed和Congratulation

大概理一下,main里面创建了子线程,用v12承接该线程返回的布尔数值,并根据该布尔值判断flag的正误

现在关键在于这几把线程在哪找

上面的参考网站里有一句话

Sender 或 SyncSender 用于向 Receiver 发送数据。

我们在函数中找一下Sender

drop那俩函数是释放内存用的,真正的sender是mpsc::Sender函数,我们对它交叉引用一下就能找到调用它的子线程了。其函数名是std::sys_common::backtrace::__rust_begin_short_backtrace::hff8c1f96768f50f6

这里面的逻辑比较简单,除开一大坨报错检查之外,其逻辑就是将输入的flag翻转,随后进行异或加密然后比对。

(中间还有一个将flag截断并延长的操作,但是好像没啥用?)

这里在进行翻转操作

这里对flag进行了flag[i] ^ i的加密,随后与最开始v0处的数据进行了比对

这里就是Sender的位置,发送了最后的比较数据

那么分析完成,写出exp即可

#include <stdio.h>
unsigned char ida_chars[] =
{
        0x7D, 0x20, 0x23, 0x22, 0x25, 0x24, 0x68, 0x72, 0x6E, 0x56,
        0x79, 0x62, 0x53, 0x79, 0x7D, 0x7A, 0x62, 0x4E, 0x7C, 0x7A,
        0x7F, 0x76, 0x73, 0x7F, 0x7B, 0x46, 0x7F, 0x68, 0x6E, 0x78,
        0x68, 0x7A, 'R', '~', '[', 'P', 'E', '@', ']', 'L', 'F', '\\', 'Z', 'H', 'I', '^'
};
int main() 
{
    for (int i = 45; i >= 0; --i) printf("%c", ida_chars[i] ^ i);
    return 0;
}

secpunk{easy_reverse_checkin_rust_is_fun!!!!!}

easy_rs

有点牛的题

这一部分是检查flag长度是否为64

注意这里的StrSearcher

汇编中发现其加载了unk数组,里面存放的是字符'-',结合下方

初步判断该flag格式是64字节,并且其中含有8个'-'

同时,调试的时候发现86行的函数内会将flag分成8个8字节的块,我们猜测flag格式是0123456-1234567-2345678-3456789-4567890-5678901-6789012-7890123均匀划分的

(最终这个题的密文是2055字节,并且很良心地把密文输出了一下,这也可以发现必须是这样均匀划分才能做到密文是2055字节)

主要加密流程在函数easy_rs::mix::_$u7b$$u7b$closure$u7d$$u7d$::hf56f2cf042c7aa74中,动调可以发现:加密分成了六个部分

并且其顺序为:transform128 -> transform228 -> transform_base28 -> transform_anti28 -> transform_shift28 -> transform_base28 -> transform_anti28 -> transform_rev28

每一轮mix函数都将8位flag放进去加密,一共执行了8轮mix,每一轮mix中又执行了5轮transform函数链。

我们将每一部分函数分开来看

transform128

参数中a2是key, a3是input

动调可以发现该函数的唯一作用就是将input与key异或

下面的do..while永远不会执行

除此之外,在动调过程中会发现key值在不断地改变

在这里下断点,ecx内存储的是当前key值,我们会发现这个key值每五轮改变一次,是[0, 1, 2, 3 ,4, 5, 6, 7],也就是说,每一次执行mix函数该值都会改变

用python翻译一下就是

transform128_key = [0, 1, 2, 3, 4, 5, 6, 7]

def transform128(input, key):
    temp = b''
    for i in input:
        temp += pack('B', i ^ key)
    return temp

transform228

这个函数比较恶心,在每一轮mix中它的五次执行都有不同的控制流和key。key是[0xa, 0xb, 0x8, 0x9, 0xe, 0xf, 0xc, 0xd],控制流如下

轮1

执行了do..while_1和do..while_2(上面是1下面是2)

轮2

仅执行了do..while_2

轮3

执行了si128_if ,就是这个的if部分

轮4

执行了si128_if和do..while_2

轮5

执行了si128_else和do..while_2

在si128_if之前有一个比较难分析的地方:这个key被shuffle_epi32函数后到底变成了什么

在汇编中可以看见被存进了浮点数寄存器xmm0中,我们写个idapython提取一下

import idaapi


def convert2hex(m): # b'\x12\x32' -> 0x3412
    s = 0
    for i in range(len(m) - 1, -1, -1):
        s += m[i]
        s *= 0x100
    return hex(s)

def convert2hex_space(m): # b'\x12\x32' -> 12 34
    s = ''
    for i in range(len(m)):
        s += hex(m[i])[2:].rjust(2, '0') + ' '
    return s

def start(regs):
    for i in regs:
        b_rax = idaapi.get_reg_val(i)
        hex_rax = convert2hex(b_rax)
        hexspace_rax = convert2hex_space(b_rax)
        print(i + ' -> : ')
        print("\t bytes view: ", end="")
        print(b_rax)
        print("\t hex view: ", end="")
        print(hex_rax)
        print("\t hex view with space: ", end="")
        print(hexspace_rax)

regs = ["xmm0",
]
start(regs)

可以看见,是把key转换成bytes类型后复制了16份

依旧用python翻译一下整个函数,为了写exp方便一点,这里我们将函数拆成五个if来写。

有点多

def _mm_load_si128(n):
    res = []
    while (n):
        res.append(n % 0x100)
        n //= 0x100
    return bytes(res)

def _mm_xor_si128(a, b):
    return bytes([x ^ y for x, y in zip(a, b)])

def _mm_add_epi8(a, b):
    return bytes([x + y for x, y in zip(a, b)])

def _mm_and_si128(a, b):
    return bytes([x & y for x, y in zip(a, b)])

transform228_key = [0xa, 0xb, 0x8, 0x9, 0xe, 0xf, 0xc, 0xd]
def transform228(input, key, round):
    length = len(input)
    v8 = []
    a2 = 0
    if round == 1:
        for i in range(3):
            v8.append(input[i] ^ (key + (a2 & 1)))
            a2 += 1

        v30 = a2 & 1
        v31 = key + v30
        v32 = key + (v30 ^ 1)

        v8.append(v31 ^ input[3])
        v8.append(v32 ^ input[4])
        v8.append(v31 ^ input[5])
        v8.append(v32 ^ input[6])
        return bytes(v8)

    elif round == 2:
        v30 = a2 & 1
        v31 = key + v30
        v32 = key + (v30 ^ 1)

        for i in range(0, 16, 4):
            v8.append(v31 ^ input[i])
            v8.append(v32 ^ input[i + 1])
            v8.append(v31 ^ input[i + 2])
            v8.append(v32 ^ input[i + 3])
        return bytes(v8)

    elif round == 3:
        v8 = b''
        v11 = pack('B', key) * 16
        si128 = _mm_load_si128(0x1000100010001000100010001000100)

        v23 = _mm_add_epi8(v11, si128)
        v24 = _mm_xor_si128(input[0:16], v23)
        v25 = _mm_xor_si128(v23, input[16:32])
        v8 += v24
        v8 += v25
        return v8

    elif round == 4:
        v8 = b''
        v11 = pack('B', key) * 16
        si128 = _mm_load_si128(0x1000100010001000100010001000100)
        v23 = _mm_add_epi8(v11, si128)
        v24 = _mm_xor_si128(input[0:16], v23)
        v25 = _mm_xor_si128(v23, input[16:32])
        v8 += v24
        v8 += v25

        v30 = a2 & 1
        v31 = key + v30
        v32 = key + (v30 ^ 1)
        for i in range(32, 56, 4):
            v8 += pack('B', v31 ^ input[i])
            v8 += pack('B', v32 ^ input[i + 1])
            v8 += pack('B', v31 ^ input[i + 2])
            v8 += pack('B', v32 ^ input[i + 3])
        return v8
    elif round == 5:
        v8 = b''
        v11 = pack('B', key) * 16
        v14 = _mm_load_si128(0xF0E0D0C0B0A09080706050403020100)
        v16 = _mm_load_si128(0x1010101010101010101010101010101)
        v17 = _mm_load_si128(0x40404040404040404040404040404040)

        v19 = _mm_add_epi8(_mm_and_si128(v14, v16), v11)
        val_0 = _mm_xor_si128(input[0:16], v19)
        val_1 = _mm_xor_si128(input[16:32], v19)
        val_2 = _mm_xor_si128(input[32:48], v19)
        val_3 = _mm_xor_si128(input[48:64], v19)

        si128 = _mm_and_si128(v14, v16)
        v23 = _mm_add_epi8(v11, si128)
        v24 = _mm_xor_si128(input[64:80], v23)
        v25 = _mm_xor_si128(input[80:96], v23)

        v30 = a2 & 1
        v31 = key + v30
        v32 = key + (v30 ^ 1)
        v8 += val_0 + val_1 + val_2 + val_3 + v24 + v25
        for i in range(96, 104, 4):
            v8 += pack('B', v31 ^ input[i])
            v8 += pack('B', v32 ^ input[i + 1])
            v8 += pack('B', v31 ^ input[i + 2])
            v8 += pack('B', v32 ^ input[i + 3])
        return v8

transform_base28

这个比较简单,一轮mix会执行两次,第一次是换表base64,第二次是不换表base64

def transform_base28(input, round):
    orig_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    new_table = '+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
    if round & 1 == 1:
        dic = str.maketrans(orig_table, new_table)
        return ((str(base64.b64encode(input)).translate(dic))[2:-1]).encode()
    else:
        return base64.b64encode(input)

transform_anti28

这里有异或加密以及一步反动调

正常做的时候会发现最后一步异或很不对劲,最终会发现这里有一步反调试

当procfs::process::Process::status::hcb60ea34dc13cf4b检测到调试器后,anti_debug处的eax的值会变成一个常数,而我们把这个函数nop掉后,该eax的值会变成0

这个函数的原型可以参考官方文档

https://docs.rs/procfs/latest/procfs/process/struct.Process.html

pub fn status(&self) -> ProcResult<Status>

Return the Status for this process, based on the /proc/[pid]/status file.

按照官方文档的说法,这里会获取进程的全部信息,那么应该是通过这种方法检测到了调试器,并将相应的值赋了上去,以形成反调试对我们的算法分析产生干扰。

那么现在这个函数的流程就很简单了

transform_anti28_key = [0x29, 0xc5]
def transform_anti28(input, key):
    anti_debug = 0
    v6 = b''
    for i in range(len(input)):
        if i & 1 != 0:
            v8 = input[i] ^ (key + 1)
        else:
            v11 = input[i] ^ key
            v8 = anti_debug ^ v11

        v6 += pack("B", v8)
    return v6

但是nop完好像调试的时候就会报错,可能小小的出了个锅emm。。

transform_shift28

评价是没用

transform_rev28

这个很简单,就是翻转字符串

解密脚本非常好写,我们发现除了base64以外的加密都是单字节异或,那直接反着跑一次就行了,base64把encode改成decode就行

from struct import *
import base64

transform128_key = [0, 1, 2, 3, 4, 5, 6, 7]

def transform128(input, key):
    temp = b''
    for i in input:
        temp += pack('B', i ^ key)
    return temp

def long2bytes(n):
    res = []
    while (n):
        res.append(n % 0x100)
        n //= 0x100
    return bytes(res)

def _mm_xor_si128(a, b):
    return bytes([x ^ y for x, y in zip(a, b)])

def _mm_add_epi8(a, b):
    return bytes([x + y for x, y in zip(a, b)])

def _mm_and_si128(a, b):
    return bytes([x & y for x, y in zip(a, b)])

transform228_key = [0xa, 0xb, 0x8, 0x9, 0xe, 0xf, 0xc, 0xd]
def transform228(input, key, round):
    length = len(input)
    v8 = []
    a2 = 0
    if round == 1:
        for i in range(3):
            v8.append(input[i] ^ (key + (a2 & 1)))
            a2 += 1

        v30 = a2 & 1
        v31 = key + v30
        v32 = key + (v30 ^ 1)

        v8.append(v31 ^ input[3])
        v8.append(v32 ^ input[4])
        v8.append(v31 ^ input[5])
        v8.append(v32 ^ input[6])
        return bytes(v8)

    elif round == 2:
        v30 = a2 & 1
        v31 = key + v30
        v32 = key + (v30 ^ 1)

        for i in range(0, 16, 4):
            v8.append(v31 ^ input[i])
            v8.append(v32 ^ input[i + 1])
            v8.append(v31 ^ input[i + 2])
            v8.append(v32 ^ input[i + 3])
        return bytes(v8)

    elif round == 3:
        v8 = b''
        v11 = pack('B', key) * 16
        si128 = long2bytes(0x1000100010001000100010001000100)

        v23 = _mm_add_epi8(v11, si128)
        v24 = _mm_xor_si128(input[0:16], v23)
        v25 = _mm_xor_si128(v23, input[16:32])
        v8 += v24
        v8 += v25
        return v8

    elif round == 4:
        v8 = b''
        v11 = pack('B', key) * 16
        si128 = long2bytes(0x1000100010001000100010001000100)
        v23 = _mm_add_epi8(v11, si128)
        v24 = _mm_xor_si128(input[0:16], v23)
        v25 = _mm_xor_si128(v23, input[16:32])
        v8 += v24
        v8 += v25

        v30 = a2 & 1
        v31 = key + v30
        v32 = key + (v30 ^ 1)
        for i in range(32, 60, 4):
            v8 += pack('B', v31 ^ input[i])
            v8 += pack('B', v32 ^ input[i + 1])
            v8 += pack('B', v31 ^ input[i + 2])
            v8 += pack('B', v32 ^ input[i + 3])
        return v8
    elif round == 5:
        v8 = b''
        v11 = pack('B', key) * 16
        v14 = long2bytes(0xF0E0D0C0B0A09080706050403020100)
        v16 = long2bytes(0x1010101010101010101010101010101)
        v17 = long2bytes(0x40404040404040404040404040404040)

        v19 = _mm_add_epi8(_mm_and_si128(v14, v16), v11)
        val_0 = _mm_xor_si128(input[0:16], v19)
        val_1 = _mm_xor_si128(input[16:32], v19)
        val_2 = _mm_xor_si128(input[32:48], v19)
        val_3 = _mm_xor_si128(input[48:64], v19)

        si128 = _mm_and_si128(v14, v16)
        v23 = _mm_add_epi8(v11, si128)
        v24 = _mm_xor_si128(input[64:80], v23)
        v25 = _mm_xor_si128(input[80:96], v23)

        v30 = a2 & 1
        v31 = key + v30
        v32 = key + (v30 ^ 1)
        v8 += val_0 + val_1 + val_2 + val_3 + v24 + v25
        for i in range(96, 107, 4):
            v8 += pack('B', v31 ^ input[i])
            v8 += pack('B', v32 ^ input[i + 1])
            v8 += pack('B', v31 ^ input[i + 2])
            v8 += pack('B', v32 ^ input[i + 3])
        return v8

def transform_base28(input, round):
    orig_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    new_table = '+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
    if round & 1 == 1:
        dic = str.maketrans(orig_table, new_table)
        return ((base64.b64encode(input)).translate(dic)).encode()
    else:
        return base64.b64encode(input)

transform_anti28_key = [0x29, 0xc5]
def transform_anti28(input, key):
    anti_debug = 0
    v6 = b''
    for i in range(len(input)):
        if i & 1 != 0:
            v8 = input[i] ^ (key + 1)
        else:
            v11 = input[i] ^ key
            v8 = anti_debug ^ v11

        v6 += pack("B", v8)
    return v6

def transform_shift28(input):
    return input

def transform_rev28(input):
    return input[::-1]

def dec_transform_base28(input, round):
    orig_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    new_table = '+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
    if round & 1 == 1:
        dic = str.maketrans(new_table, orig_table)
        return base64.b64decode(input.decode().translate(dic))
    else:
        return base64.b64decode(input)

def decrypt(data):
    flag = b''
    global transform128_key, transform228_key, transform_anti28_key
    for i in range(8):
        enflag = dec_transform_base28(data.split(b'_')[i], 0)
        for j in range(5, 0, -1):
            enflag = transform_rev28(enflag)
            enflag = transform_anti28(enflag, transform_anti28_key[1])
            enflag = dec_transform_base28(enflag, 0)
            enflag = transform_shift28(enflag)
            enflag = transform_anti28(enflag, transform_anti28_key[0])
            enflag = dec_transform_base28(enflag, 1)
            enflag = transform228(enflag, transform228_key[i], j)
            enflag = transform128(enflag, transform128_key[i])
        flag += enflag + b'-'
    return flag[:-1]


if __name__ == '__main__':
    enflag = bytes.fromhex("73494F2B67497638714A4B7668373643386132556C34767739706545736132586E494355704B4F706849434538613264686679446F344366725A7A796A3547556F6257446C344F586B5943636B4B3258676643556C493674395A794A715A47416E4B61546C4B4F50714A47327162364E674C47746B712B446736534E6A3669557459654F6C344B39725A3244672F6153675A65546E5A2B5072616144693736666E3479546A61795439704C7A725936436C364B5267717958396F3258395A4758725053456E4A536D725A53796A363258702F54306C4C61586B614F4A7159476D725053416E4B6630394A47667161366D686257556C495038726161423850614E5F684943756E5A6174674A79766837366B2F7643746C497548395A6563746169646F373332703553556B35656967506141676679746E624778717144796F594F52365A2B746E4A614C713532766B3632586E4A54326C496D74394A794F72613655673565446A6247586B354C7A76593643674975656A612B68396F3242692F5358724A2B44676F4339725A32732F4B32676F7053426B706566725A796B384B715276492B446A5A2B4D395A53787666536D6F4A53526749365439702B4E6A3553586A4B33317070794C714A532F6836365371616D2B6C3643446C4B654839494F6B725A2B7470714754673547666836756D696679716C4B79396B3643313950574E5F73494F2B67497638714A4B7668373643386132556C34767739706545736132586E49435570494750394A4B49384B3252675053556B714371393665696F6F5341725043516C3457506C4932666B4B3258676643556C4B2F3068494B4A2F4A364167356554704B4F50714A47327162364E674C47746B712B446736534672623658705032656C344B39725A322F7666615368592F306B5A7975674A65587270474E6A7679746E504C30395A4B667266574E6C4B4B726E595077396F324971617155692F53456E344774725A3253674953516E492B4F6C363648714B61636935535870344F746B614877767048707161366D686257556C495038726161423850614E5F746F4F756B597570673661766837366E2F7643546C34767739707957384936676F5965556C346D50675943387461714E674975726C72794C6B354C7974594352725053756E4A654D6A7161446B2F61516A5A4F546C342B506735654C715A4743673565446B4A79686B3565766835366A6A716D746C494F54673653786A3447417334656F6E4A61486A7079796B354F57703565546C4A6630717079586949366672492B446A627950394A47766A36326367616D726B762B506B344C706A3675556B6F69426770617471705363716F435869616D2B6C354B446C4B4F436A3675556F3757716E4B6568397047746E2F61636C3454336E374C387261616E692F574E5F73494F2B67497638714A4B7668373643386132556C34767739706545736132586E49435570494750394A4B49384B3252675053556B716578674A4B6E394A4755716643546C3457396B3443636B4B3258676643556C493674395A794A715A47416E4B61546C4B4F50714A47326836694E676247746B707A396B3562796A3669527459654F6C344B39725A324467344F6A715A4F546E6166777170796B67344F666E3479446A66476839704B4A7466656369616D726E61794C6B35794E6A3553586B6F65726A595339714A616A385A53556A592B6F6B5A4B456C4B4F6339494F6D6F4A79746B614877767048707161366D686257556C495038726161423850614E5F2F344F756E5965666736662F683453432F7644326B7261746E706545735A47417262577170346D7072705357672F616D6E34754F6F4943786B4B53686935476469725774706F6D4C39714F762F506155696679556C4B2F77717047696A49476E2F3566326C366630395A53426E36326369616D426F502B78684A4B74683447416C59656F6E4A613967365363716171516F3565426C35574567594B58694B6953736679546A66474C7149323168354758674B32726C707A7839714368384C3652693733306C3443397170324A746136433876534F6C354C306C4A654C716169556A494F746F36663039594B532F4B6963704975446E374B506735622B3950574E5F73494F2B67497638714A4B7668373643386132556C34767739706545736132586E494355704B4F706849434538613264686679446F344366725A7A796A354755366257746E49507768494B736B3632636F366D556C4B4F50725A79466E344F6D67356574677166307135667A68353658693747546A5A7941684A79556B4A4F55726F2B546F3443396736534F716665576F714B45674B6D44727161582F62366D702F79746E496D50395A65667266574E6C4B4B726E595077396F3258395A4758725053456E4A534D714A542F7461716A6C49696F6B5969446C4B4F636A3675586F344F746B614877767048707161366D686257556C495038726161423850614E5F746F4F756B597570673661766837366E2F7643546C34767739707957384936676F5965556C364B757270477A384B324E6749756F6B6247787170326E6F6661527161326570343738673661796A354F526F6147746B622F307670654A682F6567736F75446B4A79686B3565567476574E6766534F6E49434467352B696950535371496631706F6D486A714338384A4F53386F2F306C3447746C494B582F5947676F354F746C36483071354C7A394B366D6C364B726B6F36546B3433706A3536583834656570355478714A5366716F435869592B4F6C374348714A795739494F67702F53716E4B6568397047746E2F61636C3454336E374C387261616E692F574E")
    flag = decrypt(enflag)
    print(flag)

welecom-toooooo-nu1lctf-2023hah-havegoo-odluckk-seeyouu-againnn

Ferris' proxy

占坑,这个更是个重量级

posted @ 2023-03-14 23:00  iPlayForSG  阅读(875)  评论(0编辑  收藏  举报