2023腾讯游戏安全竞赛-PC方向初赛复现

2023腾讯游戏安全竞赛-PC方向初赛复现

第一问

问题描述:在64位Windows10系统上运行contest.exe, 找到明文的信息,作为答案提交(1分)

直接运行程序,在contest.txt中拿到密文ImVkImx9JG12OGtlImV+,很像base64后的结果,但是直接解码得到的不是自然语言,整个exe程序也完全被VM了,动调看看。

一直动调到contest.txt被创建,在内存中搜索字符串可以找到一张表QRSTUVWXYZabcdefABCDEFGHIJKLMNOPwxyz0123456789+/ghijklmnopqrstuv,用这张表解码拿到明文catchmeifyoucan。在内存中也可以直接找到明文

第二问

问题描述:编写程序,运行时修改尽量少的contest.exe内存,让contest.exe 由写入密文信息变为写入明文信息成功。(满分2分)

这题在调试的时候费了一番功夫,既然exe一直在创建文件,写入内容,必然调用了CreateFileA以及WriteFile这些API,但是用x64dbg下断发现断不下来,自己写Hook也没有任何输出。没招了想着用驱动探探路(虽然题目不允许),CreateFile一系列的API最终是陷入NtCreateFile这个内核函数,我尝试Hook后直接蓝屏ATTEMPT_TO_WRITE_READONLY,恍然大悟原来之前断点和Hook都不成功是程序把自己的内存属性设成了只读。那么理论上来说,用VirtualProtect更改程序内存属性就可以了,但是还是无效。 用火绒剑扫一下钩子果然是赛题在作怪。

image-20250317123733472

看了下这个钩子,直接跳转到一片unuse的内存,所以VirtualProtect失效。另外还有一个DbgUiRemoteBreakin的钩子,影响不是很大。

得想办法UnHook这个钩子,然后再用VirtualProtect重设页属性。正常的NtProtectVirtualMemory长这样:

image-20250317170511808

被挂钩后其实只有第一句汇编变了,不过这个系统调用也不长,干脆全部还原一下。注意这个地址的属性是PAGE_EXECUTE_READ,在UnHook前要改成PAGE_EXECUTE_READWRITE,你可能会想NtProtectVirtualMemory不是被挂钩了吗,怎么改?注意这里挂钩的是三环下陷入内核的一个系统调用函数,对于每个进程都是独立的,挂钩自己的NtProtectVirtualMemory不影响其他进程。所以我们先自己写一个UnHook程序。

#include <Windows.h>
#include <psapi.h>
#include <TlHelp32.h>
#include <stdio.h>
#include <tchar.h>
#pragma comment(lib, "psapi.lib")
//ULONG64 g_UnHookAddr = 0x00007FFC23963D50;
//ULONG64 g_ntdllBase = 0x7FFC238C0000;
ULONG64 g_UnHookAddr = 0;
ULONG64 g_NtProtectVirtualMemoryOff = 0xA3D50;
const wchar_t* targetExeName = L"contest.exe";
const wchar_t* dllPath = L"C:\\Users\\Administrator\\source\\repos\\Tencent-Dll1\\x64\\Release\\Tencent-Dll1.dll";
BYTE UnHookShellCode[] = {
0x4C, 0x8B, 0xD1, 0xB8, 0x50, 0x00, 0x00, 0x00, 0xF6, 0x04, 0x25, 0x08, 0x03, 0xFE, 0x7F, 0x01,0x75, 0x03, 0x0F, 0x05, 0xC3, 0xCD, 0x2E, 0xC3
};//正常的字节码
HMODULE GetRemoteNtdllBase(HANDLE hProcess) {HMODULE hModules[1024];DWORD cbNeeded;// 枚举目标进程的所有模块if (EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded)) {WCHAR szModuleName[MAX_PATH];// 遍历所有模块for (DWORD i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {// 获取模块名称if (GetModuleFileNameEx(hProcess, hModules[i], szModuleName, sizeof(szModuleName) / sizeof(WCHAR))) {// 检查是否为 ntdll.dllif (_wcsicmp(wcsrchr(szModuleName, L'\\') + 1, L"ntdll.dll") == 0) {return hModules[i];}}}}return NULL;
}
DWORD ProcessFind(const wchar_t* Exename)
{HANDLE hProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);if (!hProcess){return FALSE;}PROCESSENTRY32 info;info.dwSize = sizeof(PROCESSENTRY32);if (!Process32First(hProcess, &info)){return FALSE;}while (true){if (memcmp(info.szExeFile, Exename, _tcslen(Exename)) == 0){return info.th32ProcessID;}if (!Process32Next(hProcess, &info)){return FALSE;}}return FALSE;
}
int main() {DWORD tarPid = ProcessFind(targetExeName);HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, tarPid);if (!hProcess) {printf("Failed when open process!\n");return 0;}ULONG64 ntdllBase = (ULONG64)GetRemoteNtdllBase(hProcess);if (!ntdllBase) {printf("Failed when get ntdll base!\n");return 0;}g_UnHookAddr = ntdllBase + g_NtProtectVirtualMemoryOff;//改ntdll!NtProtectVirtualMemory的内存属性DWORD oldProtect = 0;if (!VirtualProtectEx(hProcess, (LPVOID)g_UnHookAddr, sizeof(UnHookShellCode), PAGE_EXECUTE_READWRITE, &oldProtect)){printf("Failed when VirtualProtectEx!\n");return 0;}//现在ntdll!NtProtectVirtualMemory可写了,取消钩子if (!WriteProcessMemory(hProcess, (LPVOID)g_UnHookAddr, UnHookShellCode, sizeof(UnHookShellCode), 0)){printf("UnHook failed!!! [%d]\n", GetLastError());return 0;}printf("UnHooked!\n");system("pause");
}

取消成功:

image-20250317180331213

接下来我的想法是直接在DLL中Hook WriteFile来更改写入的字符串,但是发现无论是Hook还是在调试器中给WriteFile下断都断不下来这个函数,这块没太懂,感觉是通过函数指针间接调用导致的吧。看来我之前分析的还有点问题,0环Hook NtCreateFile蓝屏的原因确实是因为内存属性的问题,但是3环Hook CreateFileA没反应应该是函数指针的问题,不过之前做的UnHook肯定也不是无用功。

那这下不得不看汇编了。把程序运行起来,右下角的堆栈窗口一直在变,其中就有WriteFile的符号,下个硬断追一下看看能不能发现什么。

神秘打野点,这里拿了WriteFile的函数指针。给他dump下来丢进IDA看看这块到底干了啥。发现IDA伪代码是一坨巨大的东西看了都要晕倒了,应该是VM的问题,没办法耐着性子继续追。

image-20250317184623254

跑了一段,追踪到一个call,在这个call之后字符串就被写入了:

image-20250317200318523

此时的参数窗口:

image-20250317200346250

[rsp+30],也就是第7个参数填入的是写入的字符串,IDA中能看到这是一个函数指针表的其中一个,实际上call的是这个函数:

image-20250317201538811

这是一个跳板函数,目标是:

image-20250317201553434

然后其中的a7,也就是第七个参数,就是字符串缓冲区的指针,Hook这两个函数的其中一个应该就可以实现修改字符串了,Hook后面那个试试看。这个地方我调了很久,一直崩溃没法解决。后面仔细调了一下发现一个问题,这个函数的外层call长这样:

00007FF7FA85CEDE | 48:C74424 48 00000000     | mov qword ptr ss:[rsp+48],0             |
00007FF7FA85CEE7 | C74424 20 05000000        | mov dword ptr ss:[rsp+20],5             |
00007FF7FA85CEEF | 4C:89E9                   | mov rcx,r13                             |
00007FF7FA85CEF2 | BA FFFFFFFF               | mov edx,FFFFFFFF                        |
00007FF7FA85CEF7 | 45:31C9                   | xor r9d,r9d                             |
00007FF7FA85CEFA | FFD3                      | call rbx                                | Call Write
00007FF7FA85CEFC | 48:83C4 50                | add rsp,50                              |
00007FF7FA85CF00 | 48:8B45 A0                | mov rax,qword ptr ss:[rbp-60]           |

call rbx处跳转到跳板函数:

.text:00007FF7FA85D6A0                 lea     rax, sub_7FF7FA868750
.text:00007FF7FA85D6A7                 jmp     rax

从跳板函数跳到Write函数sub_7FF7FA868750,也就是刚刚说的目标Hook函数,这里值得注意的是,sub_7FF7FA868750不止写入密文时被调用,一定要是从call rbx处跳入这个函数才是写入密文,其他的调用不会经过call rbx,我还没有搞清楚为什么还会有其他的调用,总之除了写入密文之外的调用都是无效的,这种无效调用经过Hook函数的话可能会造成崩溃,因此只能从call rbx入手,只有当确定是写入密文的操作时,我们才Hook,也就是通过shellcode直接把call rbx改成跳入我们的Hook函数。

这一步没什么框架可用,手搓吧。

算出来call rbx这条汇编的Offset是0xCEFA

Original:

00007FF7FA85CEF7 | 45:31C9                   | xor r9d,r9d                             |
00007FF7FA85CEFA | FFD3                      | call rbx                                | 
00007FF7FA85CEFC | 48:83C4 50                | add rsp,50        					   |

这边六个字节,可以用个相对跳转,还剩一个字节填90

Patched:

00007FF7FA85CEF7 | 45:31C9                   | xor r9d,r9d                           
00007FF7FA85CEFA | E9 000000                 | jmp myHookShellCodeAddr                   
00007FF7FA85CEFC | 90                		 | nop

这边先填00 00 00 00,还没想好跳哪,myHookShellCodeAddr = Base + Offset_0xCEFA + 5 + relativeAddr;

只要决定好myHookShellCodeAddr就能算出relativeAddr了,我们知道在exe程序的末尾往往会有一小段不使用的内存,可以把shellcode往这里写而不影响整体程序的执行。

image-20250318224457845

比方我们选取00指令的起始位置,偏移为0x26C00,这里就是常规的inlineHook思路。

mov rax有两种字节码,这一种48:B8后接一个8字节立即数,后续在代码中改成myHookAddr就行。最后的回跳是跳到00007FF7FA85CEFC | 90 nop这个位置,接着正常执行。

注:下图回跳应为E9 EA62FEFF,应该跳到nop后一句,像图中这样写会崩

image-20250318225532903

确定好myHookShellCodeAddr后,就可以计算上面的relativeAddr了,可以用我给出的公式算,也可以直接让x64dbg帮我们算好:

image-20250318225957329

另外以上的操作注意端序问题。接下来终于终于可以开始写代码了:

#include "pch.h"
#include <Windows.h>
#include <process.h>
#include <stdio.h>
void DebugLog(const char* format, ...)
{char buffer[1024];va_list args;va_start(args, format);vsprintf_s(buffer, format, args);va_end(args);OutputDebugStringA(buffer);
}BYTE hookWriteFileShellCode[] = {0xE9,0x01,0x9D,0x01,0x00,0x90};
BYTE callMyWriteFile[] = {0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //mov rax,00 00 00 00 00 00 00 000xFF,0xD0,  // call rax0x48,0x83,0xC4,0x50,    //add rsp,500x90,   //nop0xE9,0xEA,0x62,0xFE,0xFF,   //jmp 
};DWORD64 oriWriteFileFunc = (DWORD64)GetModuleHandleA("contest.exe") + 0xD6A0;//call rbx后的跳板函数
DWORD64 oriCallRBX = (DWORD64)GetModuleHandleA("contest.exe") + 0xCEFA;
DWORD64 myHookWriteFileShellCodeAddr = (DWORD64)GetModuleHandleA("contest.exe") + 0x26C00;char plainText[] = "catchmeifyoucan";typedef void (*fphookWriteFile)(DWORD64 RCX, DWORD64 RDX, DWORD64 R8, DWORD64 R9,DWORD64 Par5, DWORD64 Par6, DWORD64 Par7, DWORD64 Par8,DWORD64 Par9);void __fastcall myHookWriteFile(DWORD64 RCX, DWORD64 RDX, DWORD64 R8, DWORD64 R9,DWORD64 Par5, DWORD64 Par6, DWORD64 Par7, DWORD64 Par8,DWORD64 Par9) {DebugLog("[+]replaced!\n");Par8 = (Par8 & 0xFFFFFFFFFFFFFF00) | (strlen(plainText));memcpy((void*)Par7, plainText, sizeof(plainText));fphookWriteFile ptr = (fphookWriteFile)oriWriteFileFunc;return ptr(RCX, RDX, R8, R9, Par5, Par6, Par7, Par8, Par9);
}
UINT WINAPI MainThread(PVOID) {/*Install Hook Write File*/DWORD oldProtect = 0;VirtualProtect((LPVOID)oriCallRBX, sizeof(hookWriteFileShellCode), PAGE_EXECUTE_READWRITE, &oldProtect);memcpy((void*)oriCallRBX, hookWriteFileShellCode, sizeof(hookWriteFileShellCode));VirtualProtect((LPVOID)myHookWriteFileShellCodeAddr, sizeof(callMyWriteFile), PAGE_EXECUTE_READWRITE, &oldProtect);*(DWORD64*)&callMyWriteFile[2] = (DWORD64)myHookWriteFile;memcpy((void*)myHookWriteFileShellCodeAddr, callMyWriteFile, sizeof(callMyWriteFile));return 1;
}
BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{if (ul_reason_for_call == DLL_PROCESS_ATTACH) {DebugLog("Begin Hooked!");HANDLE hThread = (HANDLE)_beginthreadex(nullptr, NULL, MainThread, nullptr, 0, nullptr);}
}

代码比较简单。看看效果:

image-20250318233914840

实现了不崩溃的持续写入明文,第二问solved。

第三问

问题描述:编写程序,运行时修改尽量少的contest.exe内存,让contest.exe 往入自行指定的不同的文件里写入明文信息成功。(满分3分)

第二问分析的很完善了,第三问是一个道理,不过是把WriteFile变成了CreateFileA,先逆出Call CreateFileA

image-20250319081308718

image-20250319081351497

值得注意的是这里call rax后的跳板函数跟call WriteFile是同一个函数,应该是改变了参数。

同时观察传参窗口,第六个参数是文件名:

image-20250319082411284

开始写代码,这里把myHookCreateFileAShellCodeAddr写在偏移0x26C20

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>
#include <process.h>
#include <stdio.h>
void DebugLog(const char* format, ...)
{char buffer[1024];va_list args;va_start(args, format);vsprintf_s(buffer, format, args);va_end(args);OutputDebugStringA(buffer);
}BYTE hookWriteFileShellCode[] = {0xE9,0x01,0x9D,0x01,0x00,0x90};
BYTE callMyWriteFile[] = {0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //mov rax,00 00 00 00 00 00 00 000xFF,0xD0,  // call rax0x48,0x83,0xC4,0x50,    //add rsp,500x90,   //nop0xE9,0xEA,0x62,0xFE,0xFF,   //jmp 
};BYTE hookCreateFileAShellCode[] = { 0xE9,0x8B,0xA0,0x01,0x00,0x90 };
BYTE callMyCreateFileA[] = {0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //mov rax,00 00 00 00 00 00 00 000xFF,0xD0,  // call rax0x48,0x83,0xC4,0x60,    //add rsp,600x90,   //nop0xE9,0x60,0x5F,0xFE,0xFF,   //jmp 
};
DWORD64 oriWriteFileFunc = (DWORD64)GetModuleHandleA("contest.exe") + 0xD6A0;//call rbx后的跳板函数
DWORD64 oriCallRBX = (DWORD64)GetModuleHandleA("contest.exe") + 0xCEFA;
DWORD64 myHookWriteFileShellCodeAddr = (DWORD64)GetModuleHandleA("contest.exe") + 0x26C00;DWORD64 oriCreateFileFunc = (DWORD64)GetModuleHandleA("contest.exe") + 0xD6A0;//call rbx后的跳板函数
DWORD64 oriCallRAX = (DWORD64)GetModuleHandleA("contest.exe") + 0xCB90;
DWORD64 myHookCreateFileAShellCodeAddr = (DWORD64)GetModuleHandleA("contest.exe") + 0x26C20;char plainText[] = "catchmeifyoucan";
char fileName[] = "flag.txt";typedef void (*fphookWriteFile)(DWORD64 RCX, DWORD64 RDX, DWORD64 R8, DWORD64 R9,DWORD64 Par5, DWORD64 Par6, DWORD64 Par7, DWORD64 Par8,DWORD64 Par9);typedef void (*fphookCreateFileA)(DWORD64 RCX, DWORD64 RDX, DWORD64 R8, DWORD64 R9,DWORD64 Par5, DWORD64 Par6, DWORD64 Par7, DWORD64 Par8,DWORD64 Par9);void __fastcall myHookWriteFile(DWORD64 RCX, DWORD64 RDX, DWORD64 R8, DWORD64 R9,DWORD64 Par5, DWORD64 Par6, DWORD64 Par7, DWORD64 Par8,DWORD64 Par9) {DebugLog("[+]plaintext replaced!\n");Par8 = (Par8 & 0xFFFFFFFFFFFFFF00) | (strlen(plainText));memcpy((void*)Par7, plainText, sizeof(plainText));fphookWriteFile ptr = (fphookWriteFile)oriWriteFileFunc;return ptr(RCX, RDX, R8, R9, Par5, Par6, Par7, Par8, Par9);
}void __fastcall myHookCreateFileA(DWORD64 RCX, DWORD64 RDX, DWORD64 R8, DWORD64 R9,DWORD64 Par5, DWORD64 Par6, DWORD64 Par7, DWORD64 Par8,DWORD64 Par9) {DebugLog("[+]file name replaced!\n");memcpy((void*)Par6, fileName, sizeof(fileName));fphookCreateFileA ptr = (fphookCreateFileA)oriCreateFileFunc;return ptr(RCX, RDX, R8, R9, Par5, Par6, Par7, Par8, Par9);
}UINT WINAPI MainThread(PVOID) {/*Install Hook WriteFile*/DWORD oldProtect = 0;VirtualProtect((LPVOID)oriCallRBX, sizeof(hookWriteFileShellCode), PAGE_EXECUTE_READWRITE, &oldProtect);memcpy((void*)oriCallRBX, hookWriteFileShellCode, sizeof(hookWriteFileShellCode));VirtualProtect((LPVOID)myHookWriteFileShellCodeAddr, sizeof(callMyWriteFile), PAGE_EXECUTE_READWRITE, &oldProtect);*(DWORD64*)&callMyWriteFile[2] = (DWORD64)myHookWriteFile;memcpy((void*)myHookWriteFileShellCodeAddr, callMyWriteFile, sizeof(callMyWriteFile));/*Install Hook CreateFileA*/VirtualProtect((LPVOID)oriCallRAX, sizeof(hookCreateFileAShellCode), PAGE_EXECUTE_READWRITE, &oldProtect);memcpy((void*)oriCallRAX, hookCreateFileAShellCode, sizeof(hookCreateFileAShellCode));VirtualProtect((LPVOID)myHookCreateFileAShellCodeAddr, sizeof(callMyCreateFileA), PAGE_EXECUTE_READWRITE, &oldProtect);*(DWORD64*)&callMyCreateFileA[2] = (DWORD64)myHookCreateFileA;memcpy((void*)myHookCreateFileAShellCodeAddr, callMyCreateFileA, sizeof(callMyCreateFileA));return 1;
}
BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{if (ul_reason_for_call == DLL_PROCESS_ATTACH) {DebugLog("Begin Hooked!");HANDLE hThread = (HANDLE)_beginthreadex(nullptr, NULL, MainThread, nullptr, 0, nullptr);}
}

成功:

image-20250319085939734

那么三问就都解决了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/901369.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何选择合适的供应商协同平台,解决数据交互的安全性与高效性?

在当今竞争激烈的商业环境中,企业的供应链管理面临着诸多挑战。传统的供应商合作模式在信息沟通、流程效率等方面存在着明显的问题,这些问题不仅制约了企业的发展,也影响了整个供应链的竞争力,企业需要寻找供应商协同平台,实现企业与供应商之间的信息共享、业务协同和数据…

【深度好文】是时候重新评估您当前的MFT文件传输供应商了

随着文件传输需求的不断演变,更复杂的数据安全威胁的出现、⼈⼯智能等颠覆性技术、成本压⼒以及从医疗保健到⾦融再到供应链等⾏业⽇益严格的监管标准,企业可能需要重新评估其受管文件传输(MFT)供应商。本文将探讨推动企业更换MFT系统的因素,以及在评估潜在新MFT供应商时需…

Nginx错误处理与排查:运维人员的必备手册

前言:在日常的 Web 开发与运维工作中,Nginx 作为一款高性能的 Web 服务器和反向代理工具,被广泛应用于各种项目中。然而,即使是最优秀的工具也难免会遇到各种问题。Nginx 的报错信息虽然简洁,但往往让人摸不着头脑,尤其是对于新手来说,更是如此。而重定向配置,作为 Ngi…

RequestMapping

其中最关键的是path属性(等价于value),它决定了当前方法处理的请求路径,注意路径必须全局唯一,任何路径只能有一个方法进行处理,它是一个数组,也就是说此方法不仅仅可以只用于处理某一个请求路径,我们可以使用此方法处理多个请求路径: @RequestMapping({"/index&…

C#/.NET/.NET Core技术前沿周刊 | 第 30 期(2025年3.10-3.16)

前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿,助力技术成长与视野拓宽。欢迎投稿、推荐或自荐优质文章、项目、学习资源等…

读DAMA数据管理知识体系指南24数据集成概念(下)

读DAMA数据管理知识体系指南24数据集成概念(下)1. 复制 1.1. 复制技术将分析和查询对主事务操作环境性能的影响降至最低 1.2. 复制解决方案通常监视数据集的更改日志,而不是数据集本身 1.3. 标准复制解决方案是准实时的,数据集的一个副本和另一个副本之间的更改有很小的延迟…

20244112 实验一《Python程序设计》实验报告

20244112 2024-2025-2 《Python程序设计》实验一报告 课程:《Python程序设计》 班级:2441 姓名:李其鲔 学号:20244112 实验教师:王志强 实验日期:2025年3月18日 必修/选修:公选课 1. 实验内容 1.熟悉Python开发环境; 2.练习Python运行、调试技能; 3.编写程序,练习…

Groq软件定义的横向扩展张量流多处理器-从芯片到系统架构概述

Groq软件定义的横向扩展张量流多处理器-从芯片到系统架构概述 1.大纲 1)张量流处理器(TSP)背景 2)软件定义的硬件和确定性执行 3)TSP微架构 4)系统封装、拓扑、路由和流控制 5)小结 2.软件定义方法 1)软硬件协同设计并不是什么新鲜事 2)重新检查硬件软件接口 ① 静态-…

Radeon GPU上使用ROCm一些技术点

Radeon GPU上使用ROCm一些技术点 1. 使用最新的高端AMD Radeon™7000系列GPU,将桌面变成机器学习平台 AMD已扩展了对RDNA™3 GPU上机器学习开发的支持,该GPU具有Radeon™软件,适用于Linux 24.10.3和ROCm™6.1.3! 使用PyTorch、ONNX运行时或TensorFlow的机器学习(ML)模型,…

推荐几本书1《AI芯片开发核心技术详解》、2《智能汽车传感器:原理设计应用》、3《TVM编译器原理与实践》、4《LLVM编译器原理与实践》

4本书推荐《AI芯片开发核心技术详解》、《智能汽车传感器:原理设计应用》、《TVM编译器原理与实践》、《LLVM编译器原理与实践》由清华大学出版社资深编辑赵佳霓老师策划编辑的新书《AI芯片开发核心技术详解》已经出版,京东、淘宝天猫、当当等网上,相应陆陆续续可以购买。该…

ragflow-naive模式pdf解析 调试日志

测试用例1:test_emf 无图映射解析结果 boxes1: {x0: 70.0, x1: 308.6666666666667, top: 76.0, text: Test with three images in unusual formats, bottom: 90.0, page_number: 1, layout_type: } {x0: 70.66666666666667, x1: 152.33333333333334, top: 109.66666666666667, …

树形DP 树的重心

树形DP 树的重心 给定一颗树,树中包含 \(n\) 个结点(编号 \(1 \sim n\))和 \(n-1\) 条无向边。 请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。 重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个…