0x00 前置知识

这道题使用了Oat++

参考:https://blog.csdn.net/qq_44519484/article/details/123250415

据说题目用到了IDA的bindiff插件,没用过,正好试试

最后也用到了一个python库:Z3

这玩意好像只要会用就行,没必要深究(

0x01 解

查壳

ELF,上linux

运行结果如上

拖IDA吧

函数全是地址,看来没有标志

先查查字符串吧

字符串查到很多otapp的字样,otapp库相关问题参考前置知识

从这里可以知道程序使用了otapp进行编译,为了让反编译后的程序可读性增加,可以利用IDA的bindiff插件进行处理

Bindiff插件参考:https://cloud.tencent.com/developer/article/2175714

自行编写使用otapp库的程序,并进行编译

由于重点是把库给展示出来,因此可以随便一点

参考:https://blog.csdn.net/qq_44519484/article/details/123250415

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(helloworld)

set(CMAKE_CXX_STANDARD 11)
set(SOURCE_FILES main.cpp handler.h)

# 查找 oatpp 依赖
find_package(oatpp REQUIRED)

add_executable(${PROJECT_NAME} ${SOURCE_FILES})

# 将目标文件与库文件进行链接
target_link_libraries(${PROJECT_NAME} oatpp::oatpp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// handler.h
// handler.h
#ifndef HANDLER_H
#define HANDLER_H

#include "oatpp/web/server/HttpRequestHandler.hpp"

#define O_UNUSED(x) (void)x;

// 自定义请求处理程序
class Handler : public oatpp::web::server::HttpRequestHandler
{
public:
// 处理传入的请求,并返回响应
std::shared_ptr<OutgoingResponse> handle(const std::shared_ptr<IncomingRequest>& request) override {
O_UNUSED(request);

return ResponseFactory::createResponse(Status::CODE_200, "Hello, World!");
}
};

#endif // HANDLER_H

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
// main.cpp
#include "oatpp/web/server/HttpConnectionHandler.hpp"
#include "oatpp/network/tcp/server/ConnectionProvider.hpp"
#include "oatpp/network/Server.hpp"
#include "handler.h"

void run()
{
// 为 HTTP 请求创建路由器
auto router = oatpp::web::server::HttpRouter::createShared();

// 路由 GET - "/hello" 请求到处理程序
router->route("GET", "/hello", std::make_shared<Handler>());

// 创建 HTTP 连接处理程序
auto connectionHandler = oatpp::web::server::HttpConnectionHandler::createShared(router);

// 创建 TCP 连接提供者
auto connectionProvider = oatpp::network::tcp::server::ConnectionProvider::createShared({"0.0.0.0", 8080, oatpp::network::Address::IP_4});

// 创建服务器,它接受提供的 TCP 连接并将其传递给 HTTP 连接处理程序
oatpp::network::Server server(connectionProvider, connectionHandler);

// 打印服务器端口
OATPP_LOGI("MyApp", "Server running on port %s", connectionProvider->getProperty("port").getData());

// 运行服务器
server.run();
}

int main()
{
// 初始化 oatpp 环境
oatpp::base::Environment::init();

// 运行应用
run();

// 销毁 oatpp 环境
oatpp::base::Environment::destroy();

return 0;
}
1
2
3
4
5
mkdir build
cd build
cmake ..
make
./helloworld

编译后运行结果如下:

将自己编译的程序拖入IDA,由于是自己编译的,所以所有的函数都有标志,即函数名都写出来了

通过查找字串找到”Hello, World!”字串,并用交叉引用找到调用此字串的位置

从自己编译的程序可以看出,这个createResponse应该就是回显了,所以找到这个函数一般就能找到加密的位置了

保存自己编译的程序为IDA数据库(i64)

接下来打开题目程序,Ctrl+6将保存的IDA数据库导入

在Bindiff的Matched Function窗口可以找到所有匹配的函数

可以在此窗口进行搜索

搜索createResponse

经过对比,第二个(即在题中的地址为0043A348的函数)与自编译的得到的createResponse函数相同(通过输入变量判断)

因此查找这个函数的交叉引用

都看看,结果发现这个函数sub_40617E特别像加密函数

前面一个大数组,然后一个计算,怎么看都像加密吧(

这里大概就是两个数组,类似矩阵乘法,然后最后再遍历一遍,有个IF判断是否是0,如果是0就跳到正确的分支,使用Z3可以直接求解出来

根据Z3要求的输入,这个v16就是密文,而这个v17就是key

因此上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
from z3 import *
key=[0x00000017, 0x0000000D, 0x00000004, 0x00000030, 0x00000029, 0x00000029, 0x0000002A, 0x00000021, 0x0000001E, 0x00000003, 0x00000045, 0x00000001, 0x0000000D, 0x0000002D, 0x00000029, 0x00000040, 0x00000008, 0x00000050, 0x0000000F, 0x0000002A, 0x00000038, 0x00000013, 0x0000003E, 0x00000046, 0x00000017, 0x0000003F, 0x0000001E, 0x00000044, 0x00000011, 0x00000038, 0x0000005C, 0x0000000C, 0x00000010, 0x00000040, 0x0000001F, 0x00000003, 0x00000011, 0x00000047, 0x0000003A, 0x00000009, 0x00000040, 0x00000053, 0x00000047, 0x00000034, 0x00000063, 0x00000059, 0x0000004C, 0x00000044, 0x00000001, 0x00000063, 0x00000010, 0x00000010, 0x00000034, 0x0000002B, 0x00000000, 0x0000002C, 0x00000032, 0x00000020, 0x00000032, 0x0000001F, 0x00000014, 0x0000003F, 0x00000002, 0x00000063, 0x00000000, 0x00000039, 0x0000004F, 0x0000002B, 0x00000047, 0x00000013, 0x00000050, 0x0000005C, 0x0000005D, 0x0000003A, 0x00000054, 0x0000004A, 0x00000051, 0x0000002D, 0x00000037, 0x00000015, 0x00000001, 0x00000063, 0x0000001E, 0x0000001C, 0x00000038, 0x00000001, 0x0000000C, 0x0000004D, 0x0000005C, 0x00000004, 0x00000025, 0x00000043, 0x0000003C, 0x00000036, 0x00000033, 0x0000004F, 0x00000026, 0x00000057, 0x00000030, 0x00000010]
enc=[0]*40
enc[0] = 33211
enc[1] = 36113
enc[2] = 28786
enc[3] = 44634
enc[4] = 30174
enc[5] = 39163
enc[6] = 34923
enc[7] = 44333
enc[8] = 33574
enc[9] = 23555
enc[10] = 35015
enc[11] = 42724
enc[12] = 34160
enc[13] = 49166
enc[14] = 35770
enc[15] = 45984
enc[16] = 39754
enc[17] = 51672
enc[18] = 38323
enc[19] = 27511
enc[20] = 31334
enc[21] = 34214
enc[22] = 28014
enc[23] = 41090
enc[24] = 29258
enc[25] = 37905
enc[26] = 33777
enc[27] = 39812
enc[28] = 29442
enc[29] = 22225
enc[30] = 30853
enc[31] = 35330
enc[32] = 30393
enc[33] = 41247
enc[34] = 30439
enc[35] = 39434
enc[36] = 31587
enc[37] = 46815
enc[38] = 35205
enc[39] = 20689
inp = [Int('inp%d' % i) for i in range(len(enc))]
# 创建了一个Solver对象s,用于解决后续添加的约束
s=Solver()
flag=''

for k in range(4):
for m in range(10):
for n in range(10):
enc[10 * k + m] -=inp[10 * k + n] * key[10 * n + m]

# 添加约束条件
for i in range(len(enc)):
s.add(enc[i]==0)

# sat表示“满足的”或“可解的”,意味着所有约束都可以同时满足,并存在一个解决方案。
if s.check()==sat:
# 获取解决方案
m=s.model()
for i in range(40):
# .as_long() 从Z3模型中提取出的整数值。
flag+=chr(m[inp[i]].as_long())
print(flag)

# DASCTF{CI5ZCM5piv5aaC5L2V5pS26LS555qE5Y}