24年ciscn初赛Re(部分)

一、asm_re

一开始还想还原这个ida工程文件hhh,发现根本做不到,后面纯看arm汇编代码,直接手撕就好,加密逻辑在这儿,密文一开始还找半天,后面发现应该是存在变量unk_100003F10里面

img

搓出脚本之后也还是卡了一小会儿,最后反应过来大小端的问题,改一下小端就好了,爆破一下直接出

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
k = [
0x1fd7, 0x21b7, 0x1e47, 0x2027, 0x26e7, 0x10d7, 0x1127, 0x2007,
0x11c7, 0x1e47, 0x1017, 0x1017, 0x11f7, 0x2007, 0x1037, 0x1107,
0x1f17, 0x10d7, 0x1017, 0x1017, 0x1f67, 0x1017, 0x11c7, 0x11c7,
0x1017, 0x1fd7, 0x1f17, 0x1107, 0x0f47, 0x1127, 0x1037, 0x1e47,
0x1037, 0x1fd7, 0x1107, 0x1fd7, 0x1107, 0x2787
]

for i in range(len(k)):
for j in range(128):
if (((j * ord('P') + 0x14) ^ ord('M')) + 0x1e) == k[i]:
print(chr(j), end="")
#flag{67e9a228e45b622c2992fb5174a4f5f5}

二、whereThel1b

还真是第一次遇见这种,给了个so和一个py文件,一开始的想法是能不能给so解包之类的,因为py文件里面密文给了,就差一个加密逻辑,找了一大圈还是没找到,最后还是想到了调一下so文件,像调安卓那样

动调起来锁定出了两个函数,得知输入的数据先经过base64编码之后再进行的异或img

img

加密逻辑知道了,但是不知道异或的值是什么,一开始以为是存在r18里面的,最后调了一下找不到规律,最后想到重新写一份密文输入,然后把加密之后的数据输出一下,前后异或得到所需异或的值,想办法输入一个输构造出经过base64编码之后长度为56的数

img

exp:

其中aa是上图构造的“55555555555555555555555555555555555555555555”的base64之后的值,然后bb是运行上图之后得到的异或之后的值,最后运行出来的结果解一下base64就行

1
2
3
4
5
6
7
8
encry = [108, 117, 72, 80, 64, 49, 99, 19, 69, 115, 94, 93, 94, 115, 71, 95, 84, 89, 56, 101, 70, 2, 84, 75, 127, 68, 103, 85, 105, 113, 80, 103, 95, 67, 81, 7, 113, 70, 47, 73, 92, 124, 93, 120, 104, 108, 106, 17, 80, 102, 101, 75, 93, 68, 121, 26]

aa = [78,84,85,49,78,84,85,49,78,84,85,49,78,84,85,49,78,84,85,49,78,84,85,49,78,84,85,49,78,84,85,49,78,84,85,49,78,84,85,49,78,84,85,49,78,84,85,49,78,84,85,49,78,84,85,49]
bb = [120, 76, 101, 9, 84, 86, 69, 17, 81, 77, 103, 4, 93, 74, 67, 20, 67, 116, 93, 35, 70, 100, 83, 22, 125, 68, 119, 28, 125, 114, 92, 34, 72, 122, 81, 7, 101, 65, 75, 18, 72, 66, 78, 37, 105, 124, 88, 18, 80, 72, 98, 16, 94, 87, 102, 18]

for i in range(len(aa)):
print(chr(((aa[i]^bb[i]))^encry[i]),end='')
#ZmxhZ3s3ZjlhMmQzYy0wN2RlLTExZWYtYmU1ZS1jZjFlODg2NzRjMGJ9

img

三、gdb_debug

进入主函数之后逻辑还是相当清楚的,锁定了一下伪随机数

img

动调跑起来取出随机数

1
0xd9, 0x0f, 0x18, 0xBD, 0xC7, 0x16, 0x81, 0xbe, 0xf8, 0x4A, 0x65, 0xf2, 0x5D, 0xab, 0x74, 0x33, 0xd4, 0xa5, 0x67, 0x98, 0x9f, 0x7E, 0x2B, 0x5D, 0xc2, 0xaf, 0x8e, 0x3A, 0x4C, 0xa5, 0X75, 0X25, 0xb4, 0x8d, 0xe3, 0X7B, 0xa3, 0x64

然后直接从后往前逆就好

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>

int main() {
int indexArray[38];
int buffer[38];
int outputBuffer[38];
int originalNumbers[] = {
94, 30, 2, 68, 157, 32, 134, 99, 227, 214,
182, 105, 24, 193, 153, 168, 188, 5, 121, 159,
25, 110, 218, 76, 117, 174, 192, 185, 247, 122,
149, 77, 23, 135, 148, 84, 191, 185
};
unsigned char byteSequence[] = {
128, 180, 64, 184, 148, 200, 52, 101, 238, 69,
215, 157, 60, 136, 140, 169, 107, 174, 125, 135,
214, 135, 15, 218, 70, 100, 57, 147, 169, 144,
184, 113, 131, 232, 172, 201, 231, 83
};
unsigned int shuffledIndices[38];
for (int i = 0; i < 38; i++) {
shuffledIndices[i] = originalNumbers[i] ^ byteSequence[i];
}
int encryptionKeys[] = {0xd9, 0x0f, 0x18, 0xBD, 0xC7, 0x16, 0x81, 0xbe, 0xf8, 0x4A, 0x65, 0xf2, 0x5D, 0xab, 0x74, 0x33, 0xd4, 0xa5, 0x67, 0x98, 0x9f, 0x7E, 0x2B, 0x5D, 0xc2, 0xaf, 0x8e, 0x3A, 0x4C, 0xa5, 0x75, 0x25, 0xb4, 0x8d, 0xe3, 0x7B, 0xa3, 0x64};
int permutationOrder[] = {33, 0, 10, 0, 32, 31, 10, 29, 9, 24, 26, 11, 20, 24, 21, 3, 12, 10, 13, 2, 15, 4, 13, 10, 8, 3, 3, 6, 0, 4, 1, 1, 5, 4, 0, 0, 1};
unsigned char dataXor[] = {0xBF, 0xD7, 0x2E, 0xDA, 0xEE, 0xA8, 0x1A, 0x10, 0x83, 0x73, 0xAC, 0xF1, 0x06, 0xBE, 0xAD, 0x88, 0x04, 0xD7, 0x12, 0xFE, 0xB5, 0xE2, 0x61, 0xB7, 0x3D, 0x07, 0x4A, 0xE8, 0x96, 0xA2, 0x9D, 0x4D, 0xBC, 0x81, 0x8C, 0xE9, 0x88, 0x78};
char inputData[] = "congratulationstoyoucongratulationstoy";

for (int i = 0; i < 38; i++) {
indexArray[i] = i;
}
for (int k = 37; k > 0; --k) {
int swapIndex = permutationOrder[37 - k] % (k + 1);
int tempIndex = indexArray[k];
indexArray[k] = indexArray[swapIndex];
indexArray[swapIndex] = tempIndex;
}
for (int i = 0; i < 38; i++) {
buffer[i] = shuffledIndices[i] ^ inputData[i] ^ dataXor[i];
outputBuffer[indexArray[i]] = encryptionKeys[indexArray[i]] ^ buffer[i];
}
for (int i = 0; i < 38; i++) {
printf("%c", outputBuffer[i]);
}
return 0;
}

四、rust_baby(复现)

经典Rust终于还是出现了,比赛时候属于是草草看一眼就直接过了,结束之后复现一下,直接生逆太复杂了歪日,选择用frida插桩进行字节爆破来解。

大概逛了一下程序流程,直接从main里面锁定了目标函数如下图:

image-20240711104946378

跟进之后发现巨长,又加上是rust,瞬间不想看加密逻辑了,先寻找check flag的地方,然后往前回溯,此处下面有判断,然后还有个错误提示,所以锁定这块儿应该就是check部分

image-20240711105253758

然后走流程图来看,循环取值作比较处的逻辑如下,其中cmp rcx, rdx属于是长度的check,如果长度对劲的话直接走右边的逻辑,其中rcx中存储的长度为8C,即为密文长度,为什么这里不用check值是否对就直接走右边的正确逻辑了呢,因为check值是在左边下面的流程做的,所以直接将桩子插在 0x3EA8处,后续脚本里面做一个次数的比较,每尝试爆破一个字符的时候,如果该处的暗桩被打到的次数大于之前的次数的话即说明该字符可取,并将新的次数存起来用于下一轮的爆破。

image-20240711105806143

下图标签处即为左边逻辑进行flag check的地方,其中mov r8b, [rax+rdx]即是取出 input 经过加密之后的内容,cmp r8b, [r14+rdx]即是正常与密文进行比较操作,特别注意此处属于是单字节check吧,所以完全可以单字节爆破一手

image-20240711110411651

下面是check之后的走向红线即是比较没过,直接判错了,然后直接走nonono的逻辑,过了的话继续走绿线循环这块儿的整个个判断逻辑

image-20240711110830880

明确可以字节爆破之后,修改控制流走一下右边的逻辑看一下程序正确输入之后的提示输出是什么,correct flag将会作为结束爆破的信息,彼时爆破进程会以一个异常跳出循环

image-20240711154143910

先写一个frida的hook插桩脚本(就在对应调用的地方插了个桩,然后每次调用有一个累加的过程,最后将数字传给主脚本):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var number = 0

function main()
{
var base = Module.findBaseAddress("rust_baby.exe") //使用 Frida 的 Module.findBaseAddress 方法查找名为 "rust_baby.exe" 的模块的基地址。

if(base){ //如果基地址存在,先在对应地址上设置一个拦截器即是 Interceptor
Interceptor.attach(base.add(0x3EA8), {//在基地址的基础上加上了0x3EA8的偏移,即对应程序中的字节累加进行判断的地方
onEnter: function(args) { //定义拦截器的 onEnter 回调函数,当目标地址的指令被执行时,会调用此回调函数。(也是插上桩了)
number+=1;
send(number); //每次进入函数时,将 number 变量加 1,并使用 send 方法发送 number 的当前值。send 通常用于将数据传输到脚本的宿主应用程序
// 延迟 10 秒钟
var delay = 0x4;//定义一个延迟时间 delay(十六进制的 4 相当于十进制的 4 毫秒),然后进入一个空循环,直到达到延迟时间为止。这里的延迟时间很短(4 毫秒),可能并没有达到明显的延迟效果。
var start = new Date().getTime();
while (new Date().getTime() < start + delay);
}
});
}
}
setImmediate(main);

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import subprocess
import frida

import os
os.chdir("E:\\VScode_learning\\.vscode\\CTF2.0\\24国赛复现")

filename = "rust_baby.exe"
cmd = ['./rust_baby.exe']
jscode = open("Hook.js", "rb").read().decode()

class SUCCESSFLAG(Exception):
"""这是一个自定义异常类"""
pass

def brute(F): #程序插桩知道checkflag位置被运行了多少次
def on_message(message, data):
global result
if message['type'] == 'send':
result = message['payload']
else:
print(message)
process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)

session = frida.attach(filename)
script = session.create_script(jscode)
script.on('message', on_message)
script.load()
process.stdin.write(F.decode())

output, error = process.communicate()
if "correct flag" in output:
print("正确的flag是:",F.decode())
raise SUCCESSFLAG("抛出flag成功的异常!!!")
process.terminate()
return result

def bp(startflag,old_number1,tag1):
global temptag
global maxnumber
idx = 0
temp = old_number1
targetidx = -1
table = "-0123456789abcdefghijklmnopqrstuvwxyz}"
for ch in table:
startflag.append(ord(ch))
startflag.append(10) #换行符,用于cmd中的命令启动
my_bytearray = bytearray(startflag)
new_number = brute(my_bytearray) #由brute函数取出暗桩流过的次数,传给new_number
if(new_number>old_number1 ):
if tag1 == 0 :
old_number1 = new_number
targetidx = idx
if(tag1 > 0):
tag1 -= 1
idx += 1
startflag = startflag[:-2]

# print(targetidx)
if(targetidx == -1): #说明两个两个字节爆破的时候前一个字节有错误,故需要开始回溯
print("错误的:")
my_bytearray = bytearray(startflag)
print(my_bytearray.decode())
print("去除错误字符!")
startflag = startflag[:-1]
print("magic:",temp-1)
temptag = temptag + 1
print("magictag:",temptag+1)
return startflag,temp-1,temptag #遍历一遍密码板之后没有找到合适的取值,所以往前回溯,temp-1

bpret = table[targetidx:targetidx+1] #此处targetidx意指的是table密码板中的下标
print("目标数据:",bpret,ord(bpret))
startflag.append(ord(bpret))
my_bytearray = bytearray(startflag)
print(my_bytearray.decode())
return startflag,old_number1,0

#1.一开始爆破的固定5个字符
startflag1 = [102, 108, 97, 103, 123] #flag的格式是固定的所以是,flag{
#2.爆破到一半突然开始回溯,非常不理解直接手动添加前爆破出来的flag数据,大概是因为程序插桩的问题,js和python通信有误差还得手动添加数组
#手动添加刚刚爆破出来的flag:"flag{6e2480b3-4f02-4cf",[102, 108, 97, 103, 123, 54, 101, 50, 52, 56, 48, 98, 51, 45, 52, 102, 48, 50, 45, 52, 99, 102]
startflag = [102, 108, 97, 103, 123, 54, 101, 50, 52, 56, 48, 98, 51, 45, 52, 102, 48, 50, 45, 52, 99, 102]
startflag.append(10)
my_bytearray = bytearray(startflag) #bytearray使得序列可以用做二进制内容作为输入,为的是与终端交互

old_number = brute(my_bytearray)
# print(old_number)
startflag = startflag[:-1] #去除一个回车
temptag = 0
tag = 0
idx = 0
temp = len(startflag) #计算一下初始长度

cleartag = 5
while True:
startflag,old_number,tag = bp(startflag,old_number,tag) #计算爆破

if(temp < len(startflag)): #如果爆破出来的字节数zhen
temp = len(startflag)
print("----->",temp-5) #此处temp-5即是减去答案开头的"flag{",代表的即是数字序列的位置
if((temp-cleartag)%3 == 0): #两个两个的字节爆破!!
print("》》》》清理temptag")
temptag = 0
cleartag += 2

#flag{6e2480b3-4f02-4cf1-9bc0-123b75f9a922}