Simple_File_System

这道题当时卡在这里就是不知道存放的数据在哪里,结果后来发现就是在image.flag文件之中(被自己蠢哭了!!!!)

数据提取

将程序运行一遍,将加密之后的文本的内容写入一个文件之中,再将这个文件放入010之中,提取前5个字节的内容,也就是 *CTF{ 加密之后的结果,然后再将image.flag文件放入ida之中,搜素这5个字节,将这段数据提取出来,这段数据就是flag加密之后的结果了,分析加密过程,逆得结果

解密过程

分析代码就能找到对flag文件加密的关键代码

异或的对象 0xDEEDBEEF

image-20220418125729850

解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
src = [0x00,0xD2,0xFC,0xD8,0xA2,0xDA,0xBA,0x9E,0x9C,0x26,0xF8,0xF6,0xB4,0xCE,0x3C,0xCC,0x96,0x88,0x98,0x34,0x82,0xDE,0x80,0x36,0x8A,0xD8,0xC0,0xF0,0x38,0xAE,0x40]
for i in range(len(src)):
src[i] = ((src[i]<<5)&0xff) |((src[i]>>3)&0xff)
src[i]^=0xDE
src[i] = ((src[i]<<4)&0xff) |((src[i]>>4)&0xff)
src[i]^=0xED
src[i] = ((src[i] << 3) & 0xff) | ((src[i] >> 5) & 0xff)
src[i]^=0xBE
src[i] = ((src[i]<<2)&0xff) |((src[i]>>6)&0xff)
src[i]^=0xEF
src[i] = ((src[i]<<1)&0xff) |((src[i]>>7)&0xff)
print(chr(src[i]),end="")
*CTF{Gwed9VQpM4Lanf0kEj1oFJR6}

NaCl

这道题可以通过动调的方式搞清楚加密的逻辑,然后来解密,但是这里是通过去 “花指令”的方式来达到目的,使得加密的过程能通过F5反编译得到

去花指令

首先通过字符串定位到程序的关键代码处

花指令的分析

其中的特殊的代码解释,这个保存到r15-8这个地址的函数,是程序接下来运行的函数

image-20220421195649369

动态调试能够发现很多部分函数的结束的地方都会有这样一段代码,这段代码会在r15所指向的内存之中保存一个数组的内存地址,以及一个函数的地址,最后跳转到一个函数

image-20220421200405124

image-20220421200416106

继续向下动调,发现跳转到的函数的结尾,从r15-8这个位置之中取出存放的函数的地址,并且跳转到这个函数(jmp rdi),因为跳转到的这个函数是上一个函数接下来要运行的程序,所以相当于return了

取出跳转回来的函数地址

image-20220421202150714

向r15-8之中存放的地址是这个函数接下来要运行的函数( 跳转到另外一个函数之后,跳转回来的地址)

image-20220421202125732

并且在每段程序之中align 10h的数据是并不运行的,是花指令,我们需要nop掉这部分的代码

idaPython去花指令

使用idapython 实现的效果

  • 将r15-8之中放入函数的过程,直接nop掉(我觉得也可以不nop掉这部分的程序,我也将不nop和nop掉着部分的汇编都进行了尝试,发现都是可以的),将jmp到的函数,改为call对应的函数(call => 0xE8)
  • 然后将跳转到的这个函数之中后面为了跳转回来的指令修改了成ret指令
  • 将align 10h 之中的无效指令nop掉
  • 将 jmp rdi 的指令转换成ret (我觉得对r15操作的指令就每必要修改了)

idaPython之中经常会用到的APT

  • **idc.print_insn_mnem(ea)**来获取每条指令的操作符,判断操作符是否为 call 或者 jmp

  • print(“0x%x %s” % (line, idc.GetDisasm(line))) 将对应地址的汇编得到

  • dism_addr = list(idautils.FuncItems(func)) 实际上返回的是一个迭代器,但是我们将它强制转换为 list 类型。这个 list 以一种连续的方式存储着所有指令的起始地址。

  • idc.next_head(line) 和 idc.prev_head(line) 获取附近指令的地址

  • idc.get_operand_type(ea,n) 获取操作数类型 ea 是一个地址,n 是一个索引。

    操作数类型的种类

    image-20220421225837234

  • ida_ida.inf_get_min_ea()ida_ida.inf_get_max_ea() 获取可执行文件的最大地址和最小地址,我们遍历了所有的函数和指令。

  • 用来打补丁的 ida_bytes.patch_byte(ea, value)ida_bytes.patch_word(ea, value)ida_bytes.patch_dword(ea, value)ida_bytes.patch_qword(ea, value) 这里面的 value 是替换的机器码

  • idc.GetOperandValue(ea, n) 获取操作数引用的地址(取值

  • idc.print_operand(here(),0) 获取操作数 如rdx(取符号

  • idc.print_insn_mnem(here()) 获取操作符 如 mov

  • idc.GetDisasm(ea) 获取汇编 如mov rdx, rdi

  • idc.NextHead(ea)- 返回下一条指令(无效指令算一条)

  • idc.PrevHead(ea)- 返回上一条指令

  • idc.NextAddr(ea)- 返回下一地址

  • idc.PrevAddr(ea)- 返回上一地址

需要处理的3个位置的汇编代码

第一种:

1
2
3
4
SFI:000000000808040C                 lea     r15, [r15-8]
SFI:0000000008080410 lea r12, sub_8080420
SFI:0000000008080417 mov [r15], r12
SFI:000000000808041A jmp loc_80802A0

第二种:

1
2
3
4
5
SFI:00000000080802E0                 lea     r15, [r15+8]    ; 取地址
SFI:00000000080802E4 mov edi, [r15-8] ; 获取跳转的函数
SFI:00000000080802E8 and edi, 0FFFFFFE0h
SFI:00000000080802EB lea rdi, [r13+rdi+0]
SFI:00000000080802F0 jmp rdi

第三种nop掉的指令(第一种汇编代码之后的代码)

1
上面jmp之后的指令align 20h 中的数据全部nop掉

nop掉jmp前面的汇编代码的效果是:

image-20220422220410076

不nop掉jmp前面那部分的汇编代码的效果:

image-20220422220437967

两种方式中主要的加密函数都被保存下来了,只是中间数据的展示方式是不一样的

idaPython脚本:

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
import ida_ida
import idc
import ida_bytes
start = ida_ida.inf_get_min_ea()
end = ida_ida.inf_get_max_ea()
# 两种需要处理的汇编代码的地方
part1 = ["lea r15,","lea r12,","mov [r15],","jmp loc_"]
part2 = ["lea r15,","mov edi,","and edi,","lea rdi","jmp rdi"]
# 用p来存放当前指向的汇编指令的位置
p = start
# 存放连续取得的五条指令的地址
current_instruct = [0]*5
# 转为call指令
def nop(start,end):
while start<end:
ida_bytes.patch_byte(start, 0x90)
start+=1

def part1_Operate():
# nop(current_instruct[0],current_instruct[3])
temStr = idc.print_operand(current_instruct[1], 1)[4:]
nopEndAddr = int("0x"+temStr,16)
nopStartAddre = idc.next_head(current_instruct[3])
# nopEndAddr = idc.next_head(nopStartAddre)
print(nopEndAddr)
print(nopStartAddre)
nop(nopStartAddre,nopEndAddr)
ida_bytes.patch_byte(current_instruct[3], 0xE8)
print("part1 Successful!")

# 转为ret指令
def part2_Operate():
# nop(current_instruct[0],current_instruct[4])
ida_bytes.patch_byte(current_instruct[4], 0x90)
ida_bytes.patch_byte(current_instruct[4]+1,0xC3)
print("part2 Successful!")

print("start")
while p<=end:
current_instruct[0] = p
current_instruct[1] = idc.next_head(current_instruct[0])
current_instruct[2] = idc.next_head(current_instruct[1])
current_instruct[3] = idc.next_head(current_instruct[2])
current_instruct[4] = idc.next_head(current_instruct[3])
p = current_instruct[1]
for i in range(len(part1)):
if idc.GetDisasm(current_instruct[i])[0:len(part1[i])] != part1[i]:
break
else:
p = idc.next_head(current_instruct[3])
# print("将jmp指令转换成call指令,并且将剩下的align中的指令nop掉")
part1_Operate()
for i in range(len(part2)):
if idc.GetDisasm(current_instruct[i])[0:len(part2[i])] != part2[i]:
break
else:
p=idc.next_head(current_instruct[4])
print(p)
part2_Operate()
# print("将jmp指令转化成ret指令来返回")
print("finish")

xtae解密

这个地方的xtea的delta变了,轮数变了

加密过程分析

8个32位的数据,分成了4次加密,每次加密2个32位的数据,加密的轮数依次是 2 4 8 16

image-20220425194620856

xtea的数据提取

密钥:

1
3020100h, 7060504h, 0B0A0908h, 0F0E0D0Ch

密文:

1
2
3
4
5
0x66, 0xC2, 0xF5, 0xFD, 0x86, 0x82, 0x32, 0x7A, 0x04, 0x40, 
0x94, 0xCE, 0xDC, 0x8A, 0xE0, 0x5D, 0x0A, 0xBD, 0xE4, 0xA6,
0xDC, 0xAD, 0xCA, 0x16, 0x0C, 0x6F, 0xCD, 0x13, 0x36, 0xD9,
0x75, 0x1A
0xfdf5c266,0x7a328286,0xce944004,0x5de08adc,0xa6e4bd0a,0x16caaddc,0x13cd6f0c,0x1a75d936

4次加密的轮数 2 4 8 16

xtea解密脚本
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
#include <stdio.h>
#include <stdint.h>


void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], delta=0x10325476, sum=delta*num_rounds;
for (i=0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0]=v0; v[1]=v1;
}

int main()
{
uint32_t v[3]={0xfdf5c266,0x7a328286,0x0};
uint32_t v1[3]={0xce944004,0x5de08adc,0x0};
uint32_t v2[3]={0xa6e4bd0a,0x16caaddc,0x0};
uint32_t v3[3]={0x13cd6f0c,0x1a75d936,0x0};
uint32_t const k[4]={0X3020100,0X7060504,0X0B0A0908,0X0F0E0D0C};
unsigned int r=32;//num_rounds建议取值为32
decipher(2, v, k);
decipher(4, v1, k);
decipher(8, v2, k);
decipher(16,v3, k);

printf("0x%x,0x%x\n",v[0], v[1]);
printf("0x%x,0x%x\n",v1[0], v1[1]);
printf("0x%x,0x%x\n",v2[0], v2[1]);
printf("0x%x,0x%x\n",v3[0], v3[1]);
return 0;
}

xtea解密之后的4个数据

1
2
3
4
0xe71f5179,0xb55f9204
0x722d4a3a,0x238e8b65
0x4385e0f2,0x6703757a
0xaabe9be3,0x4de4253b

异或操作解密

异或加密的过程分析

异或加密的函数

  • 将异或的数据直接提取出来(动调)

  • v5->tailData4 的 值等于这个循环完成之后 v5->headData4 的 值,所以逆向的时候异或现在的head的这个值,这样就能够得到之前的head的值,然后将新的head的值给head,把之前的head的值给tail

  • 要异或的数据的值是要倒着来的

  • 将tailData的数据作为前面的4个字节 将headData的数据作为后面的4个字节 这样组成结果的8个字节

image-20220423225006570

异或数据提取

通过动调的方式获得异或的对象(每4个字节为一组来取用),并且在解密的时候解密使用的异或的对象是倒着来取的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x0F, 0x0E, 
0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x1B, 0xE8, 0x3F, 0xCD,
0x77, 0x54, 0xC4, 0xD7, 0x36, 0x92, 0x3E, 0x9F, 0x87, 0xF1,
0x07, 0x01, 0x81, 0xCB, 0x93, 0xF9, 0x6C, 0x16, 0x74, 0xBF,
0x27, 0x84, 0x19, 0xDA, 0xFF, 0xAB, 0x05, 0x1A, 0xE4, 0xE5,
0x07, 0x93, 0x45, 0x0E, 0x8B, 0xCB, 0xF5, 0xF7, 0x6D, 0x30,
0x97, 0x01, 0x30, 0xAD, 0x56, 0xB0, 0x86, 0xAA, 0xBA, 0x63,
0x92, 0x44, 0x1B, 0x40, 0xA4, 0x3F, 0x17, 0xF9, 0x41, 0x1E,
0x7D, 0x1E, 0xCB, 0xC6, 0x7A, 0x0D, 0xEB, 0x18, 0x00, 0x48,
0xEC, 0xD4, 0x2B, 0xF9, 0x86, 0xB4, 0xF3, 0xF9, 0x37, 0x87,
0x25, 0x3D, 0x5E, 0x76, 0x37, 0x35, 0x3D, 0xDB, 0x2B, 0x55,
0x44, 0xEE, 0x4C, 0xC9, 0xD0, 0x11, 0xCB, 0x5B, 0x60, 0x9B,
0xB3, 0x98, 0x3B, 0x90, 0xA3, 0xEE, 0xC2, 0x24, 0xA2, 0x10,
0x6E, 0x89, 0xC0, 0xF0, 0x47, 0x22, 0xAA, 0x5C, 0x4E, 0xB8,
0xF0, 0x04, 0x2C, 0x8D, 0x2C, 0x84, 0xC7, 0x3B, 0x06, 0xD6,
0x50, 0x1A, 0x7C, 0x91, 0xA1, 0x49, 0x0C, 0xB5, 0x1C, 0x7E,
0x26, 0xB8, 0x27, 0xFC, 0xBC, 0xDF, 0xDD, 0x5F, 0x04, 0xC4,
0x0F, 0xDE, 0x07, 0x09, 0xB3, 0xB2
解密脚本

逆向的时候需要注意的是 使用的src中的数是逆着取用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
src = [0x04050607,0x00010203,0x0c0d0e0f,0x08090a0b,0xcd3fe81b,0xd7c45477,0x9f3e9236,0x0107f187,0xf993cb81,0xbf74166c,0xda198427,0x1a05abff,0x9307e5e4,0xcb8b0e45,0x306df7f5,0xad300197,0xaa86b056,0x449263ba,0x3fa4401b,0x1e41f917,0xc6cb1e7d,0x18eb0d7a,0xd4ec4800,0xb486f92b,0x8737f9f3,0x765e3d25,0xdb3d3537,0xee44552b,0x11d0c94c,0x9b605bcb,0x903b98b3,0x24c2eea3,0x896e10a2,0x2247f0c0,0xb84e5caa,0x8d2c04f0,0x3bc7842c,0x1a50d606,0x49a1917c,0x7e1cb50c,0xfc27b826,0x5fdddfbc,0xde0fc404,0xb2b30907]
headData = 0xe71f5179
tailData = 0xb55f9204
encDate = [0xe71f5179,0xb55f9204,0x722d4a3a,0x238e8b65,0x4385e0f2,0x6703757a,0xaabe9be3,0x4de4253b]
tem = 0
for j in range(0,len(encDate),2):
headData = encDate[j]
tailData = encDate[j+1]
for i in range(44):
tem = headData
v6 = (((headData & 0x7fffffff) << 1) & 0xffffffff) | ((headData >> (32 - 1)) & 0x01)
v7 = ((((headData & 0x00ffffff) << 8) & 0xffffffff) | ((headData >> (32 - 8)) & 0xff)) & v6
headData = ((((headData & 0x3fffffff) << 2) & 0xffffffff) | ((headData >> (32 - 2)) & 0x03)) ^ v7
headData = headData ^ tailData ^ src[43 - i]
tailData = tem
print(hex(headData),end=",")
print(hex(tailData),end=",")
0x4a496f62,0x6d4d3770,0x504f3652,0x73435451,0x386b4645,0x30672d4c,0x6976424e,0x78685975

分析代码知道tailData是低位的数据 headData是高位的数据

image-20220425184851589

并且这四个字节的排列顺序是:将input的数据的低字节放到的高字节,高字节放到了低字节

image-20220425185217259

所以最后数据输出:

1
2
3
4
5
6
7
encode = [0x4a496f62,0x6d4d3770,0x504f3652,0x73435451,0x386b4645,0x30672d4c,0x6976424e,0x78685975]
for i in range(0,len(encode),2):
for j in range(2):
while encode[i+1-j]:
print(chr((encode[i+1-j]>>24)&0xff),end="")
encode[i+1-j] = (encode[i+1-j]<<8)&0xffffffff
得到最后的结果:mM7pJIobsCTQPO6R0g-L8kFExhYuivBN

*CTF{mM7pJIobsCTQPO6R0g-L8kFExhYuivBN}