格雅百科

一些 Rust 逆向步骤

格雅百科

之前做过一两次 RS 题,唯一感觉就是这是一些垃圾语言

今天好好练习,两道简单题和一道又长又臭的题

签入-rs

上手Rust逆向工程的感觉真好

首先你要找到主要功能

但这东西不是主要的,它在组装中

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

v0 主要存储 42 条数据,很像 enflag

中间有一些错误处理,然后下面是重点

thread::spawn是创建线程,然后thread::JoinHandle是将该线程添加到处理队列中

mpsc::Receiver 是主线程,用于接收子线程的数据。 mpsc 是多生产者单消费者。简单来说就是多个线程发送数据,然后单个线程接收数据

参考:https://www.gyballet.com/learning-rust/std/sync/mpsc.html

v14 是 Failed 和 Congratulation

的输出

粗略的说一下,main中创建了一个子线程,使用v12来接收线程返回的布尔值,根据布尔值来判断flag

现在的关键是在哪里可以找到这些线程

上面参考网站有一句话

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

我们在功能中寻找发件人

两个drop函数用于释放内存。真正的发送者是 mpsc::Sender 函数。我们可以交叉引用它来找到调用它的子线程。函数名称为 std::sys_common::backtrace::__rust_begin_short_backtrace::hff8c1f96768f50f6

这里的逻辑比较简单。除了一堆错误检查之外,逻辑就是翻转输入标志,然后执行异或加密,然后进行比较。

(中间还有截断和延长flag的操作,不过好像没什么用?)

此处正在进行翻转操作

5 {IMG_5:Ahr0Chm6ly9pbwcymdizlMNUYMXVZ3MUY2JSB2CVMTU5NJG4NYMDIZMDMDMTU5NJG4NYMDIZMDMXNTM0XNZK5NJU0MDGYL nbuzw ==/}

这里用flag[i]^i对flag进行加密,然后与v0开头的数据进行比较

这是发送者的位置,发送了最终的比对数据

那么分析就完成了,直接写exp

#包括
无符号字符 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'、'我'、'^'
};
int main()
{
for (int i = 45; i >= 0; --i) printf("%c", ida_chars[i] ^ i);
返回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中。可以发现加密分为六部分

1 {IMG_11:Ahr0chm6ly9pbwcymdizlmnuymxvz3Muy29tl2jsb2cvmtu5NJG4YMDIZMDMTU5NJG4NYMDIZMDAZMZEXNZKYMTY1MTK4 Lnbuzw ==/}

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

每轮mix函数都会将8位标志放入加密中。总共执行8轮mix,每轮mix执行5轮变换函数链。

我们分别看一下各个部分的功能

变换128

参数中,a2为按键,a3为输入

可以发现这个函数唯一的作用就是与按键

异或输入

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

另外,动态调整过程中,你会发现key值在不断变化

在此设置断点。当前键值存储在 ecx 中。我们会发现这个键值每五轮变化一次,即[0, 1, 2, 3, 4, 5, 6, 7],也就说每次执行mix函数时该值都会变化

翻译成Python就是

transform128_key = [0, 1, 2, 3, 4, 5, 6, 7]
deftransform128(输入,键):
温度 = b''
对于输入中的 i:
temp += pack('B', i ^ key)
返回温度

变换228

这个功能相当恶心。每轮混合中的五次执行具有不同的控制流程和密钥。关键是[0xa,0xb,0x8,0x9,0xe,0xf,0xc,0xd],控制流程如下

第1轮

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

第2轮

只做..while_2

第3轮

执行了si128_if,也就是这个

的if部分

第四轮

执行 si128_if 和 do..while_2

第五轮

执行si128_else并执行..while_2

si128_if之前有一个难点要分析:这个key经过shuffle_epi32函数后变成了什么

在汇编中,可以看到它存储在浮点寄存器xmm0中。我们来写idapython来提取它吧

导入idaapi
def Convert2hex(m): # b'\x12\x32' -> 0x3412
s = 0
对于范围内的 i(len(m) - 1, -1, -1):
s += m[i]s *= 0x100
返回十六进制
def Convert2hex_space(m): # b'\x12\x32' -> 12 34
s = ''
对于范围内的 i(len(m)):
s += hex(m[i])[2:].rjust(2, '0') + ' '
返回
def 开始(regs):
对于 reg 中的 i:
b_rax = idaapi.get_reg_val(i)
hex_rax = Convert2hex(b_rax)
hexspace_rax = Convert2hex_space(b_rax)
打印(i + ' -> : ')
print("\t 字节视图: ", end="")
打印(b_rax)
print("\t 十六进制视图: ", end="")
打印(十六进制_rax)
print("\t 带空格的十六进制视图: ", end="")
打印(hexspace_rax)
regs = ["xmm0",
]
开始(注册)

可以看到,将key转换为bytes类型后,复制了16份

仍然使用python来翻译整个函数。为了方便写exp,这里我们把函数拆成5个if来写。

有点多

def _mm_load_si128(n):
分辨率=[]
同时(n):
res.append(n % 0x100)
n //= 0x100
返回字节(res)
def _mm_xor_si128(a, b):
返回字节([x ^ y for x, y in zip(a, b)])
def _mm_add_epi8(a, b):
返回字节([x + y for x, y in zip(a, b)])def _mm_and_si128(a, b):
返回字节([zip(a, b) 中的 x, y 的 x & y])
变换228_key = [0xa,0xb,0x8,0x9,0xe,0xf,0xc,0xd]
deftransform228(输入,键,轮):
长度 = len(输入)
v8 = []
a2 = 0
如果回合 == 1:
对于范围 (3) 内的 i:
v8.append(输入[i] ^ (key + (a2 & 1)))
a2+=1
v30 = a2 & 1
v31 = 密钥 + v30
v32 = 密钥 + (v30 ^ 1)
v8.append(v31 ^ 输入[3])
v8.append(v32 ^ 输入[4])
v8.append(v31 ^ 输入[5])
v8.append(v32 ^ 输入[6])
返回字节(v8)
elif 轮 == 2:
v30 = a2 & 1
v31 = 密钥 + v30
v32 = 密钥 + (v30 ^ 1)
对于范围 (0, 16, 4) 内的 i:
v8.append(v31 ^ 输入[i])
v8.append(v32 ^ 输入[i + 1])
v8.append(v31 ^ 输入[i + 2])
v8.append(v32 ^ 输入[i + 3])
返回字节(v8)
elif 轮 == 3:
v8 = b''
v11 = 包('B', 密钥) * 16
si128 = _mm_load_si128(0x1000100010001000100010001000100)
v23 = _mm_add_epi8(v11, si128)
v24 = _mm_xor_si128(输入[0:16], v23)
v25 = _mm_xor_si128(v23, 输入[16:32])
v8 += v24
v8 += v25
返回v8
elif 轮 == 4:
v8 = b''v11 = 包('B', 密钥) * 16
si128 = _mm_load_si128(0x1000100010001000100010001000100)
v23 = _mm_add_epi8(v11, si128)
v24 = _mm_xor_si128(输入[0:16], v23)
v25 = _mm_xor_si128(v23, 输入[16:32])
v8 += v24
v8 += v25
v30 = a2 & 1
v31 = 密钥 + v30
v32 = 密钥 + (v30 ^ 1)
对于范围 (32, 56, 4) 内的 i:
v8 += pack('B', v31 ^ 输入[i])
v8 += pack('B', v32 ^ 输入[i + 1])
v8 += pack('B', v31 ^ 输入[i + 2])
v8 += pack('B', v32 ^ 输入[i + 3])
返回v8
elif 轮 == 5:
v8 = b''
v11 = 包('B', 密钥) * 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(输入[0:16], v19)
val_1 = _mm_xor_si128(输入[16:32],v19)
val_2 = _mm_xor_si128(输入[32:48],v19)
val_3 = _mm_xor_si128(输入[48:64],v19)
si128 = _mm_and_si128(v14, v16)
v23 = _mm_add_epi8(v11, si128)v24 = _mm_xor_si128(输入[64:80], v23)
v25 = _mm_xor_si128(输入[80:96], v23)
v30 = a2 & 1
v31 = 密钥 + v30
v32 = 密钥 + (v30 ^ 1)
v8 += val_0 + val_1 + val_2 + val_3 + v24 + v25
对于范围 (96, 104, 4) 内的 i:
v8 += pack('B', v31 ^ 输入[i])
v8 += pack('B', v32 ^ 输入[i + 1])
v8 += pack('B', v31 ^ 输入[i + 2])
v8 += pack('B', v32 ^ 输入[i + 3])
返回v8

transform_base28

这个比较简单。一轮混合将执行两次。第一次是改表base64,第二次是不改表base64

deftransform_base28(输入,圆形):
orig_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
new_table = '+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
如果回合 & 1 == 1:
dic = str.maketrans(orig_table, new_table)
return ((str(base64.b64encode(input)).translate(dic))[2:-1]).encode()
别的:
返回base64.b64encode(输入)

transform_anti28

这里是异或加密和一步反应调整

正常做的时候,你会发现最后一步异或非常错误,最终你会发现有一步反调试

当procfs::process::Process::status::hcb60ea34dc13cf4b检测到调试器时,anti_debug处的eax值将成为常量,当我们nop此函数后,eax值将变为0

这个函数的原型可以在官方文档中找到

https://www.gyballet.com/procfs/latest/procfs/process/struct.Process.html

pub fn status(&self) -> ProcResult
根据 /proc/[pid]/status 文件返回此进程的状态。

根据官方文档,这里会获取进程的所有信息,所以要通过该方法检测调试器,并为其赋值相应的值,从而形成反调试,干扰我们的算法分析。

那么现在这个功能的流程就很简单了

transform_anti28_key = [0x29,0xc5]
deftransform_anti28(输入,键):
反调试=0
v6 = b''
对于范围内的 i(len(输入)):
如果 i & 1 != 0:
v8 = 输入[i] ^ (键 + 1)
别的:
v11 = 输入[i] ^ 键
v8 = 反调试 ^ v11
v6 += 包(“B”,v8)
返回 v6

但是nop调试的时候好像报错。这可能是一个小错误。 。

transform_shift28

评价没用

transform_rev28

这个很简单,翻转一下绳子就可以了

解密脚本非常容易编写。我们发现base64以外的加密都是单字节异或,所以反向运行即可。只需更改编码即可解码为base64

来自结构导入*
导入base64
变换128_key = [0, 1, 2, 3, 4, 5, 6, 7]
deftransform128(输入,键):
温度 = b''
对于输入中的 i:
temp += pack('B', i ^ key)
返回温度
def long2bytes(n):
分辨率=[]
同时(n):
res.append(n % 0x100)
n //= 0x100
返回字节(res)
def _mm_xor_si128(a, b):
返回字节([x ^ y for x, y in zip(a, b)])
def _mm_add_epi8(a, b):
返回字节([x + y for x, y in zip(a, b)])
def _mm_and_si128(a, b):
返回字节([zip(a, b) 中的 x, y 的 x & y])
变换228_key = [0xa,0xb,0x8,0x9,0xe,0xf,0xc,0xd]
deftransform228(输入,键,轮):
长度 = len(输入)
v8 = []
a2 = 0
如果回合 == 1:
对于范围 (3) 内的 i:
v8.append(输入[i] ^ (key + (a2 & 1)))
a2+=1
v30 = a2 & 1
v31 = 密钥 + v30v32 = 密钥 + (v30 ^ 1)
v8.append(v31 ^ 输入[3])
v8.append(v32 ^ 输入[4])
v8.append(v31 ^ 输入[5])
v8.append(v32 ^ 输入[6])
返回字节(v8)
elif 轮 == 2:
v30 = a2 & 1
v31 = 密钥 + v30
v32 = 密钥 + (v30 ^ 1)
对于范围 (0, 16, 4) 内的 i:
v8.append(v31 ^ 输入[i])
v8.append(v32 ^ 输入[i + 1])
v8.append(v31 ^ 输入[i + 2])
v8.append(v32 ^ 输入[i + 3])
返回字节(v8)
elif 轮 == 3:
v8 = b''
v11 = 包('B', 密钥) * 16
si128 = long2bytes(0x1000100010001000100010001000100)
v23 = _mm_add_epi8(v11, si128)
v24 = _mm_xor_si128(输入[0:16], v23)
v25 = _mm_xor_si128(v23, 输入[16:32])
v8 += v24
v8 += v25
返回v8
elif 轮 == 4:
v8 = b''
v11 = 包('B', 密钥) * 16
si128 = long2bytes(0x1000100010001000100010001000100)
v23 = _mm_add_epi8(v11, si128)
v24 = _mm_xor_si128(输入[0:16], v23)
v25 = _mm_xor_si128(v23, 输入[16:32])
v8 += v24
v8 += v25
v30 = a2 & 1
v31 = 密钥 + v30
v32 = 密钥 + (v30 ^ 1)
对于范围 (32, 60, 4) 内的 i:
v8 += pack('B', v31 ^ 输入[i])v8 += pack('B', v32 ^ 输入[i + 1])
v8 += pack('B', v31 ^ 输入[i + 2])
v8 += pack('B', v32 ^ 输入[i + 3])
返回v8
elif 轮 == 5:
v8 = b''
v11 = 包('B', 密钥) * 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(输入[0:16], v19)
val_1 = _mm_xor_si128(输入[16:32],v19)
val_2 = _mm_xor_si128(输入[32:48],v19)
val_3 = _mm_xor_si128(输入[48:64],v19)
si128 = _mm_and_si128(v14, v16)
v23 = _mm_add_epi8(v11, si128)
v24 = _mm_xor_si128(输入[64:80], v23)
v25 = _mm_xor_si128(输入[80:96], v23)
v30 = a2 & 1
v31 = 密钥 + v30
v32 = 密钥 + (v30 ^ 1)
v8 += val_0 + val_1 + val_2 + val_3 + v24 + v25
对于范围 (96, 107, 4) 内的 i:
v8 += pack('B', v31 ^ 输入[i])
v8 += pack('B', v32 ^ 输入[i + 1])
v8 += pack('B', v31 ^ 输入[i + 2])
v8 += pack('B', v32 ^ 输入[i + 3])
返回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

占坑,这个更是个重量级

发表评论 (已有0条评论)

还木有评论哦,快来抢沙发吧~