【Win32】资源文件(对话框),逆向对话框回调函数,消息断点(附带恶意软件源码)

之前在学习windows编程的时候已经写过对话框的创建了,其中包括了对话框的分类,原理等等,大家可以去看一下:【windows编程之对话框】对话框原理,对话框的创建。原理今天就讲的不是很多了,直接给大家给出步骤+代码,如果大家不懂原理的话,可以去那一片博客中看一下。

我们今天主要讲解资源文件(对话框)的创建步骤,以及在逆向过程中很有用的一种断点方式:消息断点。

一.创建对话框

    1. 可视化界面绘制对话框
      绘制对话框
    1. 我们在可视化界面绘制对话框之后,实际上就可以使对话框显示了:
      我们使用DialogBox函数即可让会话框显示:
DialogBox(hInst, (LPCWSTR)IDD_DIALOG1, NULL, DiaLogProc);

对话框

实际上这里是模式对话框,是根据返回值退出的,具体可以看着一篇博客:【windows编程之对话框】对话框原理,对话框的创建。

二.获取文本框内容

我们的应用程序,很多时候都需要获取文本框中的内容做判断,比如判断密码是否正确等等,那么我们怎么获取文本框的内容呢?

  • 1.获取文本框句柄
    使用GetDlgItem()函数,可以获取指定文本框的句柄,MSDN官方文档解释该函数
HWND GetDlgItem(
  [in, optional] HWND hDlg,
  [in]           int  nIDDlgItem
);

参数解释:

  • hDlg:对话框句柄
  • nIDDlgItem:要检索的控件标识符
    返回值:HWND,指定对话框的窗口句柄

在获取到文本框句柄后,就可以获取文本框内容了:

    1. 获取文本内容:
      使用GetWindowText()函数,MSDN官方文档解释该函数
int GetWindowTextA(
  [in]  HWND  hWnd,
  [out] LPSTR lpString,
  [in]  int   nMaxCount
);

参数解释:

  • hWnd:对话框句柄,我们上一个函数的返回值
    lpString:注意这是一个OUT类型的参数,接收文本的缓冲区,在使用函数之前,我们需要申请一块内存,用于接收文本内容
    nMaxCount:复制到缓冲区的最大字符数,包括NULL字符。

三.消息断点(附对话框回调函数的寻找)

我们之前一直是找到回调函数之后,根据消息堆栈对应用程序进行条件断点,但是有很多情况下,应用程序都很复杂,我们很难找到回调函数,那今天我们就来带领大家学习一种新的断点方式:消息断点,我们不需要找到回调函数当应用程序接收到消息后,会自动断点,以便我们的逆向工作。
这里先给大家一个我自己编写的应用程序,我们后续做消息断点就使用这个应用程序,大家注意生成应用程序的时候发布为Release版本,因为大多数Debug版本的程序,寻址方式与Release版本不同(Debug大多使用ebp寻址,而Release版大多使用esp寻址,而我们平时做逆向肯定做的是Release版):

很好用的一个软件(肯定不是恶意软件)

// 破解一切.cpp : 定义应用程序的入口点。
//

#include "framework.h"
#include "破解一切.h"
#include "Resource.h"
int x = 0;
int y = 0;
#define MAX_LOADSTRING 100
int style;
// 全局变量:
HINSTANCE hInst;                                // 当前实例
WCHAR szTitle[MAX_LOADSTRING]=TEXT("好用的破解软件");                  // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING]=TEXT("main");            // 主窗口类名

// 此代码模块中包含的函数的前向声明:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK DiaLogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    style = nCmdShow;
    // TODO: 在此处放置代码。

    // 初始化全局字符串
    MyRegisterClass(hInstance);

    // 执行应用程序初始化:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MY));

    MSG msg;

    // 主消息循环:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}



//
//  函数: MyRegisterClass()
//
//  目标: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MY));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_MY);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    WNDCLASSEXW sc = { 0 };
    sc.style = CS_HREDRAW | CS_VREDRAW;
    sc.lpfnWndProc = WndProc;
    sc.cbClsExtra = 0;
    sc.cbWndExtra = 0;
    sc.hInstance = hInstance;
    sc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MY));
    sc.hCursor = LoadCursor(nullptr, IDC_ARROW);
    sc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    sc.lpszMenuName = MAKEINTRESOURCEW(IDC_MY);
    sc.lpszClassName = szWindowClass;
    sc.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

//
//   函数: InitInstance(HINSTANCE, int)
//
//   目标: 保存实例句柄并创建主窗口
//
//   注释:
//
//        在此函数中,我们在全局变量中保存实例句柄并
//        创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // 将实例句柄存储在全局变量中

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,x,y,
      CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }
   DialogBox(hInst, (LPCWSTR)IDD_DIALOG1, NULL, DiaLogProc);
   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);
   return TRUE;
}

//
//  函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目标: 处理主窗口的消息。
//
//  WM_COMMAND  - 处理应用程序菜单
//  WM_PAINT    - 绘制主窗口
//  WM_DESTROY  - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            switch (wmId)
            {
            case IDM_ABOUT:
               DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            case ID_32774: {
                      int i, j;
                      for (i = 0; i < 100; x += 200) {
                           for (j = 0; j < 30; y += 36) {
                                 HWND MyhWnd = CreateWindowW(szWindowClass, (LPCWSTR)TEXT("谁叫你乱点!!!"), WS_OVERLAPPEDWINDOW, x, y,
                                       300, 40, nullptr, nullptr, hInst, nullptr);
                                 ShowWindow(MyhWnd, style);
                                 // Sleep(100);
                                 j++;
                                 }
                            y = 0;
                            i++;
                            }
                      break;
                  }
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 在此处添加使用 hdc 的任何绘图代码...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

//对话框处理函数
INT_PTR CALLBACK DiaLogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
    case WM_COMMAND: {
        if (wParam == IDR_ACCELERATOR1) {
            EndDialog(hwndDlg, 100);
            break;
        }
        if (wParam == IDI_SMALL) {
            int i, j;
            MessageBox(hwndDlg, TEXT("你确定要退出码?"), NULL, MB_OK);
            for (i = 0; i < 100; x+=200) {
                for (j = 0; j < 30; y += 36) {
                    HWND MyhWnd = CreateWindowW(szWindowClass, (LPCWSTR)TEXT("妈的让你退出!!!"), WS_OVERLAPPEDWINDOW, x, y,
                                        300,40, nullptr, nullptr, hInst, nullptr);
                    ShowWindow(MyhWnd, style);
                   // Sleep(100);
                    j++;
                }
                y = 0;
                i++;
            }
            PostMessage(NULL, WM_QUIT, 0, 0);
            EndDialog(hwndDlg, 100);
            break;
        }
    }
    case WM_SYSCOMMAND: {
        PostQuitMessage(0);
    }
    }
    return FALSE;
}

相信稍微有点基础的都能看出来我编写的这个应用程序不是做什么好事的。
由于我不是做开发的,所以代码写的很烂,但是我们做底层的,了解了基本原理就可以了,这个软件我会在后续做很多改进,大家可以关注我的文章,待程序改进好之后,大家可以去“玩儿玩儿”。

那我们就是用这个应用程序来给大家讲解消息断点:

  • 首先,我们来看看这个应用程序:
    当打开之后,会弹出这样一个对话框,提示输入账号和密码:
    1
  • 当然,你肯定不知道账号和密码,当然我也不知道,因为我还没写账号密码的判定,那肯定会去点“直接破解进去”这个按钮了,当点击这个按钮之后,应用程序主窗口就弹出来了:
    2
    里面有一个释放病毒,实际上不是什么病毒,我们今天不研究这个,这里就不细细讲解了,我们来看一下另一个按钮:取消按钮,这里就是个坑了:
  • 当我们点击取消按钮的时候,会弹出一个提示框:
    3
    这里就是一个坑了,不管你点击哪里,都会让这个恶意代码运行,我们来看看运行之后是什么样的:
    4
    不管点击哪里,都会让这段代码运行,会弹出满屏的窗口,大家也不用担心,应用程序会自动退出。

对话框回调函数

上一篇文章带领大家找过主窗口的回调函数了,相信大家对回调函数一定不陌生了,对话框的回调函数实际上和主窗口的回调函数一样。
我们来看看上文中讲的显示对话框的函数:

DialogBox(hInst, (LPCWSTR)IDD_DIALOG1, NULL, DiaLogProc);

我们看到,第四个参数就是对话框的回调函数。
在调用这个函数的时候,肯定会将参数压栈,那我们直接到堆栈窗口找函数地址,就能很容易地找到回调函数了:
这里实际上OD已经帮我们解析出来了很多东西:
DialogBox
我们能很清晰地看到DialogBox()函数的几个参数,我们直接根据函数地址跟到回调函数就找到了,我们在对话框回调函数中也可以做条件断点

那么我们需要逆向出当点击了取消按钮后底层做了什么工作

  • 我们首先来看一下到底是哪个消息触发了这个消息,我们前面将结果消息类型了,我们也知道是当鼠标左键点击之后触发了消息,那么到底是鼠标左键按下(WM_LBUTTONDOWN),还是鼠标左键弹起(WM_LBUTTONUP)时呢?
  • 这里大家可以去试一下,当鼠标左键按下的时候,代码很明显没有运行,当我们鼠标左键放开的时候,能很直观地看到代码运行了。
  • 经过上面的操作,我们已经知道了我们要做断点的消息是什么了

我们知道了要做断点的条件,根据上一篇文章讲解的,直接做条件断点就可以了:

  • 堆栈窗口:
    堆栈窗口
    我们能看到回调函数地址,跟到反汇编:
    回调函数
    大家可以看到,这个就是对话框回调函数了,我们根据上篇文章的讲解做断点即可。

消息断点讲解

我们在逆向比较简单的应用程序的时候,可以直接找窗口的回调函数,那么当我们要逆向别人的程序的时候,别人发布的应用程序肯定不会像我这个应用程序这样简单,他们的有应用程序很复杂,那么我们该逆向呢?

我们需要逆向出当点击了取消按钮后底层做了什么工作:

  • 我们首先来看一下到底是哪个消息触发了这个消息,我们前面将结果消息类型了,我们也知道是当鼠标左键点击之后触发了消息,那么到底是鼠标左键按下(WM_LBUTTONDOWN),还是鼠标左键弹起(WM_LBUTTONUP)时呢?
  • 这里大家可以去试一下,当鼠标左键按下的时候,代码很明显没有运行,当我们鼠标左键放开的时候,能很直观地看到代码运行了。
  • 经过上面的操作,我们已经知道了我们要做断点的消息是什么了(WM_LBUTTONDOWN),那么我们就来到OllyDbg来做一下消息断点:

1. 找到按钮:

我们可以看到在OD中有这样一行按钮:
OD按钮
我们先让应用程序运行,对话框运行之后,我们点击这里的W按钮,就可以看到很多窗口句柄等信息:
窗口信息
这里有当前应用程序显示出来的所有窗口,我们发现ClassProc已经帮我们找到了定义窗口类的时候写进去的回调函数,我们可以鼠标右键单击我们想要找的窗口,点击跟随ClassProc,就可以找到系统定义的回调函数了。注意这是系统定义的默认回调函数!!!
跟随ClassProc
我们在这里就可以设置消息断点了:
消息断点
当我们设置了消息断点之后,我们发现有两处都做了断点:
两处消息断点
这是因为不管是哪个按钮,都会调用系统定义的同一个回调函数。
我们来点击按钮测试一下:
应用程序断点
这时候我们发现应用程序断在了系统提供的默认的回调函数这里,但是我们想让它停在我们自己写的回调函数,该怎样操作?
你也可以一直单步跟下去,但是这样真的太浪费时间了,而且没有人会这样做。
我们发现,系统提供的回调函数,地址都是7…,说明在应用程序领空,应该是在DLL领空。
这里给大家教一种技巧:大家想想,系统提供的默认回调函数,是不是迟早会调用我们写的那个回调函数?那么我们来到Memory map窗口:
Mmeory map窗口
大家都是学过PE结构的人,相信大家都能理解上述的data,rdata,rsrc几段吧?那么我们在代码断下一个访问断点:
访问断点
这样呢,当访问应用程序的代码段的时候,也就是系统提供的默认回调函数调用我们自己编写的回调函数的时候,就会在我们自己写的那个回调函数上做断点了。
今天的文章就分享到这里,如果大家发现文章中有错误之处或者是个人理解不到位的地方,还请大家指出来,我会非常虚心地学习,希望大家共同进步!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《Go专家编程(第2版)》书评

首先感谢官方的肯定&#xff0c;让我在【图书活动第四期】的活动中获得了《Go专家编程(第2版)》这本书&#xff0c;以下是从我的观点对这本书的书评 文章目录 前言书籍部分读者评价总结 前言 很高兴有机会写一篇关于《Go专家编程&#xff08;第2版&#xff09;》的书评。大致读…

APIO2023 游记

GDOI 和 GDKOI 的游记都咕咕咕了&#xff0c;而且都炸了&#xff0c;APIO 的游记提前发&#xff0c;就是要破釜沉舟。 我是线上选手。 Day -7 我们原题检测&#xff0c;阿克了&#xff0c;毕竟是原题&#xff0c;虽然有两道博弈论黑题确实挺毒瘤的。 教练让我做 APIO2012 的…

产品经理必读丨如何找准产品定位?

我们都知道&#xff0c;当一款新产品开始立项之前&#xff0c;势必需要经过谨慎的市场调研才能整合资源启动新的项目&#xff0c;但除此之外&#xff0c;作为产品经理还需要做好一件关键的事情——找准产品在市场中的定位。 什么是产品定位 百度百科对产品定位的解释是非常准确…

【STM32】基础知识 第十六课 窗口看门狗 WWDG 深入浅出

【STM32】基础知识 第十六课 窗口看门狗 WWDG 深入浅出 概述窗口看门狗 (WWDG)WWDG_SR 状态寄存器WWDG 配置与使用使用 WWDG 进行故障检测案例 概述 在嵌入式开发中, 可靠性和稳定性是至关重要的. 这就是为什么许多单片机, 比如 STM32, 提供了窗口看门狗 (Window Watchdog, WW…

k8s部署dashboard

1.查看k8s集群版本 kubectl version 2.在github中查看k8s对应的ui版本 Releases kubernetes/dashboard GitHub 3.下载对应版本的dashboard yaml文件 wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml 4.更改yaml文件配置 …

HTB-Agile

HTB-Agile 信息收集80端口漫长的兔子洞之旅 立足www-data -> corumcorum -> edwardsedwards -> root 信息收集 80端口 漫长的兔子洞之旅 我注意到系统为我分配了一个session&#xff0c;是以eyj开头的。 拿去jwt.io看看。 额&#xff0c;可能后面会用先留在这&#…

多线程-程序、进程、线程与并行、并发的概念

多线程 本章要学习的内容&#xff1a; 专题1&#xff1a;相关概念的理解专题2&#xff1a;多线程创建方式一&#xff1a;继承Thread类专题3&#xff1a;多线程创建方式二&#xff1a;实现Runnable接口专题4&#xff1a;Thread类的常用方法专题5&#xff1a;多线程的优点、使用…

5月编程排行榜出炉,最佳编程语言是谁?

技术的发展日新月异&#xff0c;作为开发者&#xff0c;应该时刻关注这些变化&#xff0c;不断学习才能跟上时代步伐。 编程语言层出不穷&#xff0c;关于“ 最佳编程语言 ”的争论也从未停止&#xff0c;网友们各抒己见...... 网友A&#xff1a; 人生苦短&#xff0c;我选Pyt…

软件测试工程师如何提高自己的竞争力?

案例一来自我们的资深功能测试工程师招聘。当时&#xff0c;有一位拥有近 9 年测试经验的资深测试候选人&#xff0c;我对他的简历还是比较满意的&#xff0c;所以就安排了面谈。但是&#xff0c;在聊的过程中我很快发现&#xff0c;这位候选人绝大多数的测试经验积累都“强”绑…

利用 DynamoDB 和 S3 结合 gzip 压缩,最大化存储玩家数据

前言 一些传统游戏架构中&#xff0c;采用 MySQL 存储玩家存档数据&#xff0c;利用分库分表分散单库单表的存储和性能压力&#xff0c;从而达到支持更多玩家的目的。随着数据量增长&#xff0c;数据表中 varchar 类型已经无法满足游戏中单字段的存储需求&#xff0c;而 blob …

去哪儿酒店数据下载

字段内容包含&#xff1a; id int(11) NOT NULL AUTO_INCREMENT, hotelid varchar(50) DEFAULT NULL, url varchar(200) DEFAULT NULL, hotelname2 varchar(100) DEFAULT NULL, name varchar(100) DEFAULT NULL, province varchar(50) DEFAULT NULL, d…

RabbitMQ集群安装

RabbitMQ集群安装 1.前言 OS: CentOS Linux release 7.9.2009 (Core) 机器: IPnodecpu内存存储10.106.1.241max-rabbitmg-018 核16 G100 G10.106.1.242max-rabbitmg-028 核16 G100 G10.106.1.243max-rabbitmg-038 核16 G100 G 因为操作系统版本是 centos7&#xff0c;所以…

跟着chatGPT学习:kubernetes中的Reflector、list-watcher、informer等概念

以下是我跟chatGPT学习kubernetes中Reflector、list-watcher、informer等的概念的过程 不敢保证chatGPT回答的百分之百准确。但是&#xff0c;确实帮助我了我理解&#xff01; 最终学习的是下面的图&#xff0c; 1、在kubernetes中Reflector原理&#xff1f; 在Kubernetes…

【操作系统】线程简介

线程简介 线程概念 在许多经典的操作系统教科书中&#xff0c;总是把进程定义为程序的执行实例&#xff0c;它并不执行什么, 只是维护应用程序所需的各种资源&#xff0c;而线程则是真正的执行实体。 所以&#xff0c;线程是轻量级的进程&#xff08;LWP&#xff1a;light w…

短视频矩阵源码-智能剪辑生成技术数值组如何编程?

短视频混剪生成时长逻辑一般采用根据用户设定的总时长、视频数量、时长比例等参数计算出每个视频在混剪中所占的时长&#xff0c;然后根据视频的总时长与所占比例来划分每个视频在混剪中的时长&#xff0c;最后将各个视频拼接起来形成混剪视频。此算法可以进行灵活的时长调整和…

RabbitMQ 小白教程,从安装到使用

主要内容 AMQP简介 RabbitMQ简介 RabbitMQ原理 Erlang安装 安装RabbitMQ RabbitMQ账户管理 交换器 学习目标 知识点要求AMQP简介掌握RabbmitMQ简介掌握RabbitMQ原理掌握Erlang安装掌握安装RabbitMQ掌握RabbitMQ账户管理掌握交换器掌握 一、 AMQP简介 1 AMQP是什么?…

【Midjourney】Midjourney 连续性人物创作 ④ ( 使用 URL + Seed 随机种子生成连续性的人物 )

文章目录 一、生成图片并获取 Seed二、使用 URL Seed 随机种子生成连续性的人物 使用 URL 链接 和 Seed 随机种子 生成连续性人物 , 必须先生成一组图片 , 然后按 U 按钮 , 选择一张大图 , 之后所有的连续性人物图片都基于该图片进行生成 ; 使用 URL Seed 随机种子生成连续性…

Flink学习——状态编程

目录 一、Flink中的状态 二、状态编程 (一)ValueState案例——判断传感器的数据 1.代码实现 2.端口进行传输数据 3.运行结果 (二)ListState (三)MapState案例——比较学生每次考试成绩 1.代码实现 2.端口传输学生成绩 3.运行结果 (四)ReducingState 一、Flink中的状…

DETR3D 论文学习

1. 解决了什么问题&#xff1f; 对于低成本自动驾驶系统&#xff0c;仅凭视觉信息进行 3D 目标检测是非常有挑战性的。目前的多相机 3D 目标检测方法有两类&#xff0c;一类直接对单目图像做预测&#xff0c;没有考虑 3D 场景的结构或传感器配置。这类方法需要多步后处理&…

C语言小游戏——扫雷

前言 结合前边我们所学的C语言知识&#xff0c;本期我们将使用C语言实现一个简单的小游戏——扫雷 目录 前言 总体框架设计 多文件分装程序 各功能模块化实现 初始化棋盘 棋盘打印 埋雷 判赢与排雷 游戏逻辑安排 总结 总体框架设计 和三子棋相同&#xff0c;游戏开始时…