redistemplate注入为null_Windows DLL 注入技术

Windows DLL 注入技术

本文主要介绍四种常见的 Windows DLL 注入技术。

分别为全局钩子、远线程钩子、突破 SESSION 0 隔离的远线程注入和 APC 注入。

全局钩子注入

Windows 中大部分应用是基于 Windows 的消息机制,Windows提供截获这些消息的钩子函数。

根据钩子作用的不同范围,钩子可以被分为全局和局部钩子。局部钩子是针对某个线程的,全局钩子是只要有使用消息机制的应用。接下来我们主要来看下利用SetWindowsHookEx实现全局钩子。

SetWindowsHookEx函数介绍

HHOOK SetWindowsHookEx(int       idHook, // 要安装的钩子程序的类型
  HOOKPROC  lpfn, // 指向钩子函数的指针
  HINSTANCE hmod, // 指向钩子过程的DLL的句柄
  DWORD     dwThreadId // 与钩子进程关联的线程标识符
);
// 具体详细介绍可以查阅msdn文档,里面有更为详细的介绍

实现过程

首先我们需要创建一个windowsHookDll DLL,这个DLL在安装全局钩子后只要系统中的其他进程接收到可以发出钩子的消息,这个DLL就会被加载到此进程的地址空间中。这样便实现了Dll注入。

具体实现代码如下:

#include "Hook.h"
#include 

extern HMODULE g_hDllModule;
// 共享内存
#pragma data_seg("mydata") 
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")


// 钩子回调函数
LRESULT GetMsgProc(int code,
 WPARAM wParam,
 LPARAM lParam){
 return ::CallNextHookEx(g_hHook, code, wParam, lParam);
}

// 注册钩子
BOOL SetHook(){
 g_hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0); //使用WH_GETMESSAGE 确保DLL能够注入所有的进程
 if (g_hHook)
 {
  return TRUE;
 }
 return FALSE;
}

// 卸载钩子
BOOL UnSetHook(){
 if (g_hHook)
 {
  UnhookWindowsHookEx(g_hHook);
 }
 return TRUE;
}

试验结果

新建TestHook.exe程序,首先加载windowsHookDll.dll导出函数SetHook并调用。在注册成功后用ProcessExplorer.exe察看explorer.exe中的加载情况。

20f0d07b5a9cc298f66ea84f5ad32028.png
windowsHookDll成功被explorer.exe加载

如果想要卸载可调用函数UnSetHook,执行完成后再观察explorer.exe是否加载了windowsHookDll.dll。

远线程注入

远线程注入是指一个进程在另一个进程中创建线程的技术。主要是利用LoadLibrary在所有进程空间中的地址是一样,进程在另一个进程中创建线程时传入LoadLibrary的地址和我们要注入的DLL的路径,这样在另一个进程中就能通过LoadLibray加载DLL到进程空间中。

说起来简单,但是实现起来却有几大难点需要注意的。

  • 如何将DLL路径传给另一个进程,因为进程之间是相互隔离的,所以我们要在我们要注入的进程中申请内存。
  • 需要对打开的进程进行提权,这就要求我们的注入程序要有管理员以上的权限。

函数介绍

HANDLE OpenProcess(
  DWORD dwDesiredAccess, // 进程访问权限
  BOOL  bInheritHandle, // 子进程是否继承此句柄
  DWORD dwProcessId // 要打开的进程id
);

// 分配指定进程的内存
LPVOID VirtualAllocEx(
  HANDLE hProcess, // 进程句柄
  LPVOID lpAddress, // 要分配内存起始地址,为NULL则自动分配
  SIZE_T dwSize, // 要分配的内存大小
  DWORD  flAllocationType,// 内存分配类型
  DWORD  flProtect // 内存保护(读写)
);

// 写入内存数据
BOOL WriteProcessMemory(
  HANDLE  hProcess, // 进程句柄
  LPVOID  lpBaseAddress, // 目标进程缓冲区地址
  LPCVOID lpBuffer, // 要写入数据的地址
  SIZE_T  nSize, // 数据大小
  SIZE_T  *lpNumberOfBytesWritten // 写入数据的返回大小, 为NULL则忽略此参数
);

// 在另一个进程中创建线程
HANDLE CreateRemoteThread(
  HANDLE                 hProcess, // 进程句柄
  LPSECURITY_ATTRIBUTES  lpThreadAttributes, // 线程安全描述符
  SIZE_T                 dwStackSize, // 堆栈的初始大小
  LPTHREAD_START_ROUTINE lpStartAddress, // 函数地址(存在于另一个进程中)
  LPVOID                 lpParameter, // 函数的参数
  DWORD                  dwCreationFlags, // 线程创建标志
  LPDWORD                lpThreadId // 线程标识符
);

// 详细信息可参阅msdn

代码实现


/*dwProcessId: 目标进程的pid
**pszDllFileName: 要注入DLL的路径
*/
BOOL CreateRemoteThreadInjectDll(DWORD dwProcessId, char *pszDllFileName){
 HANDLE hProcess = NULL;
 SIZE_T dwSize = 0;
 LPVOID pDllAddr = NULL;
 FARPROC pFuncProcAddr = NULL;

 // 打开注入进程,获取进程句柄
 hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
 if (NULL == hProcess)
 {
  EP_ShowError("OpenProcess");
  return FALSE;
 }
 // 在注入进程中申请内存
 dwSize = 1 + ::lstrlenA(pszDllFileName);
 pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
 if (NULL == pDllAddr)
 {
  EP_ShowError("VirtualAllocEx");
  return FALSE;
 }
 // 向申请的内存中写入数据
 if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL))
 {
  EP_ShowError("WriteProcessMemory");
  return FALSE;
 }
 // 获取LoadLibraryA函数地址
 pFuncProcAddr = ::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "LoadLibraryA");
 if (NULL == pFuncProcAddr)
 {
  EP_ShowError("GetProcAddress_LoadLibraryA");
  return FALSE;
 }
 // 使用 CreateRemoteThread 创建远线程, 实现 DLL 注入
 HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, NULL);
 if (NULL == hRemoteThread)
 {
  EP_ShowError("CreateRemoteThread");
  return FALSE;
 }
 // 关闭句柄
 ::CloseHandle(hProcess);

 return TRUE;
}

试验结果

bfc1e06b9919c00b60c984d1186d7f2a.png
注入成功后效果图
ab2f4f854f0abc8252c54b931a55fa1e.png
在explorer.exe中RemoteThreadDll.dll被正确加载

远线程注册进阶 突破SESSION 0隔离

在系统普通进程中可以使用远线程注入DLL,但如果要注入系统服务则不行,因为有SESSION 0隔离。想要突破隔离需要使用ZwCreateThreadEx函数。这也是和远线程注入的区别。

ZwCreateThreadEx 是未公开的函数,在ntdll.dll中,它的函数声明如下:

#ifdef _WIN64
 typedef DWORD(WINAPI *typedef_ZwCreateThreadEx)(
  PHANDLE ThreadHandle,
  ACCESS_MASK DesiredAccess,
  LPVOID ObjectAttributes,
  HANDLE ProcessHandle,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID lpParameter,
  ULONG CreateThreadFlags,
  SIZE_T ZeroBits,
  SIZE_T StackSize,
  SIZE_T MaximumStackSize,
  LPVOID pUnkown);
#else
 typedef DWORD(WINAPI *typedef_ZwCreateThreadEx)(
  PHANDLE ThreadHandle,
  ACCESS_MASK DesiredAccess,
  LPVOID ObjectAttributes,
  HANDLE ProcessHandle,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID lpParameter,
  BOOL CreateSuspended,
  DWORD dwStackSize,
  DWORD dw1,
  DWORD dw2,
  LPVOID pUnkown);
#endif

具体实现代码:

// 使用 ZwCreateThreadEx 实现远线程注入
BOOL ZwCreateThreadExInjectDll(DWORD dwProcessId, char *pszDllFileName){
 HANDLE hProcess = NULL;
 SIZE_T dwSize = 0;
 LPVOID pDllAddr = NULL;
 FARPROC pFuncProcAddr = NULL;
 HANDLE hRemoteThread = NULL;
 DWORD dwStatus = 0;

 // 打开注入进程,获取进程句柄
 hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
 if (NULL == hProcess)
 {
  EP_ShowError("OpenProcess");
  return FALSE;
 }
 // 在注入进程中申请内存
 dwSize = 1 + ::lstrlenA(pszDllFileName);
 pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
 if (NULL == pDllAddr)
 {
  EP_ShowError("VirtualAllocEx");
  return FALSE;
 }
 // 向申请的内存中写入数据
 if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL))
 {
  EP_ShowError("WriteProcessMemory");
  return FALSE;
 }
 // 加载 ntdll.dll
 HMODULE hNtdllDll = ::LoadLibraryA("ntdll.dll");
 if (NULL == hNtdllDll)
 {
  EP_ShowError("LoadLirbary");
  return FALSE;
 }
 // 获取LoadLibraryA函数地址
 pFuncProcAddr = ::GetProcAddress(::GetModuleHandleA("Kernel32.dll"), "LoadLibraryA");
 if (NULL == pFuncProcAddr)
 {
  EP_ShowError("GetProcAddress_LoadLibraryA");
  return FALSE;
 }
 // 获取ZwCreateThread函数地址
#ifdef _WIN64
 typedef DWORD(WINAPI *typedef_ZwCreateThreadEx)(
  PHANDLE ThreadHandle,
  ACCESS_MASK DesiredAccess,
  LPVOID ObjectAttributes,
  HANDLE ProcessHandle,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID lpParameter,
  ULONG CreateThreadFlags,
  SIZE_T ZeroBits,
  SIZE_T StackSize,
  SIZE_T MaximumStackSize,
  LPVOID pUnkown);
#else
 typedef DWORD(WINAPI *typedef_ZwCreateThreadEx)(
  PHANDLE ThreadHandle,
  ACCESS_MASK DesiredAccess,
  LPVOID ObjectAttributes,
  HANDLE ProcessHandle,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID lpParameter,
  BOOL CreateSuspended,
  DWORD dwStackSize,
  DWORD dw1,
  DWORD dw2,
  LPVOID pUnkown);
#endif
 typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
 if (NULL == ZwCreateThreadEx)
 {
  EP_ShowError("GetProcAddress_ZwCreateThread");
  return FALSE;
 }
 // 使用 ZwCreateThreadEx 创建远线程, 实现 DLL 注入
 dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL);
 if (NULL == hRemoteThread)
 {
  EP_ShowError("ZwCreateThreadEx");
  return FALSE;
 }
 // 关闭句柄
 ::CloseHandle(hProcess);
 ::FreeLibrary(hNtdllDll);

 return TRUE;
}

试验结果

同远线程注入一致,只是在服务程序中不能弹窗。

APC注入

APC(Asynchronous Procedure Call)为异步过程调用,APC注入是指利用线程本身的APC队列进行DLL注入。

函数介绍

// 将异步调用函数添加到指定线程的APC队列中
DWORD QueueUserAPC(
  PAPCFUNC  pfnAPC, // 函数指针
  HANDLE    hThread, // 线程句柄
  ULONG_PTR dwData // 函数参数
);

// 详细信息可参阅msdn

实现原理

APC队列中的函数需要等待线程挂起时才会被执行,所以要保证我们注入的程序能被执行需要将我们的函数插入到进程的所有线程中。具体代码实现如下:

// apc.h
#include 
#include 


// 根据进程名称获取PID
DWORD GetProcessIdByProcessName(char *pszProcessName);

// 根据PID获取所有的相应线程ID
BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD **ppThreadId, DWORD *dwThreadIdLength);

// APC注入
BOOL ApcInjectDll(char *pszProcessName, char *pszDllName);


//apc.cpp
#include "APC.h"


void ShowError(char *pszText){
 char szErr[MAX_PATH] = { 0 };
 ::wsprintf(szErr, "%s Error[%d]\n", pszText);
 ::MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR);
}


// 根据进程名称获取PID
DWORD GetProcessIdByProcessName(char *pszProcessName){
 DWORD dwProcessId = 0;
 PROCESSENTRY32 pe32 = { 0 };
 HANDLE hSnapshot = NULL;
 BOOL bRet = FALSE;
 ::RtlZeroMemory(&pe32, sizeof(pe32));
 pe32.dwSize = sizeof(pe32);

 // 获取进程快照
 hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
 if (NULL == hSnapshot)
 {
  ShowError("CreateToolhelp32Snapshot");
  return dwProcessId;
 }

 // 获取第一条进程快照信息
 bRet = ::Process32First(hSnapshot, &pe32);
 while (bRet)
 {
  // 获取快照信息
  if (0 == ::lstrcmpi(pe32.szExeFile, pszProcessName))
  {
   dwProcessId = pe32.th32ProcessID;
   break;
  }

  // 遍历下一个进程快照信息
  bRet = ::Process32Next(hSnapshot, &pe32);
 }

 return dwProcessId;
}


// 根据PID获取所有的相应线程ID
BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD **ppThreadId, DWORD *pdwThreadIdLength){
 DWORD *pThreadId = NULL;
 DWORD dwThreadIdLength = 0;
 DWORD dwBufferLength = 1000;
 THREADENTRY32 te32 = { 0 };
 HANDLE hSnapshot = NULL;
 BOOL bRet = TRUE;

 do
 {
  // 申请内存
  pThreadId = new DWORD[dwBufferLength];
  if (NULL == pThreadId)
  {
   ShowError("new");
   bRet = FALSE;
   break;
  }
  ::RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD)));

  // 获取线程快照
  ::RtlZeroMemory(&te32, sizeof(te32));
  te32.dwSize = sizeof(te32);
  hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
  if (NULL == hSnapshot)
  {
   ShowError("CreateToolhelp32Snapshot");
   bRet = FALSE;
   break;
  }

  // 获取第一条线程快照信息
  bRet = ::Thread32First(hSnapshot, &te32);
  while (bRet)
  {
   // 获取进程对应的线程ID
   if (te32.th32OwnerProcessID == dwProcessId)
   {
    pThreadId[dwThreadIdLength] = te32.th32ThreadID;
    dwThreadIdLength++;
   }

   // 遍历下一个线程快照信息
   bRet = ::Thread32Next(hSnapshot, &te32);
  }

  // 返回
  *ppThreadId = pThreadId;
  *pdwThreadIdLength = dwThreadIdLength;
  bRet = TRUE;

 } while (FALSE);

 if (FALSE == bRet)
 {
  if (pThreadId)
  {
   delete[]pThreadId;
   pThreadId = NULL;
  }
 }

 return bRet;
}


// APC注入
BOOL ApcInjectDll(char *pszProcessName, char *pszDllName){
 BOOL bRet = FALSE;
 DWORD dwProcessId = 0;
 DWORD *pThreadId = NULL;
 DWORD dwThreadIdLength = 0;
 HANDLE hProcess = NULL, hThread = NULL;
 PVOID pBaseAddress = NULL;
 PVOID pLoadLibraryAFunc = NULL;
 SIZE_T dwRet = 0, dwDllPathLen = 1 + ::lstrlen(pszDllName);
 DWORD i = 0;

 do
 {
  // 根据进程名称获取PID
  dwProcessId = GetProcessIdByProcessName(pszProcessName);
  if (0 >= dwProcessId)
  {
   bRet = FALSE;
   break;
  }

  // 根据PID获取所有的相应线程ID
  bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength);
  if (FALSE == bRet)
  {
   bRet = FALSE;
   break;
  }

  // 打开注入进程
  hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
  if (NULL == hProcess)
  {
   ShowError("OpenProcess");
   bRet = FALSE;
   break;
  }

  // 在注入进程空间申请内存
  pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  if (NULL == pBaseAddress)
  {
   ShowError("VirtualAllocEx");
   bRet = FALSE;
   break;
  }
  // 向申请的空间中写入DLL路径数据 
  ::WriteProcessMemory(hProcess, pBaseAddress, pszDllName, dwDllPathLen, &dwRet);
  if (dwRet != dwDllPathLen)
  {
   ShowError("WriteProcessMemory");
   bRet = FALSE;
   break;
  }

  // 获取 LoadLibrary 地址
  pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
  if (NULL == pLoadLibraryAFunc)
  {
   ShowError("GetProcessAddress");
   bRet = FALSE;
   break;
  }

  // 遍历线程, 插入APC
  i = dwThreadIdLength;
  for (; i > 0; --i)
  {
   // 打开线程
   hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]);
   if (hThread)
   {
    // 插入APC
    ::QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress);
    // 关闭线程句柄
    ::CloseHandle(hThread);
    hThread = NULL;
   }
  }

  bRet = TRUE;

 } while (FALSE);

 // 释放内存
 if (hProcess)
 {
  ::CloseHandle(hProcess);
  hProcess = NULL;
 }
 if (pThreadId)
 {
  delete[]pThreadId;
  pThreadId = NULL;
 }

 return bRet;
}

总结

Windows注入技术可以方便我们对目标进程作修改,但是也可能使目标进程崩溃,在使用的时候需要小心谨慎。

694e0ee754b576785ad2967fd75a5308.png
长按图片自动识别~~~关注我哦~~~~
- END -

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

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

相关文章

nginx location 正则表达式匹配多个地址_就是要让你搞懂Nginx,这篇就够了!

Nginx 是一个高性能的 HTTP 和反向代理服务器,特点是占用内存少,并发能力强,事实上 Nginx 的并发能力确实在同类型的网页服务器中表现较好。Nginx 专为性能优化而开发,性能是其最重要的要求,十分注重效率,有…

mysql查询网址_bootstrap+flask+mysql实现网站查询

之前那篇文章是flaskredis的,如果用flaskmysql怎么实现呢?创建数据库:CREATE DATABASE web12306 DEFAULT CHARACTER SET utf8;创建表:CREATE TABLE web12306 (user_email varchar(100) NOT NULL DEFAULT ,user_pass varchar(100)…

dubbo官方文档_不可忽视的Dubbo线程池

问题描述线上突然出现Dubbo超时调用,时间刚好为Consumer端设置的超时时间。有好几个不同的接口都报超时了第1次调用超时,第2次(或第3次)重试调用非常快(正常水平)Dubbo调用超时的情况集中出现了3次&#xf…

python中比较重要的几个函数_Python 几个重要的内置函数 python中的内置函数和关键字需要背过吗...

python重要的几个内置函数用法python内置函数什么用忘不掉的是回忆,继续的是生活,错过的,就当是路过吧。来来往往身边出现很多人,总有一个位置,一直没有变。看看温暖的阳光,偶尔还是会想一想。Python内置函…

netty worker线程数量_Dubbo线程模型

Dubbo中线程池的应用还是比较广泛的,按照consumer端到provider的RPC的方向来看,consumer端的应用业务线程到netty线程、consuemr端dubbo业务线程池,到provider端的netty boss线程、worker线程和dubbo业务线程池等。这些线程各司其职相互配合&…

update inner join mysql_Mysql update inner join

一:需求A表和B表的表结构相同,A表是历史表,B表是增量数据表;想要根据关联条件更新A表中的数据。二:表结构CREATE TABLE A (id bigint(20) NOT NULL AUTO_INCREMENT,bid bigint(20) NOT NULL ,sid bigint(20) NOT NULL …

function click_click事件的累加问题解决

click事件的 累加问题解决:$判断是否隐藏:hidden.c>span 只包含儿子.c span 包含儿子和孙子data-*嵌入自定义数据 .data(to)获取数据remove是移除标签 delete删除数组元素each()函数$(".layui-table-total .layui-table-cell").each(functi…

mysql stragg_如何在MySQL中將子查詢行的結果顯示為一列?

I have three tables Category, Movies and RelCatMov我有三個表分類,電影和RelCatMovCategory-tablecategoryid, categoryName1 thriller2 supsense3 romantic4 action5 sci-fiMovies-tablemovieid, movieName1 Avataar2 Titanic3 NinjaAssassinRelCatMov-tablecategoryid, Mo…

知道python语言应用2020答案_热点:大学moocPython语言基础与应用答案

2020年智慧树网课答案为您详细解读azMisb热点:大学moocPython语言基础与应用答案的详情,题主的教授应该是想要同学们找出一个值得研究和讨论的theory,简单来说就是你论文探讨的中心。然后需要你们定topic,然后搜索大量靠谱的资料,…

timestamp 转换 date mysql_MySQL时间函数 | 时间戳和日期之间得转换

一、时间戳转日期select FROM_UNIXTIME(1606028010, %Y-%m-%d %H:%i:%s);二、日期转时间戳select unix_timestamp(2018-01-15 09:45:16);三、时间戳格式化十位时间戳转为固定格式(yyyy-MM-dd HH:mm:ss)日期1.格式规定%M 月名字(January……December)%W 星期名字(Sunday……Satu…

python 窗口程序开发课程_从零开始学Python - 第019课:使用PyCharm开发Python应用程序...

坚持学习完前18课的小伙伴应该已经感受到了,随着我们对Python语言的认知在逐步加深,我们写的代码也越来越复杂了。“工欲善其事,必先利其器”,如果希望能够更快更好的写出代码,选择一个称手的开发工具是很有必要的。虽…

MySQL服务迁移到opt_mysql文件*.opt *.frm *.MYI *.MYD的迁移

注意:这里使用mysql 5.1,Ubuntu 11.10在机器A的/var/lib/mysql/文件夹下面有数据库smallone的文件。现在想把smallone下面对应的*.MYD *.frm *.MYI直接迁移到机器B的/var/lib/mysql/smallone下面1,直接在mysql中查看是否迁移成功在/etc/init.d文件夹下面…

python是一种动态语言这意味着_Python如何能成为全球最受欢迎的编程语言?该不该学Python?...

全文共3304字,预计学习时长10分钟 图源:(Python logo courtesy of https 有一种语言在过去十年受喜爱度一路飙升,成为最受欢迎的一种编程语言,它是谁? 千呼万唤始出来,没错,它就是我们的老弟Pyt…

mysql远程压力测试_mysql压力测试脚本实例

本文实例讲述了mysql压力测试的脚本,分享给大家供大家参考。具体如下:创建表deptcreate table dept( /*部门表*/deptno mediumint unsigned not null default 0,dname varchar(20) not null default "",loc varchar(13) not null defaul…

python pyc文件是啥_python pyc文件

这么编译:>>> import py_compile>>> py_compile.compile(rtest2.py)这么运行:MacBook-Air:Downloads huangyong$ python test2.pyc中国成都需要批量编译的话,这样:import compileallcompileall.compile_dir(rH:…

python生成csv文件带水印_Python从CSV文件导入数据和生成简单图表

我们已经完成Python的基础环境搭建工作,现在我们尝试导入CSV数据 我们准备一个csv测试数据,文件名是csv-test-data.csv数据。我们将文件传到Linux上准备导入的代码我们运行代码首先导入csv模块以便能访问所需的方法用with语句打开数据文件并把它绑定到对…

python处理csv文件案例_让繁琐的工作自动化——python处理CSV文件

让繁琐的工作自动化——python处理CSV文件CSV:CSV文件是一种简化的电子表格,不同于Excle(二进制文件),CSV是纯文本文件。1.环境python3.8pycharm2020.12.读取本期实例数据haha,18,10.0jiji,16,12.1lala,17,11.9papa,11,13.3首先导入csv模块&a…

python爬取js加载的数据_JS动态加载数据不会爬?老司机教你两个方法爬取想要的数据...

学习Python的人绝大部分都是在用Python做爬虫,毕竟对于爬虫而言Python是不二选。但是一般简单的静态页面网站还是很好爬取的,对于很多动态加载的网站就不知道怎么办了,今天小编就给大家介绍两种爬取js加载的动态数据,希望对爬虫方…

python实现坐标求取_根据相机位姿求指定点的世界坐标及其python实现

Authorshaniadolphin求解目的本文将展示位姿估计的一种应用,即通过单目相机对环境进行测量。简单来说,本文的工作就是利用下面的两幅图,在已知P1、P2、P3、P4四点世界坐标的情况下,计算出其它点的世界坐标。如图所示,一…

c# treeview查找并选中节点_最通俗易懂的二叉查找树(BST)详解

原来来自 呆萌数据结构-06二叉查找树​imoegirl.com二叉查找树(Binary Search Tree),简写BST,是满足某些条件的特殊二叉树。任何一个节点的左子树上的点,都必须小于当前节点。任何一个节点的右子树上的点,都…