Dll注入与调用

  • Add.dll
  • One.exe
  • HookDll.dll
  • Hook.exe

去年的时候想做个脚本,开始是用python,但是有些实现想用Hook,然而自己本来python就是半桶水,而C++的window编程更是一窍不通,然而网上的教程又是零零散散
好吧,其实就是我没基础,大佬:接下来就不用我说了吧(超需要啊~~~)

注入需要准备一个dll,与配套的exe程序(当然,hook那么多种形式,我只是碰巧学了这么个半通不通的)
所以,在这个Demo里,有2个Dll:

  • Add.dll(假设的要hook的函数所在dll)
  • HookDll.dll(包含要注入用的函数所在dll)
  • 2个exe项目:One.exe(假设要Hook的程序)
  • Hook.exe(hook用的程序)

导包跟头文件我会发出来,但是函数修改的话,只会在修改部分发出

Add.dll

在本案例中,假设要hook程序中Add.dll中的ExportFunc(LPCTSTR pszContent)函数

这个dll只有一个ExportFunc函数,主体One.exe会通过该module函数弹出提示窗

add.h

#pragma once
#include <Windows.h>__declspec(dllexport) void ExportFunc(LPCTSTR pszContent);

dllmain.cpp

HMODULE g_hModule;
BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:               // 这里表示在dll加载的时候进行的操作g_hModule = (HMODULE)hModule;     //获取谁加载本dll,这样ExportFunc就能在对应的窗口弹出消息了break;}return TRUE;
}
//pszContent:要弹出的提示内容
void ExportFunc(LPCTSTR pszContent) {char sz[MAX_PATH];GetModuleFileNameA(g_hModule, sz, MAX_PATH);MessageBoxA(NULL, pszContent, strrchr(sz, '\\') + 1, MB_OK);
}

One.exe

在本案例中,One.exe就是要被hook的程序了

One在点击界面的时候,会调用Add.dll的ExportFunc,从而弹出窗口

One.h

#pragma once
#include "resource.h"
#include<windows.h>
#include<atlstr.h>
#include <TlHelp32.h>HMODULE hModule;                             //存放加载Add.dll
typedef void (*PFNEXPORTFUNC) (LPCTSTR);        //声明类型(?c++真心搞不懂)
PFNEXPORTFUNC mef;                              //存放加载ExportFunc

One.cpp

#include "framework.h"
#include "One.h"
#include <string>
using namespace std;void OnOk();LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){case WM_LBUTTONDOWN:OnOk();           //在点击界面的时候触发该函数break;return 0;
}//这个是点击响应函数
void OnOk() {/*mef是ExportFunc的函数参数,加载过dll后,读取到的函数都会有个句柄(?应该是这么叫吧)这个名称不重要,之所以是PFNEXPORTFUNC是因为我们这个ExportFunc返回值是void大概就是这么理解吧=3=这里简单的说就是,如果ExportFunc还没加载就去看看Add.dll加载了没,没有就去加载有就从Add.dll读取ExportFunc然后就去调用这个ExportFunc然后弹出个success当然,这些根本不关我们事,毕竟One.exe模拟的是被我们hook的程序,又不是我们自己的程序=3=*/if (mef == NULL) {if (hModule == NULL) {hModule = ::LoadLibrary("D:/workspace/c/yysv3/Add/Debug/Add.dll");}if (hModule != NULL) {mef = (PFNEXPORTFUNC)::GetProcAddress(hModule, "ExportFunc");}else {MessageBox(NULL, "加载ADD失败", "", NULL);}}if (mef != NULL) {mef("success");}
}

效果大概就是这样

上面的在实际的Hook中就是现成的程序了,我们只需要了解我们要Hook什么dll中的什么函数,这些就不是我这没入门的能理解的范畴了
下面这就是Hook的重头戏了~~

HookDll.dll

HookDll.dll就是我们要用来注入的dll了
在这里,我们也定义了一个void ExportFunc(LPCTSTR pszContent);这里这个函数名称不重要,重要的是,参数必须跟我们要hook的函数参数保持一致(个人理解,,)

说一下个人对于Hook的理解(markdown绘制的,将就看看吧=3=)

2.想去找
6.所以真正找到的是
4.而它hook了A
5.A入口变成了B
7.重新去找到函数A
7.或直接不执行A<一般来说不选择这种>
8.A傻傻执行执行
1.X逻辑调用了A
函数A
函数B
3.HookDll
6.执行Hook的代码完了
9.X逻辑继续进行

所以,我们需要重新写一个函数B,用B去替换原本的正主A,这时候函数的参数要跟原来的保持一致,因为其他的代码并不知道这个A已经被掉包了,所以还是带着那么些参数过来了(这里函数名无所谓是因为,函数名只是一个指代一个入口,所以叫啥最后指向的入口对了就OK了)

最开始是想着怎么注入dll,到注入后一直在找资料说怎么调用怎么调用。后来才大概明白俩点:

  • 我们写的Hook.exe的注入操作,只是为了把HookDll.dll送到目标程序里面,其他的跳转什么的,全靠HookDll.dll自己去行动了
  • Dll在被加载的时候,会触发一个操作,就是默认的DllMain::DLL_PROCESS_ATTACH,也就是说在注入的时候,我们就该把所有事情安排好了(也就是函数B入口替换A的操作要完成),完了之后,只要逻辑X过来,它走的就是函数B(也就是我们的逻辑了),这时候就是目标程序主动来调用我们的函数了,而不用我们去控制了

简单说,就像一个特工要潜入目标,Hook.exe就是带路党把他带到目标营地里,而至于要搞破坏还是投资料,全都得这位特工去完成了,全凭他当前的得到的命令去做事情了。这时候,虽然说不是联系不上他,但是要重新给他发布指令也就是说外部指挥部去传递信息进去,肯定是相当麻烦且危险的事情了

dllmain.cpp

#include "pch.h"
HMODULE g_hModule;
HANDLE cur;
PROC OldProc;
unsigned long* lpAddr;
bool m_bInjected = false;
bool bHook;
DWORD dwPid;//这里是用来保存我们hook的函数原地址(原函数)以及新地址(我们的函数)的
BYTE NewCode[5];
BYTE OldCode[5];void ExportFunc(LPCTSTR pszContent);
typedef void( *ExportProc)(LPCSTR pszContent);
ExportProc proc;
//这里除了上面定义的用来加载HookDll中的ExportFunc外,还需要一个FARPROC,个人理解是用来记录远程的ExporProc
FARPROC fproc;//hook操作函数
void HookOn();
void HookOff();
void Inject();
void printf(const char* msg, ...);BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{switch (ul_reason_for_call){//这里是加载了dll后就会执行的case DLL_PROCESS_ATTACH://拿到当前的进程号dwPid = ::GetCurrentProcessId();cur = OpenProcess(PROCESS_ALL_ACCESS, 0, dwPid);printf("Hook 注入");g_hModule = GetModuleHandle("Add.dll");//所以我们需要在这里偷天换日Inject();break;case DLL_PROCESS_DETACH:  //这个是dll卸载前会执行的HookOff();               //所以在这里就要回复原状了(重要)MessageBoxA(NULL, "Hook 卸载", "", MB_OK);break;}return TRUE;
}/*** dll注入之后通过dll的加载机制,调用了该函数* 首先,是先进行判断我们要hook的目标add.dll是不是加载了,避免搞错目标* 确认了dll的位置后,就通过dll去找到我们真正要替换的目标ExportFunc* 然后就将我们拿到的正主A转成FPROC(这个我也不大懂它的意义,不过能通过它去确定A的位置就是了)* 然后就把目标的地址跟自身地址记录好* 简单说,我们的特工潜入后,首先是去找找这里是不是有XX房间(避免潜入错营地的尴尬)* 找房间后又去房间里确认A是不是有来工作(dll加载后,函数不一定有加载调用)* 确认有后,就偷偷记录A跟自己的铭牌了*/
void Inject()
{if (m_bInjected == false){//保证只调用1次m_bInjected = true;//获取add.dll中的add()函数HMODULE hmod = GetModuleHandle("Add.dll");if (hmod != NULL) {proc = (ExportProc)::GetProcAddress(hmod, "ExportFunc");//procEndScene = (EndSceneProc)::GetProcAddress(hmod, "EndScene");}else {printf("fail to found add.dll");}fproc = (FARPROC)proc;if (fproc == NULL){printf("fail to load farproc");}// 将add()中的入口代码保存入OldCode[]_asm{lea edi, OldCodemov esi, fproccldmovsdmovsb}NewCode[0] = 0xe9;//实际上0xe9就相当于jmp指令//获取Myadd()的相对地址_asm{lea eax, ExportFuncmov ebx, fprocsub eax, ebxsub eax, 5mov dword ptr[NewCode + 1], eax}//填充完毕,现在NewCode[]里的指令相当于Jmp MyaddHookOn(); //可以开启钩子了}
}/*** HookOn跟HookOff类似* 就是修改程序的内存,然后变更函数的入口了* 这一步相当于我们的特工B潜入目标阵营后确认A的存在后,将自己 铭牌跟A的偷换了* 当某人C通过铭牌来找A的时候,B接过信息并顺手转交给了A* 由于信息格式是符合规定的,A也没怀疑就做好处理并返回给了C* 这时候原本天知地知,AC知的事,就被B知道甚至可能偷偷做了一点手脚了*/
void HookOn()
{DWORD dwTemp = 0;DWORD dwOldProtect;//将内存保护模式改为可写,老模式保存入dwOldProtectVirtualProtectEx(cur, fproc, 5, PAGE_READWRITE, &dwOldProtect);//将所属进程中add()的前5个字节改为Jmp MyaddWriteProcessMemory(cur, fproc, NewCode, 5, 0);//将内存保护模式改回为dwOldProtectVirtualProtectEx(cur, fproc, 5, dwOldProtect, &dwTemp);bHook = true;
}//将所属进程中add()的入口代码恢复
void HookOff()
{DWORD dwTemp = 0;DWORD dwOldProtect;VirtualProtectEx(cur, fproc, 5, PAGE_READWRITE, &dwOldProtect);WriteProcessMemory(cur, fproc, OldCode, 5, 0);VirtualProtectEx(cur, fproc, 5, dwOldProtect, &dwTemp);bHook = false;
}/*** 这个函数就是我们自己的函数B* 因为我们Inject后就HookOn()在监听着进入的函数了* 也就是在逻辑走到原程序的ExportFunc就会进入到这里了* 所以执行完我们自己的逻辑就该将入口恢复,然后调用真正的A函数* 而A函数运行完会返回什么就返回什么(这里是void自然就是运行完就结束了)* 然后重新修改A的入口到我们的B上面*/
void ExportFunc(LPCTSTR pszContent) {printf("Hook!!!!");HookOff();proc(pszContent);HookOn();
}//封装的弹框函数,没啥特别重点的
void printf(const char* msg, ...)
{va_list args;const char* params;va_start(args, msg);params = va_arg(args, const char*);va_end(args);TCHAR str[MAX_PATH];wsprintf(str, msg, params);MessageBox(NULL, str, "", NULL);
}

Hook.exe

Hook.exe就是我们用来注入跟卸载HookDll.dll用的,这里主要是的功能就是点击时,如果One.exe存在Add.dll就去判断HooDll.dll是否注入了,是就卸载,否则注入

这一步内容可能会稍微多一些,主要是多了许多的步骤判断,最开始是因为为了确认错误所在,后来也就没修改了,核心内容倒是不多,跟网上的其他说明就大同小异了

  • 判断进程中是否存在目标
  • 是的话获取进程操作句柄
  • 然后开辟内存(这一步可能会失败,具体自己网上找解决)
  • 将我们的dll路径赋值进去(到这里其实就是相当于给我们的特工在目标营地创建了一个身份档案)
  • 然后去找LoadLibrary(这里可理解为这个世界的所有营地人事都是同一家人力公司派遣的,他们只认身份档案不认人的,然后人事部就派车出来接特工进营地了)

Hook.cpp

#include "framework.h"
#include "Hook.h"BOOL CheckDllInProcess(DWORD dwPID, LPCTSTR szDllPath);      //检查指定dll是否存在目标进程
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath);
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllPath);
void d_printf(const char* msg, ...);
bool InitalizeInject();LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){case WM_LBUTTONDOWN:      //点击监听if (CheckDllInProcess(dwPID, orignDllName)){if (CheckDllInProcess(dwPID, dllName)){//已经注入就卸载EjectDll(dwPID, dllName);}else{//还没注入就加载InjectDll(dwPID, dllPath);}}else {d_printf("不存在");}break;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;
}/*** 检测进程中是否包含对应的Dll* @param dwPID        进程ID* @param szDllPath 所要注入Dll名称** 创建内存快照* 遍历比较*/
BOOL CheckDllInProcess(DWORD dwPID, LPCTSTR szDllPath)
{BOOL bMore = FALSE;HANDLE hSnapshot = INVALID_HANDLE_VALUE;MODULEENTRY32 me = { sizeof(me), };if (INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID))){DWORD err = GetLastError();d_printf("CheckDllInProcess():CreateToolHelp32Snapshot(%d)failed!!![%d]\n", dwPID, err);return FALSE;}bMore = Module32First(hSnapshot, &me);FILE* out;errno_t err;err = fopen_s(&out, "D:\\a.txt", "a");for (; bMore; bMore = Module32Next(hSnapshot, &me)){if (out != NULL){fprintf(out, "%s\t", me.szModule);}if (!_tcsicmp(me.szModule, szDllPath) || !_tcsicmp(me.szModule, szDllPath)){CloseHandle(hSnapshot);if (out != NULL){fprintf(out, "\n-------------\n");fclose(out);}return TRUE;}}if (out != NULL){fprintf(out, "\n-------------\n");fclose(out);}CloseHandle(hSnapshot);//d_printf("找不到%s", szDllPath);return FALSE;
}//向指定的进程注入相应的模块
//dwPID         目标进程的PID
//szDllPath     被注入的dll的完整路径
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
{HANDLE                  hProcess = NULL;//保存目标进程的句柄LPVOID                  pRemoteBuf = NULL;//目标进程开辟的内存的起始地址DWORD                   dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);//开辟的内存的大小LPTHREAD_START_ROUTINE  pThreadProc = NULL;//loadLibreayW函数的起始地址HMODULE                 hMod = NULL;//kernel32.dll模块的句柄BOOL                    bRet = FALSE;HANDLE                  hThread = NULL;if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))//打开目标进程,获得句柄{d_printf("InjectDll() : OpenProcess(%d) failed!!! [%d]\n",dwPID, GetLastError());goto INJECTDLL_EXIT;}pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize,MEM_COMMIT, PAGE_READWRITE);//在目标进程空间开辟一块内存if (pRemoteBuf == NULL){d_printf("InjectDll() : VirtualAllocEx() failed!!! [%d]\n",GetLastError());goto INJECTDLL_EXIT;}if (!WriteProcessMemory(hProcess, pRemoteBuf,(LPVOID)szDllPath, dwBufSize, NULL))//向开辟的内存复制dll的路径{d_printf("InjectDll() : WriteProcessMemory() failed!!! [%d]\n",GetLastError());goto INJECTDLL_EXIT;}hMod = GetModuleHandle("kernel32.dll");//获得本进程kernel32.dll的模块句柄if (hMod == NULL){d_printf("InjectDll() : GetModuleHandle(\"kernel32.dll\") failed!!! [%d]\n",GetLastError());goto INJECTDLL_EXIT;}//获得LoadLibrary函数的起始地址,这个要区分对方程序是否是宽字符的if (isUnicode) {pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA");}else{pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");}if (pThreadProc == NULL){d_printf("InjectDll() : GetProcAddress(\"LoadLibraryW\") failed!!! [%d]\n",GetLastError());goto INJECTDLL_EXIT;}hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);if (!hThread)//执行远程线程{d_printf("InjectDll() : MyCreateRemoteThread() failed!!!\n");goto INJECTDLL_EXIT;}
INJECTDLL_EXIT:if (hThread != NULL) {WaitForSingleObject(hThread, INFINITE);}bRet = CheckDllInProcess(dwPID, dllName);                //确认结果if (pRemoteBuf)VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);if (hProcess)CloseHandle(hProcess);return bRet;
}//让指定的进程卸载相应的模块
//dwPID         目标进程的PID
//szDllPath     被注入的dll的完整路径,注意:路径不要用“/”来代替“\\”
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllPath)
{BOOL                    bMore = FALSE, bFound = FALSE, bRet = FALSE;HANDLE                  hSnapshot = INVALID_HANDLE_VALUE;HANDLE                  hProcess = NULL;MODULEENTRY32           me = { sizeof(me), };LPTHREAD_START_ROUTINE  pThreadProc = NULL;HMODULE                 hMod = NULL;TCHAR                   szProcName[MAX_PATH] = { 0, };if (INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID))){d_printf("EjectDll() : CreateToolhelp32Snapshot(%d) failed!!! [%d]\n",dwPID, GetLastError());goto EJECTDLL_EXIT;}bMore = Module32First(hSnapshot, &me);for (; bMore; bMore = Module32Next(hSnapshot, &me))//查找模块句柄{if (!_tcsicmp(me.szModule, szDllPath) || !_tcsicmp(me.szExePath, szDllPath)){bFound = TRUE;break;}}if (!bFound){d_printf("EjectDll() : There is not %s module in process(%d) memory!!!\n", szDllPath, dwPID);goto EJECTDLL_EXIT;}if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))){d_printf("EjectDll() : OpenProcess(%d) failed!!! [%d]\n",dwPID, GetLastError());goto EJECTDLL_EXIT;}hMod = GetModuleHandle("kernel32.dll");if (hMod == NULL){d_printf("EjectDll() : GetModuleHandle(\"kernel32.dll\") failed!!! [%d]\n",GetLastError());goto EJECTDLL_EXIT;}pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "FreeLibrary");if (pThreadProc == NULL){d_printf("EjectDll() : GetProcAddress(\"FreeLibrary\") failed!!! [%d]\n",GetLastError());goto EJECTDLL_EXIT;}if (!CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL)){d_printf("EjectDll() : MyCreateRemoteThread() failed!!!\n");goto EJECTDLL_EXIT;}bRet = TRUE;
EJECTDLL_EXIT:if (hProcess)CloseHandle(hProcess);if (hSnapshot != NULL && hSnapshot != INVALID_HANDLE_VALUE)CloseHandle(hSnapshot);return bRet;
}void d_printf(const char* msg,...)
{va_list args;const char* params;va_start(args, msg);params = va_arg(args, const char*);va_end(args);TCHAR str[MAX_PATH];wsprintf(str, msg,params);MessageBox(NULL, str, "", NULL);
}/*** 初始化注入操作*** 判断是否取得窗口句柄hHwnd*       获取窗口句柄*     获取线程号、进程号*      判断是否多字符类型:A/W * * 所要注入dll完整路径,以及对应的dll名称* * 验证初始化内容是否完成,否则返回失败*/
bool InitalizeInject()
{if (hHwnd == NULL) {hHwnd = FindWindow("One", NULL);dwTID = GetWindowThreadProcessId(hHwnd, &dwPID);isUnicode = IsWindowUnicode(hHwnd);}if (dllPath == NULL) {dllPath = "D:\\workspace\\c\\yysv3\\HookDll\\Debug\\HookDll.dll";    //这是要Inject的dll的绝对路径dllName = "HookDll.dll";                                         //这是要Inject的dll名称orignDllName = "Add.dll";                                           //这是要hook的dll名称}if (hHwnd == NULL) {d_printf("Get HWND faild!");return false;}if (dwTID == NULL){d_printf("Get TID&PID faild!");return false;}if (dllPath == NULL){d_printf("Get DLL faild!");return false;}return TRUE;
}

最终效果:

可以看到,One的弹框变化(虽然没写好弹框定位,但是通过任务栏确实能确定弹框的归属)

【Windows编程】Dll的注入与调用相关推荐

  1. Qt:Windows编程—DLL注入与卸载

    前言 这里说的DLL注入 是将我们指定的DLL注入到指定的进程中,DLL卸载也就是将指定进程中的DLL卸载下来.在Windows提供的API中有 CreateRemoteThread函数 见名知意 创 ...

  2. C/C++:Windows编程—Hook IE浏览器实现URL拦截及更改(上)

    Hook IE浏览器实现URL拦截及更改(上) 前言+思路 笔者这里有个需求,针对IE浏览器 用户访问URL 做一个判断,是否为 限制访问的url,如果是 在另一个软件上给与警告提示.笔者在拿到这个需 ...

  3. Qt:Windows编程—代码注入

    前言 ​ 这里所说的代码注入和上篇的DLL注入有类似之处.DLL文件的注入与卸载在上篇中都完成了,整个注入与卸载的过程其实就是让远程线程执行一次LoadLibrary函数或者FreeLibrary函数 ...

  4. windows平台实现dll远程注入的简单例子

    最新看了下关于dll远程注入的东西,这个技术原来是用来隐藏木马很好的方式,现在貌似很难通过了,一般的杀软都能检测到相关的行为, // 一个dll的代码,随便加了一个messagebox函数,仅用来测试 ...

  5. Windows下查看dll被哪个进程调用

    转载博客菜鸟leihttp://www.cnblogs.com/leipei2352/archive/2013/02/05/2892482.html 卸载程序,结果没卸载干净---程序的安装目录中还剩 ...

  6. 保护模式下C语言编程,关于windows ring3保护模式与中断调用

    关于windows ring3保护模式与中断调用 最近发现了一个问题,就是进行某些中断调用时,并不如想象中的那样执行. 比如一个简单的ROM BASIC调用,我的机器(XP系统)弹出一个对话框:NTV ...

  7. 跨进程的 键盘钩子_Delphi下深入Windows编程之钩子原理一

    我的理想是能够写出一个可以永不封号的游戏外G 嗯,所以需要学习Windows下编程,最近好不容易有一点点空余时间,抓紧时间读书[Delphi下深入Windows编程],人丑就该多读书 钩子原理 定义: ...

  8. Windows编程之--桌面壁纸实现深入探索

    本文原创,最早发表于公司内部博客, 禁止转载 文章目录 一. 前言 二. Windows桌面壁纸原理 1. 桌面窗口层次 2. 桌面嵌入窗口实现壁纸 2.1. Desktop Window Manag ...

  9. Windows编程基础(转)

    前几天在网上看了"病毒"兄写的<WIN下编程须知>一文,觉得在编程方面要写出一篇适合初学者们看 的入门级文章的确很重要,可惜病毒兄只在该文里介绍了线程.消息.句柄等几个 ...

最新文章

  1. 《大道至简》一书第三版,与编辑就本书写作风格的讨论
  2. 最新动态,电信屏蔽Godaddy部分DNS服务
  3. 对URLEncode的解码
  4. 循环递归,相互结合,释放数据的价值
  5. 牛客题霸 [最大数] C++题解/答案
  6. orange pi java_[中文]Orange Pi家族各大成员一览表
  7. python 实例创建
  8. So easy!10 行代码写个“让你惊叹”的文章生成器 | 原力计划
  9. 计算机信息管理企业资源规划综合实训,企业资源规划(ERP)综合实训细则
  10. java基于springboot高校信息资源共享网站系统
  11. 1916 Problem C 合唱队形
  12. linux系统如何运行ansys,ANSYS在linux下使用
  13. android studio项目实例基于Uniapp+SSM实现的定制旅游APP
  14. hive 传递外部变量的方式
  15. 多普达同步软件4.5_我试用过的十几款记账软件
  16. IOS HTTPS 服务器信任评估
  17. arcgis api for javaScript学习-Measurement
  18. JS一元运算符(前++,后++)详解
  19. 华为 IPv6与IPv4
  20. 斯卡布罗集市口琴bd谱

热门文章

  1. SHELL的脚本编写(2)
  2. 设备全生命周期数智化管理平台
  3. 【计量经济学】跨时期横截面的混合
  4. java从小白到老白①
  5. 使用并行的方法计算斐波那契数列 (Fibonacci)
  6. python破解zip密码
  7. python培训推广
  8. 乘法逆元 java_浅谈乘法逆元(示例代码)
  9. 【笔记分享】焦耳小偷电路
  10. python考核试题及答案