去年的时候想做个脚本,开始是用python,但是有些实现想用Hook,然而自己本来python就是半桶水,而C++的window编程更是一窍不通,然而网上的教程又是零零散散
好吧,其实就是我没基础,大佬:接下来就不用我说了吧(超需要啊~~~)
注入需要准备一个dll,与配套的exe程序(当然,hook那么多种形式,我只是碰巧学了这么个半通不通的)
所以,在这个Demo里,有2个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; //存放加载ExportFuncOne.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=)
所以,我们需要重新写一个函数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[] lea edi, OldCode mov esi, fproc movsd movsb NewCode[0] = 0xe9;//实际上0xe9就相当于jmp指令 //获取Myadd()的相对地址 lea eax, ExportFunc mov ebx, fproc sub eax, ebx sub eax, 5 mov dword ptr[NewCode + 1], eax //填充完毕,现在NewCode[]里的指令相当于Jmp Myadd HookOn(); //可以开启钩子了 * HookOn跟HookOff类似 * 就是修改程序的内存,然后变更函数的入口了 * 这一步相当于我们的特工B潜入目标阵营后确认A的存在后,将自己 铭牌跟A的偷换了 * 当某人C通过铭牌来找A的时候,B接过信息并顺手转交给了A * 由于信息格式是符合规定的,A也没怀疑就做好处理并返回给了C * 这时候原本天知地知,AC知的事,就被B知道甚至可能偷偷做了一点手脚了 void HookOn() DWORD dwTemp = 0; DWORD dwOldProtect; //将内存保护模式改为可写,老模式保存入dwOldProtect VirtualProtectEx(cur, fproc, 5, PAGE_READWRITE, &dwOldProtect); //将所属进程中add()的前5个字节改为Jmp Myadd WriteProcessMemory(cur, fproc, NewCode, 5, 0); //将内存保护模式改回为dwOldProtect VirtualProtectEx(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); //还没注入就加载 InjectDll(dwPID, dllPath); 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"); 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;最终效果:
Dll注入与调用Add.dllOne.exeHookDll.dll去年的时候想做个脚本,开始是用python,但是有些实现想用Hook,然而自己本来python就是半桶水,而C++的window编程更是一窍不通,然而网上的教程又是零零散散好吧,其实就是我没基础,大佬:接下来就不用我说了吧(超需要啊~~~)注入需要准备一个dll,与配套的exe程序(当然,hook那么多种形式,我只是碰巧... #include #include #import c:Program FilesCommon FilesSystemadomsado15.dll no_namespace rename(EOF,adoEOF) rename(BOF,adoBOF) class ADOConn // 定义变量 public: //添加一个指向Connection对象的指针: _ConnectionPtr m_pConnection; //添加一个指向Recordset对象的指针:
可以看到,One的弹框变化(虽然没写好弹框定位,但是通过任务栏确实能确定弹框的归属)1:非递归方法: 代码如下:// File Name: CSearch.h #pragma once#include <vector>#include <atlstr>#include <stack> class Search{private: std::vector<CString> m_strPath; // 保存查找到了文件路径 std::vector<CString> m_strSearchName; // 搜索的关键字 std::stack<CString> strPathStack; // 栈,保存磁盘ID Add.dll(假设的要hook的函数所在dll) HookDll.dll(包含要注入用的函数所在dll) 2个exe项目:One.exe(假设要Hook的程序) Hook.exe(hook用的程序) 导包跟头文件我会发出来,但是函数修改的话,只会在修改部分发出 Add.dll本文中我将介绍DLL注入的相关知识。不算太糟的是,DLL注入技术可以被正常软件用来添加/扩展其他程序,调试或逆向工程的功能性;该技术也常被恶意软件以多种方式利用。这意味着从安全角度来说,了解DLL注入的工作原理是十分必要的。 不久前在为攻击方测试(目的是为了模拟不同类型的攻击行为)开发定制工具的时候,我编写了这个名为“injectAllTheThings”的小工程的大...实现DLL注入的思路是让进程运行LoadLibrary函数,把我们的DLL装载进去,怎样让进程调用LoadLibrary函数呢?首先想到的办法就是在目标进程中创建一个新的线程来调用。比如: HANDLE WINAPI CreateRemoteThread( __in HANDLE hProcess, //线程被创建的进程句原理:通过修改注入表的方式将我们自己的dll中的菜单及响应添加到应用中去,实现我们想要的功能。 1、注入dll编写 这里是通过 Stud_PE工具将dll注入到目标程序中去的 1.1 dll中添加导出函数 Stud_PE添加到目标程序是需要添加导出函数,因此这里添加一个导出函数,没有实际功能。 jtfz.cpp void test() AfxMessageBox(“加载成功!”); 在de...我们在写DLL的时候,需要在DLL项目里面加上window的API函数DllMain.然后DllMain函数里面写上你自己的函数, 这样,windows会在用LoadLibrary加载DLL后,调用DllMain函数来执行,而你把自己的函数已经先写在在DllMain里面了,那么你的函数也自然而然被间接调用了. wx.cloud.uploadFile({ cloudPath: 'goodsImgs/' + new Date().getTime() + '_' + Math.floor(Math.random() * 1000) + '.jpg', //给图片命名 filePath: this.data.selectImg[index], //本地图片路径 success: (res) => { console.log('上传成功', res.fileID) this.data.uploadImgs[index] = res.fileID wx.hideLoading({ success: (res) => {}, //判断是否全部上传 if (this.data.selectImg.length - 1 == index) { console.log('已全部上传') } else { this.uploadImages(index + 1) fail: (err) => { wx.showToast({ title: '上传失败,请重新上传', type: 'none' [/code] 【promise与递归调用】 Jserm: 有用,解决一个我的有点相似的问题 【小程序开发技巧】input框保持focus状态 Athain: 这个方法确实不错,至于滑动这个应该可以考虑重写onfocus ,不过没尝试过就是了。。 【小程序开发技巧】input框保持focus状态 gongshunkai3: 可以加一个hold-keyboard属性在文本框, 问题是在页面上手指滑动文本框还是会失焦。。 【小程序开发】地图circle自适应大小(radius适配) 公孙元二: 楼主,你的this.wxmap.getRegion是在什么时候用,我是在RegionChange在end且type===scale的时候才调用,会导致一个问题:缩放的时候,圈圈的尺寸不太对。但是实际上计算的radius是没问题