某一天,好奇用了许久的截图软件 ocr 功能是如何实现的,于是打开 ida 走上了两天的不归之路()
本文仅供参考,学习逆向
首先使用 Process Monitor 或者 火绒剑 等监视工具查看该软件文件相关的操作,我们可以看到在截图时这个软件在读取以下文件

但是通过互联网搜索我们可以知道这几个文件是关于 qrcode 也就是二维码识别有关的,不要被带偏

而当我们点击识别图像,也就是 ocr 操作的时候,他会读取以下内容

很明显这是一个 sqlite 数据库文件,我们看看里面有什么

里面有个 OcrResult 的字段引起了我的关注,至此信息收集就完毕了
拖入 ida,搜索 OcrResult 字符串,就两个结果,并且很容易猜出来上面一个是有关 sqlite 创建的

查看第二个字符串的引用,可以猜测这里是 sqlite 的写入

看该函数的上层,上面 sub_7FF60B4606D0
很明显是一个类的创建,进去看看

看看发现了什么,这正是我们要找的 ocr 功能相关的类

通过分析该类的 vftable 我们大概可以猜出各个函数的名称

其中有个函数长这样,有很多图片相关的操作引起了我的注意

也就是上图中的 ??_7OcrRunnable@@6B@_02 dq offset ocrFunc?
(这个函数我自己重命名了)
然后就是一个一个对函数进行猜测然后重命名,其中里面有一个函数似乎读取了模型,但是我们并搜不到该文件,答案是这个是一个 qt 的资源类型,qt 会将他打包在程序中,需要用到的时候从程序中读取出来,所以我们直接在 readMemFileAll
之后断点就能截取到数据啦

通过询问 ai ,我们可以知道这个文件可能是 ncnn 的格式文件,所以我们直接猜测该程序使用 ncnn 作为框架,进行 ocr 的识别

通过分析,大概的流程长这样,然后还有一些对于图像的操作,找模型识别出来的轮廓、找出文本区域

接着,程序会加载第二个 ai 模型来识别文本,而前一个模型是用来找出哪些区域需要识别文本的,其中 ocr_text_dict
为字典

ai 模型调用部分和上面一样,最后会输出一堆 float
的数组

最后输出识别的文本比较麻烦,它主要长这样,通过分析,我们能够猜测和跟踪得出源代码差不多长这样


识别结果

for
(
int
k = 0; k < out2.h; ++k) {
auto
i = out2.row(k);
auto
j = out2.row(k + 1);
auto
v = std::ranges::max_element(i, j);
auto
offset = (v - i)
;
if
(offset) {
std::println(
"{}, offset: {}, result: {}"
, *v, offset, dict[offset]);
}
}
for
(
int
k = 0; k < out2.h; ++k) {
auto
i = out2.row(k);
auto
j = out2.row(k + 1);
auto
v = std::ranges::max_element(i, j);
auto
offset = (v - i)
;
if
(offset) {
std::println(
"{}, offset: {}, result: {}"
, *v, offset, dict[offset]);
}
}
#include <opencv2/opencv.hpp>
#include "ncnn/net.h" // NCNN 主头文件
#undef max;
#undef min;
#include <algorithm> // For std::min, std::sort, std::max
#include <array> // For std::array
#include <fstream>
#include <print>
#include <string>
#include <vector>
#include <iostream>
uint64_t calcTargetSize(
unsigned
int
cols,
unsigned
int
rows,
unsigned
int
unk1,
unsigned
int
*unk2,
unsigned
int
*unk3,
unsigned
int
unk4
) {
unsigned
int
*v7;
if
(cols < unk1 && rows < unk1) {
unk1 = cols;
if
(cols < rows)
unk1 = rows;
if
(unk1 % unk4)
unk1 += unk4 - unk1 % unk4;
}
if
(rows <= cols) {
*unk2 = unk1;
v7 = unk3;
*unk3 = unk1 * rows / cols;
}
else
{
v7 = unk3;
*unk3 = unk1;
*unk2 = unk1 * cols / rows;
}
unsigned
int
v8 = *unk2 % unk4;
if
(v8)
*unk2 = unk4 + *unk2 - v8;
unsigned
int
v10 = *v7 % unk4;
__int64
result = *v7 / unk4;
if
(v10)
*v7 = unk4 + *v7 - v10;
return
result;
}
int
main() {
ncnn::Net net;
if
(net.load_param(
"../det.param"
) != 0) {
std::println(stderr,
"Failed to load .param file"
);
return
-1;
}
if
(net.load_model(
"../det.bin"
) != 0) {
std::println(stderr,
"Failed to load .bin file"
);
return
-1;
}
cv::Mat image = cv::imread(
"../2025-05-09_22-43-00-0.png"
, cv::IMREAD_UNCHANGED);
if
(image.empty()) {
fprintf
(stderr,
"Failed to read image\n"
);
return
-1;
}
cv::cvtColor(image, image, cv::COLOR_BGR2BGRA);
unsigned
int
calcCols, calcRows;
calcTargetSize(image.cols, image.rows, 0x3C0u, &calcCols, &calcRows, 0x20u);
ncnn::Mat in = ncnn::Mat::from_pixels_resize(
image.data,
ncnn::Mat::PIXEL_RGBA2RGB,
image.cols,
image.rows,
calcCols,
calcRows
);
uint32_t mean_vals[3];
uint32_t norm_vals[3];
mean_vals[0] = 0x3EF851EC;
mean_vals[1] = 0x3EE978D5;
mean_vals[2] = 0x3ECFDF3B;
norm_vals[0] = 0x3C8C4936;
norm_vals[1] = 0x3C8F6AD8;
norm_vals[2] = 0x3C8EC7AB;
in.substract_mean_normalize(
reinterpret_cast
<
float
*>(mean_vals),
reinterpret_cast
<
float
*>(norm_vals));
ncnn::Extractor ex = net.create_extractor();
ex.set_light_mode(
true
);
ex.input(
"x"
, in);
ncnn::Mat out;
ex.extract(
"sigmoid_0.tmp_0"
, out, 0);
uint32_t norms2[3];
norms2[0] = 0x437F0000;
norms2[1] = 0x437F0000;
norms2[2] = 0x437F0000;
out.substract_mean_normalize(nullptr,
reinterpret_cast
<
float
*>(norms2));
cv::Mat binaryMask(out.h, out.w, CV_8U);
out.to_pixels(binaryMask.data, ncnn::Mat::PIXEL_GRAY);
cv::threshold(binaryMask, binaryMask, 76.5, 255.0, cv::THRESH_BINARY);
assert
(binaryMask.type() == CV_8UC1 &&
"mask type must be CV_8UC1"
);
auto
mat = cv::Mat(0xe, 0xf8, CV_16UC2);
auto
picPartData = readFile(
"../pic.part"
);
auto
dictData = std::fstream(
"../ocr_text_dict.txt"
, std::ios::in);
std::string line;
std::vector<std::string> dict;
while
(std::getline(dictData, line)) {
dict.push_back(line);
}
mat.data = picPartData.data();
net.load_param(
"../rec.param"
);
net.load_model(
"../rec.bin"
);
ncnn::Mat in2, out2;
in2 = ncnn::Mat::from_pixels_resize(
mat.data,
ncnn::Mat::PIXEL_BGRA2BGR,
mat.cols,
mat.rows,
0x353,
0x30
);
uint32_t mean_vals2[3] { 0x42FF0000, 0x42FF0000, 0x42FF0000 };
uint32_t norm_vals2[3] { 0x3C008081, 0x3C008081, 0x3C008081 };
in2.substract_mean_normalize(
reinterpret_cast
<
float
*>(mean_vals2),
reinterpret_cast
<
float
*>(norm_vals2));
ncnn::Extractor ex2 = net.create_extractor();
ex2.set_light_mode(
true
);
ex2.input(
"x"
, in2);
ex2.extract(
"softmax_11.tmp_0"
, out2, 0);
for
(
int
k = 0; k < out2.h; ++k) {
auto
i = out2.row(k);
auto
j = out2.row(k + 1);
auto
v = std::ranges::max_element(i, j);
auto
offset = (v - i)
;
if
(offset) {
std::println(
"{}, offset: {}, result: {}"
, *v, offset, dict[offset - 1]);
}
}
return
0;
}
#include <opencv2/opencv.hpp>
#include "ncnn/net.h" // NCNN 主头文件
#undef max;
#undef min;
#include <algorithm> // For std::min, std::sort, std::max
#include <array> // For std::array
#include <fstream>
#include <print>
#include <string>
#include <vector>
#include <iostream>
uint64_t calcTargetSize(
unsigned
int
cols,
unsigned
int
rows,
unsigned
int
unk1,
unsigned
int
*unk2,
unsigned
int
*unk3,
unsigned
int
unk4
) {
unsigned
int
*v7;
if
(cols < unk1 && rows < unk1) {
unk1 = cols;
if
(cols < rows)
unk1 = rows;
if
(unk1 % unk4)
unk1 += unk4 - unk1 % unk4;
}
if
(rows <= cols) {
*unk2 = unk1;
v7 = unk3;
*unk3 = unk1 * rows / cols;
}
else
{
v7 = unk3;
*unk3 = unk1;
*unk2 = unk1 * cols / rows;
}
unsigned
int
v8 = *unk2 % unk4;
if
(v8)
*unk2 = unk4 + *unk2 - v8;
unsigned
int
v10 = *v7 % unk4;
__int64
result = *v7 / unk4;
if
(v10)
*v7 = unk4 + *v7 - v10;
return
result;
}
int
main() {
ncnn::Net net;
if
(net.load_param(
"../det.param"
) != 0) {
std::println(stderr,
"Failed to load .param file"
);
return
-1;
}
if
(net.load_model(
"../det.bin"
) != 0) {
std::println(stderr,
"Failed to load .bin file"
);
return
-1;
}
cv::Mat image = cv::imread(
"../2025-05-09_22-43-00-0.png"
, cv::IMREAD_UNCHANGED);
if
(image.empty()) {
fprintf
(stderr,
"Failed to read image\n"
);
return
-1;
}
cv::cvtColor(image, image, cv::COLOR_BGR2BGRA);
unsigned
int
calcCols, calcRows;
calcTargetSize(image.cols, image.rows, 0x3C0u, &calcCols, &calcRows, 0x20u);
ncnn::Mat in = ncnn::Mat::from_pixels_resize(
image.data,
ncnn::Mat::PIXEL_RGBA2RGB,
image.cols,