霍雅
NSSCTF GHCTF2025新生赛公开赛道 reverse ASM?Signin
来检验一下你的8086汇编基础吧!回顾或许能找到更好的方式。
给了一个汇编文件,具体代码如下
.MODEL SMALL
.STACK 100H
.DATA
WELCOME_MSG db 'Welcome to GHCTF!', 0DH, 0AH, '$'
INPUT_MSG db 'Input your flag:', '$'
WRONG_MSG db 0DH, 0AH, 'Wrong!', 0DH, 0AH, '$'
RIGHT_MSG db 0DH, 0AH, 'Right!', 0DH, 0AH, '$'
DATA1 DB 26H,27H,24H,25H,2AH,2BH,28H,00H
DB 2EH,2FH,2CH,2DH,32H,33H,30H,00H
DB 36H,37H,34H,35H,3AH,3BH,38H,39H
DB 3EH,3FH,3CH,3DH,3FH,27H,34H,11H
DATA2 DB 69H,77H,77H,66H,73H,72H,4FH,46H
DB 03H,47H,6FH,79H,07H,41H,13H,47H
DB 5EH,67H,5FH,09H,0FH,58H,63H,7DH
DB 5FH,77H,68H,35H,62H,0DH,0DH,50H
BUFFER1 db 33 dup(0)
BUFFER2 db 33 dup(0)
.CODE
START:
MOV AX,@DATA
MOV DS,AX
MOV AH,09H
MOV DX,OFFSET WELCOME_MSG
INT 21H
MOV DX,OFFSET INPUT_MSG
INT 21H
MOV AH,0AH
MOV DX,OFFSET BUFFER1
MOV BYTE PTR[BUFFER1],33
INT 21H
CALL DO1
CALL ENC
MOV SI,OFFSET BUFFER1 + 2
MOV DI,OFFSET DATA2
MOV CX,32
LOOP1:
MOV AL,[SI]
CMP AL,[DI]
JNE P2
INC SI
INC DI
LOOP LOOP1
P1:
MOV AH,09H
LEA DX,RIGHT_MSG
INT 21H
JMP EXIT_PROGRAM
P2:
MOV AH,09H
LEA DX,WRONG_MSG
INT 21H
EXIT_PROGRAM:
MOV AX,4C00H
INT 21H
DO1 PROC
PUSH SI
PUSH DI
PUSH CX
XOR SI,SI
MOV CX,8
SWAP_LOOP:
PUSH CX
MOV DI,SI
ADD DI,4
CMP DI,28
JL NOWRAP
SUB DI,28
NOWRAP:
MOV BX,SI
CALL DO2
ADD SI,4
POP CX
LOOP SWAP_LOOP
POP CX
POP DI
POP SI
RET
DO1 ENDP
DO2 PROC
PUSH CX
MOV CX,4
LOOP3:
MOV AL,DATA1[BX]
MOV AH,DATA1[DI]
MOV DATA1[BX],AH
MOV DATA1[DI],AL
INC BX
INC DI
LOOP LOOP3
POP CX
RET
DO2 ENDP
ENC PROC
PUSH CX
MOV SI,OFFSET BUFFER1 + 2
MOV DI,OFFSET DATA1
MOV CX,8
LOOP2:
MOV AX,WORD PTR[DI + 1]
XOR WORD PTR[SI],AX
MOV AX,WORD PTR[DI + 2]
XOR WORD PTR[SI + 2],AX
ADD SI,4
ADD DI,4
LOOP LOOP2
POP CX
RET
ENC ENDP
END START
ai解
deepseek r1
好,我现在得仔细看看这个CTF的加密算法,然后想办法逆向得到flag。题目里给的是汇编代码,我得先理解这段代码做了什么。首先,用户输入的flag经过两个处理函数DO1和ENC,然后和DATA2里的数据比较。如果相同就输出正确,否则错误。所以关键是要弄清楚DO1和ENC这两个过程,然后逆向算出正确的输入。
首先,整个程序的结构大概是这样的:
- 显示欢迎信息和输入提示,读取用户输入到BUFFER1。
- 调用DO1函数,这个函数可能对DATA1数组做了某种处理。
- 调用ENC函数,对BUFFER1中的输入数据进行加密处理。
- 将加密后的BUFFER1与DATA2中的预设数据比较,一致则正确。
因此,我们的目标是找到输入字符串,经过ENC处理后等于DATA2中的数据。而DO1可能改变了DATA1的结构,从而影响ENC的处理。所以需要先分析DO1和ENC的作用,然后逆向计算。
首先分析DO1函数。DO1是一个过程,里面有一个循环,循环8次。每次循环调用DO2函数,参数可能与SI和DI有关。在DO1中,初始时SI=0,每次循环增加4,循环次数是8次。每次循环中,DI被设置为SI+4,然后检查是否超过28,如果超过则减去28。然后调用DO2,参数是BX=SI,DI=DI。DO2函数交换DATA1中BX和DI开始的4个字节。比如,第一次循环时,SI=0,DI=0+4=4,然后交换DATA1中从0开始的4字节和从4开始的4字节。然后SI增加4,进入下一次循环,直到循环8次。所以DO1的作用是对DATA1数组进行某种交换操作,可能是一个交换块的步骤。
具体来说,DO1函数中的SWAP_LOOP循环了8次,每次处理一个块。每次循环中,DI = SI +4。如果DI >=28,则减去28。例如,当SI是24的时候,DI=24+4=28,此时28等于28,所以DI=28-28=0?然后调用DO2,交换SI和DI处的4个字节。因此,DO1实际上是在对DATA1数组进行块交换,每个块是4字节,交换的位置是每隔4个块,循环8次。可能需要具体模拟一下这个过程,才能确定DATA1在DO1处理后的结果。
然后,ENC函数的作用是对输入的BUFFER1进行加密。在ENC函数中,首先将SI指向BUFFER1+2(输入的数据),DI指向处理后的DATA1数组。循环次数是8次,每次循环处理4个字节。在循环中,从DI+1处取一个字(AX=word ptr [DI+1]),然后异或到SI指向的当前字。然后从DI+2处取下一个字,异或到SI+2的位置。然后SI和DI各加4,循环8次。这里需要注意,因为DI每次指向DATA1的某个位置,然后每次处理4字节,所以每个循环处理输入的4字节,使用DATA1中的4字节(从DI+1开始的两个字,也就是两个16位的数据)进行异或。
但这里可能需要注意,汇编中的word是16位,也就是两个字节。例如,当DI指向DATA1的某个位置时,DI+1的位置取一个字的话,比如DATA1的起始地址是0,那么第一次循环时,DI=0,所以DI+1是1的位置,取的是DATA1[1]和DATA1[2]组成的字。接着,DI+2是2的位置,取的是DATA1[2]和DATA1[3]组成的字?或者可能我的理解有误?
或者,DI开始指向DATA1的起始地址,然后每次循环,DI增加4。例如,第一次循环时,DI=0,然后取DI+1的位置的字,即DATA1[1]和DATA1[2](小端顺序的话,低字节在前),接着取DI+2的位置的字,即DATA1[2]和DATA1[3]。然后,这两个字分别异或到输入缓冲区的当前字和下一个字。例如,假设输入的四个字节是A,B,C,D,那么用AX=DATA1[DI+1]和DATA1[DI+2]处的字,分别异或到输入的前两个字节和后两个字节。例如,第一个字异或到SI(A和B),第二个字异或到SI+2(C和 D)。然后SI增加4,处理下一个四字节块。所以每个四字节块被两个连续的16位异或值处理。
不过,可能这里需要注意,在汇编中,word ptr [DI+1] 会从DI+1的地址开始取两个字节。例如,假设DI指向地址0x00,那么word ptr [0x00]是第一个和第二个字节,而word ptr [0x01]是第二个和第三个字节。所以在ENC函数中,每次循环处理输入的四字节块,使用DATA1中的从DI+1开始的两个字(共四个字节)进行异或。例如,当DI=0时,异或的第一个字是DATA1[1], DATA1[2]组成的16位值,第二个字是DATA1[2], DATA1[3]组成的16位值。或者,这可能不是正确的,因为DI是每次循环后增加4,所以每个循环处理DATA1中的四个字节?例如,假设DATA1在DO1处理后,每四个字节为一组,那么在ENC中,每个循环处理一组四个字节中的中间两个字?比如,DATA1数组中的每个四字节块,比如从位置DI开始,四个字节是B0, B1, B2, B3。那么在循环中,取的是B1和 B2组成的第一个字(word ptr [DI+1]),然后是 B2和 B3组成的第二个字(word ptr [DI+2])?这样可能的话,每个四字节块中的中间两个字节被取出作为两个字,然后异或到输入的四个字节中的前两个和后两个。
或者,可能我搞错了。例如,假设DI初始指向DATA1的起始位置,那么第一次循环时,DI是0,那么:
- word ptr [DI +1] 就是地址0+1=1,取两个字节:DATA1[1]和DATA1[2]组成一个字。
- word ptr [DI +2] 就是地址0+2=2,取DATA1[2]和DATA1[3]组成另一个字。
然后,这两个字分别异或到输入块的第一个字(SI指向的位置)和第二个字(SI+2的位置)。输入块是四个字节,所以用这两个字异或。例如,输入块的四个字节为 a,b,c,d,对应的两个字是 ab和 cd(假设小端序的话,实际上是按字节顺序组合)。然后异或上从DATA1取的两个字,即 (ab) XOR (DATA1[1] | DATA1[2] <<8) ,然后 (cd) XOR (DATA1[2] | DATA1[3] <<8)。这可能比较复杂。
但不管怎样,ENC函数的作用是将输入的每个四字节块与DATA1中的某些部分进行异或处理。而DATA1在DO1中被修改过,所以必须明确DO1处理后的DATA1的值,才能知道ENC函数中的异或密钥。
因此,整个加密流程是:
- 用户输入存储在BUFFER1+2的位置(因为输入是通过0AH功能读取的,BUFFER1的第一个字节是最大长度,第二个是实际输入长度,后面是输入的字符)。
- DO1函数对DATA1进行交换处理。
- ENC函数将BUFFER1+2的输入数据与处理后的DATA1中的数据进行异或。
- 加密后的结果与DATA2中的32字节比较,一致则正确。
所以,要逆向得到flag,需要:
- 确定经过DO1处理后的DATA1的内容。
- 对DATA2中的每个四字节块,用对应的DATA1中的两个异或字进行异或解密。
- 可能要考虑输入的长度是否为32字节,因为DATA2是32字节,所以输入的flag长度应该也是32字节(因为BUFFER1被定义为33 dup(0),其中输入的最大长度是33-2=31字节?或者原题中的输入可能刚好是32字节)。
现在,首先需要弄清楚DO1对DATA1的处理。因为DATA1的初始值是给出的:
DATA1 DB 26H,27H,24H,25H,2AH,2BH,28H,00H
DB 2EH,2FH,2CH,2DH,32H,33H,30H,00H
DB 36H,37H,34H,35H,3AH,3BH,38H,39H
DB 3EH,3FH,3CH,3DH,3FH,27H,34H,11H
这共有32字节(每行8字节,四行共32字节)。DO1函数中的交换过程需要模拟一下,才能得到处理后的DATA1的值。这一步可能需要手动模拟或者写脚本计算。
DO1中的循环结构是:循环8次,每次调用DO2交换两个四字节块。初始时,SI=0,每次循环后SI增加4。每次循环中,DI=SI+4。如果DI >=28,则减去28。然后交换SI和DI处的四字节块。因此,需要计算每次交换的位置。
让我们详细分析DO1中的交换过程:
循环次数是8次,初始SI=0。
第1次循环:
SI=0 → DI=0+4=4。4 <28,所以不用调整。交换DATA1中SI=0和DI=4处的四字节块。即交换DATA1[0-3]和DATA1[4-7]。
交换前,DATA1的前8字节是:26,27,24,25, 2A,2B,28,00 → 交换后变成:2A,2B,28,00, 26,27,24,25。接下来的四字节是原来的DATA1[8-11]等。
第2次循环:
SI +=4 → SI=4。DI=4+4=8。交换DATA1[4-7]和DATA1[8-11]。原来的DATA1[4-7]现在是26,27,24,25;DATA1[8-11]是2E,2F,2C,2D。交换后,DATA1[4-7]是2E,2F,2C,2D;DATA1[8-11]是26,27,24,25.
第3次循环:
SI=8 → DI=8+4=12。交换DATA1[8-11]和DATA1[12-15]。原来的DATA1[8-11]是26,27,24,25;DATA1[12-15]是32,33,30,00。交换后,DATA1[8-11]是32,33,30,00;DATA1[12-15]是26,27,24,25.
第4次循环:
SI=12 → DI=12+4=16。交换DATA1[12-15]和DATA1[16-19]. DATA1[12-15]是26,27,24,25;DATA1[16-19]是36,37,34,35 → 交换后,DATA1[12-15]是36,37,34,35;DATA1[16-19]是26,27,24,25.
第5次循环:
SI=16 → DI=20。交换DATA1[16-19]和DATA1[20-23]. DATA1[16-19]是26,27,24,25;DATA1[20-23]是3A,3B,38,39 → 交换后,DATA1[16-19]是3A,3B,38,39;DATA1[20-23]是26,27,24,25.
第6次循环:
SI=20 → DI=24 → 24 <28 → 交换DATA1[20-23]和DATA1[24-27]. DATA1[20-23]是26,27,24,25;DATA1[24-27]是3E,3F,3C,3D → 交换后,DATA1[20-23]是3E,3F,3C,3D;DATA1[24-27]是26,27,24,25.
第7次循环:
SI=24 → DI=24+4=28 → 28 >=28 → DI=28-28=0. 交换DATA1[24-27](现在是26,27,24,25)和DATA1[0-3](现在是2A,2B,28,00)。交换后,DATA1[0-3]是26,27,24,25;DATA1[24-27]是2A,2B,28,00.
第8次循环:
SI=28 → 此时SI是28?但是原来的循环次数是8次,而初始SI=0,每次加4,那么第8次循环时,SI应该是0 + 4*7=28?这时候DI=28+4=32 → 32 >=28 → 32-28=4。交换DATA1[28-31]和DATA1[4-7]。DATA1[28-31]的初始值在交换前可能已经被之前的交换改变。原来的DATA1在初始中,28-31的位置是:3F,27,34,11H。但经过之前的交换,比如第7次循环时,DATA1的24-27被交换为2A,2B,28,00。所以28-31的位置可能还是原来的数据?或者可能之前的交换是否会影响这些位置?
这可能比较复杂,可能需要重新仔细计算每一步的交换情况。这时候可能需要一个表格来记录每次交换后的DATA1的值。
可能这个过程比较复杂,所以最好是写一个Python脚本来模拟每一步交换后的结果。或者,手动跟踪每一步交换后的DATA1数组。
初始DATA1数组的32字节:
索引0-7: 26 27 24 25 2A 2B 28 00
索引8-15: 2E 2F 2C 2D 32 33 30 00
索引16-23: 36 37 34 35 3A 3B 38 39
索引24-31:3E 3F 3C 3D 3F 27 34 11
现在处理DO1中的交换:
循环次数为8次,每次交换两个4字节块。初始SI=0,每次循环后SI +=4。
第一次循环(i=0):
SI=0 → DI=0+4=4 → 交换索引0-3和4-7的数据。
交换前:
0-3:26 27 24 25
4-7:2A 2B 28 00
交换后:
0-3:2A 2B 28 00
4-7:26 27 24 25
其他部分不变。
此时DATA1数组变为:
0-7:2A 2B 28 00 26 27 24 25
8-15:2E 2F 2C 2D 32 33 30 00
16-23:36 37 34 35 3A 3B 38 39
24-31:3E 3F 3C 3D 3F 27 34 11
第二次循环(i=1):
SI=4 → DI=4+4=8 → 交换索引4-7和8-11。
索引4-7当前是26 27 24 25
索引8-11是2E 2F 2C 2D
交换后:
4-7:2E 2F 2C 2D
8-11:26 27 24 25
此时DATA1数组:
0-7:2A 2B 28 00 2E 2F 2C 2D
8-15:26 27 24 25 32 33 30 00
16-23:36 37 34 35 3A 3B 38 39
24-31:3E 3F 3C 3D 3F 27 34 11
第三次循环(i=2):
SI=8 → DI=8+4=12 → 交换索引8-11和12-15.
索引8-11:26 27 24 25
索引12-15:32 33 30 00
交换后:
8-11:32 33 30 00
12-15:26 27 24 25
此时DATA1数组:
0-7:2A 2B 28 00 2E 2F 2C 2D
8-15:32 33 30 00 26 27 24 25
16-23:36 37 34 35 3A 3B 38 39
24-31:3E 3F 3C 3D 3F 27 34 11
第四次循环(i=3):
SI=12 → DI=12+4=16 → 交换索引12-15和16-19.
索引12-15:26 27 24 25
索引16-19:36 37 34 35
交换后:
12-15:36 37 34 35
16-19:26 27 24 25
此时DATA1数组:
0-7:2A 2B 28 00 2E 2F 2C 2D
8-15:32 33 30 00 36 37 34 35
16-23:26 27 24 25 3A 3B 38 39
24-31:3E 3F 3C 3D 3F 27 34 11
第五次循环(i=4):
SI=16 → DI=16+4=20 → 交换索引16-19和20-23.
索引16-19:26 27 24 25
索引20-23:3A 3B 38 39
交换后:
16-19:3A 3B 38 39
20-23:26 27 24 25
此时DATA1数组:
0-7:2A 2B 28 00 2E 2F 2C 2D
8-15:32 33 30 00 36 37 34 35
16-23:3A 3B 38 39 26 27 24 25
24-31:3E 3F 3C 3D 3F 27 34 11
第六次循环(i=5):
SI=20 → DI=20+4=24 → 交换索引20-23和24-27.
索引20-23:26 27 24 25
索引24-27:3E 3F 3C 3D
交换后:
20-23:3E 3F 3C 3D
24-27:26 27 24 25
此时DATA1数组:
0-7:2A 2B 28 00 2E 2F 2C 2D
8-15:32 33 30 00 36 37 34 35
16-23:3A 3B 38 39 3E 3F 3C 3D
24-31:26 27 24 25 3F 27 34 11
第七次循环(i=6):
SI=24 → DI=24+4=28 → 28 >=28 → DI=28-28=0 → 交换索引24-27和0-3.
索引24-27:26 27 24 25
索引0-3:2A 2B 28 00
交换后:
0-3:26 27 24 25
24-27:2A 2B 28 00
此时DATA1数组:
0-7:26 27 24 25 2E 2F 2C 2D
8-15:32 33 30 00 36 37 34 35
16-23:3A 3B 38 39 3E 3F 3C 3D
24-31:2A 2B 28 00 3F 27 34 11
第八次循环(i=7):
SI=28 → DI=28+4=32 → 32 >=28 → 32-28=4 → 交换索引28-31和4-7.
索引28-31:3F 27 34 11
索引4-7:2E 2F 2C 2D
交换后:
4-7:3F 27 34 11
28-31:2E 2F 2C 2D
所以最终的DATA1数组在DO1处理后的结果如下:
索引0-3:26 27 24 25
索引4-7:3F 27 34 11
索引8-11:32 33 30 00
索引12-15:36 37 34 35
索引16-19:3A 3B 38 39
索引20-23:3E 3F 3C 3D
索引24-27:2A 2B 28 00
索引28-31:2E 2F 2C 2D
现在,DATA1数组在DO1处理后的值已经确定。接下来,ENC函数会使用这个处理后的DATA1来对输入进行异或加密。
ENC函数的处理过程是:
- 将SI指向BUFFER1+2(输入数据的起始位置)
- DI指向处理后的DATA1数组的起始位置。
- 循环8次,每次处理4字节的输入块。
- 每次循环中,取DI+1处的字(两个字节)异或到输入块的第一个字(SI指向的位置),然后取DI+2处的字异或到输入块的第三个字节(SI+2的位置)。
- 然后,SI增加4,DI增加4,处理下一个四字节块。
需要注意的是,这里的“字”是小端序还是大端序。在x86汇编中,word的存储是小端序的。例如,地址DI+1处的字节是低字节,DI+2是高字节?或者,word ptr [DI+1] 是将DI+1作为低地址,DI+2作为高地址,所以组成的字是 (DATA1[DI+2] <<8) | DATA1[DI+1]。例如,如果DATA1[DI+1]是0x12,DATA1[DI+2]是0x34,那么该字的值是0x3412。
在ENC函数中,每个循环处理四个输入字节,用DATA1中对应的两个连续字进行异或。例如,对于第一个四字节块,处理后的DATA1的DI=0,所以:
DI+1=1 → DATA1[1]=27, DATA1[2]=24 → 字是 24 27 → 小端序的话,是0x2427吗?或者,是0x2724?
哦,这里可能有误解,因为在内存中,低地址是低字节。例如,地址DI+1处的两个字节是DATA1[DI+1]和DATA1[DI+2],组成的字是 (DATA1[DI+2] << 8) | DATA1[DI+1]。例如,当DI=0时,DI+1是1,DI+2是2,所以DATA1[1]是0x27,DATA1[2]是0x24 → 字是0x24<<8 |0x27 = 0x2427?
或者是不是这样?比如,对于小端序来说,字在内存中是低位字节在前。例如,如果内存中地址0处存储的是0x12,地址1处是0x34,那么word ptr [0]的值是0x3412。所以,当取DI+1处的word时,其值是 (DATA1[DI+2] <<8) + DATA1[DI+1]。
所以,当DI=0时,DI+1=1,DI+2=2:
DATA1[1] = 0x27, DATA1[2] =0x24 → 字是 0x24 <<8 |0x27 = 0x2427.
同理,DI+2=2处的word是DATA1[2]和DATA1[3],即 0x24和0x25 → 0x2524.
所以,对于第一个四字节块(输入的前四个字节),会被这两个字异或。
具体来说,假设输入的四个字节是A B C D(地址SI到SI+3),那么:
第一个字异或的是A B → 变成 (A B) XOR 0x2427.
第二个字异或的是 C D → 变成 (C D) XOR 0x2524.
那么,这四个字节处理后的结果是:
A' = A ^ 0x27
B' = B ^ 0x24
C' = C ^ 0x24
D' = D ^ 0x25
因为0x2427的低字节是0x27,高字节是0x24。异或的时候,每个字节分别异或对应的高或低字节?或者异或是整个字作为一个整体异或?
这里需要明确异或的方式。例如,假设输入的四个字节是 A B C D,对应的两个字为 AB(小端序)和 CD。异或操作是:
AB ^ word1 → 结果的两个字节是 (A ^ low_byte(word1)) 和 (B ^ high_byte(word1)).
同理,CD ^ word2 → C ^ low_byte(word2) 和 D ^ high_byte(word2).
例如,word1是0x2427,则 low_byte是0x27,high_byte是0x24。所以:
A XOR 0x27 → 新的A'
B XOR 0x24 → 新的B'
同理,word2是0x2524(假设DI+2处的word是0x2524,即低字节是24,高字节是25):
C XOR 0x24 → C'
D XOR 0x25 → D'
所以,整个四字节块被异或后的结果是:A^27, B^24, C^24, D^25.
这可能就是异或的方式。因此,ENC函数中对每个四字节块的异或方式是将前两个字节分别异或第一个字的低字节和高字节,后两个字节分别异或第二个字的低字节和高字节。
因此,对于每个四字节块的处理,我们可以拆解成四个单独的异或操作,每个字节对应不同的密钥字节。
现在,我们需要根据处理后的DATA1数组,确定每个四字节块对应的异或密钥。
处理后的DATA1数组如下:
索引0:26 27 24 25 → 这四个字节是DATA1的0-3字节。
索引4:3F 27 34 11 → 这四个字节是4-7。
索引8:32 33 30 00 → 8-11.
索引12:36 37 34 35 → 12-15.
索引16:3A 3B 38 39 → 16-19.
索引20:3E 3F 3C 3D → 20-23.
索引24:2A 2B 28 00 → 24-27.
索引28:2E 2F 2C 2D → 28-31.
在ENC函数中,每个循环处理四字节块,使用DI指向的DATA1中的四字节块,取其中的第二个和第三个字节作为第一个异或字,第三个和第四个字节作为第二个异或字。例如,当DI=0时,四字节块是26 27 24 25。那么,DI+1=1处的字是27 24 → 0x2427(低字节是27,高字节是24?或者小端序的话,是24<<8 |27 → 0x2427)。而DI+2=2处的字是24 25 → 0x2524。所以,第一个四字节块使用的异或字是0x2427和0x2524,对应的四个异或字节是27,24,24,25。
同理,当DI=4时,四字节块是3F 27 34 11。DI+1=5,所以字是27 34 → 0x3427,DI+2=6处的字是34 11 → 0x1134。对应的四个异或字节是27,34,34,11。
依次类推,每个四字节块对应的异或密钥如下:
DI=0 → 四字节块是26,27,24,25 → 异或字1=27 24 → 0x2427 → 字节是27(低)和24(高);异或字2=24 25 → 0x2524 → 字节24(低)和25(高)。所以四个异或字节是27,24,24,25.
DI=4 → 四字节块是3F,27,34,11 → 字1=27,34 → 0x3427 → 异或字节27,34;字2=34,11 → 0x1134 → 异或字节34,11 → 四个异或字节是27,34,34,11.
DI=8 → 四字节块是32,33,30,00 → 字1=33,30 → 0x3033 → 异或字节33,30;字2=30,00 → 0x0030 → 异或字节30,00 → 四个异或字节是33,30,30,00.
DI=12 → 四字节块是36,37,34,35 → 字1=37,34 → 0x3437 → 异或字节37,34;字2=34,35 → 0x3534 → 异或字节34,35 → 四个异或字节是37,34,34,35.
DI=16 → 四字节块是3A,3B,38,39 → 字1=3B,38 → 0x383B → 异或字节3B,38;字2=38,39 → 0x3938 → 异或字节38,39 → 四个异或字节是3B,38,38,39.
DI=20 → 四字节块是3E,3F,3C,3D → 字1=3F,3C → 0x3C3F → 异或字节3F,3C;字2=3C,3D → 0x3D3C → 异或字节3C,3D → 四个异或字节是3F,3C,3C,3D.
DI=24 → 四字节块是2A,2B,28,00 → 字1=2B,28 → 0x282B → 异或字节2B,28;字2=28,00 → 0x0028 → 异或字节28,00 → 四个异或字节是2B,28,28,00.
DI=28 → 四字节块是2E,2F,2C,2D → 字1=2F,2C → 0x2C2F → 异或字节2F,2C;字2=2C,2D → 0x2D2C → 异或字节2C,2D → 四个异或字节是2F,2C,2C,2D.
现在,DATA2中的加密后的数据是:
DATA2 DB 69H,77H,77H,66H,73H,72H,4FH,46H
DB 03H,47H,6FH,79H,07H,41H,13H,47H
DB 5EH,67H,5FH,09H,0FH,58H,63H,7DH
DB 5FH,77H,68H,35H,62H,0DH,0DH,50H
共32字节,分成8个四字节块:
块1: 69,77,77,66
块2:73,72,4F,46
块3:03,47,6F,79
块4:07,41,13,47
块5:5E,67,5F,09
块6:0F,58,63,7D
块7:5F,77,68,35
块8:62,0D,0D,50
要解密的话,需要将每个块中的四个字节分别异或对应的四个密钥字节,顺序是:第一个字节异或密钥的第一个字节,第二个字节异或密钥的第二个字节,第三个字节异或密钥的第三个字节,第四个字节异或密钥的第四个字节。
比如,第一个块(69,77,77,66)对应的密钥是来自DI=0的块,密钥字节是27,24,24,25。所以:
69 ^ 27 = 69 ^ 27 = 4E
77 ^ 24 = 77 ^ 24 = 53
77 ^24 = 53
66 ^25 = 43
所以第一个四字节块解密后的值是4E,53,53,43 → ASCII字符为 'N','S','S','C',这可能对应NSSCTF{...}中的开头。
第二个块(73,72,4F,46)对应的密钥是DI=4的块,密钥字节是27,34,34,11:
73 ^27 = 73 ^27 = 54 → 'T'
72 ^34 = 72 ^34 = 46 → 'F'
4F ^34 = 4F ^34 = 7B → '{'
46 ^11 = 57 → 'W' → 但这可能不对,因为通常flag格式是字母、数字、下划线等。可能哪里出错了?
或者可能我的密钥推导有误?
让我们仔细核对每个块的密钥:
根据之前的分析,每个四字节块对应的密钥如下:
块1(DI=0):
密钥:27,24,24,25 → 异或字节顺序为第0块的四字节中的DI+1和DI+2处的字拆解。
块2(DI=4):
密钥是3F,27,34,11 → 对应的DI+1=5,字是27 34 → 密钥的前两个字节是27,34;DI+2=6处的字是34 11 → 34,11 → 所以四个异或字节是27,34,34,11。
因此,块2的四个字节异或:
73 ^27 → 73-27=4C → 73的十六进制是0x73,0x73 ^0x27 = 0x54 → 十进制是84,对应字符'T'。
72 ^34 → 0x72 ^0x34 = 0x46 → 'F'.
4F ^34 → 0x4F ^0x34 = 0x7B → 这是左花括号 '{' 的ASCII码。
46 ^11 → 0x46 ^0x11 = 0x57 → 'W'。但flag格式中的第二个块可能应该是后续字符,比如可能此处应该出现 '}' 或其他符号,所以可能这里我的分析有问题?
或者,可能在处理密钥的时候出错了?
这时候需要仔细检查每个块的密钥是否正确。比如块2对应的四字节DATA1块是3F 27 34 11。那么:
DI=4时,四字节块是3F,27,34,11。DI+1=5,对应的字是DATA1[5]和DATA1[6],即27和34 → 小端序是0x3427。所以第一个异或字是3427,对应的两个字节是27(低字节)和34(高字节)。因此,在处理块的前两个字节时,第一个字节异或27,第二个字节异或34。同样,第二个异或字是DATA1[6]和DATA1[7],即34和11 → 0x1134 → 低字节34,高字节11 → 第三个字节异或34,第四个字节异或11。
哦,原来我之前弄反了高字节和低字节的异或顺序。这可能就是错误的原因。例如,对于第一个异或字3427(小端序),在内存中是低地址存27,高地址存34。当作为字使用时,其值是0x3427。但异或的时候,输入的第一个字会被拆解为两个字节,例如,输入的两个字节是A和B,组成字AB(小端序,即A是低字节,B是高字节),然后与3427异或后的字是 (A ^27) | (B ^34) <<8 → 所以,第一个字节异或的是27,第二个字节异或的是34。同样,第二个异或字是1134 → 低字节34,高字节11 → 第三个字节异或34,第四个字节异或11.
因此,每个四字节块的异或顺序是:
块中的第一个字节 ^ 第一个字的低字节,
第二个字节 ^ 第一个字的高字节,
第三个字节 ^ 第二个字的低字节,
第四个字节 ^ 第二个字的高字节。
这样,块2的四个字节处理如下:
73 ^27 → 0x73 ^0x27 = 0x54 → 'T'
72 ^34 → 0x72 ^0x34 = 0x46 → 'F'
4F ^34 → 0x4F ^0x34 = 0x7B → '{'
46 ^11 → 0x46 ^0x11 = 0x57 → 'W'
这似乎和flag的格式不符。可能哪里出错了?
或者可能DATA1的处理过程有误?
例如,在第七次循环时,交换的是24-27和0-3。假设在第七次循环时,交换的是索引24-27和0-3。此时,原来的0-3是26,27,24,25;24-27是2A,2B,28,00。交换后,0-3变为2A,2B,28,00,而24-27变为26,27,24,25?或者可能之前的交换步骤有误?
这可能说明我在模拟交换过程中犯了错误,导致最终的DATA1数组不正确,从而影响了密钥的计算。这时候可能需要重新检查交换步骤。
回到DO1的处理过程,第七次循环的情况:
第七次循环(i=6):
SI=24 → DI=24+4=28 → 28 >=28 → DI=0 → 交换索引24-27和0-3。
原来的DATA1在第六次循环后的状态是:
0-3:26 27 24 25
4-7:3F 27 34 11
8-11:32 33 30 00
12-15:36 37 34 35
16-19:3A 3B 38 39
20-23:3E 3F 3C 3D
24-27:2A 2B 28 00
28-31:3F 27 34 11
哦,这里可能之前的分析有错误。在第七次循环时,交换的是24-27和0-3。原来的0-3是26 27 24 25,24-27是2A 2B 28 00。交换后,0-3变为2A 2B 28 00,而24-27变为26 27 24 25。而第八次循环时,SI=28,此时DATA1的索引是28-31,即原来的3F 27 34 11。但DI=28+4=32 → 32-28=4 → 交换索引28-31和4-7.
此时,4-7的内容是3F 27 34 11。交换后,4-7变成3F 27 34 11和28-31的3F 27 34 11交换?或者可能索引28-31的原始数据是3F 27 34 11,和4-7的内容交换?
可能我之前在跟踪索引时出现了混乱,导致最终的DATA1数组错误。因此,这种情况下,手动模拟容易出错,建议用表格或代码模拟交换过程。
或者,或许我们可以换一种思路:由于ENC函数使用的是处理后的DATA1数组中的各个四字节块的中间两个字节来生成异或的密钥,那么如果我们能正确得到处理后的DATA1数组,就能正确解密。
可能的解决方法是:
- 编写一个脚本,模拟DATA1的初始值,然后执行DO1函数的交换过程,得到最终的DATA1数组。
- 根据处理后的DATA1数组,提取每个四字节块的中间两个字节,生成对应的异或密钥。
- 对DATA2的每个四字节块进行异或解密,得到原始输入。
这样会更准确,避免手动计算的错误。因此,我需要用Python来模拟DATA1的交换过程。
首先,初始化DATA1的原始数据:
data1_initial = [
0x26, 0x27, 0x24, 0x25, 0x2A, 0x2B, 0x28, 0x00,
0x2E, 0x2F, 0x2C, 0x2D, 0x32, 0x33, 0x30, 0x00,
0x36, 0x37, 0x34, 0x35, 0x3A, 0x3B, 0x38, 0x39,
0x3E, 0x3F, 0x3C, 0x3D, 0x3F, 0x27, 0x34, 0x11
]
然后模拟DO1函数的交换过程。DO1中有8次循环,每次交换两个四字节块。交换的位置由SI和DI决定。
编写一个Python函数来模拟这个过程:
def swap_blocks(data, pos1, pos2):
交换从pos1开始的四个字节和pos2开始的四个字节
temp = data[pos1:pos1+4]
data[pos1:pos1+4] = data[pos2:pos2+4]
data[pos2:pos2+4] = temp
data1 = data1_initial.copy()
si_values = []
for i in range(8):
si = i *4
di = si +4
if di >=28:
di -=28
si_values.append(si)
swap_blocks(data1, si, di)
print("After DO1 processing, DATA1 is:")
print([hex(x) for x in data1])
运行这段代码后,得到处理后的DATA1数组。然后根据这个数组,确定每个四字节块的中间两个字节作为异或密钥。
接下来,我们看看这个模拟的结果。假设运行后的data1数组为:
假设模拟后的data1数组是正确的,那么每个四字节块对应的异或密钥就可以正确提取。
假设模拟得到的数据为正确的处理后的DATA1,那么根据这个数据,我们可以得到每个块的密钥:
例如,处理后的DATA1数组分为8个四字节块:
块0: data1[0:4] → bytes0-3
块1: data1[4:8]
块2: data1[8:12]
块3: data1[12:16]
块4: data1[16:20]
块5: data1[20:24]
块6: data1[24:28]
块7: data1[28:32]
对于每个块,异或密钥是:
第一个异或字:块的第1和第2字节 → 低字节是块的第1字节,高字节是块的第2字节 → 异或输入的第一个字(两个字节)时,第一个字节异或低字节,第二个字节异or高字节。
第二个异或字:块的第2和第3字节 → 低字节是块的第2字节,高字节是块的第3字节 → 异或输入的第二个字时,第一个字节异或低字节,第二个字节异或高字节。
或者,可能每个块的处理方式是:
在ENC函数中,对于每个四字节块(从DI开始),取DI+1处的字作为第一个异或字,DI+2处的字作为第二个异或字。例如,块0的DI是0,所以:
第一个异或字是 data1[1]和data1[2] → 组成的字是 (data1[2] <<8) | data1[1]
第二个异或字是 data1[2]和data1[3] → 组成的字是 (data1[3] <<8) | data1[2]
因此,每个四字节块的四个异或密钥字节为:
byte1: data1[DI+1] → 低字节 of first word
byte2: data1[DI+2] → 高字节 of first word
byte3: data1[DI+2] → 低字节 of second word
byte4: data1[DI+3] → 高字节 of second word
例如,对于块0(DI=0):
data1[0] = 26, data1[1]=27, data1[2]=24, data1[3]=25
第一个异或字是 data1[1]和data1[2] → 0x2427 → 低字节27,高字节24 → 所以输入的第一个字节异或27,第二个字节异或24。
第二个异或字是 data1[2]和data1[3] → 0x2524 → 低字节24,高字节25 → 输入的第三个字节异或24,第四个字节异或25.
因此,四个异或密钥是27,24,24,25.
同理,其他块的密钥可以这样提取。
现在,模拟DO1后的data1数组:
运行上述Python代码,得到的data1数组的交换过程如下:
初始data1:
[0x26, 0x27, 0x24, 0x25, 0x2A, 0x2B, 0x28, 0x00, 0x2E, 0x2F, 0x2C, 0x2D, 0x32, 0x33, 0x30, 0x00, 0x36, 0x37, 0x34, 0x35, 0x3A, 0x3B, 0x38, 0x39, 0x3E, 0x3F, 0x3C, 0x3D, 0x3F, 0x27, 0x34, 0x11]
每次交换的si和di:
i=0 → si=0, di=4 → 交换块0和块1(索引0-3和4-7)
交换后,块0变为 [0x2A, 0x2B, 0x28, 0x00],块1变为 [0x26, 0x27, 0x24, 0x25]
i=1 → si=4, di=8 → 交换块1和块2(索引4-7和8-11)
交换后,块1变为 [0x2E,0x2F,0x2C,0x2D],块2变为 [0x26,0x27,0x24,0x25]
i=2 → si=8, di=12 → 交换块2和块3(索引8-11和12-15)
交换后,块2变为 [0x32,0x33,0x30,0x00],块3变为 [0x26,0x27,0x24,0x25]
i=3 → si=12, di=16 → 交换块3和块4(索引12-15和16-19)
交换后,块3变为 [0x36,0x37,0x34,0x35],块4变为 [0x26,0x27,0x24,0x25]
i=4 → si=16, di=20 → 交换块4和块5(索引16-19和20-23)
交换后,块4变为 [0x3A,0x3B,0x38,0x39],块5变为 [0x26,0x27,0x24,0x25]
i=5 → si=20, di=24 → 交换块5和块6(索引20-23和24-27)
交换后,块5变为 [0x3E,0x3F,0x3C,0x3D],块6变为 [0x26,0x27,0x24,0x25]
i=6 → si=24, di=0 → 交换块6和块0(索引24-27和0-3)
此时,块0是 [0x2A,0x2B,0x28,0x00],块6是 [0x26,0x27,0x24,0x25]
交换后,块0变为 [0x26,0x27,0x24,0x25],块6变为 [0x2A,0x2B,0x28,0x00]
i=7 → si=28 → 初始的si=28,此时di=28+4=32 → 32-28=4 → 交换块7(索引28-31)和块1(索引4-7)
块1当前是 [0x2E,0x2F,0x2C,0x2D],块7初始是 [0x3F,0x27,0x34,0x11]
交换后,块1变为 [0x3F,0x27,0x34,0x11],块7变为 [0x2E,0x2F,0x2C,0x2D]
因此,最终的data1数组如下:
块0(0-3): 0x26,0x27,0x24,0x25 → 26 27 24 25
块1(4-7): 3F 27 34 11
块2(8-11):32 33 30 00
块3(12-15):36 37 34 35
块4(16-19):3A 3B 38 39
块5(20-23):3E 3F 3C 3D
块6(24-27):2A 2B 28 00
块7(28-31):2E 2F 2C 2D
所以,模拟后的data1数组是正确的。
接下来,提取每个块的密钥:
对于每个四字节块,DI指向块起始地址,然后取:
第一个异或字:块的DI+1和DI+2 → 低字节是DI+1,高字节是DI+2.
第二个异或字:块的DI+2和DI+3 → 低字节是DI+2,高字节是DI+3.
例如:
块0(DI=0):
第一个异或字:data1[1], data1[2] → 0x27, 0x24 → 0x2427 → 低字节0x27,高字节0x24.
第二个异或字:data1[2], data1[3] → 0x24, 0x25 → 0x2524 → 低字节0x24,高字节0x25.
所以,异或密钥为:27,24,24,25.
块1(DI=4):
data1[5], data1[6] → 0x27, 0x34 → 0x3427 → 低27,高34.
data1[6], data1[7] →0x34,0x11 →0x1134 →低34,高11.
密钥:27,34,34,11.
块2(DI=8):
data1[9], data1[10] →0x33,0x30 →0x3033 →低33,高30.
data1[10], data1[11] →0x30,0x00 →0x0030 →低30,高00.
密钥:33,30,30,00.
块3(DI=12):
data1[13], data1[14] →0x37,0x34 →0x3437 →低37,高34.
data1[14], data1[15] →0x34,0x35 →0x3534 →低34,高35.
密钥:37,34,34,35.
块4(DI=16):
data1[17], data1[18] →0x3B,0x38 →0x383B →低3B,高38.
data1[18], data1[19] →0x38,0x39 →0x3938 →低38,高39.
密钥:3B,38,38,39.
块5(DI=20):
data1[21], data1[22] →0x3F,0x3C →0x3C3F →低3F,高3C.
data1[22], data1[23] →0x3C,0x3D →0x3D3C →低3C,高3D.
密钥:3F,3C,3C,3D.
块6(DI=24):
data1[25], data1[26] →0x2B,0x28 →0x282B →低2B,高28.
data1[26], data1[27] →0x28,0x00 →0x0028 →低28,高00.
密钥:2B,28,28,00.
块7(DI=28):
data1[29], data1[30] →0x2F,0x2C →0x2C2F →低2F,高2C.
data1[30], data1[31] →0x2C,0x2D →0x2D2C →低2C,高2D.
密钥:2F,2C,2C,2D.
现在,将DATA2的每个块用对应的密钥解密:
块1(密文:69,77,77,66) → 密钥:27,24,24,25:
69 ^ 27 = 0x69 ^ 0x27 = 0x4E → 'N'
77 ^ 24 = 0x77 ^ 0x24 = 0x53 → 'S'
77 ^ 24 = 0x53 → 'S'
66 ^ 25 = 0x66 ^ 0x25 = 0x43 → 'C'
块1解密结果:N S S C → 'NSSC'
块2(密文:73,72,4F,46) → 密钥:27,34,34,11:
73 ^27 = 0x54 → 'T'
72 ^34 = 0x72 ^0x34 = 0x46 → 'F'
4F ^34 = 0x4F ^0x34 = 0x7B → '{'
46 ^11 = 0x57 → 'W'
块2解密结果:T F { W → 看起来有问题,可能应该是 'T' 'F' '{' 和一些其他字符。但可能中间部分需要继续解密。
块3(密文:03,47,6F,79) → 密钥:33,30,30,00:
03 ^33 = 0x30 → '0'
47 ^30 = 0x77 → 'w'
6F ^30 = 0x5F → '_'
79 ^00 = 0x79 → 'y'
块3结果:0 w _ y → 0w_y
块4(密文:07,41,13,47) → 密钥:37,34,34,35:
07 ^37 = 0x30 → '0'
41 ^34 = 0x75 → 'u'
13 ^34 = 0x27 → '''
47 ^35 = 0x72 → 'r'
块4结果:0u'r
块5(密文:5E,67,5F,09) → 密钥:3B,38,38,39:
5E ^3B = 0x65 → 'e'
67 ^38 = 0x5F → '_'
5F ^38 = 0x67 → 'g'
09 ^39 = 0x30 → '0'
块5结果:e_g0
块6(密文:0F,58,63,7D) → 密钥:3F,3C,3C,3D:
0F ^3F = 0x30 → '0'
58 ^3C = 0x64 → 'd'
63 ^3C = 0x5F → '_'
7D ^3D = 0x40 → '@'
块6结果:0d_@
块7(密文:5F,77,68,35) → 密钥:2B,28,28,00:
5F ^2B = 0x74 → 't'
77 ^28 = 0x5F → '_'
68 ^28 = 0x40 → '@'
35 ^00 = 0x35 → '5'
块7结果:t_@5
块8(密文:62,0D,0D,50) → 密钥:2F,2C,2C,2D:
62 ^2F = 0x4D → 'M'
0D ^2C = 0x21 → '!'
0D ^2C = 0x21 → '!'
50 ^2D = 0x7D → '}'
块8结果:M!!}
将所有的解密结果拼接起来:
NSSCTF{W0w_you'r3_g00d_@t5M!!}
看起来中间有一些乱码,比如“W0w_you'r3_g00d_@t5M!!}”,这可能就是正确的flag。其中可能有一些字符被错误转换,比如 '@' 或者 '5',但可能实际正确。例如,可能解密后的结果应该是"NSSCTF{W0w_you'r3_g00d_at_th15_M!!}",但可能异或过程中某些密钥计算错误。
检查块2的第四个字节:密文46 ^11 = 0x46 ^0x11 = 0x57 → 'W'。但根据常规的flag格式,应该此处是闭合的'}',但可能中间的部分需要进一步调整。
或者,可能在处理DATA2的块分割时有误。DATA2的原始数据是:
69H,77H,77H,66H,73H,72H,4FH,46H,
03H,47H,6FH,79H,07H,41H,13H,47H,
5EH,67H,5FH,09H,0FH,58H,63H,7DH,
5FH,77H,68H,35H,62H,0DH,0DH,50H
将其分为8个四字节块:
块1: 69,77,77,66 → 解密为NSSC
块2: 73,72,4F,46 → 解密为TF{W
块3: 03,47,6F,79 → 解密为0w_y
块4:07,41,13,47 → 解密为0u'r
块5:5E,67,5F,09 → 解密为e_g0
块6:0F,58,63,7D → 解密为0d_@
块7:5F,77,68,35 → 解密为t_@5
块8:62,0D,0D,50 → 解密为M!!}
合并后的字符串是:
NSSCTF{W0w_you'r3_g00d_@t5M!!}
看起来中间部分有些问题,比如“you'r3_g00d_@t5M!!}”中的“@t5”可能应为“at_th15”,可能我的异或密钥有误。
或者,可能某些块的密钥提取错误。例如,块6的密钥是3F,3C,3C,3D:
块6的密文是0F,58,63,7D → 异或后:
0F ^3F = 0x30 → '0'
58 ^3C = 0x64 → 'd'
63 ^3C = 0x5F → '_'
7D ^3D = 0x40 → '@'
所以块6的结果是0d_@ → 可能正确应该是 "0d_}" 或其他,但这里得到的是@。但密文是7D,密钥是3D,7D ^3D =0x40,即 '@'。这可能说明正确的密钥不同,或者我的密钥提取存在错误。
再检查块6的密钥:
块5的DI是20,对应的四字节块是3E,3F,3C,3D → 所以DI=20,四字节块是3E,3F,3C,3D。
DI+1=21 → data1[21] =3F
DI+2=22 → data1[22] =3C
因此,第一个异或字是3F和3C → 0x3C3F → 低字节3F,高字节3C。所以,第一个字节异或3F,第二个字节异或3C.
第二个异或字是3C和3D → 0x3D3C → 低字节3C,高字节3D。第三个字节异或3C,第四个字节异或3D.
所以,块6的密文是0F,58,63,7D → 解密为:
0F ^3F = 0x30 → '0'
58 ^3C = 0x64 → 'd'
63 ^3C = 0x5F → '_'
7D ^3D = 0x40 → '@'
所以块6的结果是0d_@ → 看起来这部分可能有问题,但或许这就是正确的解密结果,而flag是NSSCTF{W0w_you'r3_g00d_@t5M!!},尽管看起来不太常规,但可能符合题目设定。
或者,可能我的模拟过程中哪里出错了,导致密钥不正确。例如,在处理块5的密钥时,是否应该使用不同的字节?
或者,可能我应该在解密时,将异或后的字节转换为对应的ASCII字符,无论是否可打印。比如,假设块8的第四个字节是50 ^2D =0x7D,即'}',这符合flag的结束符。
将各部分组合起来:
N S S C T F { W 0 w y o u ' r 3 g 0 0 d _ @ t 5 M ! ! }
看起来中间的“W0w_you'r3_g00d_@t5M!!}”可能存在错误。例如,块2的第四个字节' W'应该变成其他字符,比如'}'?或者可能块2的第四个字节正确的解密是某个字母,使得整个flag正确。
或者,可能我在异或过程中出现了错误。例如,块2的第四个字节密文是46,密钥是11,异或后是46^11=0x57,即 'W'。假设这里应该异或的是块1的第四个密钥字节,即25?可能我之前弄错了块的索引?
或者,可能每个四字节块的索引对应错误。例如,块1的密文是73,72,4F,46,对应的密钥是块1(DI=4)的密钥27,34,34,11。所以:
73 ^27 = 0x54 → 'T'
72 ^34 = 0x46 → 'F'
4F ^34 = 0x7B → '{'
46 ^11 = 0x57 → 'W'
所以块2解密后的结果是 T F { W,即 "TF{W",但正确的flag应该以 "NSSCTF{" 开头。哦,我之前犯了一个错误:块1的解密结果是NSSC,块2解密结果是 TF{W,所以合并后是NSSC TF{W → NSSCTF{W... 中间没有空格。所以前面的块1和块2解密后的结果合并为 "NSSCTF{W",然后后续的块可能正确。
所以,整个解密后的字符串是:
NSSCTF{W0w_you'r3_g00d_@t5M!!}
可能这个结果中的"@t5M"部分是正确的,或者可能我哪里弄错了。不过根据目前的推算,这可能就是正确的flag。需要检查是否符合题目要求。
另一个可能性是,在加密时,输入是flag,经过ENC加密后变成DATA2中的值。所以,正确的解密应该得到原始flag。假设当前解密结果是NSSCTF{W0w_you'r3_g00d_@t5M!!},而正确的flag可能更合理,比如:NSSCTF{W0w_you'r3_g00d_at_th15_M!!},其中某些字符可能被异或错误。
但根据目前的解密步骤,可能这就是正确的答案。因此,最终答案可能是:
NSSCTF{W0w_you'r3_g00d_@t5M!!}
或者,可能其中某些字节的异或密钥错误,需要重新检查。
例如,块7的密文是5F,77,68,35,密钥是2B,28,28,00:
5F ^2B = 0x74 → 't'
77 ^28 = 0x5F → '_'
68 ^28 = 0x40 → '@'
35 ^00 = 0x35 → '5'
所以解密结果为t_@5。这可能应该对应到 "th15" 中的部分。但异或后的结果是't' '_' '@' '5',可能中间存在错误。
例如,假设正确的密钥字节应该是其他的值。例如,块7的密钥是否正确?
块7对应的是DI=28,四字节块是2E,2F,2C,2D。所以:
DI+1=29 → data1[29]=2F,DI+2=2A → data1[2A]=2C → 第一个异或字是2C2F → 低字节是2F,高字节是2C.
因此,第一个字节异或2F,第二个异或2C。第二个异或字是2C和2D → 2D2C → 低字节2C,高字节2D.
所以,块8的密文是62,0D,0D,50,密钥是2F,2C,2C,2D:
62 ^2F = 0x4D → 'M'
0D ^2C = 0x21 → '!'
0D ^2C = 0x21 → '!'
50 ^2D = 0x7D → '}'
所以块8的结果是M!!}。这符合flag的结束符},但前面的M!!可能正确,或者可能应该是其他字符,比如 "_M!!}"。但根据解密结果,可能这部分是正确的。
综上所述,flag可能是:
NSSCTF{W0w_you'r3_g00d_@t5M!!}
或者需要根据题目要求调整其中的某些字符。可能其中某些字符的异或结果需要调整,但根据目前的推导,这是最可能的结果。
根据加密算法的分析,经过逆向处理后得到的flag如下:
NSSCTF{W0w_you'r3_g00d_@t5M!!}
步骤解析:
分析加密流程:
输入经过DO1处理后的DATA1数组进行块交换。
ENC函数使用处理后的DATA1对输入进行异或加密。
加密结果与DATA2比较,需逆向得到原始输入。
模拟DO1处理后的DATA1数组:
手动模拟块交换,确定最终DATA1的每个四字节块结构。
提取异或密钥:
每个四字节块取中间两个字节生成两个异或字,拆分为四个密钥字节。
解密DATA2数据:
将DATA2的每个四字节块与对应密钥异或,得到原始字符。
flag解密结果:
plaintext
复制
NSSCTF{W0w_you'r3_g00d_@t5M!!}
chatgpt
输入处理
程序首先显示欢迎信息和提示,利用 DOS 的 0Ah 功能读取用户输入。输入的数据放在 BUFFER1 中(注意:前两个字节保存缓冲区长度和实际输入长度,真正的 flag 数据从 BUFFER1+2 开始,总共 32 字节)。
DATA1 的置换(函数 DO1/DO2)
程序定义了一个 32 字节的常量数组 DATA1。在调用 ENC 之前,会先调用函数 DO1 对 DATA1 进行处理:
DO1 按 4 字节为一组循环 8 次,每次取当前位置 SI 与 SI+4(若超出 28 则减 28,实现循环)所对应的 4 字节块,调用 DO2 将这两个 4 字节块互换。
这样,原始的 DATA1 被“打乱”成一个新的排列,用作后续的密钥。
异或加密(函数 ENC)
接下来进入加密函数 ENC,对用户输入的 flag(32 字节)按块进行处理:
将 flag 分成 8 个 4 字节的块,每块按如下方式处理:
从处理后的 DATA1 中,取当前块内偏移 1 和 2 的 2 字节(16 位)作为密钥1,对该块前 2 字节进行 XOR。
再从 DATA1 中取偏移 2 和 3 的 2 字节作为密钥2,对该块后 2 字节进行 XOR。
整个过程对 8 个块分别进行,共 32 字节的异或操作。
验证
最后,程序将加密后(即经过 XOR 变换后)的 32 字节与另一个常量数组 DATA2 逐字节比较。如果完全一致,则显示“Right!”(正确),否则显示“Wrong!”。
反向思考(解密思路)
由于异或运算是可逆的,对每个 4 字节块:
明文前2字节 = 加密后前2字节 XOR (DATA1 当前块偏移1~2处 16 位数据)
明文后2字节 = 加密后后2字节 XOR (DATA1 当前块偏移2~3处 16 位数据)
因此只要知道经过 DO1 置换后的 DATA1 和常量 DATA2,就可以逐块还原原始 flag。
关键数据
假设我们按程序中对 DATA1 的交换过程计算,最终得到的 DATA1 分块(每 4 字节一组,大致结果如下):
块0(字节 0~3):26h, 27h, 24h, 25h
密钥1:[27h, 24h]
密钥2:[24h, 25h]
块1(字节 4~7):3Fh, 27h, 34h, 11h
密钥1:[27h, 34h]
密钥2:[34h, 11h]
块2(字节 8~11):32h, 33h, 30h, 00h
密钥1:[33h, 30h]
密钥2:[30h, 00h]
块3(字节 12~15):36h, 37h, 34h, 35h
密钥1:[37h, 34h]
密钥2:[34h, 35h]
块4(字节 16~19):3Ah, 3Bh, 38h, 39h
密钥1:[3Bh, 38h]
密钥2:[38h, 39h]
块5(字节 20~23):3Eh, 3Fh, 3Ch, 3Dh
密钥1:[3Fh, 3Ch]
密钥2:[3Ch, 3Dh]
块6(字节 24~27):2Ah, 2Bh, 28h, 00h
密钥1:[2Bh, 28h]
密钥2:[28h, 00h]
块7(字节 28~31):2Eh, 2Fh, 2Ch, 2Dh
密钥1:[2Fh, 2Ch]
密钥2:[2Ch, 2Dh]
常量 DATA2(加密后的 32 字节)为:
复制
编辑
69h,77h,77h,66h, 73h,72h,4Fh,46h,
03h,47h,6Fh,79h, 07h,41h,13h,47h,
5Eh,67h,5Fh,09h, 0Fh,58h,63h,7Dh,
5Fh,77h,68h,35h, 62h,0Dh,0Dh,50h
解密实例(逐块计算)
例如,对于第 1 块(块编号从 0 开始):
DATA1 块1:3F, 27, 34, 11
密钥1 = [27h, 34h]
密钥2 = [34h, 11h]
DATA2 对应块(字节 4~7):73h, 72h, 4Fh, 46h
运算如下(注意:内存中采用小端存储,异或操作对各字节独立进行):
明文字节4 = 0x73 XOR 0x27 = 0x54 → 'T'
明文字节5 = 0x72 XOR 0x34 = 0x46 → 'F'
明文字节6 = 0x4F XOR 0x34 = 0x7B → '{'
明文字节7 = 0x46 XOR 0x11 = 0x57 → 'W'
依此类推,对所有 8 个块做同样操作,就能还原整个 32 字节的 flag。
根据计算(如下各块结果):
块0 得到: "NSSC"
块1 得到: "TF{W"
块2 得到: "0w_y"
块3 得到: "0u'r"
块4 得到: "e_g0"
块5 得到: "0d_@"
块6 得到: "t_@5"
块7 得到: "M!!}"
将所有块拼接后,完整 flag 为:
swift
复制
编辑
NSSCTF{W0w_y0u're_g00d_@t_@5M!!}
(注意:这里使用了常见的 leetspeak 替换,例如 0 替代 o,@ 替代 a,整体也是一种祝贺式的信息。)
总结
加密原理:
程序先对内置密钥数组 DATA1 进行 4 字节块的循环置换,然后将 32 字节的输入分为 8 块,每块用 DATA1 中偏移 1~2 和 2~3 处各取一个 16 位密钥分别异或输入的前 2 字节和后 2 字节。最终与常量 DATA2 比对。
解密方法:
对于每个块,只需将 DATA2 中对应的 16 位数据分别与相应密钥异或,即可还原原始 flag。
还原的 flag(根据逆运算计算)为:
NSSCTF{W0w_y0u're_g00d_@t_@5M!!}
这就是整个算法的运作原理和反向解密的思路。希望对你做 CTF 题目有所帮助!
[...]最终排名128/802(只计算有分的战队)因为没有web手,一个pwn和一个流量是我队友做的reverseASM?Signin!来检验一下你的8086汇编基础吧!回顾或许能找到更好的方式。详见https://www.huoya.work/bk/index.php/archives/157/PWNHello_worldWelcome come to the world of PWN简单栈溢出有she[...]