以下是本文涉及到的关键词:

  • 架构:x86, mips, JVM
  • 文件类型:exe, elf, apk
  • 考点:angr, maze, base64, .NET, Unity游戏, sm4, rc4, 混淆, SMC, upx

PS. 用到的 python 脚本运行环境除特殊标注外均为 python2如果内容有误或者图片缺失,欢迎联系我修正


[FlareOn2]very_success | x86,exe,angr

ida 打开,start 直接调用了 sub_401000,判断程序应该是用汇编写的。sub_401000 由于栈不平衡反编译失败,打开栈指针,在导致分析错误的一行 Alt + k,将 sp 值设置为 0:

FlareOn2very_success

再 F5:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
BOOL __usercall sub_401000@<eax>(int a1@<ebp>)
{
HANDLE stdin; // ST1C_4
BOOL result; // eax
HANDLE stdout; // [esp-Ch] [ebp-Ch]
signed int v4; // [esp-8h] [ebp-8h]
int v5; // [esp-4h] [ebp-4h]
int retaddr; // [esp+0h] [ebp+0h]

v5 = a1;
stdin = GetStdHandle(0xFFFFFFF6);
stdout = GetStdHandle(0xFFFFFFF5);
WriteFile(stdout, aYouCrushedThat, 0x43u, (LPDWORD)&v4, 0);
ReadFile(stdin, &input, 0x32u, (LPDWORD)&v4, 0);
if ( sub_401084((int)&v4, retaddr, (char *)&input, v4) )
result = WriteFile(stdout, aYouAreSuccess, 0x11u, (LPDWORD)&v4, 0);
else
result = WriteFile(stdout, aYouAreFailure, 0x11u, (LPDWORD)&v4, 0);
return result;
}

只有一个检验函数 sub_401084,这个函数的汇编指令里 5 个我有 4 个不认识(我很骄傲)。动调后发现检查的思路是每次取来输入的一个字符(输入有 37 个字符),经过一堆运算后,倒着从 0x401108 开始向低地址逐一比较。正好 start 函数的 call sub_401000 下面有 37 个字节:

FlareOn2very_success

具体咋算的我也不想弄明白了,上 angr 跑吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# python3
import angr
import claripy

project = angr.Project("very_success", auto_load_libs = False)

state = project.factory.blank_state(addr = 0x4010A2)
flag = claripy.BVS("flag", 8 * 37)
inputAddr = 0x402159
state.memory.store(inputAddr, flag)
state.regs.ebx = 0
state.regs.ecx = 37
state.regs.esi = inputAddr
state.regs.edi = 0x401108
for c in flag.chop(8):
state.solver.add(state.solver.And(c <= '~', c >= ' '))

sm = project.factory.simulation_manager(state)
sm.explore(find = 0x4010D5)

solution = sm.found[0].solver.eval(flag, cast_to = bytes)
print(solution.decode())

[SCTF2019]babyre | x86,elf,maze,base64,sm4

main 函数有几处花指令,把它们 nop 掉以后反编译。逻辑很清晰,一共有三关,分别是:

  • 5 * 5 * 5 的三维迷宫
  • 将输入 base64 解码,得到的结果应该是 sctf_9102
  • 对输入进行类似 sm4 加密算法的编码,结果与目标数组比较

三维迷宫提取出来:

SCTF2019babyre

每层 5 * 5,共 5 层,入口在 s,出口在 #,. 可以通行,* 不能通行。每次行动有 6 个方向可选,同一层内:上(w)、下(s)、左(a)、右(d),不同层间:下一层(x)、上一层(y)。走出来最短路径为 ddwwxxssxaxwwaasasyywwdd

第二个部分的输入应为 base64(“sctf_9102”) = c2N0Zl85MTAy

第三个部分需要分析 sub_400FFA,脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
table = "D6 90 E9 FE CC E1 3D B7 16 B6 14 C2 28 FB 2C 05 2B 67 9A 76 2A BE 04 C3 AA 44 13 26 49 86 06 99 9C 42 50 F4 91 EF 98 7A 33 54 0B 43 ED CF AC 62 E4 B3 1C A9 C9 08 E8 95 80 DF 94 FA 75 8F 3F A6 47 07 A7 FC F3 73 17 BA 83 59 3C 19 E6 85 4F A8 68 6B 81 B2 71 64 DA 8B F8 EB 0F 4B 70 56 9D 35 1E 24 0E 5E 63 58 D1 A2 25 22 7C 3B 01 21 78 87 D4 00 46 57 9F D3 27 52 4C 36 02 E7 A0 C4 C8 9E EA BF 8A D2 40 C7 38 B5 A3 F7 F2 CE F9 61 15 A1 E0 AE 5D A4 9B 34 1A 55 AD 93 32 30 F5 8C B1 E3 1D F6 E2 2E 82 66 CA 60 C0 29 23 AB 0D 53 4E 6F D5 DB 37 45 DE FD 8E 2F 03 FF 6A 72 6D 6C 5B 51 8D 1B AF 92 BB DD BC 7F 11 D9 5C 41 1F 10 5A D8 0A C1 31 88 A5 CD 7B BD 2D 74 D0 12 B8 E5 B4 B0 89 69 97 4A 0C 96 77 7E 65 B9 F1 09 C5 6E C6 84 18 F0 7D EC 3A DC 4D 20 79 EE 5F 3E D7 CB 39 48 C6 BA B1 A3 50 33 AA 56 97 91 7D 67 DC 22 70 B2".split(' ')
table = [int(i, 16) for i in table]

ROL = lambda x, y : ((x << y) | (x >> (32 - y))) & 0xFFFFFFFF
ROR = lambda x, y : ((x >> y) | (x << (32 - y))) & 0xFFFFFFFF

def sub_40143B(a1, a2, a3, a4):
tmp = a2 ^ a3 ^ a4
v1 = table[tmp & 0xFF] | table[tmp >> 8 & 0xFF] << 8 | table[tmp >> 16 & 0xFF] << 16 | table[tmp >> 24 & 0xFF] << 24
return a1 ^ ROL(v1, 12) ^ ROL(v1, 8) ^ ROR(v1, 2) ^ ROR(v1, 6)

target = [0] * 26
target.append(0xBE040680)
target.append(0xC5AF7647)
target.append(0x9FCC401F)
target.append(0xD8BF92EF)
for i in range(25, -1, -1):
target[i] = sub_40143B(target[i+4], target[i+1], target[i+2], target[i+3])

for i in range(4):
print hex(target[i])[2:-1].decode('hex')[::-1]
# fl4g_is_s0_ug1y!

[QCTF2018]Xman-babymips | mips,elf

mips 架构下的 elf,mips 静态分析(反编译)的软件就那几个(IDA7.5, ghidra, jeb-mips, retdec),用 IDA7.5 的话事情将会非常简单,毕竟氪金的力量不容小觑。下面我使用 免费 的 ghidra 来做这道题

ghidra 打开程序,Functions 里没找见 main 函数,那就从 entry 开始看,上来就调用 SUB_00400694:

QCTF2018Xmanbabymips

SUB_00400694 中调用 __libc_start_main 时,第一个参数就是 main 函数,这里是 FUN_004009a8:

QCTF2018Xmanbabymips

然后就是对符号的还原:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void main(void)
{
int isEqual;
int idx;
byte input [36];

setbuf(stdout,(char *)0x0);
setbuf(stdin,(char *)0x0);
printf("Give me your flag:");
scanf("%32s",input);
idx = 0;
while (idx < 0x20) {
input[idx] = input[idx] ^ 0x20U - (char)idx;
idx = idx + 1;
}
isEqual = strncmp((char *)input,_fdata,5);
if (isEqual == 0) {
FUN_004007f0(input);
}
else {
puts("Wrong");
}
return;
}

逻辑很简单,先对长度为 32 的输入进行一个异或变换,验证前 5 个字节,然后调用 FUN_004007f0 验证剩余的 27 个字节:

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
void FUN_004007f0(char *input)
{
size_t inputLen;
int isEqual;
uint idx;

idx = 5;
while (inputLen = strlen(input), idx < inputLen) {
if ((idx & 1) == 0) {
input[idx] = (byte)((uint)((int)input[idx] << 0x1a) >> 0x18) | input[idx] >> 6;
}
else {
input[idx] = input[idx] >> 2 | (byte)((uint)((int)input[idx] << 0x1e) >> 0x18);
}
idx = idx + 1;
}
isEqual = strncmp(input + 5,PTR_BYTE_00410d04,0x1b);
if (isEqual == 0) {
puts("Right!");
}
else {
puts("Wrong!");
}
return;
}

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
flag = ''
func = lambda b, i : chr(b ^ 0x20 - i)

t1 = map(ord, "Q|j{g")
for i in range(5):
flag += func(t1[i], i)

t2 = "52 fd 16 a4 89 bd 92 80 13 41 54 a0 8d 45 18 81 de fc 95 f0 16 79 1a 15 5b 75 1f".split(' ')
t2 = [int(i, 16) for i in t2]
for i in range(len(t2)):
idx = 5 + i
if idx % 2 == 0:
high_2 = (t2[i] & 0b11) << 6
low_6 = t2[i] >> 2 & 0b111111
flag += func(high_2 | low_6, idx)
else:
high_6 = (t2[i] & 0b111111) << 2
low_2 = t2[i] >> 6 & 0b11
flag += func(high_6 | low_2, idx)
print flag

[SCTF2019]Who is he | x86,exe,.NET,Unity游戏

dnSpy 打开 Assembly-CSharp.dll,在 TestClick 类中找到 OnClick 方法:

SCTF2019Whoishe

分析一下 Decrypt 方法,就是一个简单的 DES_CBC 解密,密文、key、iv 都能找到,所以上脚本:

1
2
3
4
5
6
7
8
9
10
11
# python3
import base64
from Crypto.Cipher import DES

encryptData = "1Tsy0ZGotyMinSpxqYzVBWnfMdUcqCMLu0MA+22Jnp+MNwLHvYuFToxRQr0c+ONZc6Q7L0EAmzbycqobZHh4H23U4WDTNmmXwusW4E+SZjygsntGkO2sGA=="
e = base64.b64decode(encryptData)
keyiv = b"1\x002\x003\x004\x00"

d = DES.new(keyiv, DES.MODE_CBC, keyiv)
print(d.decrypt(e).decode("utf-16"))
# He_P1ay_Basketball_Very_We11!Hahahahaha!

在程序中提交发现不对。于是尝试 patch Assembly-CSharp.dll,发现不论怎么改(比如直接弹出 flag、输入框的内容改为 flag),程序都没有变动,于是猜测按钮部分的功能在另外的 dll 中进行了实现。来到 Data/Managed 目录下,按修改日期对所有 dll 进行排序:

SCTF2019Whoishe

最新被修改的只有四个,用 CE 进行附加,选择 Mono -> 分析 mono,把这 4 个模块都展开,发现 UnityEngine.UmbraModule 模块十分可疑,成员变量和方法与 TestClick 类基本相同:

SCTF2019Whoishe

在类 UnityEngine.UmbraModule.Main 处右键,选择 Fields -> Add Static Field Address ,回到 CE 主界面,激活 lua 脚本,浏览 encryptKey 附近内存,发现真正的 key 和 iv:

SCTF2019Whoishe

在类方法 Decrypt 处右键,选择 Jit,在函数入口打个断点,点击按钮,程序断下来,第一个参数在 rdx 中,该参数就是成员变量 EncryptData:

SCTF2019Whoishe

提取出来替换上面脚本中的 encryptData 和 keyiv,得到真正的 flag:

1
2
3
4
5
6
7
8
9
import base64
from Crypto.Cipher import DES

encryptData = "xZWDZaKEhWNMCbiGYPBIlY3+arozO9zonwrYLiVL4njSez2RYM2WwsGnsnjCDnHs7N43aFvNE54noSadP9F8eEpvTs5QPG+KL0TDE/40nbU="
e = base64.b64decode(encryptData)
keyiv = b"t\x00e\x00s\x00t\x00"

d = DES.new(keyiv, DES.MODE_CBC, keyiv)
print(d.decrypt(e).decode("utf-16"))

[FlareOn1]Javascrap | misc

走错片场的一道题。php 代码在图片 flare-on.png 的末尾,提取出来:

1
2
3
4
5
6
7
8
9
10
<?php
$terms = array("M", "Z", "]", "p", "\\", "w", "f", "1", "v", "<", "a", "Q", "z", " ", "s", "m", "+", "E", "D", "g", "W", "\"", "q", "y", "T", "V", "n", "S", "X", ")", "9", "C", "P", "r", "&", "\'", "!", "x", "G", ":", "2", "~", "O", "h", "u", "U", "@", ";", "H", "3", "F", "6", "b", "L", ">", "^", ",", ".", "l", "$", "d", "`", "%", "N", "*", "[", "0", "}", "J", "-", "5", "_", "A", "=", "{", "k", "o", "7", "#", "i", "I", "Y", "(", "j", "/", "?", "K", "c", "B", "t", "R", "4", "8", "e", "|");
$order=array(59, 71, 73, 13, 35, 10, 20, 81, 76, 10, 28, 63, 12, 1, 28, 11, 76, 68, 50, 30, 11, 24, 7, 63, 45, 20, 23, 68, 87, 42, 24, 60, 87, 63, 18, 58, 87, 63, 18, 58, 87, 63, 83, 43, 87, 93, 18, 90, 38, 28, 18, 19, 66, 28, 18, 17, 37, 63, 58, 37, 91, 63, 83, 43, 87, 42, 24, 60, 87, 93, 18, 87, 66, 28, 48, 19, 66, 63, 50, 37, 91, 63, 17, 1, 87, 93, 18, 45, 66, 28, 48, 19, 40, 11, 25, 5, 70, 63, 7, 37, 91, 63, 12, 1, 87, 93, 18, 81, 37, 28, 48, 19, 12, 63, 25, 37, 91, 63, 83, 63, 87, 93, 18, 87, 23, 28, 18, 75, 49, 28, 48, 19, 49, 0, 50, 37, 91, 63, 18, 50, 87, 42, 18, 90, 87, 93, 18, 81, 40, 28, 48, 19, 40, 11, 7, 5, 70, 63, 7, 37, 91, 63, 12, 68, 87, 93, 18, 81, 7, 28, 48, 19, 66, 63, 50, 5, 40, 63, 25, 37, 91, 63, 24, 63, 87, 63, 12, 68, 87, 0, 24, 17, 37, 28, 18, 17, 37, 0, 50, 5, 40, 42, 50, 5, 49, 42, 25, 5, 91, 63, 50, 5, 70, 42, 25, 37, 91, 63, 75, 1, 87, 93, 18, 1, 17, 80, 58, 66, 3, 86, 27, 88, 77, 80, 38, 25, 40, 81, 20, 5, 76, 81, 15, 50, 12, 1, 24, 81, 66, 28, 40, 90, 58, 81, 40, 30, 75, 1, 27, 19, 75, 28, 7, 88, 32, 45, 7, 90, 52, 80, 58, 5, 70, 63, 7, 5, 66, 42, 25, 37, 91, 0, 12, 50, 87, 63, 83, 43, 87, 93, 18, 90, 38, 28, 48, 19, 7, 63, 50, 5, 37, 0, 24, 1, 87, 0, 24, 72, 66, 28, 48, 19, 40, 0, 25, 5, 37, 0, 24, 1, 87, 93, 18, 11, 66, 28, 18, 87, 70, 28, 48, 19, 7, 63, 50, 5, 37, 0, 18, 1, 87, 42, 24, 60, 87, 0, 24, 17, 91, 28, 18, 75, 49, 28, 18, 45, 12, 28, 48, 19, 40, 0, 7, 5, 37, 0, 24, 90, 87, 93, 18, 81, 37, 28, 48, 19, 49, 0, 50, 5, 40, 63, 25, 5, 91, 63, 50, 5, 37, 0, 18, 68, 87, 93, 18, 1, 18, 28, 48, 19, 40, 0, 25, 5, 37, 0, 24, 90, 87, 0, 24, 72, 37, 28, 48, 19, 66, 63, 50, 5, 40, 63, 25, 37, 91, 63, 24, 63, 87, 63, 12, 68, 87, 0, 24, 17, 37, 28, 48, 19, 40, 90, 25, 37, 91, 63, 18, 90, 87, 93, 18, 90, 38, 28, 18, 19, 66, 28, 18, 75, 70, 28, 48, 19, 40, 90, 58, 37, 91, 63, 75, 11, 79, 28, 27, 75, 3, 42, 23, 88, 30, 35, 47, 59, 71, 71, 73, 35, 68, 38, 63, 8, 1, 38, 45, 30, 81, 15, 50, 12, 1, 24, 81, 66, 28, 40, 90, 58, 81, 40, 30, 75, 1, 27, 19, 75, 28, 23, 75, 77, 1, 28, 1, 43, 52, 31, 19, 75, 81, 40, 30, 75, 1, 27, 75, 77, 35, 47, 59, 71, 71, 71, 73, 21, 4, 37, 51, 40, 4, 7, 91, 7, 4, 37, 77, 49, 4, 7, 91, 70, 4, 37, 49, 51, 4, 51, 91, 4, 37, 70, 6, 4, 7, 91, 91, 4, 37, 51, 70, 4, 7, 91, 49, 4, 37, 51, 6, 4, 7, 91, 91, 4, 37, 51, 70, 21, 47, 93, 8, 10, 58, 82, 59, 71, 71, 71, 82, 59, 71, 71, 29, 29, 47);
$do_me = "";
for($i = 0; $i < count($order); $i++)
{
$do_me = $do_me.$terms[$order[$i]];
}
eval($do_me);
?>

直接 echo 输出一下 $do_me,得到:

1
2
3
4
$_= 'aWYoaXNzZXQoJF9QT1NUWyJcOTdcNDlcNDlcNjhceDRGXDg0XDExNlx4NjhcOTdceDc0XHg0NFx4NEZceDU0XHg2QVw5N1x4NzZceDYxXHgzNVx4NjNceDcyXDk3XHg3MFx4NDFcODRceDY2XHg2Q1w5N1x4NzJceDY1XHg0NFw2NVx4NTNcNzJcMTExXDExMFw2OFw3OVw4NFw5OVx4NkZceDZEIl0pKSB7IGV2YWwoYmFzZTY0X2RlY29kZSgkX1BPU1RbIlw5N1w0OVx4MzFcNjhceDRGXHg1NFwxMTZcMTA0XHg2MVwxMTZceDQ0XDc5XHg1NFwxMDZcOTdcMTE4XDk3XDUzXHg2M1wxMTRceDYxXHg3MFw2NVw4NFwxMDJceDZDXHg2MVwxMTRcMTAxXHg0NFw2NVx4NTNcNzJcMTExXHg2RVx4NDRceDRGXDg0XDk5XHg2Rlx4NkQiXSkpOyB9';
$__='JGNvZGU9YmFzZTY0X2RlY29kZSgkXyk7ZXZhbCgkY29kZSk7'; // $code=base64_decode($_); eval($code);
$___="\x62\141\x73\145\x36\64\x5f\144\x65\143\x6f\144\x65"; // base64_decode
eval($___($__));

于是对 $_ 进行base64_decode,得到 \97\49\49\68\x4F\84\116\x68\97\x74\x44\x4F\x54\x6A\97\x76\x61\x35\x63\x72\97\x70\x41\84\x66\x6C\97\x72\x65\x44\65\x53\72\111\110\68\79\84\99\x6F\x6D,\ 后面跟十进制数,\x 后面跟十六进制数。转换一下再输出,得到 a11DOTthatDOTjava5crapATflareDASHonDOTcom。按照 flareon 比赛 flag 的尿性,DOT 要换成 .,AT 换成 @,DASH 换成 -,得到最终的 flag


[2019红帽杯]Snake | x86,exe,.NET,Unity游戏

dnSpy 打开 Assembly-CSharp.dll,一顿找,没找到和 flag 有关的方法。翻阅游戏目录的时候倒是发现了一个 Plugins 目录,里面有一个 Interface.dll,正好在 Assembly-CSharp.dll 里也有导入:

2019红帽杯Snake

ida 打开 Interface.dll,shift + F12,查找 “You win! flag is “ 字符串的交叉引用,来到函数 GameObject,该函数正是被 Assembly-CSharp.dll 导过去使用的。回到 dnSpy 查找对于 GameObject 的引用:

2019红帽杯Snake

调用时传入游戏角色的 x,y 坐标,也就是说输入为两个 int,而 GameObject 只用到了 x,并且只有当 x 在 [0, 0x63] 范围内时,才会判断是否输出 flag:

2019红帽杯Snake

考虑写个 C++ 脚本直接调 GameObject 来爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <Windows.h>
using namespace std;
typedef void* (WINAPI* FGameObject)(int x, int y);

void main()
{
HMODULE hDll = LoadLibraryA("Interface.dll");
if (hDll) {
FGameObject GameObject = (FGameObject)GetProcAddress(hDll, "GameObject");
if (GameObject) {
for (int i = 0; i <= 0x63; i++)
{
cout << "Testing: " << i << endl;
GameObject(i, 0);
}
}
}
else {
cout << "Failed to load dll, plz check dll path." << endl;
}
}

x 为 19 时得到 flag:

2019红帽杯Snake

PS. 当然用 python 也能调 dll 的方法,甚至更方便,不过效率肯定是不如 C++ 的:

1
2
3
4
5
6
from ctypes import *

dll = cdll.LoadLibrary("Interface.dll")
for i in range(0, 0x64):
print("Testing: " + str(i))
dll.GameObject(i)

[MRCTF2020]EasyCpp | x86,elf

分析 main 函数:

MRCTF2020EasyCpp

其中匿名函数 check 中用到的 ans 数组由 init_array 中的 sub_I_keys 函数初始化。其余没啥好说的,上脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import re
import hashlib

target = ["=zqE=z=z=z", "=lzzE", "=ll=T=s=s=E", "=zATT", "=s=s=s=E=E=E", "=EOll=E", "=lE=T=E=E=E", "=EsE=s=z", "=AT=lE=ll"]
dic = {'O':'0', 'l':'1', 'z':'2', 'E':'3', 'A':'4', 's':'5', 'G':'6', 'T':'7', 'B':'8', 'q':'9', '=':' '}
repl = lambda obj : dic[obj.group(0)]

s = ''
for t in target:
val = 1
tmp = re.sub(".", repl, t).strip().split(' ')
for i in tmp:
val *= int(i)
s += str(val ^ 1)
print hashlib.md5(s).hexdigest().upper()

[GUET-CTF2019]encrypt | x86,elf,rc4,base64

程序逻辑:

GUETCTF2019encrypt

首先对输入做 rc4 加密,之后对加密结果进行变种 base64 编码:

GUETCTF2019encrypt

从上图可以发现编码表变成了 ascii 码从 ‘=’ 开始的字符数组,解题脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
import base64
from Crypto.Cipher import ARC4

func = lambda x : '=' if x == '=' else table[ord(x) - ord('=')]
table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="

target = r"Z`TzzTrD|fQP[_VVL|yneURyUmFklVJgLasJroZpHRxIUlH\vZE="
target = map(func, target)
encoded = base64.b64decode(''.join(target))

key = "\x10\x20\x30\x30\x20\x20\x10\x40"
rc4 = ARC4.new(key)
print rc4.decrypt(encoded)

[GKCTF2020]EzMachine | x86,exe,vm虚拟机

遇到虚拟机一般分三个阶段,第一阶段 angr 开冲。没跑出来的话进第二阶段 —— 乖乖分析 指令/数据寄存器、内存、指令 等要素都被抽象到哪些变量中去了,然后跟着指令流走一遍理解虚拟机干了啥,写脚本。第三个阶段就是关掉 ida,世界清静……

1
flag{Such_A_EZVM}

jadx 打开,com.example.blink 包的 r2d2 类里有一串 base64:

BSidesSF2019blink

解码成图片得到 flag


[NPUCTF2020]Baby Obfuscation | x86,exe,混淆

首先根据函数行为还原 FOX1 ~ FOX5 函数的符号:

NPUCTF2020BabyObfuscation

分别是 gcd(最大公因数),nor(或非),and(a & b),sub(a - b),pow(a 的 b 次方)。先看判断结果是否正确的部分:

NPUCTF2020BabyObfuscation

得到两个信息,输入为 15 个字符、输入变换后的结果与 v31 数组有关。再来看对输入变换的部分:

NPUCTF2020BabyObfuscation

也是 for 循环逐字节处理,其中需要 if 判断后才对 v31 进行操作的部分有 4 个,分别称其为 op1 ~ 4,使用 angr 来测试每个字符经过了哪些 op:

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

count = 0
project = angr.Project("./attachment.exe")

@project.hook(addr = 0x401712)
def hook0(state):
# modify variable "inputLen"
state.mem[state.regs.rbp+0x1470].dword = 15

@project.hook(0x40174C)
def hook1(state):
global count
print("char %d:" % count)
count += 1

@project.hook(0x4017C6)
def hook2(state):
print("op1")

@project.hook(0x40189D)
def hook3(state):
print("op2")

@project.hook(0x401994)
def hook4(state):
print("op3")

@project.hook(0x401A48)
def hook5(state):
print("op4")

@project.hook(0x401B51)
def hook6(state):
project.terminate_execution()

project.execute()

从输出可以得知每个字符都只经过了 op1 和 op3 变换,并且最后会乘上 10。解题脚本:

1
2
3
4
5
6
7
8
9
10
key = [2, 3, 4, 5]
AOX6 = [0, 7801, 7801, 8501, 5901, 8001, 6401, 11501, 4601, 9801, 9601, 11701, 5301, 9701, 10801, 12501, 0]

flag = ''
for j in range(1, len(AOX6) - 1, 1):
tmp = (AOX6[j] - 1) // 100
tmp ^= key[(j - 1) % 4] # op3
tmp += key[(j - 1) % 4] # op1
flag += chr(tmp)
print flag

[ACTF新生赛2020]Splendid_MineCraft | x86,exe,SMC

shift + F12 查找字符串 “Wrong\n” 的交叉引用,来到 main 函数 sub_401080。在检查输入长度是否为 26,开头是否为 “ACTF{“,最后一个字符是否为 “}” 后,通过 strtok 将输入分为 3 段,猜测 flag 形似 ACTF{aaaaaa_bbbbbb_cccccc}。之后调用 loc_4051D8 进行第一次 SMC,使用 python ida 脚本还原 0x4051FC 处的代码:

1
2
3
4
5
6
import idc

addr = 0x4051FC
for i in range(0x152):
b = get_bytes(addr + i, 1)
idc.PatchByte(addr + i, ord(b) ^ 0x72)

阅读 0x4051FC 处的汇编代码后,得到 flag 的第一段:

1
2
3
4
5
6
s1 = ''
v2 = [87, 101, 108, 99, 111, 109, 101, 32]
v5 = [51, 64, 49, 98, 59, 98]
for i in range(6):
s1 += chr((v2[i+1] ^ v5[i]) + 35)
print s1

之后返回 main 函数进行第二次 SMC,脚本还原:

1
2
3
4
5
6
import idc

addr = 0x405018
for i in range(256, 496):
b = get_bytes(addr + i, 1)
idc.PatchByte(addr + i, ord(b) ^ 0x20)

同理阅读第二段汇编后得到 flag 第二段:

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
s2 = ''
table = [0xF6, 0xA3, 0x5B, 0x9D, 0xE0, 0x95, 0x98, 0x68, 0x8C, 0x65,
0xBB, 0x76, 0x89, 0xD4, 0x09, 0xFD, 0xF3, 0x5C, 0x3C, 0x4C,
0x36, 0x8E, 0x4D, 0xC4, 0x80, 0x44, 0xD6, 0xA9, 0x01, 0x32,
0x77, 0x29, 0x90, 0xBC, 0xC0, 0xA8, 0xD8, 0xF9, 0xE1, 0x1D,
0xE4, 0x67, 0x7D, 0x2A, 0x2C, 0x59, 0x9E, 0x3D, 0x7A, 0x34,
0x11, 0x43, 0x74, 0xD1, 0x62, 0x60, 0x02, 0x4B, 0xAE, 0x99,
0x57, 0xC6, 0x73, 0xB0, 0x33, 0x18, 0x2B, 0xFE, 0xB9, 0x85,
0xB6, 0xD9, 0xDE, 0x7B, 0xCF, 0x4F, 0xB3, 0xD5, 0x08, 0x7C,
0x0A, 0x71, 0x12, 0x06, 0x37, 0xFF, 0x7F, 0xB7, 0x46, 0x42,
0x25, 0xC9, 0xD0, 0x50, 0x52, 0xCE, 0xBD, 0x6C, 0xE5, 0x6F,
0xA5, 0x15, 0xED, 0x64, 0xF0, 0x23, 0x35, 0xE7, 0x0C, 0x61,
0xA4, 0xD7, 0x51, 0x75, 0x9A, 0xF2, 0x1E, 0xEB, 0x58, 0xF1,
0x94, 0xC3, 0x2F, 0x56, 0xF7, 0xE6, 0x86, 0x47, 0xFB, 0x83,
0x5E, 0xCC, 0x21, 0x4A, 0x24, 0x07, 0x1C, 0x8A, 0x5A, 0x17,
0x1B, 0xDA, 0xEC, 0x38, 0x0E, 0x7E, 0xB4, 0x48, 0x88, 0xF4,
0xB8, 0x27, 0x91, 0x00, 0x13, 0x97, 0xBE, 0x53, 0xC2, 0xE8,
0xEA, 0x1A, 0xE9, 0x2D, 0x14, 0x0B, 0xBF, 0xB5, 0x40, 0x79,
0xD2, 0x3E, 0x19, 0x5D, 0xF8, 0x69, 0x39, 0x5F, 0xDB, 0xFA,
0xB2, 0x8B, 0x6E, 0xA2, 0xDF, 0x16, 0xE2, 0x63, 0xB1, 0x20,
0xCB, 0xBA, 0xEE, 0x8D, 0xAA, 0xC8, 0xC7, 0xC5, 0x05, 0x66,
0x6D, 0x3A, 0x45, 0x72, 0x0D, 0xCA, 0x84, 0x4E, 0xF5, 0x31,
0x6B, 0x92, 0xDC, 0xDD, 0x9C, 0x3F, 0x55, 0x96, 0xA1, 0x9F,
0xCD, 0x9B, 0xE3, 0xA0, 0xA7, 0xFC, 0xC1, 0x78, 0x10, 0x2E,
0x82, 0x8F, 0x30, 0x54, 0x04, 0xAC, 0x41, 0x93, 0xD3, 0x3B,
0xEF, 0x03, 0x81, 0x70, 0xA6, 0x1F, 0x22, 0x26, 0x28, 0x6A,
0xAB, 0x87, 0xAD, 0x49, 0x0F, 0xAF]
target = [0x30, 4, 4, 3, 0x30, 0x63, 0x90]
for i in range(6):
idx = table.index(target[i])
s2 += chr(idx ^ (0x83 + i))
print s2

再次返回 main 函数,比较第三段:

ACTF新生赛2020Splendid_MineCraft

拼接起来得到 flag


[De1CTF2019]Re_Sign | x86,exe,upx,base64

upx 提供的工具脱不了壳,手动脱壳,OEP 在 0x405012,查找字符串 “Success” 的交叉引用来到函数 sub_401000:

De1CTF2019Re_Sign

在 0x4016F5 下断点可以获取改变后的 base 编码表([ebp-0x20]):

De1CTF2019Re_Sign

然后通过动调获得目标数组(元素为各字符相较于正常编码表的偏移,下标从 1 开始),解题脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import base64
ori = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
new = "0123456789QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm+/="
target = [8, 0x3B, 1, 0x20, 7, 0x34, 9, 0x1F, 0x18, 0x24,
0x13, 3, 0x10, 0x38, 9, 0x1B, 8, 0x34, 0x13, 2,
8, 0x22, 0x12, 3, 5, 6, 0x12, 3, 0xF, 0x22, 0x12,
0x17, 8, 1, 0x29, 0x22, 6, 0x24, 0x32, 0x24, 0xF,
0x1F, 0x2B, 0x24, 3, 0x15, 0x41, 0x41]
f1 = lambda x : ori[x - 1]
tmp = map(f1, target)

f2 = lambda x : ori[new.index(x)]
bflag = map(f2, tmp)
print base64.b64decode(''.join(bflag))

[watevrCTF 2019]Timeout | x86,elf

generate 函数里 puts 输出的就是 flag

1
2
a = [119, 97, 116, 101, 118, 114, 123, 51, 110, 99, 114, 121, 116, 105, 111, 110, 95, 105, 115, 95, 111, 118, 101, 114, 114, 97, 116, 101, 100, 95, 121, 111, 117, 116, 117, 98, 101, 46, 99, 111, 109, 47, 119, 97, 116, 99, 104, 63, 118, 61, 79, 80, 102, 48, 89, 98, 88, 113, 68, 109, 48, 125]
print ''.join(map(chr, a))

[CISCN2018]2ex | mips,elf,base64

简单的替换编码表的变种 base64

1
2
3
4
5
6
7
8
9
import base64

new = '@,.1fgvw#`/2ehux$~"3dity%_;4cjsz^+{5bkrA&=}6alqB*-[70mpC()]89noD'
old = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
out = "_r-+_Cl5;vgq_pdme7#7eC0="
func = lambda x : '=' if x == '=' else old[new.index(x)]

bflag = ''.join(map(func, out))
print base64.b64decode(bflag)