获得80分即可得到flag,可以手动打80分,也可以模拟DES算法求解密文。
最简单的办法是直接patch掉判断条件,是程序进入flag解密的模块解密即可
一道安卓逆向题,打开软件后
反编译后找到入口secondActivity
,发现一个二维数组和处理两个数组索引变量的函数:
分析可知该迷宫中初始位置为v1[1][1]
,成功判定点为v1[11][11]
,1 是墙壁0是道路,且不能走已经走过的路;向上走会输出J
,向下走会输出V
,向左走会输出A
,向右走会输出a
,写脚本或直接看迷宫得出剩余flag。
所以最终flag为flag{VaaaVaVVaaaaaVVVVAAJJAAVVVAVVaaaaaJa}
看似是自己扫,实则是自动扫。主要逻辑在encrypt和Game里
点开Game里面有个Sweep,再进去,会发现即使输入了x和y,之后也会赋值成0,所以扫雷都是自动的。
本质上就是一个个扫过,每个点都要加上get_num的值,查看encrypt
这是自己写的一个加密逻辑,按照相反方向把利用mine还原即可
(但是没考虑好多解问题 暴力的xdm可能能得到很多个能过得答案 非常抱歉!)
这里的mine是经过扫雷之后的,与初始值不同,可以通过静态分析得到也可以通过动调dump
将 UPX 加壳后的标志位魔改为了.bss
,改标志位为 UPX0
后用工具脱壳即可。
迷宫:
SCHEME
['S', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '.', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '.', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', 'R', '.', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '.', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', 'R', '.', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '.', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#']
['#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', 'R', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '.', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '.', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '.', 'R', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '.', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '.', 'R', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '.', '.', '.', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', 'N', '.']
通过分析可知是一个双层迷宫 且通过R进行上下层的交换
且需要满足一定的步数。
enc = [1, 2, 1, 2, 1, 0, 1, 2, 1, 2, 1, 0, 1, 1, 2, 2, 1, 0, 3, 2, 1, 2, 2, 0, 1, 1, 2, 2, 1, 0, 3, 2, 1, 1, 2, 3]
flag = ""
for i in range(9):
tmp = enc[i*4:i*4+4]
flag += chr((tmp[0] << 6) + (tmp[1] << 4) + (tmp[2] << 2) + tmp[3])
print(flag)
动调可以发现就是对输入字符串中字符与后一个字符异或后与给定的数据对比,稍微解一下混淆后逆推即可:
from base64 import b64decode
enc = [
81, 61, 107, 41, 120, 45, 99, 54, 100, 10, 126,
53, 123, 51, 106, 90, 57, 11, 69, 60, 113, 41,
107, 91, 3, 49, 1, 49, 80, 42, 100, 2, 96,
52, 122, 28, 69, 118, 63, 15, 106, 4, 111, 7,
97, 48, 13, 48
]
for i in range(len(enc)-1, -1, -1):
enc[i] = enc[i] ^ enc[i-1]
flag = "".join(chr(i) for i in enc)
print(b64decode(flag))
分析变量的行为以及所在偏移,在IDA里构建结构体在反编译代码中修改类型,a1修改为vm*
类型接下来无论是调试还是静态分析都相对容易了
根据每个case看出对应的操作类型
1 LOAD
2 MOVE
3 ADD
4 SUB
5 MUL
6 DIV
7 XOR
8 JMP
9 INPUT
10 CMP
11 JNE
12 LOADM
13 END
可以发现就是对输入的 flag 逐个异或 0xE9,异或回去即可
对 Linux 下 rand() 进行逆向。 逆向一下算法,得到 32 轮输出后能大致还原 state,然后后续精度在右移 15 位后差不多也能满足,实在不行可以进行两次逆推 state,使用z3解出状态矩阵,并进行后续推导:
# !/usr/bin/env python
from time import sleep
from pwn import process, ELF, remote
from z3 import *
import restate = [BitVec('s%d' % i, 32) for i in range(31)]
fptr, rptr = 4, 1
def rand():
global fptr, rptr
state[fptr] += state[rptr]
state[fptr] &= 0xffffffff
val = state[fptr]
result = (val >> 1) & 0x7fffffff
fptr += 1
fptr %= 31
rptr += 1
rptr %= 31
return result & 0xffffffff
def getrand():
return rand() >> 15
def rand1():
global fptr, rptr
ans[fptr] += ans[rptr]
ans[fptr] &= 0xffffffff
val = ans[fptr]
result = (val >> 1) & 0x7fffffff
fptr += 1
fptr %= 31
rptr += 1
rptr %= 31
return result & 0xffffffff
def getrand1():
return rand1() >> 15
so = Solver()
sh = process('./randomize')
i = 0
sleep(2)
sh.recv()
while i < 38:
sh.sendline(b'5')
sleep(0.1)
x = sh.recv().decode()
x = int(re.findall(r'\d+',x)[0])
print(x)
so.add(getrand() == x)
i += 1
so.check()
ans = [0]*31
model=so.model()
for i in model:
ans[int(i.name()[1:])] = model[i].as_long()
print(ans)
fptr, rptr = 4, 1
for i in range(38):
print(getrand1())
while True:
try:
x = getrand1()
# print(x)
sh.sendline(str(x).encode())
sleep(0.1)
s=sh.recv().decode()
print(s)
except:
break
这是一个独特的虚拟机,每个寄存器、ROM、RAM 并行执行。首先,为每个寄存器、代码段(ROM)、读写数据段(RAM)分配一个线程。它们会从第0层开始执行到下一个状态,会有几个进程等待数据准备。比如R1寄存器,它的下一个状态会由R类指令和I类指令决定。根据不同的指令,会查询不同数据的准备状态,比如添加ra,rb,状态传递函数会先判断第一个操作数是否为r1,如果是,则等待数据准备就绪rb 的先前状态,然后执行计算。
指令集如下
R [00000]: add ra, rb R [00001]: sub ra, rb R [00010]: mov ra, rb R [00011]: xor ra, rb
I [01000]: add ra, imm I [01001]: sub ra, imm I [01010]: mov ra, imm I [01011]: lsh ra, imm I [01100]: rsh ra, imm
B [01111]: jne ra, imm
R [10000]: ld ra, [rb] R [10001]: st ra, [rb]
Z [11100]: write r0 Z [11101]: read r0 E [11111]: exit
r0~r9:32bit
pc:32bit
ROM:1024 Byte
RAM:200 Byte
每条指令是按位分割的,也就是说,它不拘泥于整字节的长度,而会不断的按位append,得到的最终的内容再按字节进行存储。 反汇编,代码如下(用movi似乎不太规范,可以想成li的作用)
# 0x0~0x18: Input
# 0x18~0x28: Key
# 0x28~0x30: String
# 0x30~0x34: Iter
# 0x40~0x58: Cipher
# 0x58~0x70: Cmpmovi r1,24
movi r2,0
input:
read r0
st r0,[r2]
subi r1,1
addi r2,1
jne r1,input
blocks:
xor r2,r2
movi r3,29687
lsh r3,16
movi r4,5513
xor r3,r4
movi r1,48
movi r2,4
ld r1,[r1]
lsh r1,3
xor r4,r4
loadv0:
ld r5,[r1]
lsh r4,8
xor r4,r5
addi r1,1
subi r2,1
jne r2,loadv0
movi r2,4
xor r5,r5
loadv1:
ld r6,[r1]
lsh r5,8
xor r5,r6
addi r1,1
subi r2,1
jne r2,loadv1
# r4=v0,r5=v1,r2=sum,r3=delta
movi r1,32
round:
add r2,r3
mov r6,r5
lsh r6,4
movi r0,4
xor r7,r7
loadk0:
addi r0,23
lsh r7,8
ld r8,[r0]
xor r7,r8
subi r0,24
jne r0,loadk0 # r7=key[0]
add r6,r7
mov r7,r2
add r7,r5
xor r6,r7
mov r7,r5
rsh r7,5
movi r0,4
xor r8,r8
loadk1:
addi r0,27
lsh r8,8
ld r9,[r0]
xor r8,r9
subi r0,28
jne r0,loadk1 # r8=key[1]
add r7,r8
xor r6,r7
add r4,r6
mov r6,r4
lsh r6,4
movi r0,4
xor r7,r7
loadk2:
addi r0,31
lsh r7,8
ld r8,[r0]
xor r7,r8
subi r0,32
jne r0,loadk2 # r7=key[2]
add r6,r7
mov r7,r2
add r7,r4
xor r6,r7
mov r7,r4
rsh r7,5
movi r0,4
xor r8,r8
loadk3:
addi r0,35
lsh r8,8
ld r9,[r0]
xor r8,r9
subi r0,36
jne r0,loadk3 # r8=key[3]
add r7,r8
xor r6,r7
add r5,r6
subi r1,1
jne r1,round
movi r1,48
ld r1,[r1]
lsh r1,3
addi r1,64
mov r2,r1
addi r2,4
movi r3,4
store:
subi r3,1
st r4,[r1]
st r5,[r2]
addi r1,1
addi r2,1
rsh r4,8
rsh r5,8
jne r3,store
movi r2,48
ld r1,[r2]
addi r1,1
st r1,[r2]
subi r1,3
jne r1,blocks
movi r1,64
movi r2,88
movi r3,24
cmp:
ld r4,[r1]
ld r5,[r2]
xor r4,r5
jne r4,fail
addi r1,1
addi r2,1
subi r3,1
jnz r3,cmp
movi r1,40
ld r0,[r1]
write r0
addi r1,1
ld r0,[r1]
write r0
addi r1,1
ld r0,[r1]
write r0
addi r1,1
ld r0,[r1]
write r0
exit
fail:
movi r1,44
ld r0,[r1]
write r0
addi r1,1
ld r0,[r1]
write r0
addi r1,1
ld r0,[r1]
write r0
addi r1,1
ld r0,[r1]
write r0
exit
分析汇编可知,是一个更换了delta的tea加密,key和cipher均已在代码中填进内存,找到key和cipher,进行解密即可
from ctypes import *def decrypt(v, k):
v0, v1 = c_uint32(v[0]), c_uint32(v[1])
delta = 0x73f71589
k0, k1, k2, k3 = k[0], k[1], k[2], k[3]
total = c_uint32(delta * 32)
for i in range(32):
v1.value -= ((v0.value<<4) + k2) ^ (v0.value + total.value) ^ ((v0.value>>5) + k3)
v0.value -= ((v1.value<<4) + k0) ^ (v1.value + total.value) ^ ((v1.value>>5) + k1)
total.value -= delta
return v0.value, v1.value
from libnum import n2s
# test
if __name__ == "__main__":
value = [0xba6a1271, 0xf71fe442, 0xa89e704b, 0x5619f593, 0xe1645e87, 0x5be57baf]
key = [0x1, 0x5, 0x8, 0xf]
flag=''
for i in range(0,len(value),2):
res = decrypt(value[i:i+2], key)
flag+=(n2s(res[0])+n2s(res[1])).decode()
print(f'Flag is BUAACTF{{{flag}}}')
文案 | 高丰奕
排版 | 张 涔
审核 | 潘卓成