C/C++逆向:寻找main函数(Debug-x86)

news2024/11/15 8:24:06

在程序的逆向分析中,寻找main函数在逆向分析中是非常重要的,它是程序的核心执行点,从这里开始,程序的主要逻辑开始展开;在这边我们需要明确两个概念:用户入口(User Entry Point)应用程序入口(Application Entry Point);它们分别指代了程序的不同阶段的执行起点。

用户入口
用户入口是开发者编写的用于程序执行开始的函数。对于大多数 C/C++ 程序而言,这个入口函数通常是 `main`,但也可以是 `WinMain`(在 Windows GUI 程序中)或其他用户定义的入口函数。
应用程序入口
应用程序入口是操作系统在加载可执行文件时调用的第一个代码位置。这个位置通常是由编译器或链接器自动生的,它负责初始化运行时环境,准备好执行用户编写的 `main` 函数、`WinMain`(在 Windows GUI 程序中)其他用户定义的入口函数。

在逆向工程中,通过理解和识别这两个不同的入口点,可以更好地分析程序的结构和执行流程。例如,通过定位应用程序入口,你可以看到如何设置和调用用户入口函数;而通过分析用户入口函数,可以理解程序的主要逻辑和功能。

影响主函数寻找的因素

影响主函数寻找的因素多种多样,这边就选择几个因素来进行记录与描述:

1.编译器优化:编译器的优化级别(如-O0、-O2、-O3等)直接影响生成代码的复杂性和结构。在高优化级别下,编译器可能会将多个小函数内联,移除未使用的代码,或重新组织控制流,这会使得主函数难以识别。
2.程序类型: ①控制台程序的入口函数通常是main,而GUI程序可能是WinMain、wWinMain或其他自定义入口点。这种差异会影响你寻找主函数的方式。
            ②DLL文件没有传统意义上的main函数,而是使用DllMain作为入口函数。对于DLL,主函数通常会被替换为DllMain,而主要逻辑可能在其他导出函数中实现。
3.编译器和链接器的版本:①编译器版本:不同版本的编译器生成的代码可能有显著不同。例如,新版本的编译器可能使用了新的优化技术,或者改变了函数调用约定,从而使得代码结构发生变化。
                    ②链接器行为:不同的链接器可能会生成不同的启动代码。例如,一些链接器可能在最终的可执行文件中插入额外的初始化代码,这些代码可能混淆主函数的识别。
4.操作系统和平台:①操作系统差异:不同的操作系统有不同的启动流程。例如,Linux上main函数通常由_start或__libc_start_main调用,而在Windows上,入口点可能是WinMainCRTStartup。这些启动流程差异会影响寻找主函数的方式。
               ②处理器架构:不同的处理器架构(如x86、x64、ARM)可能有不同的调用约定和寄存器使用,这些差异会影响反汇编代码的理解和主函数的识别。

寻找main函数

1.根据main函数的三个参数(x86程序)定位main函数

在进行x86程序的静态分析时,寻找main函数的入口点可以通过识别传递给main函数的三个标准参数来实现。main函数的三个参数通常在C/C++程序中用来传递命令行参数和环境变量,这三个参数是argcargvenvp:

int main(int argc, char *argv[], chat *envp[])

它们通常由启动代码传递给main函数;这三个参数的具体作用如下:

1.argc:表示传递给程序的命令行参数的数量;这个值包括程序的名称(即第一个参数),因此argc的值至少为1。
    例子: 如果程序以./program arg1 arg2方式运行,那么argc的值为3。
2.argv:表示命令行的各个参数。argv[0]通常是程序的名称,argv[1]是第一个参数,以此类推。
    例子: 对于./program arg1 arg2,argv[0]是"./program",argv[1]是"arg1",argv[2]是"arg2"。
3.envp:是一个指向环境变量的字符串数组。每个元素是一个以"key=value"形式表示的环境变量字符串。这个参数在很多编译器中是可选的,因此不总是出现在main函数中。
    例子: 环境变量可能包括PATH、HOME等,表示系统的配置信息和环境设置。

main函数的三个参数示例:这段C代码是一个演示如何处理命令行参数和环境变量的简单程序。

#include <stdio.h>
#include <stdlib.h>
​
int main(int argc, char *argv[], char *envp[]) {
    printf("Number of arguments : ");
    printf("%d\r\n", argc);//参数个数
​
    for (size_t i = 0; i < argc; i++)
    {
        printf("Argument%d:", i);
        printf("%s\r\n", argv[i]);//参数内容
    }
​
    for (char **env = envp; *env != 0; env++) {
        char *currentEnv = *env;
        printf("%s", currentEnv); //环境
    }
    return 0;
}

int main(int argc, char *argv[], char *envp[]): 这是程序的主函数。printf("Number of arguments : "); 打印出 "Number of arguments : " 字符串。printf("%d\r\n", argc); 打印出命令行参数的个数 argc

这个 for 循环遍历所有的命令行参数:

  • printf("Argument%d:", i); 打印出 "Argument" 及当前参数的索引号。

  • printf("%s\r\n", argv[i]); 打印出具体的参数内容。

for (char **env = envp; *env != 0; env++) {
    char *currentEnv = *env;
    printf("%s", currentEnv);
}

这个 for 循环遍历所有的环境变量:

char **env = envp; 初始化一个指向环境变量数组的指针。

*env != 0; 循环条件是当前环境变量指针不为 NULL

env++ 移动到下一个环境变量。

printf("%s", currentEnv); 打印出当前的环境变量字符串。

生成程序后(程序名为ConsoleApplication3.exe),运行目录并指定对应的参数:

ConsoleApplication3.exe Hello WolvenChan

这个点补充完毕后,我们使用IDA针对最简单的Hello World程序进行逆向分析尝试:使用IDA对程序进行分析,因为程序比较简单所以IDA能帮我们识别main函数:

我们可以直接在Function Window中按下ctrl+F,并输入main进行主函数的定位(左边的红色方框),同样的在View-A窗口中我们可以使用Alt+T进行关键字main的搜索:

除了main关键词我们还可以搜索如argcargvenvp三个关键字进行定位;这个方法能够正常使用的前提是程序没有混淆。在2012版的VS中我们可以尝试找到一个call指令前面紧跟着3个push(因为main函数的参数有三个,这三个push是将参数压入栈的操作);

有读者可能会问,如果我写程序的时候main函数不带参数又该如何应对呢,这边要声明一点是不管我们代码中实际使用了几个参数,在程序被编译时其main函数肯定是三个参数的。当然还有一点要声明:3个push一个call的方式寻找main只能在2012版左右的VS编译生成的程序逆向中使用,现在的vs2017以及以后的版本用这种方法找main函数的几率就比较小了。此外,在Debug模式下,由于没有激进的优化,代码通常更接近源代码结构,使得这种方法更为有效。另外,x64程序如今使用fastcall的调用约定,无法使用这个方法进行main的定位。

2.字符串搜索定位main函数
①IDA

main 函数通常会调用一些标准库函数,例如 printfputs,这些函数可能会引用一些字符串。可以通过字符串搜索来定位可能的 main 函数。这里我们可以使用快捷键Shift + F12 打开字符串窗口。在字符串窗口中查找在程序中出现过的字符串,如在本次作为例子的程序中就有一个最直接明显的字符串:Hello World

②x86dbg/x64dbg

x96dbg中使用字符串搜索定位main函数:使用x96dbg加载程序进行动态调试分析。

加载完毕后发现当前所处的模块是ntdll.dllntdll.dll 是 Windows 操作系统的一个系统动态链接库,包含了许多低级别的系统服务,特别是与内核相关的操作。这包括系统调用接口、异常处理、内存管理等关键功能,它是几乎所有 Windows 应用程序都依赖的一个模块,这个模块并不需要我们进行分析,这个时候我们可以按 F9 继续执行程序,直到遇到你自己程序中的断点或其他感兴趣的地方。

通过上图可以看到,按下F9后,上面的模块就切换成了我的程序名;接着可以右击反汇编的窗口,选择搜索->所有模块->字符串进行关键字搜索,此时就可以根据程序的一些字符串特征进行搜索,定位程序的main函数。

接着双击搜索出来的结果,转到原反汇编代码中:找到main函数。

寻找字符串也是有着很多限制,如果程序中没有进行输出和输入,那么就没有办法利用字符串去寻找主函数。如果存在字符串的话他也是最快找到入口点的方法之一。

3.通过编译器特征定位 main 函数

不同版本的编译器生成的代码可能会有特定的标记或行为模式,比如某些版本的编译器会生成特定的栈帧布局或函数 prologue/epilogue(函数的 prologue 和 epilogue 是函数在执行过程中用于设置和清理栈帧的标准序列。这些序列是编译器在生成函数代码时自动插入的,用于管理栈和寄存器,确保函数调用的正确性),这些都可以作为识别 main 函数的依据。

在这边我主要分析特征的方法是逆推,使用不同(版本)的编译器生成对应的Demo程序(Demo程序尽量简单,如Hello World),接着对Demo程序进行静态分析,因为程序比较简单,所以能够很快定位到main函数,从main函数不断往上一层推,在每一层函数调用中提取特征,直到找不到更上一层函数。然后通过动态调试分析我们真正需要调试的应用程序(非Demo),依靠特征定位main函数。接下来我们就举一个例子:

①Demo程序提取特征

找到main函数:

后,选定main函数名,使用Ctrl+X进行交叉引用找到该函数的上一层引用;

当前引用中仅有一个jmp,先进行记录:

当前记录:

第一个jmp

接着选中当前函数的函数名,再次进行交叉引用,获取上一层引用:

当前函数情况:

可以看到上一个函数在第4个call被调用,进行记录,当前的记录为:

第4个call
第一个jmp

接着还是选中当前函数

使用交叉引用获取上一层引用:

可以看到上层函数在这边的第一个call被调用,但是如果这边只是一个call的话可能到时候不太好定位,所以此时我们往上/下提取特征:

call    ?invoke_main@@YAHXZ ; invoke_main(void)
mov     [ebp+Code], eax
call    j____scrt_is_managed_app
movzx   ecx, al
test    ecx, ecx
jnz     short loc_411E9F
mov     edx, [ebp+Code]

当前记录:

第一个call
call    ?invoke_main@@YAHXZ ; invoke_main(void)   
mov     [ebp+Code], eax
call    j____scrt_is_managed_app
movzx   ecx, al
test    ecx, ecx
jnz     short loc_411E9F
mov     edx, [ebp+Code]
​
第4个call
第一个jmp

接着进行交叉引用:

特征:

第一个jz
call    sub_41123A
mov     [ebp+var_24], eax
mov     edx, [ebp+var_24]
cmp     dword ptr [edx], 0
jz      short loc_411E82

当前总记录:

第一个jz
call    sub_41123A
mov     [ebp+var_24], eax
mov     edx, [ebp+var_24]
cmp     dword ptr [edx], 0
jz      short loc_411E82
​
第一个call
call    ?invoke_main@@YAHXZ ; invoke_main(void)   
mov     [ebp+Code], eax
call    j____scrt_is_managed_app
movzx   ecx, al
test    ecx, ecx
jnz     short loc_411E9F
mov     edx, [ebp+Code]
​
第4个call
第一个jmp

选中函数,接着向上进行引用获取:

当前特征:

第一个jz
movzx   ecx, [ebp+var_1A]
push    ecx
call    j____scrt_release_startup_lock
add     esp, 4
call    sub_411195
mov     [ebp+var_20], eax
mov     edx, [ebp+var_20]
cmp     dword ptr [edx], 0
jz      short loc_411E51

当前总记录:

第一个jz
movzx   ecx, [ebp+var_1A]
push    ecx
call    j____scrt_release_startup_lock
add     esp, 4
call    sub_411195
mov     [ebp+var_20], eax
mov     edx, [ebp+var_20]
cmp     dword ptr [edx], 0
jz      short loc_411E51
​
第一个jz
call    sub_41123A
mov     [ebp+var_24], eax
mov     edx, [ebp+var_24]
cmp     dword ptr [edx], 0
jz      short loc_411E82
​
第一个call
call    ?invoke_main@@YAHXZ ; invoke_main(void)   
mov     [ebp+Code], eax
call    j____scrt_is_managed_app
movzx   ecx, al
test    ecx, ecx
jnz     short loc_411E9F
mov     edx, [ebp+Code]
​
第4个call
第一个jmp

接着交叉引用获取上一层引用:

特征:

第一个jmp
mov     [ebp+var_1A], al
cmp     dword_41A158, 1
jnz     short loc_411DA0
push    7
call    j____scrt_fastfail
jmp     short loc_411E01

当前记录:

第一个jmp
mov     [ebp+var_1A], al
cmp     dword_41A158, 1
jnz     short loc_411DA0
push    7
call    j____scrt_fastfail
jmp     short loc_411E01
​
第一个jz
movzx   ecx, [ebp+var_1A]
push    ecx
call    j____scrt_release_startup_lock
add     esp, 4
call    sub_411195
mov     [ebp+var_20], eax
mov     edx, [ebp+var_20]
cmp     dword ptr [edx], 0
jz      short loc_411E51
​
第一个jz
call    sub_41123A
mov     [ebp+var_24], eax
mov     edx, [ebp+var_24]
cmp     dword ptr [edx], 0
jz      short loc_411E82
​
第一个call
call    ?invoke_main@@YAHXZ ; invoke_main(void)   
mov     [ebp+Code], eax
call    j____scrt_is_managed_app
movzx   ecx, al
test    ecx, ecx
jnz     short loc_411E9F
mov     edx, [ebp+Code]
​
第4个call
第一个jmp

接着交叉引用:

特征:

第一个jnz
mov     large fs:0, eax
mov     [ebp+ms_exc.old_esp], esp
push    1
call    j____scrt_initialize_crt
add     esp, 4
movzx   eax, al
test    eax, eax
jnz     short loc_411D7B

总记录:

第一个jnz
mov     large fs:0, eax
mov     [ebp+ms_exc.old_esp], esp
push    1
call    j____scrt_initialize_crt
add     esp, 4
movzx   eax, al
test    eax, eax
jnz     short loc_411D7B
​
第一个jmp
mov     [ebp+var_1A], al
cmp     dword_41A158, 1
jnz     short loc_411DA0
push    7
call    j____scrt_fastfail
jmp     short loc_411E01
​
第一个jz
movzx   ecx, [ebp+var_1A]
push    ecx
call    j____scrt_release_startup_lock
add     esp, 4
call    sub_411195
mov     [ebp+var_20], eax
mov     edx, [ebp+var_20]
cmp     dword ptr [edx], 0
jz      short loc_411E51
​
第一个jz
call    sub_41123A
mov     [ebp+var_24], eax
mov     edx, [ebp+var_24]
cmp     dword ptr [edx], 0
jz      short loc_411E82
​
第一个call
call    ?invoke_main@@YAHXZ ; invoke_main(void)   
mov     [ebp+Code], eax
call    j____scrt_is_managed_app
movzx   ecx, al
test    ecx, ecx
jnz     short loc_411E9F
mov     edx, [ebp+Code]
​
第4个call
第一个jmp

接着交叉引用:

当前特征:

第二个call

总记录:

第二个call
​
第一个jnz
mov     large fs:0, eax
mov     [ebp+ms_exc.old_esp], esp
push    1
call    j____scrt_initialize_crt
add     esp, 4
movzx   eax, al
test    eax, eax
jnz     short loc_411D7B
​
第一个jmp
mov     [ebp+var_1A], al
cmp     dword_41A158, 1
jnz     short loc_411DA0
push    7
call    j____scrt_fastfail
jmp     short loc_411E01
​
第一个jz
movzx   ecx, [ebp+var_1A]
push    ecx
call    j____scrt_release_startup_lock
add     esp, 4
call    sub_411195
mov     [ebp+var_20], eax
mov     edx, [ebp+var_20]
cmp     dword ptr [edx], 0
jz      short loc_411E51
​
第一个jz
call    sub_41123A
mov     [ebp+var_24], eax
mov     edx, [ebp+var_24]
cmp     dword ptr [edx], 0
jz      short loc_411E82
​
第一个call
call    ?invoke_main@@YAHXZ ; invoke_main(void)   
mov     [ebp+Code], eax
call    j____scrt_is_managed_app
movzx   ecx, al
test    ecx, ecx
jnz     short loc_411E9F
mov     edx, [ebp+Code]
​
第4个call
第一个jmp

接着交叉引用:

特征:

push
mov 
call

总记录:

第一个call
push
mov 
call
​
第二个call
​
第一个jnz
mov     large fs:0, eax
mov     [ebp+ms_exc.old_esp], esp
push    1
call    j____scrt_initialize_crt
add     esp, 4
movzx   eax, al
test    eax, eax
jnz     short loc_411D7B
​
第一个jmp
mov     [ebp+var_1A], al
cmp     dword_41A158, 1
jnz     short loc_411DA0
push    7
call    j____scrt_fastfail
jmp     short loc_411E01
​
第一个jz
movzx   ecx, [ebp+var_1A]
push    ecx
call    j____scrt_release_startup_lock
add     esp, 4
call    sub_411195
mov     [ebp+var_20], eax
mov     edx, [ebp+var_20]
cmp     dword ptr [edx], 0
jz      short loc_411E51
​
第一个jz
call    sub_41123A
mov     [ebp+var_24], eax
mov     edx, [ebp+var_24]
cmp     dword ptr [edx], 0
jz      short loc_411E82
​
第一个call
call    ?invoke_main@@YAHXZ ; invoke_main(void)   
mov     [ebp+Code], eax
call    j____scrt_is_managed_app
movzx   ecx, al
test    ecx, ecx
jnz     short loc_411E9F
mov     edx, [ebp+Code]
​
第4个call
第一个jmp

接着交叉引用:

特征:

第一个jmp

总记录:

第一个jmp
​
第一个call
push
mov 
call
​
第二个call
​
第一个jnz
mov     large fs:0, eax
mov     [ebp+ms_exc.old_esp], esp
push    1
call    j____scrt_initialize_crt
add     esp, 4
movzx   eax, al
test    eax, eax
jnz     short loc_411D7B
​
第一个jmp
mov     [ebp+var_1A], al
cmp     dword_41A158, 1
jnz     short loc_411DA0
push    7
call    j____scrt_fastfail
jmp     short loc_411E01
​
第一个jz
movzx   ecx, [ebp+var_1A]
push    ecx
call    j____scrt_release_startup_lock
add     esp, 4
call    sub_411195
mov     [ebp+var_20], eax
mov     edx, [ebp+var_20]
cmp     dword ptr [edx], 0
jz      short loc_411E51
​
第一个jz
call    sub_41123A
mov     [ebp+var_24], eax
mov     edx, [ebp+var_24]
cmp     dword ptr [edx], 0
jz      short loc_411E82
​
第一个call
call    ?invoke_main@@YAHXZ ; invoke_main(void)   
mov     [ebp+Code], eax
call    j____scrt_is_managed_app
movzx   ecx, al
test    ecx, ecx
jnz     short loc_411E9F
mov     edx, [ebp+Code]
​
第4个call
第一个jmp

接着进行交叉引用:

此处显示:这个函数并未被引用,至此我们的特征提取完成。

动态调试定位main函数

接着将程序加载进x96dbg中,根据IDA获取的特征(总记录)进行动态调试定位main函数:

按下F9进入当前分析的程序相关模块;

根据特征,我们要选择第一个jmp,按下F7/F8进行跳转:

紧接着再根据特征进行跳转:

第一个call
push
mov 
call

进入第一个call;

此处需要使用F7进行步入;步入后接着根据特征找到第二个call

经过第一个call时,点击F8进行步过,第二个call时点击F7步入;接着根据特征找到第一个jnz,特征如下:

第一个jnz
mov     large fs:0, eax
mov     [ebp+ms_exc.old_esp], esp
push    1
call    j____scrt_initialize_crt
add     esp, 4
movzx   eax, al
test    eax, eax
jnz     short loc_411D7B

这边jne与jnz是一样的,这点如果有疑惑请回头看看我之前的汇编相关文章;再这边我们可以再jne出点击F2进行断点标记,接着使用f9运行程序至断点处,再点击F7/F8进行步入或者步过:

接着还是根据特征找到第一个jmp进行跳转:

第一个jmp
mov     [ebp+var_1A], al
cmp     dword_41A158, 1
jnz     short loc_411DA0
push    7
call    j____scrt_fastfail
jmp     short loc_411E01

使用F2在jmp处打断点,接着使用F9运行至断点处,但是因为上述jne在进行跳转时会跳过jmp命令的执行,所以我们这边转换思路:将光标移动至jmp跳转的位置,按下F4运行程序至jmp跳转的位置:

接着根据特征进行寻找第一个jz:

第一个jz
movzx   ecx, [ebp+var_1A]
push    ecx
call    j____scrt_release_startup_lock
add     esp, 4
call    sub_411195
mov     [ebp+var_20], eax
mov     edx, [ebp+var_20]
cmp     dword ptr [edx], 0
jz      short loc_411E51

找到后使用F2打断点,F9运行至断点处,接着使用F7/F8进行跳转,接着根据特征寻找第一个jz

第一个jz
call    sub_41123A
mov     [ebp+var_24], eax
mov     edx, [ebp+var_24]
cmp     dword ptr [edx], 0
jz      short loc_411E82

依照上述步骤进行跳转后,接着根据特征寻找第一个call

第一个call
call    ?invoke_main@@YAHXZ ; invoke_main(void)   
mov     [ebp+Code], eax
call    j____scrt_is_managed_app
movzx   ecx, al
test    ecx, ecx
jnz     short loc_411E9F
mov     edx, [ebp+Code]

F7步入;再根据特征寻找第4个call;

F7步入后,就可以定位到main函数的跳转表:

跳转表(Jump Table)是一种编程结构,用于实现程序中的多分支控制流,尤其是在处理 switch-case 语句或类似逻辑时。在汇编或机器码中,跳转表是一组内存地址的集合,每个地址对应不同代码块的入口点。程序在执行时,通过索引跳转表来选择要执行的代码块。

按下F7/F8后就可以定位到当前程序的main函数了:

因为笔者当前使用的程序是使用VS2017生成的,解决方案为Debug,解决平台为x86;即上述总记录中的特征就是VS2017\Debug x86程序的特征,若是相同环境生成的程序则都可以根据这个特征去定位到main函数。其他环境生成的程序则与本文也是一个思路。

对该方法进行总结:根据相同编译器去写一个demo(尽量简单),接着根据静态调试在demo中获取的特征,在动态调试中进行主函数定位。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言进阶】深入C语言指针:基础到进阶的跨越

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C语言 “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;C语言数据在内存中的存储 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀C语言指针进阶 &a…

Linux中安装java和tomcat(保姆级教程)

java 篇 JDK是用于开发Java应用程序的软件开发工具包。它包含了编译器、调试器、运行时环境和其他一些开发工具&#xff0c;可以帮助开发人员创建、编译、调试和部署Java应用程序。JDK提供了Java编程语言的开发工具和运行时库&#xff0c;使开发人员能够编写和执行Java代码。 …

TypeScript教程(一)之我们为什么要学TypeScript

根据软件开发设计公司 The Software House 针对 2022 年前端市场状态的调查显示&#xff0c;84% 的受访者都在使用 TypeScript&#xff0c;43% 的受访者甚至认为 TypeScript 将超越 JavaScript 成为前端开发的主要语言。TypeScript 这些年越来越火&#xff0c;可以说是前端工程…

影视会员官方渠道api对接

API对接是指两个不同的软件系统或应用程序之间通过API&#xff08;应用程序编程接口&#xff09;进行交互的过程。这种交互允许数据和功能的共享&#xff0c;而不必暴露系统的内部工作原理。在影视会员充值场景中&#xff0c;API对接具有以下几个关键特点和优势&#xff1a; 数…

【Linux系列】AWK命令使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Java面试题--JVM大厂篇之JVM大厂面试题及答案解析(4)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而我的博…

使用VBA快速创建非规则数据图表

实例需求&#xff1a;工作表中共有4组数据&#xff0c;第一组数据涵盖所有日期&#xff0c;其他3组均为断续数据。 现在需要创建如下图所示的线图&#xff0c;由于数据区域是非连续的&#xff0c;因此无法直接创建图表。 需要先将数据表按照日期对齐&#xff0c;如下图所示&…

《深度学习》 OpenCV 计算机视觉入门 (中篇)

目录 一、OpenCV函数使用 1、改变像素值 2、图像切片合并 1&#xff09;直接截取相加 2&#xff09;使用cv2.add合并 3&#xff09;此时亮度太高了&#xff0c;需要降低亮度 3、边界填充 1&#xff09;常数填充 2&#xff09;镜面反射填充&#xff08;复制交界处&…

论文仍在苦恼?分享最实用6款AI论文工具网站的汇总!

论文写作是学术研究中的重要环节&#xff0c;然而&#xff0c;面对繁重的写作任务&#xff0c;许多学者和学生常常感到力不从心。幸运的是&#xff0c;随着人工智能技术的飞速发展&#xff0c;一系列AI论文写作工具应运而生&#xff0c;极大地简化了论文写作流程&#xff0c;提…

【Mybatis】介绍+搭建+参数传递+增删改查操作+事务与连接池

目录 一. Mybatis介绍 二. Mybatis搭建 1. 导入Mybatis依赖的jar包 2. 创建Mybatis全局配置文件 3. 定义一个接口 4. 创建sql映射文件 5. 测试 * MyBatisX插件安装 三. 数据库连接池 四. 参数传递 1. 将数据封装到对象中 2. 使用Param注解 五. 数据库事务 六.…

11、Redis高级:Key设置、BigKey解决、批处理优化、集群下批处理、慢查询

Redis高级篇之最佳实践 今日内容 Redis键值设计批处理优化服务端优化集群最佳实践 1、Redis键值设计 1.1、优雅的key结构 Redis的Key虽然可以自定义&#xff0c;但最好遵循下面的几个最佳实践约定&#xff1a; 遵循基本格式&#xff1a;[业务名称]:[数据名]:[id]长度不超过…

【golang学习之旅】复杂数据类型——切片(slice)

系列文章 【golang学习之旅】使用VScode安装配置Go开发环境 【golang学习之旅】报错&#xff1a;a declared but not used 【golang学习之旅】Go 的基本数据类型 【golang学习之旅】深入理解字符串string数据类型 【golang学习之旅】go mod tidy 【golang学习之旅】记录一次 p…

OpenHarmony实战开发: unittest单元测试的编写

背景 程序开发与单元测试二者密不可分&#xff0c;是每个开发人员的基本业务。当功能性代码开发完成后&#xff0c;要自行测试其是否满足设计预期&#xff0c;如果不满足就要回去完善代码&#xff0c;满足则可以提交功能代码及测试用例。 测试用例的另一个重要作用就是阅读者…

【USRP】 Link 16 战术数据链 实训系统

Link 16 战术数据链 实训系统 一、基于USRP的Link16平台简介1、整体架构2、JTIDS终端架构3、平台特点3.1、提高技术理解与应用能力3.2、培养创新思维与问题解决能力3.3、加强跨学科融合与团队合作 4、平台建设4.1、基础理论教学模块4.2、LabVIEW 算法模块4.3、USRP仿真模块4.4、…

如何用ChatGPT快速提升论文质量:实用技巧大公开

近年来&#xff0c;人工智能技术急速发展&#xff0c;尤其在自然语言处理领域取得了显著进展。作为OpenAI推的一款先进的语言模型&#xff0c;ChatGPT不仅在日常交互中表现卓越&#xff0c;也在专业写作领域显示了巨大的潜力。本文旨在详尽介绍如何高效运用ChatGPT来撰写和修改…

JWT双令牌认证实现无感Token自动续约

概念 JSON Web Token (JWT)是一个开放标准(RFC 7519) &#xff0c;它定义了一种紧凑和自包含的方式&#xff0c;用于作为 JSON 对象在各方之间安全地传输信息。此信息可以进行验证和信任&#xff0c;因为它是经过数字签名的。JWT 可以使用机密(使用 HMAC 算法)或使用 RSA 或 E…

LeetCode --- 411周赛

题目列表 3258. 统计满足 K 约束的子字符串数量 I 3259. 超级饮料的最大强化能量 3260. 找出最大的 N 位 K 回文数 3261. 统计满足 K 约束的子字符串数量 II 一、统计满足K约束的子字符串数量I 这种要求满足区间内某种性质的题&#xff0c;一般都可以用滑动窗口来做。这题…

STM32MP157_uboot_初次编译

STM32MP157_uboot_初次编译 前言&#xff1a; 为了快速入门&#xff0c;这边选择直接使用正点原子提供的uboot源码&#xff0c;先体验一下uboot的编译流程&#xff0c;为后面的移植原厂uboot做环境准备。 1、获取正点原子的uboot源码&#xff08;复制到虚拟机里面&#xff09; …

均值漂移算法原理及Python实践

均值漂移算法&#xff08;Mean Shift Algorithm&#xff09;是一种基于密度的非参数聚类算法&#xff0c;其原理主要基于核密度估计和梯度上升方法。以下是均值漂移算法原理的详细解析&#xff1a; 1. 基本思想 均值漂移算法的基本思想是通过迭代地更新数据点的位置&#xff…

Android APK优化系列瘦身篇:实战一个APK从11MB压缩到4MB,APK无用资源去除与代码压缩、混淆,瘦身维度的选型分析

目录&#xff1a; 为什么要进行APK瘦身呢&#xff1f;APK瘦身主要是瘦身哪些呢&#xff1f; a. 优化resources.arsc&#xff1a; b. res优化 c. lib优化 d. 资源优化&#xff0c;代码混淆和压缩总结 一、为什么要进行APK瘦身呢&#xff1f; 减少下载时间和流量消耗&#xff1…