N1CTF Junior

Last updated on February 7, 2024 pm

Guess 风云四海

Guess

先是初始化一些内存,然后进入虚拟机,call_func_table就是调用虚拟机,第一个参数和第二个参数互相对应,func_table_1就对应opcode的内存,直接看func_table_1

会先调用table_3的虚拟机

table3主要是逻辑运算,101000是会被拆分为三个参数,对应不同内存

分析出此加密对应python代码如下

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
from ctypes import c_uint64


mem = [0x0, 0x2bf4933a1cb3b58e, 0x7443445b968d2dda, 0x24d81331ab93926b, 0xedb9a55a2706e7f1, 0x3b29a6ca60076d30,
0xf02a50dd94c6794c, 0x788397af131ae243, 0x4592198be422b199, 0xd0e49650aafe2a0b, 0xc3f2e8a8b76b4b5e,
0xf4680e5b38eb45f0, 0x9737184f4b5fc4fa, 0x3b6f79dd0fe28a91, 0xa1aadc7345ec7461, 0x2e704e6598ad8b3c,
0x16771f41b5c1641b, 123, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0]


def add(argv, argc):
argv[0] += 2
argv[1 + ((argc >> 16) & 0x3f)] = c_uint64(argv[((argc >> 8) & 63) + 1] + argv[((argc & 0x3F) + 1)]).value


def sub(argv, argc):
argv[0] += 2
argv[1 + ((argc >> 16) & 0x3f)] = c_uint64(argv[((argc & 0x3F) + 1)] - argv[((argc >> 8) & 63) + 1]).value


def xor(argv, argc):
argv[0] += 2
argv[1 + ((argc >> 16) & 0x3f)] = c_uint64(argv[((argc >> 8) & 63) + 1] ^ argv[((argc & 0x3F) + 1)]).value


def sub_5950(argv, argc):
argv[0] += 2
if argv[((argc >> 8) & 63) + 1] == argv[((argc & 0x3F) + 1)]:
argv[1 + ((argc >> 16) & 0x3f)] = 1
else:
argv[1 + ((argc >> 16) & 0x3f)] = 0


def sub_59e0(argv, argc):
argv[0] += 2
if c_uint64(argv[(argc & 63) + 1]).value > c_uint64(argv[(((argc >> 8) & 0x3F) + 1)]).value:
argv[1 + ((argc >> 16) & 0x3f)] = 1
else:
argv[1 + ((argc >> 16) & 0x3f)] = 0


def sub_5a90(argv, argc):
result = argc & 0x3f
argv[0] += 2
if argv[result + 1] != 0:
argv[0] = argc >> 8


def func_table_3():
add(mem, 0x101000)
add(mem, 0x101001)
sub(mem, 0x101002)
add(mem, 0x101003)
add(mem, 0x101004)
xor(mem, 0x101005)
sub(mem, 0x101006)
add(mem, 0x101007)
xor(mem, 0x101008)
sub(mem, 0x101009)
add(mem, 0x10100a)
sub(mem, 0x10100b)
add(mem, 0x10100c)
sub(mem, 0x10100d)
sub(mem, 0x10100e)
add(mem, 0x10100f)

print(mem[17])

func_table_3()

运算完后与密文比较,输出对应字符串

其对应解密脚本

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
from ctypes import *

mem = [0x2bf4933a1cb3b58e, 0x7443445b968d2dda, 0x24d81331ab93926b, 0xedb9a55a2706e7f1, 0x3b29a6ca60076d30,
0xf02a50dd94c6794c, 0x788397af131ae243, 0x4592198be422b199, 0xd0e49650aafe2a0b, 0xc3f2e8a8b76b4b5e,
0xf4680e5b38eb45f0, 0x9737184f4b5fc4fa, 0x3b6f79dd0fe28a91, 0xa1aadc7345ec7461, 0x2e704e6598ad8b3c,
0x16771f41b5c1641b, 123, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0]


# ipt = num - ipt
#
def decrypt(ipt: c_uint64):
ipt.value -= mem[15]
ipt.value = mem[14] - ipt.value
ipt.value = mem[13] - ipt.value
ipt.value -= mem[12]
ipt.value = mem[11] - ipt.value
ipt.value -= mem[10]
ipt.value = mem[9] - ipt.value
ipt.value ^= mem[8]
ipt.value -= mem[7]
ipt.value = mem[6] - ipt.value
ipt.value ^= mem[5]
ipt.value -= mem[4]
ipt.value -= mem[3]
ipt.value = mem[2] - ipt.value
ipt.value -= mem[1]
ipt.value -= mem[0]

print(ipt.value)

decrypt(c_uint64(0xCEE5B6FED2BBC1E3))

奇怪的是并没有输出最终的flag,然后发现在密文比较完后还有一处奇怪的比较

调试发现是猜数字次数与0xa0比较,也就是在输入正确答案之前要先错误输入9次,最终才会正确解密

ctfpunk{1s_a_funny_t0y_vm? (^_^)}

风云四海

迷宫题,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
# 55F89D9FD194
ea = 0x55F89D9EB1CE
end = 0x55F89DA06188

path = dict()

addr = ea
while 1:
node = []
start = hex(addr).upper().replace('0X','loc_')
if idc.GetDisasm(addr).startswith('inc'):
while 1:
if idc.GetDisasm(addr).startswith('cmp'):
node.append(idc.print_operand(addr,1))
addr = idc.next_head(addr)
node.append(idc.print_operand(addr,0))

addr = idc.next_head(addr)
if idc.GetDisasm(addr).startswith('hlt'):
break
if idc.GetDisasm(addr).startswith('int'):
break

path[start] = node
addr = idc.next_head(addr)
if addr > end:
break

import sys

# 打开一个文件以写入输出
with open("data.py", "w") as f:
# 保存原来的标准输出
original_stdout = sys.stdout
# 重定向标准输出到文件
sys.stdout = f
print('path=',end='')
# 在这里执行需要输出的代码
print(path)

# 还原标准输出
sys.stdout = original_stdout

判断逻辑是长度要求最短,并且会根据路径进行运算,达到终点后会触发int 3中断,进行判断是否路径是否符合条件

截屏2024-02-07 19.28.14

奈何这个有向图太大了,DFS需要跑很久,而BFS只能跑最短,但是跑不出所有的路径,卡死在这目前

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
import queue

from present import graph

start = 'loc_55F89D9EFE09'
end = 'loc_55F89D9FD194'

q = queue.Queue()
q.put(start)


def maze_walk_bfs():
visited = set()
father = {'loc_55F89D9EFE09': None}
while not q.empty():
curr = q.get()
nxz = graph[curr]
for dirc in nxz:
if not dirc.startswith('loc_'):
continue

if dirc not in visited:
visited.add(dirc)
father[dirc] = curr
q.put(dirc)

if dirc == end:
p = [dirc]
while father[dirc] is not None:
visited.discard(dirc)
dirc = father[dirc]
p.append(dirc)

return p[::-1]


v = set()
a = maze_walk_bfs()

print(a)
for i in range(len(a) - 1):
node = graph[a[i]]
print(chr(int((node[node.index(a[i + 1]) - 1]).replace('h', ''), base=16)), end='')


不会图论算法,诶


N1CTF Junior
http://example.com/2024/02/05/Nu1L_junior/
Author
yring
Posted on
February 5, 2024
Licensed under