53.网游逆向分析与插件开发-游戏反调试功能的实现-通过内核信息检测调试器

码云地址(master分支):https://gitee.com/dye_your_fingers/sro_-ex.git

码云版本号:b44fddef016fc1587eda40ca7f112f02a8289504

代码下载地址,在 SRO_EX 目录下,文件名为:SRO_Ex-通过内核信息检测调试器.zip

链接:https://pan.baidu.com/s/1W-JpUcGOWbSJmMdmtMzYZg

提取码:q9n5

--来自百度网盘超级会员V4的分享

HOOK引擎,文件名为:黑兔sdk.zip

链接:https://pan.baidu.com/s/1IB-Zs6hi3yU8LC2f-8hIEw

提取码:78h8

--来自百度网盘超级会员V4的分享

以 检测调试器-CSDN博客 它的代码为基础进行修改

BeingDebugged的调试检测基本上用处不大,OD很多插件可以过掉,它核心根本的问题就是数据毕竟是在用户态(软件调试器设计的基本原理-CSDN博客通过它软件调试器设计的基本原理-CSDN博客里面写的peb的位置,然后通过汇编对它BeingDebugged这个字段进行修改成0就可以轻松破解,就一次性把 IsDebuggerPresent函数、CheckRemoteDebuggerPresent函数 这两个函数全部都过掉了),现在为了加强检测要去访问内核的数据,原则上用户态取不到内核的数据,好在微软还是留了口子,一个未公开的API(导出表里有文档里没有这样的函数就叫做未公开的API,ntdll文件里有很多未公开API),如图1。

图1:其中信息类型是一个枚举非常大,在不同操作系统下有不同的变化,我们要用的到它的值是0x07 DebugPort、0x1 EDebugObjectHandle、0x1F DebugFlags(它的值是0表示被调试,1表示未被调试),这样一般的调试器就躲不掉了,NtQueryInformationProcess能取很多内核中的数据,使用详情看下方代码

9a060b31a72f4731a6615cb31acb286b.png

24a825db3b8249d0b8332f7d77426146.jpg

 

GameEx.cpp文件的修改,修改了ExitGame函数

#include "pch.h"
#include "GameEx.h"
#include "htdHook2.h"
#include "GameProtect.h"

extern int client;
extern GameProtect* _protect;
extern unsigned _stdcall GetFunctionAddress(int index);
htd::hook::htdHook2 hooker;

#include <windows.h>
#include<stdio.h>
#include<TlHelp32.h>

/**
  声明要拦截的函数地址
*/
auto h = GetModuleHandle(NULL);
DWORD address = (DWORD)h;
DWORD addRExit = address + 0x88C77E;

size_t 被拦截修改的函数的地址 = (size_t)addRExit;

LONG NTAPI 异常回调(struct _EXCEPTION_POINTERS* Excep)
{
    printf("异常回调1\n");

    /**
      判断出异常的地方是否为 我们修改的地方
    */
    if ((size_t)Excep->ExceptionRecord->ExceptionAddress == 被拦截修改的函数的地址) {

        //const char* szStr = "nei Rong Bei Xiu Gai";
        //*(DWORD*)(Excep->ContextRecord->Esp + 0x8) = (DWORD)szStr;
        //szStr = "biao Ti Bei Xiu Gai";
        //*(DWORD*)(Excep->ContextRecord->Esp + 0xC) = (DWORD)szStr;

        AfxMessageBox(L"游戏退出!");
        DWORD* _esp = (DWORD*)Excep->ContextRecord->Esp;
        DWORD _val = _esp[1];

        if (_val == 0x1035D0C) {
            AfxMessageBox(L"游戏退出2!");
            auto hMuls = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, L"system_seamp");
            if (hMuls) ReleaseSemaphore(hMuls, 1, 0);
            ExitProcess(0);
        }

        Excep->ContextRecord->Eip = *(DWORD *) Excep->ContextRecord->Esp;
        Excep->ContextRecord->Esp += 8;
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    else {
        /**
          防止被其它地方修改了函数地址
        */
        Excep->ContextRecord->Dr0 = 被拦截修改的函数的地址;
        Excep->ContextRecord->Dr7 = 0x405;
        return EXCEPTION_CONTINUE_SEARCH;
    }

}

VOID 设置线程的dr寄存器(HANDLE 线程句柄) {

    printf("设置线程的dr寄存器1\n");
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_ALL;
    GetThreadContext(线程句柄, &ctx);
    ctx.Dr0 = 被拦截修改的函数的地址;
    ctx.Dr7 = 0x1;
    SetThreadContext(线程句柄, &ctx);
    printf("设置线程的dr寄存器2\n");
}

VOID 使用dr寄存器拦截修改函数() {
    printf("使用dr寄存器拦截修改函数1\n");
    HANDLE 线程快照句柄 = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
    if (线程快照句柄 == INVALID_HANDLE_VALUE) {
        printf("线程快照创建失败");
        return;
    }

    THREADENTRY32* 线程结构体 = new THREADENTRY32;
    线程结构体->dwSize = sizeof(THREADENTRY32);

    /**
      Thread32First获取快照中第一个线程
       返回值bool类型
    */
    // Thread32First(线程快照句柄, &线程结构体);

    HANDLE 线程句柄 = NULL;

    printf("使用dr寄存器拦截修改函数2\n");

    /**
      Thread32Next获取线程快照中下一个线程
    */
    while (Thread32Next(线程快照句柄, 线程结构体))
    {
        if (线程结构体->th32OwnerProcessID == GetCurrentProcessId()) {
            printf("使用dr寄存器拦截修改函数3\n");
            线程句柄 = OpenThread(THREAD_ALL_ACCESS, FALSE, 线程结构体->th32ThreadID);
            printf("使用dr寄存器拦截修改函数4\n");
            设置线程的dr寄存器(线程句柄);
            printf("使用dr寄存器拦截修改函数5\n");
            CloseHandle(线程句柄);
        }
    }
}

bool ExitGame(HOOKREFS2) {
    if (_protect->CheckDebugByNT())AfxMessageBox(L"检测到了DEBUG程序的存在");
	// AfxMessageBox(L"游戏退出2222!");
	DWORD* _esp = (DWORD*)_ESP;
	DWORD _val = _esp[1];

	if (_val == 0x1035D0C) {
		// AfxMessageBox(L"游戏退出!");
		auto hMuls = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, L"system_seamp");
		if (hMuls) ReleaseSemaphore(hMuls, 1, 0);
        client--;
		ExitProcess(0);
	}
	return true;
}

GameEx::GameEx()
{
	// AfxMessageBox(L"注册hook!");
	// auto h = GetModuleHandle(NULL);
	// DWORD address = (DWORD)h;
    // DWORD* addRExit = (DWORD*)(address + 0x88C77E);
    /**addRExit = 0;*/
	// CString txt;
    // txt.Format(L"addRExit[0]D:%d,addRExit[0]X:%X,addRExit:%X", addRExit[0], addRExit[0], addRExit);
    // AfxMessageBox(txt);

    // hooker.SetHook((LPVOID)addRExit, 3, ExitGame);
    //AddVectoredExceptionHandler(1, 异常回调);
    //设置线程的dr寄存器(GetCurrentThread());
}

void GameEx::InitInterface()
{
    unsigned addr =  GetFunctionAddress(0);
    hooker.SetHook((LPVOID)(addr + 0x30 - 2), 0x3, ExitGame);
    hooker.SetHook((LPVOID)(addr + 0x51 - 2), 0x3, ExitGame);

}

GameProtect.h文件的修改,新加 ZwSetInformationThread变量、NtQueryInformationProcess变量、hNtdll变量、HProcess变量、InitApiEx函数、CheckDebugByPEB函数、CheckDebugByNT函数,ZwSetInformationThreadPtr函数指针、NtQueryInformationProcessPtr函数指针

#pragma once

/*
	第一个参数是线程的id
	第二个参数是给一个0x11,0x11就代表把线程设置成隐匿的状态

*/
typedef NTSTATUS(NTAPI* ZwSetInformationThreadPtr)(DWORD, DWORD, DWORD, DWORD);
typedef NTSTATUS(NTAPI* NtQueryInformationProcessPtr)(HANDLE, DWORD, PVOID, ULONG, PULONG);

typedef struct CODEContext {
	unsigned start;
	bool hide;
	unsigned short r_count; // 重定位信息,call、jmp这样的需要重定位的数据的个数
	unsigned short len; // 代码的长度
}*PCODEContext;

typedef struct HIDE_CODE {
	unsigned Start;
	unsigned Index;
}*PHIDE_CODE;

class GameProtect
{
public:
	unsigned GetAddress(int index);
	unsigned GetAddressHide(unsigned _eip);
	GameProtect();
private:
	int _HideCount = 0;
	PHIDE_CODE _HideCode;
	LPVOID* _EntryCode;
	int _CodeCount{};
	char _EntryCodeEx[8]{(char)0xE8, (char)0x00, (char)0x00,(char)0x00, (char)0x00, (char)0xFF, (char)0xE0};
	bool MulCheckBySempore();
public:
	void  CheckMult(); // 检测有没有多开
public:
	bool InitEntryCode(); // 释放保护代码数据
	BOOL CheckDebugByPEB(); // true表示存在debug
	BOOL CheckDebugByNT(); // true表示存在debug
private:
	void InitApiEx();
	HMODULE hNtdll;
	ZwSetInformationThreadPtr ZwSetInformationThread;
	NtQueryInformationProcessPtr NtQueryInformationProcess;
	void AntiDebug();
	HANDLE HProcess;
};

GameProtect.cpp文件的修改,修改了AntiDebug函数、GameProtect函数,新加 InitApiEx函数、CheckDebugByPEB函数、CheckDebugByNT函数,删除了 ZwSetInformationThreadPtr函数指针

#include "pch.h"
#include "GameProtect.h"

GameProtect* _protect;
extern int client;

unsigned _stdcall GetFunctionAddress(int index) {
	
	//CString txt;
	//txt.Format(L"接收到:%d", index);
	//AfxMessageBox(txt);
	
	return _protect->GetAddress(index);
}

unsigned GameProtect::GetAddress(int index)
{
	//CString txt;
	unsigned result = (unsigned)this->_EntryCode[index];

	//txt.Format(L"index:%d获取地址:%x", index, result);
	// AfxMessageBox(txt);
	return result;
}

unsigned GameProtect::GetAddressHide(unsigned _eip)
{
	//CString txt;
	for (int i = 0; i < _HideCount; i++){
		if (_HideCode[i].Start == _eip) {
			return (unsigned)_EntryCode[_HideCode[i].Index];
		}
	}

	//txt.Format(L"index:%d获取地址:%x", index, result);
	// AfxMessageBox(txt);
	return 0;
}

GameProtect::GameProtect()
{
	//AfxMessageBox(L"122222");
	InitApiEx();
	_protect = this;
	// 为了后续内容 这里先注释掉
	// AntiDebug();
	if (!InitEntryCode()) {
		AfxMessageBox(L"程序加载失败!");
		ExitProcess(0);
	}
	CString txt;
	txt.Format(L"111");
	AfxMessageBox(txt);
}

bool GameProtect::MulCheckBySempore()
{
	auto hMuls = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, L"system_seamp");
	
	if (!hMuls) {
		hMuls = CreateSemaphore(0, 3, 3, L"system_seamp");
	}

	if (WaitForSingleObject(hMuls, 0) == WAIT_TIMEOUT) return true;

	return false;
}

void GameProtect::CheckMult()
{
	
	if (MulCheckBySempore()) {
		AfxMessageBox(L"当前客户端启动已经超过最大数量");
		ExitProcess(0);
	}
}

LONG _stdcall PVEHandl(PEXCEPTION_POINTERS val) {
	if (val->ExceptionRecord->ExceptionCode == STATUS_BREAKPOINT) {
		unsigned _eip = val->ContextRecord->Eip;
		unsigned _eipReal = _protect->GetAddressHide(_eip);
	/*	CString txt;
		txt.Format(L"PVEHandl当前地址:%X", _eipReal);
		AfxMessageBox(txt);*/
		if (_eipReal) {
			val->ContextRecord->Eip = _eipReal;
			return EXCEPTION_CONTINUE_EXECUTION; // 继续执行
		}
		else return EXCEPTION_CONTINUE_SEARCH;
	}
	return EXCEPTION_CONTINUE_SEARCH;
}

bool GameProtect::InitEntryCode()
{

	TCHAR FileModule[0x100];
	GetModuleFileName(NULL, FileModule, 0x100);
	auto hFile = CreateFile(FileModule, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	if (hFile == INVALID_HANDLE_VALUE) {
		return false;
	}

	DWORD dRead;
	DWORD filelen = GetFileSize(hFile, &dRead);

	char* _data = new char[filelen];

	if (ReadFile(hFile, _data, filelen, &dRead, NULL)) {
		char* _dataBegin = _data;
		unsigned* _uRead = (unsigned*)(_data + filelen - 4);

		for (int i = 0; i < filelen - _uRead[0]-4; i++) // 解密数据
		{
			_data[_uRead[0] + i] = _data[_uRead[0] + i] ^ 0x23;
		}

		//unsigned* _uRead = (unsigned*)_data[filelen - 4];
		filelen = _uRead[0];// 真实的文件大小
		_uRead = (unsigned*)(_data + filelen);
		unsigned code_count = _uRead[0];
		_data = _data + filelen + sizeof(code_count);

		PCODEContext _ContextArrys = (PCODEContext)_data;
	
		for (int i = 0; i < code_count; i++)
		{
			if (_ContextArrys[i].hide) {
				_HideCount++;
			}
		}

		if (_HideCount > 0) {
			AddVectoredExceptionHandler(1, PVEHandl);
			_HideCode = new HIDE_CODE[_HideCount];
			_HideCount = 0;
		}

		_data = _data + sizeof(CODEContext) * code_count;
		_EntryCode = new LPVOID[code_count];
		for (int i = 0; i < code_count; i++)
		{
			char* _tmpByte = new char[_ContextArrys[i].len + 2];
			_EntryCode[i] = _tmpByte;
			_tmpByte[0] = 0x9D;
			_tmpByte[1] = 0x61;

			/*CString txt;
			txt.Format(L"当前地址:%X", _EntryCode[i]);
			AfxMessageBox(txt);*/
			

			unsigned offset = sizeof(_ContextArrys[i].r_count) * _ContextArrys[i].r_count;
			memcpy((char*)_EntryCode[i] + 2, _data + offset, _ContextArrys[i].len);
			unsigned short* rel = (unsigned short*)_data;
			for (int x = 0; x < _ContextArrys[i].r_count; x++)
			{
				unsigned* _callAddr = (unsigned*)((char*)_EntryCode[i] + rel[x] + 1 + 2);
				_callAddr[0] = _callAddr[0] - (unsigned)_callAddr - 4;
				// AfxMessageBox(L"这里代码存在问题,后面改");
			}
			_data = _data + offset + _ContextArrys[i].len;
			DWORD dOld;
			VirtualProtect(_EntryCode[i], _ContextArrys[i].len, PAGE_EXECUTE_READWRITE, &dOld);
			if (_ContextArrys[i].hide) {
				_EntryCode[i] = (LPVOID)((unsigned)_EntryCode[i] + 2);
				_HideCode[_HideCount].Index = i;
				_HideCode[_HideCount].Start = _ContextArrys[i].start;
				_HideCount++;
			}
		}

		delete[]_dataBegin;
	}
	else return false;


	auto hMod = GetModuleHandle(NULL);
	unsigned addMod = (unsigned)hMod;
	unsigned addReset = addMod + 0xC2EFFC;
	DWORD dOld = GetFunctionAddress(0);
	// ::VirtualProtect((LPVOID)addReset, 4, PAGE_EXECUTE_READWRITE, &dOld);
	// ::VirtualProtect(this->_GameCode, 0x1000, PAGE_EXECUTE_READWRITE, &dOld);
	unsigned* read = (unsigned*)addReset;
	read[0] = (unsigned)this->_EntryCodeEx;
	//_EntryCode[1] = GetFunctionAddress;

	read = (unsigned*)(this->_EntryCodeEx + 1);
	read[0] = (unsigned)GetFunctionAddress - 5 - (unsigned)(this->_EntryCodeEx);
	return true;
}

/**
	下方的代码不猥琐
	   因为用了 ZwSetInformationThread、NtQueryInformationProcess 这样的常量字符串
	   然后可以在 ZwSetInformationThread、NtQueryInformationProcess 做HOOK,拦截我们
	   这样我们可以把 ZwSetInformationThread、NtQueryInformationProcess 函数拷贝到我们的内存空间里去调用
	   不再通过 GetProcAddress 方式去获取函数地址,直接通过我们复制到的地址调用它,最终这俩函数会去内核,我们也可以
	   把进内核的逻辑自己实现,这样就让别人完全搞不定了
*/
void GameProtect::InitApiEx()
{

	hNtdll = LoadLibrary(L"ntdll.dll");
	if (hNtdll) {
		/**
			GetProcAddress通过导出表获取函数
		*/
		ZwSetInformationThread = (ZwSetInformationThreadPtr)GetProcAddress(hNtdll, "ZwSetInformationThread");
		NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hNtdll, "NtQueryInformationProcess");
	}
	HProcess = GetCurrentProcess();
}

void GameProtect::AntiDebug()
{
	/**
	  这个函数在ntdll..dll里
		这个位置真正使用时不能这样写,因为这样写了之后,会被人看出来
		最好的方式是通过启动器计算好传递过来,传递过来就是悄无声息了
		相当于调用了一个函数,但破解者不知道,调用函数还可以猥琐点
		把它拷贝到我们的内存空间里调用。
	*/
	if (ZwSetInformationThread) {
		ZwSetInformationThread((DWORD)GetCurrentThread(), 0x11, 0x0, 0x0);
	}

}

BOOL GameProtect::CheckDebugByPEB()
{
	/**

		IsDebuggerPresent 检测当前进程
		CheckRemoteDebuggerPresent 检测指定进程
		它俩检测的都是peb结构
		有的调试器会把它们给处理掉,比如OD,它有插件会把这个东西修复掉
		修复掉之后就检测不到了,它修复的原理就是把 BeingDebugged 这个字段给处理了
		就是说有调试器 BeingDebugged它的值是1,然后我再给BeingDebugged写成0就行了

	*/
	BOOL bResult = IsDebuggerPresent();
	if (bResult) {
		return bResult;
	}
	CheckRemoteDebuggerPresent(GetCurrentProcess(), &bResult);
	if (bResult) {
		AfxMessageBox(L"CheckRemoteDebuggerPresent 检测到调试器");
		bResult = TRUE;
	}
	return bResult;
}

BOOL GameProtect::CheckDebugByNT()
{
	// 
	DWORD debug_port = 0;
	//NtQueryInformationProcess(HProcess, 0x07, &debug_port, sizeof(debug_port), 0x0);
	//if (debug_port ) {
	//	return TRUE;
	//}
	// 在64位下这个Object是一个HANDLE结构
	HANDLE debug_object = 0;
	//NtQueryInformationProcess(HProcess, 0x1E, &debug_object, sizeof(debug_object), 0x0);
	//if (debug_object) {
	//	return TRUE;
	//}
	BOOL debug_flags = 0;
	NtQueryInformationProcess(HProcess, 0x1F, &debug_flags, sizeof(debug_flags), 0x0);
	if (!debug_flags) {
		return TRUE;
	}
	return FALSE;
}

 

 

 

 

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

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

相关文章

京东商家数据工具讲解(一):竞品数据如何监控与分析

京东平台的店铺众多&#xff0c;同行数不胜数。作为商家&#xff0c;如果连自己竞争对手的情况都不知道的话&#xff0c;很难在这个平台存活下去。那么&#xff0c;这次鲸参谋就来重点说一下“竞品分析”。 竞品分析&#xff0c;主要是对京东店铺运营期间竞争对手的市场经营状…

Next Station of Flink CDC

摘要&#xff1a;本文整理自阿里云智能 Flink SQL、Flink CDC 负责人伍翀&#xff08;花名&#xff1a;云邪&#xff09;&#xff0c;在 Flink Forward Asia 2023 主会场的分享。Flink CDC 是一款基于 Flink 打造一系列数据库的连接器。本次分享主要介绍 Flink CDC 开源社区在过…

Linux安装GitLab教程

Linux安装GitLab教程 1、配置yum源 相当于新建一个文件&#xff0c;通过这个文件来安装gitlab vim /etc/yum.repos.d/gitlab-ce.repo 把这些配置粘进去 [gitlab-ce] nameGitlab CE Repository baseurlhttps://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el$releasever/ gp…

K8s实战-init容器

概念&#xff1a; 初始化容器的概念 比如一个容器A依赖其他容器&#xff0c;可以为A设置多个 依赖容易A1&#xff0c;A2&#xff0c;A3 A1,A2,A3要按照顺序启动&#xff0c;A1没有启动启动起来的 话&#xff0c;A2,A3是不会启动的&#xff0c;直到所有的静态容器全 部启动完毕…

Flink项目实战篇 基于Flink的城市交通监控平台(下)

系列文章目录 Flink项目实战篇 基于Flink的城市交通监控平台&#xff08;上&#xff09; Flink项目实战篇 基于Flink的城市交通监控平台&#xff08;下&#xff09; 文章目录 系列文章目录4. 智能实时报警4.1 实时套牌分析4.2 实时危险驾驶分析4.3 出警分析4.4 违法车辆轨迹跟…

hive在执行elect count(*) 没有数据显示为0(实际有数据)

set hive.compute.query.using.statsfalse; 是 Hive 的一个配置选项。它的含义是禁用 Hive 在执行查询时使用统计信息。 在 Hive 中&#xff0c;统计信息用于优化查询计划和执行。当该选项设置为 false 时&#xff0c;Hive 将不会使用任何统计信息来帮助决定查询的执行计划。这…

Flink1.17实战教程(第六篇:容错机制)

系列文章目录 Flink1.17实战教程&#xff08;第一篇&#xff1a;概念、部署、架构&#xff09; Flink1.17实战教程&#xff08;第二篇&#xff1a;DataStream API&#xff09; Flink1.17实战教程&#xff08;第三篇&#xff1a;时间和窗口&#xff09; Flink1.17实战教程&…

《深入理解Java虚拟机(第三版)》读书笔记:虚拟机类加载机制、虚拟机字节码执行引擎、编译与优化

下文是阅读《深入理解Java虚拟机&#xff08;第3版&#xff09;》这本书的读书笔记&#xff0c;如有侵权&#xff0c;请联系删除。 文章目录 第6章 类文件结构第7章 虚拟机类加载机制7.2 类加载的时机7.3 类加载的过程7.4 类加载器7.5 Java模块化系统 第8章 虚拟机字节码执…

Redis 核心知识总结

Redis 核心知识总结 认识 Redis 什么是 Redis&#xff1f; Redis 是一个由 C 语言开发并且基于内存的键值型数据库&#xff0c;对数据的读写操作都是在内存中完成&#xff0c;因此读写速度非常快&#xff0c;常用于缓存&#xff0c;消息队列、分布式锁等场景。 有以下几个特…

web三层架构

目录 1.什么是三层架构 2.运用三层架构的目的 2.1规范代码 2.2解耦 2.3代码的复用和劳动成本的减少 3.各个层次的任务 3.1web层&#xff08;表现层) 3.2service 层(业务逻辑层) 3.3dao 持久层(数据访问层) 4.结合mybatis简单实例演示 1.什么是三层架构 三层架构就是把…

Node.js--》node环境配置及nvm和nvm-desktop安装教程

博主最近换了台新电脑&#xff0c;环境得从零开始配置&#xff0c;所以以下是博主从一台纯净机中配置环境&#xff0c;绝对的小白教程&#xff0c;大家第一次安装完全可以参考我的过程&#xff0c;闲话少说&#xff0c;直接开始&#xff01;&#xff01;&#xff01; 接下来介绍…

Linux下安装QQ

安装步骤&#xff1a; 1.进入官网&#xff1a;QQ Linux版-轻松做自己 2.选择版本&#xff1a;X86版下载dep 3安装qq 找到qq安装包位置&#xff0c;然后右击在终端打开输入安装命令&#xff0c;然后点击回车 sudo dpkg -i linuxqq_3.2.0-16736_amd64.deb 卸载qq 使用命令…

如何使用Linux docker方式快速安装Plik并结合内网穿透实现公网访问

文章目录 1. Docker部署Plik2. 本地访问Plik3. Linux安装Cpolar4. 配置Plik公网地址5. 远程访问Plik6. 固定Plik公网地址7. 固定地址访问Plik 本文介绍如何使用Linux docker方式快速安装Plik并且结合Cpolar内网穿透工具实现远程访问&#xff0c;实现随时随地在任意设备上传或者…

ALSA学习(4)——Control设备的创建

参考博客&#xff1a; https://blog.csdn.net/DroidPhone/article/details/6409983 &#xff08;下面的内容基本是原博主的内容&#xff0c;我只是修改了一些格式之类的&#xff09; 文章目录 一、Control接口二、Controls的定义三、Control的名字四、访问标志&#xff08;ACC…

C# NLua Winform 热更新

一、概述 NLua 是一个用于 .NET 平台的 Lua 脚本绑定库。它允许在 C# 代码中嵌入 Lua 脚本&#xff0c;并允许两者之间进行交互。NLua 的主要特点包括&#xff1a; 轻量级&#xff1a;NLua 是一个轻量级的库&#xff0c;易于集成到现有的 .NET 项目中。动态类型&#xff1a;L…

2024年【裂解(裂化)工艺】考试报名及裂解(裂化)工艺考试总结

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 裂解&#xff08;裂化&#xff09;工艺考试报名考前必练&#xff01;安全生产模拟考试一点通每个月更新裂解&#xff08;裂化&#xff09;工艺考试总结题目及答案&#xff01;多做几遍&#xff0c;其实通过裂解&#…

Tuxera NTFS for Mac2024免费Mac读写软件下载教程

在日常生活中&#xff0c;我们使用Mac时经常会遇到外部设备不能正常使用的情况&#xff0c;如&#xff1a;U盘、硬盘、软盘等等一系列存储设备&#xff0c;而这些设备的格式大多为NTFS&#xff0c;Mac系统对NTFS格式分区存在一定的兼容性问题&#xff0c;不能正常读写。 那么什…

如何从 DSA 切换到 PMax 以使您的 Google 付费广告面向未来

为了在 Google Ads 不可避免的过渡期之前&#xff0c;我们将介绍如何从动态搜索广告切换到效果最大化广告 如何从 DSA 切换到 PMax 以使您的 Google 付费广告面向未来 变化是唯一不变的&#xff0c;尤其是在数字广告中——您可能听说过一些关于动态搜索广告 &#xff08;DSA&…

第27关 在K8s集群上使用Helm3部署最新版本v2.10.0的私有镜像仓库Harbor

------> 课程视频同步分享在今日头条和B站 大家好&#xff0c;我是博哥爱运维。 在前面的几十关里面&#xff0c;博哥在k8s上部署服务一直都是用的docker hub上的公有镜像&#xff0c;对于企业服务来说&#xff0c;有些我们是不想把服务镜像放在公网上面的&#xff1b; 同时…

25、Qt设备识别(简单的密钥生成器)

一、说明 在很多商业软件中&#xff0c;需要提供一些可以试运行的版本&#xff0c;这样就需要配套密钥机制来控制&#xff0c;纵观大部分的试用版软件&#xff0c;基本上采用以下几种机制来控制。 1、远程联网激活&#xff0c;每次启动都联网查看使用时间等&#xff0c;这种方…