day1

eare

custom_md5_init(seed);会检测断点,我们可以在输入和比较的数据下硬件断点,用写内存的方法把密文写回内存就可以在s1中看到flag。\n

密文:5C 76 4A 78 15 62 05 7C 6B 21 40 66 5B 1A 48 7A\n

1E 46 7F 28 02 75 68 2A 34 0C 4B 1D 3D 2E 6B 7A

17 45 07 75 47 27 39 78 61 0B

image

ko0h

前面的flag是假的,修一下jzjnz的花指令,可以看到主逻辑

image (1)

魔改RC4

image (2)

密文在sub_402A70()里,”DDDDAAAASSSS”

脚本如下

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
#include<stdio.h> 
#include<stdlib.h>
#include<string.h>

//s 表的长度取 256
#define size 256

unsigned char sbox[257] = { 0 };

// 初始化 s 表
void init_sbox(unsigned char* key) {
unsigned int i, j, k;
int tmp;

for (i = 0; i < size; i++) {
sbox[i] = i;
}

j =k = 0;
for (i = 0; i < size; i++) {
tmp = sbox[i];
j = (j + tmp + key[i % strlen((char*)key)]) % size; //注意此处的长度,自己计算一下密钥和数据的长度多次尝试
sbox[i] = sbox[j];
sbox[j] = tmp;
/*if (++k >= 12)
k = 0;*/
}
}

// 加解密函数
void enc_dec(unsigned char* key, unsigned char* data) {
int i, j, k, R, tmp;

init_sbox(key);

j = k = 0;
for (i = 0; i < strlen((char*)data); i++) {
j = (j + 1) % size;
k = (k + sbox[j]) % size;

tmp = sbox[j];
sbox[j] = sbox[k];
sbox[k] = tmp;

R = sbox[(sbox[j] + sbox[k]) % size];

data[i] += R;
}
}

int main() {
unsigned char key[100] = "DDDDAAAASSSS";
unsigned char data[100] = { 24,-100,71,61,59,-31,41,39,-97,52,-125,-43,-19,-75,110,89,127,-34,71,-41,101,63,122,51,91,100,-74,-6,-108,85,-121,66,32,6,12,105,-2,114,-87,-28,-47,124 };
enc_dec(key, data);
printf("dec: %s\n", data);
return 0;
}

day3

oooooore

全是花指令,大概分为两种,jzjnz和callret,用脚本去花(脚本1),去除花指令后可以看到main中的加密逻辑,看字符串表可以看到一个rc4密钥,还可以定位到输入的位置。

image-20250120001155602

mecpcy那里是密文,s是我们的输入。往下看我们的输入被传入了两个函数中,这应该就是加密函数了。第一个进去就是我们在字符串表看到的那个RC4。

image-20250120002002954

在第二个函数中我们可以在流程图中看到一些常数,按r可以看到他们的字符串”expand 32-byte k”,这个明显就是salsa20或者chacha20加密了,和RC4差不多本质就是一个异或,可以用写内存的方法解决。(其实这些找不到也没什么大问题,下面的动调可以解决一切)

image-20250120002445728

回到main函数,我们在输入的数据下硬件断点,输入一个正确长度的值进行动调,查看我们值的变化。这里我输入flag{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa},继续运行,同时看我们输入数据的变化。

第一次改变在这里,有一个异或,发现数据来源于byte ptr [rcx+rdx]和[rbp-70h],我们在00005555555593BF下断点就可以看到byte ptr [rcx+rdx]是我们的输入,[rbp-70h]是一个地址,指向的地址里面只有一个数据,我们给这个数据下硬件断点继续溯源(其实可以不用再溯源了,这里是为了深入了解)image-20250120004147145

这次断在这个位置,上面的[rcx]是我们追踪的值,这个值来自byte ptr [rcx+rdx],不过好像被覆盖了,我们下断点查看。里面就是一串表(有点像main函数里面第二个函数上面那些数据),下面还有RC4的密钥,但是不确定是哪个加密函数。取消全部的断点,只保留我们输入数据的硬件断点,继续运行。

image-20250120004807245

最后变化在这个位置,往前看看就能看到只有一个异或,还有一个RC4的盒,那么前面那个就是chacha20或者salsa20。(我们只需要知道加密只有两个异或就行)

image-20250120010127963

再进行一次动调,这次我们把我们把密文作为输入写内存,并在最后一个数据下硬件断点。运行到最后一步加密,再次查看就可以看到flag

1
2
3
81 ED 7E 2F 93 B6 6F 8D 43 E5 C9 11 A9 F4 2B DB 
AD CB 45 66 FA DF A9 61 28 65 31 D7 80 D5 18 FE
25 6E 94 05 83 51 B5 42 D2 9D

image-20250120000848703

脚本1(去花)

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
import idautils
import idc
import idaapi

def nop_jzjnz(start_addr,endaddr,pattern):
length =len(pattern)
flag = 0
while start_addr < endaddr:
getbytes =idc.get_bytes(start_addr,length) #获取指定范围内指定地址的字节码并返回成列表
if(getbytes[0]==pattern[0] and getbytes[3]==pattern[3] and getbytes[6] == pattern[6] ): #匹配花指令模板
flag = 1
for i in range(length):
idc.patch_byte(start_addr + i,0x90) #nop
print(f"success! nop 0x{hex(start_addr)} to 0x{hex(start_addr + length -1)} length:{length}")
start_addr += length - 1
start_addr += 1
if(flag == 0):
print("Not find!!!")

def nop_range(start_addr,end_addr):
while(start_addr <= end_addr):
idc.patch_byte(start_addr,0x90)
start_addr += 1
print(f"success! nop 0x{start_addr} to 0x{end_addr}")

def remount():
try:
addr = idc.get_screen_ea()
func = idaapi.get_func(addr)
func_start = func.start_ea
idaapi.del_func(func_start)
idaapi.add_func(func_start)
print(f"success!! remount the func in 0X{func}")
except Exception as a:
print("some errors occur, remount falrue")
print(f"error : {a}")

start_addr = 0x0001000
end_addr = 0x07378

pattern_callret = [0xE8, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0x04, 0x24, 0x08, 0xC3, 0xE8, 0x90, 0x90, 0x90]
pattern_jzjnz3 = [0x0F, 0x85, 0x06, 0x00, 0x00, 0x00, 0x0F, 0x84, 0x01, 0x00, 0x00, 0x00, 0x21]

nop_jzjnz(start_addr,end_addr,pattern_jzjnz3)
nop_jzjnz(start_addr,end_addr,pattern_callret)

easyasm

sub_102C0

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
seg002:0000 sub_102C0       proc near               ; CODE XREF: start+F↓p
seg002:0000 push ax
seg002:0001 push bx
seg002:0002 push cx
seg002:0003 push si
seg002:0004
seg002:0004 loc_102C4: ; CODE XREF: sub_102C0+29↓j
seg002:0004 mov bx, 1 ;设置标志默认为1,用于判断是否产生交换
seg002:0007 mov cx, ds:0 ;设置循环次数为cx为一个地址ds:0,loop默认cx统计次数
seg002:000B dec cx ;cx减一
seg002:000C lea si, ds:2 ;加载ds:2的地址到si
seg002:0010
seg002:0010 loc_102D0: ; CODE XREF: sub_102C0+22↓j
seg002:0010 mov ax, [si]
seg002:0012 cmp ax, [si+2] ;比较[si]和[si+2],即2字节组合比较
seg002:0015 jbe short loc_102DF ;若[si]<[si+2],跳转
seg002:0017 xchg ax, [si+2] ;交换
seg002:001A mov [si], ax
seg002:001C mov bx, 0 ;设置bx为0记录产生交换
seg002:001F
seg002:001F loc_102DF: ; CODE XREF: sub_102C0+15↑j
seg002:001F add si, 2
seg002:0022 loop loc_102D0 ;cx != 0时跳转,遍历数据
seg002:0024 cmp bx, 1 ;bx为1表示未产生交换,排序完毕
seg002:0027 jz short loc_102EB ;排序完毕
seg002:0029 jmp short loc_102C4 ;继续排序
seg002:002B ; ---------------------------------------------------------------------------
seg002:002B
seg002:002B loc_102EB: ; CODE XREF: sub_102C0+27↑j
seg002:002B pop si
seg002:002C pop cx
seg002:002D pop bx
seg002:002E pop ax
seg002:002F retn
seg002:002F sub_102C0 endp
seg002:002F

start

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
seg002:004C                 public start
seg002:004C start proc near
seg002:004C mov ax, seg seg000
seg002:004F mov ss, ax
seg002:0051 mov sp, 200h
seg002:0054 mov ax, seg seg001
seg002:0057 mov ds, ax
seg002:0059 assume ds:seg001
seg002:0059 mov es, ax
seg002:005B assume es:seg001
seg002:005B call sub_102C0 ;调用排序
seg002:005E mov dx, 0B2h ;输入存储位置,前两个字节存储数据的属性,所以数据从B4h开始
seg002:0061 mov ah, 0Ah
seg002:0063 int 21h ;阻断程序,用于输入 ; DOS - BUFFERED KEYBOARD INPUT
seg002:0063 ; DS:DX -> buffer
seg002:0065 mov bl, byte ptr aWelcomeToChunq+5Dh ; ""
seg002:0069 mov byte ptr [bx+0B4h], 24h ; '$'
seg002:006E mov cx, word ptr byte_10200 ;21
seg002:0072 add cx, cx ;42
seg002:0074 mov bx, 0 ;偏移量
seg002:0077
seg002:0077 loc_10337: ; CODE XREF: start+3C↓j
seg002:0077 mov al, [bx+0B4h] ;输入的数据
seg002:007B mov ah, [bx+2] ;ds:(bx+2) 指向的数据(这里是单字节)
seg002:007F xor al, ah ;两者异或
seg002:0081 mov [bx+0B4h], al ;修改输入数据
seg002:0085 add bx, 1 ;计数器(偏移量)加一
seg002:0088 loop loc_10337 ;cx 不为0时继续异或,cx会被减一
seg002:008A lea di, word_102B4 ;加载输入
seg002:008E lea si, byte_1022C ;加载密文
seg002:0092 call sub_102F0 ;比较函数
seg002:0095 jnz short loc_1035A ;根据返回判断结果,错误
seg002:0097 jmp short loc_10364 ;正确
seg002:0097 ; ---------------------------------------------------------------------------
seg002:0099 align 2
seg002:009A
seg002:009A loc_1035A: ; CODE XREF: start+49↑j
seg002:009A mov dx, 71h ; 'q'
seg002:009D mov ah, 9
seg002:009F int 21h ; DOS - PRINT STRING
seg002:009F ; DS:DX -> string terminated by "$"
seg002:00A1 jmp short loc_1036E
seg002:00A1 ; ---------------------------------------------------------------------------
seg002:00A3 align 2
seg002:00A4
seg002:00A4 loc_10364: ; CODE XREF: start+4B↑j
seg002:00A4 mov dx, 9Bh
seg002:00A7 mov ah, 9
seg002:00A9 int 21h ; DOS - PRINT STRING
seg002:00A9 ; DS:DX -> string terminated by "$"
seg002:00AB jmp short loc_1036E
seg002:00AB ; ---------------------------------------------------------------------------
seg002:00AD align 2
seg002:00AE
seg002:00AE loc_1036E: ; CODE XREF: start+55↑j
seg002:00AE ; start+5F↑j
seg002:00AE mov dx, 57h ; 'W'
seg002:00B1 mov ah, 9
seg002:00B3 int 21h ; DOS - PRINT STRING
seg002:00B3 ; DS:DX -> string terminated by "$"
seg002:00B5 mov ax, 4C00h
seg002:00B8 int 21h ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)
seg002:00B8 start endp ; AL = exit code
seg002:00B8
seg002:00B8 seg002 ends
seg002:00B8
seg002:00B8
seg002:00B8 end start

解密脚本

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
data = [
0x2030, 0x3040, 0x4050, 0x1022, 0x2011, 0x1666, 0x1522, 0x8899,
0x4155, 0x4044, 0x4288, 0x3321, 0x6033, 0xFFFF, 0x2221, 0x3366,
0x222C, 0x2CCC, 0x22CC, 0xCC22, 0xC2C2
]
box = [
0x44, 0x7C, 0x43, 0x72, 0x1D, 0x72, 0x74, 0x41, 0x05, 0x14, 0x19, 0x1A,
0x19, 0x0F, 0xF5, 0x10, 0xAE, 0x18, 0x6D, 0x01, 0x10, 0x56, 0x00, 0x1E,
0x26, 0x71, 0x65, 0x73, 0x78, 0x72, 0xEB, 0x72, 0x52, 0x06, 0xAA, 0xBB,
0xA3, 0xA4, 0x1B, 0xFC, 0xC7, 0x82
]
for i in range(len(data)):
for j in range(len(data) - i -1):
if data[j] > data[j+1]:
tmp = data[j]
data[j] = data[j+1]
data[j+1] = tmp

# for i in data:
# print(f"{hex(i)},",end='')

f = b""

for x in data:
b = x.to_bytes(2,"little") #小端序拆分成单字节
f += b

#print(f.hex())
hex_str = f.hex()

# 将十六进制字符串按每8字节(即16个字符)分块,转为列表
chunk_size = 2
chunks = [hex_str[i:i+chunk_size] for i in range(0, len(hex_str), chunk_size)]
# 将每个块转换为十进制并存入列表
decimal_list = [int(chunk, 16) for chunk in chunks]
result =list(i*0 for i in range(42))

# 打印结果
for d,b,i in zip(decimal_list,box,range(42)):
result[i] = d^b

for i in result:
print(chr(i),end='')
#flag{dea54885-92b4-11ef-b153-3c0af33af908}