-
-
[原创]代码注入
-
2021-4-16 21:39 11068
-
代码注入(含汇编注入)
一:简介
代码注入是一种向目标进程插入独立运行的代码并使之运行的技术,它一般调用CreateRemoteThread() API以远程线程形式运行插入的代码,所以也可称为线程注入。
向目标进程插入代码和数据,代码以线程过程(Thread Procedure)形式插入,而代码中的数据则以线程参数的形式传入。
对于dll注入来说,整个dll都会被插入目标进程,代码和数据共享于内存,所以代码能够正常的运行。与dll注入不同,代码注入仅仅向目标进程注入必要的代码,要想使注入的代码正常运行,还必须将代码中的数据一同注入(并将已注入数据的地址明确告知代码)。
使用代码注入的优缺分析:
占用的内存少。难以查找痕迹,采用dll注入的话会在目标进程的内存中留下痕迹,很容易就能判断出目标进程是否被执行过注入操作,使用代码注入的话几乎不会留下任何痕迹(还是有方法可以检测的),所以恶意代码中大量使用代码注入技术。
二:普通代码注入示例
简介:查看进程的pid,然后在控制台运行程序:CodeInjection.exe [pid],向对应进程插入一个消息窗口(MessageBoxA)。
(一)源码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | #include <windows.h> #include <stdio.h> #include <windef.h> / * FARPROC 是一个 4 字节指针,指向一个函数的内存地址,例如 GetProcAddress 的返回类型就是一个FARPROC。 如果你要存放这个地址,可以声明以一个 FARPROC变量来存放。 * / typedef struct _THREAD_PARAM { FARPROC pFunc[ 2 ]; / / LoadLibraryA(), GetProcAddress(),存放两个API函数的地址 char szBuf[ 4 ][ 128 ]; / / "user32.dll" , "MessageBoxA" , "www.reversecore.com" , "ReverseCore" } THREAD_PARAM, * PTHREAD_PARAM; typedef HMODULE(WINAPI * PFLOADLIBRARYA) ( LPCSTR lpLibFileName / / LoadLibraryA的函数指针,HMODULE的返回值,调用约定为WINAPI,指针名叫PFLOADLIBRARYA,参数为字符串指针lpLibFileName ); typedef FARPROC(WINAPI * PFGETPROCADDRESS) / / 同理GetProcAddress()的函数指针 ( HMODULE hModule, / / 模块句柄 LPCSTR lpProcName / / 查找的函数名字符串指针 ); typedef int (WINAPI * PFMESSAGEBOXA) / / MessageBoxA的函数指针 ( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType ); DWORD WINAPI ThreadProc(LPVOID lParam) / / 线程过程,用来存放我们的注入代码 { PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam; / / 将传入的指针强制类型转换为我们定义的线程参数结构体类型的指针,其实指针本质都是相同的,只是语言的语法要求 HMODULE hMod = NULL; / / 定义一个hMod来存放模块句柄 FARPROC pFunc = NULL; / / 定义一个FARPROC类型的变量来存放GetProcAddress的返回值 hMod = ((PFLOADLIBRARYA)pParam - >pFunc[ 0 ])(pParam - >szBuf[ 0 ]); / / LoadLibrary( "user32.dll" ) if (!hMod) return 1 ; pFunc = (FARPROC)((PFGETPROCADDRESS)pParam - >pFunc[ 1 ])(hMod, pParam - >szBuf[ 1 ]); / / GetProcAddress(hMod, "MessageBoxA" ); if (!pFunc) return 1 ; ((PFMESSAGEBOXA)pFunc)(NULL, pParam - >szBuf[ 2 ], pParam - >szBuf[ 3 ], MB_OK); / / MessageBoxA(NULL, "www.reversecore.com" , "ReverseCore" , MB_OK); return 0 ; } BOOL InjectCode(DWORD dwPID) { HMODULE hMod = NULL; THREAD_PARAM param = { 0 , }; HANDLE hProcess = NULL; HANDLE hThread = NULL; LPVOID pRemoteBuf[ 2 ] = { 0 , }; / / void型的长指针,用来指向分配的内存 DWORD dwSize = 0 ; hMod = GetModuleHandleA( "kernel32.dll" ); param.pFunc[ 0 ] = GetProcAddress(hMod, "LoadLibraryA" ); / / 给线程参数结构体赋值 param.pFunc[ 1 ] = GetProcAddress(hMod, "GetProcAddress" ); strcpy_s(param.szBuf[ 0 ], "user32.dll" ); strcpy_s(param.szBuf[ 1 ], "MessageBoxA" ); strcpy_s(param.szBuf[ 2 ], "www.reversecore.com" ); strcpy_s(param.szBuf[ 3 ], "ReverseCore" ); if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPID))) / / 根据PID获取进程句柄 { printf( "OpenProcess() fail : err_code = %d\n" , GetLastError()); return FALSE; } dwSize = sizeof(THREAD_PARAM); if (!(pRemoteBuf[ 0 ] = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE))) / / 在对应的进程内部给线程参数,即注入代码要用到的数据分配空间 { printf( "VirtualAllocEx() fail : err_code = %d\n" , GetLastError()); return FALSE; } if (!WriteProcessMemory(hProcess, / / 将线程参数(注入代码要使用的数据)结构体写入分配的内存 pRemoteBuf[ 0 ], (LPVOID)¶m, dwSize, NULL)) { printf( "WriteProcessMemory() fail : err_code = %d\n" , GetLastError()); return FALSE; } / / 就是因为这个的原因,生成的时候必须用release dwSize = (DWORD)InjectCode - (DWORD)ThreadProc; / / 函数名就是函数的起始地址,在内存中紧跟着ThreadProc函数的就是InjectCode,所以首地址相减就能够得到ThreadProc代码占用的空间 if (!(pRemoteBuf[ 1 ] = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE))) / / 为注入代码在对应进程分配空间 { printf( "VirtualAllocEx() fail : err_code = %d\n" , GetLastError()); return FALSE; } if (!WriteProcessMemory(hProcess, pRemoteBuf[ 1 ], (LPVOID)ThreadProc, dwSize, NULL)) / / 在对应空间中写入代码 { printf( "WriteProcessMemory() fail : err_code = %d\n" , GetLastError()); return FALSE; } if (!(hThread = CreateRemoteThread(hProcess, / / 创建远程线程执行注入代码 NULL, 0 , (LPTHREAD_START_ROUTINE)pRemoteBuf[ 1 ], pRemoteBuf[ 0 ], 0 , NULL))) { printf( "CreateRemoteThread() fail : err_code = %d\n" , GetLastError()); return FALSE; } WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); return TRUE; } BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) / / 设置权限 { TOKEN_PRIVILEGES tp; HANDLE hToken; LUID luid; if (!OpenProcessToken(GetCurrentProcess(), / / 打开一个与进程相关联的访问token TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { printf( "OpenProcessToken error: %u\n" , GetLastError()); return FALSE; } if (!LookupPrivilegeValue(NULL, / / 查找系统对应的权限值并储存到luid中 lpszPrivilege, &luid)) { printf( "LookupPrivilegeValue error: %u\n" , GetLastError()); return FALSE; } tp.PrivilegeCount = 1 ; / / 将这些都存入到TOKEN_PRIVILEGES结构体中 tp.Privileges[ 0 ].Luid = luid; if (bEnablePrivilege) tp.Privileges[ 0 ].Attributes = SE_PRIVILEGE_ENABLED; / / 并设置权限属性 else tp.Privileges[ 0 ].Attributes = 0 ; if (!AdjustTokenPrivileges(hToken, / / 启用提权 FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) { printf( "AdjustTokenPrivileges error: %u\n" , GetLastError()); return FALSE; } if (GetLastError() = = ERROR_NOT_ALL_ASSIGNED) { printf( "The token does not have the specified privilege. \n" ); return FALSE; } return TRUE; } int main( int argc, char * argv[]) { DWORD dwPID = 0 ; if (argc ! = 2 ) { printf( "\n USAGE : %s <pid>\n" , argv[ 0 ]); return 1 ; } if (!SetPrivilege(SE_DEBUG_NAME, TRUE)) return 1 ; dwPID = (DWORD)atol(argv[ 1 ]); / / 将字符串转换为长整型 InjectCode(dwPID); return 0 ; } |
注:生成可执行文件的时候一定要使用release版本
(二)注入和调试演示
1. 注入演示
先运行notepad++.exe,然后使用ProcessExploer查看进程PID
管理员模式运行CMD, 进入对应的文件夹后,输入命令
执行结果
2. 调试演示
将notepad拖入,并让其运行起来
然后点击选项
然后注入代码
然后就会断到我们的线程函数
三:使用汇编语言编写注入代码示例
(一)要注入的汇编分析
随便使用一个程序,拖入OD,我们使用OD来编写我们的注入汇编代码
PUSH EBP
MOV EBP,ESP
注释:
生成注入代码ThreadProc自己的栈帧,可以稍微记下硬编码55 8BEC
MOV ESI,DWORD PTR SS:[EBP+8]
注释:
[EBP+8],ThreadProc函数的唯一一个参数LPVOID lParam,这个参数就是我们定义的线程参数的结构体的地址,将其mov给了esi,那么之后ESI和ESI+4就可以取到我们定义的结构体的两个成员(API的函数地址)这个定义的结构体看后面具体代码
[EBP+4]是调用该函数时call压入的返回地址
PUSH 00006C6C "\0\0ll"
PUSH 642E3233 "d.23"
PUSH 72657375 "resu"
注释:
将user32.dll字符串压入栈中(仔细思考一下,结合小端序和栈的排列)
这算是一个必须掌握的一个简单技能
PUSH ESP
注释:
将ESP压入栈中,因为从ESP开始是储存的字符串,ESP就是字符串的起始地址,就相当于把字符串压入了栈中,作为下面调用LoadLibraryA()函数的参数
CALL DWORD PTR DS:[ESI]
注释:
调用ESI处 储存的函数地址 对应的函数,LoadLibraryA(),汇编语言调用完函数之后返回值一般都是储存到eax寄存器中
PUSH 41786F "\0Axo"
PUSH 42656761 "Bega"
PUSH 7373654D "sseM"
注释:
将"MessageBoxA"字符串压入栈中
PUSH ESP
PUSH EAX
CALL DWORD PTR DS:[ESI+4]
注释:
现在的ESP就是字符串"MessageBoxA"的起始地址,将其压入了栈中作为函数的第二个参数,EAX里面的值是调用LoadLibraryA()函数得到的模块的起始地址,hMod。
这里就是GetProcAddress(hMod,"MessageBoxA");
接下来,我们就要调用MessageBoxA这个函数了,因为GetProcAddress调用完毕后EAX中储存的已经 是函数MessageBoxA的地址了
MessageBoxA(NULL,"www.reversecore.com", "ReverseCore", MB_OK);
先压入从右到左的第一个参数MB_OK
PUSH 0
然后压入"ReverseCore"这个字符串,这里有一个小技巧,可以记住,就是利用call等价于push + jmp的特点
假设上一条指令所处:
002D002C 6A 00 PUSH 0
那么接下来,在下方002D0033处,即call指令之后开始,存放我们的字符串"ReverseCore",存放的区域就是002D0033-002D003F之间
然后我们call的话,就直接call 002D003F
这样先将字符串“ReverseCore” push进入了栈中,然后有jmp跳转到了地址002D003F处
同理,现在我们要压入参数"www.reversecore.com"
002D003F-002D0043之间存放指令call 002D0058
002D0044-002D0057之间存放我们的字符串"www.reversecore.com"
这样既将下面的数据push进入了栈中,还跳转到了下方
然后压入第一个参数NULL
push 0
最后调用我们的MessageBoxA函数
CALL EAX
之后调用完函数之后,我们要将返回值设为0
使用指令XOR EAX, EAX便可以简单并快捷的将返回值设置为0
MOV ESP,EBP
POP EBP
RETN
注释:
删除栈帧及函数返回
所有的汇编指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 004010ED 55 PUSH EBP 004010EE 8BEC MOV EBP,ESP 004010F0 8B75 08 MOV ESI,DWORD PTR SS:[EBP + 8 ] ; ESI = pParam 004010F3 68 6C6C0000 PUSH 6C6C 004010F8 68 33322E64 PUSH 642E3233 004010FD 68 75736572 PUSH 72657375 00401102 54 PUSH ESP ; - "user32.dll" 00401103 FF16 CALL DWORD PTR DS:[ESI] ; LoadLibraryA( "user32.dll" ) 00401105 68 6F784100 PUSH 41786F 0040110A 68 61676542 PUSH 42656761 0040110F 68 4D657373 PUSH 7373654D 00401114 54 PUSH ESP ; - "MessageBoxA" 00401115 50 PUSH EAX ; - hMod 00401116 FF56 04 CALL DWORD PTR DS:[ESI + 4 ] ; GetProcAddress(hMod, "MessageBoxA" ) 00401119 6A 00 PUSH 0 ; - MB_OK ( 0 ) 0040111B E8 0C000000 CALL 0040112C 00401120 <ASCII> ; - "ReverseCore" , 0 0040112C E8 14000000 CALL 00401145 00401131 <ASCII> ; - "www.reversecore.com" , 0 00401145 6A 00 PUSH 0 ; - hWnd ( 0 ) 00401147 FFD0 CALL EAX ; MessageBoxA( 0 , "www.reversecore.com" , "ReverseCore" , 0 ) 00401149 33C0 XOR EAX,EAX 0040114B 8BE5 MOV ESP,EBP 0040114D 5D POP EBP 0040114E C3 RETN |
编辑完了之后, 右键,Copy to executable-All modifications
弹出消息框之后点击Copy all
然后弹出一个显示所有修改的窗口,我们鼠标右键Save file,即可保存出去
然后我们得到shellcode
1 2 3 4 5 6 7 8 9 10 11 12 13 | BYTE g_InjectionCode[] = { 0x55 , 0x8B , 0xEC , 0x8B , 0x75 , 0x08 , 0x68 , 0x6C , 0x6C , 0x00 , 0x00 , 0x68 , 0x33 , 0x32 , 0x2E , 0x64 , 0x68 , 0x75 , 0x73 , 0x65 , 0x72 , 0x54 , 0xFF , 0x16 , 0x68 , 0x6F , 0x78 , 0x41 , 0x00 , 0x68 , 0x61 , 0x67 , 0x65 , 0x42 , 0x68 , 0x4D , 0x65 , 0x73 , 0x73 , 0x54 , 0x50 , 0xFF , 0x56 , 0x04 , 0x6A , 0x00 , 0xE8 , 0x0C , 0x00 , 0x00 , 0x00 , 0x52 , 0x65 , 0x76 , 0x65 , 0x72 , 0x73 , 0x65 , 0x43 , 0x6F , 0x72 , 0x65 , 0x00 , 0xE8 , 0x14 , 0x00 , 0x00 , 0x00 , 0x77 , 0x77 , 0x77 , 0x2E , 0x72 , 0x65 , 0x76 , 0x65 , 0x72 , 0x73 , 0x65 , 0x63 , 0x6F , 0x72 , 0x65 , 0x2E , 0x63 , 0x6F , 0x6D , 0x00 , 0x6A , 0x00 , 0xFF , 0xD0 , 0x33 , 0xC0 , 0x8B , 0xE5 , 0x5D , 0xC3 }; |
(二)注入的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | #include "windows.h" #include "stdio.h" typedef struct _THREAD_PARAM { FARPROC pFunc[ 2 ]; / / LoadLibraryA(), GetProcAddress()函数地址 } THREAD_PARAM, * PTHREAD_PARAM; BYTE g_InjectionCode[] = { 0x55 , 0x8B , 0xEC , 0x8B , 0x75 , 0x08 , 0x68 , 0x6C , 0x6C , 0x00 , 0x00 , 0x68 , 0x33 , 0x32 , 0x2E , 0x64 , 0x68 , 0x75 , 0x73 , 0x65 , 0x72 , 0x54 , 0xFF , 0x16 , 0x68 , 0x6F , 0x78 , 0x41 , 0x00 , 0x68 , 0x61 , 0x67 , 0x65 , 0x42 , 0x68 , 0x4D , 0x65 , 0x73 , 0x73 , 0x54 , 0x50 , 0xFF , 0x56 , 0x04 , 0x6A , 0x00 , 0xE8 , 0x0C , 0x00 , 0x00 , 0x00 , 0x52 , 0x65 , 0x76 , 0x65 , 0x72 , 0x73 , 0x65 , 0x43 , 0x6F , 0x72 , 0x65 , 0x00 , 0xE8 , 0x14 , 0x00 , 0x00 , 0x00 , 0x77 , 0x77 , 0x77 , 0x2E , 0x72 , 0x65 , 0x76 , 0x65 , 0x72 , 0x73 , 0x65 , 0x63 , 0x6F , 0x72 , 0x65 , 0x2E , 0x63 , 0x6F , 0x6D , 0x00 , 0x6A , 0x00 , 0xFF , 0xD0 , 0x33 , 0xC0 , 0x8B , 0xE5 , 0x5D , 0xC3 }; BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) / / 设置权限 { TOKEN_PRIVILEGES tp; HANDLE hToken; LUID luid; if ( !OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) ) { printf( "OpenProcessToken error: %u\n" , GetLastError()); return FALSE; } if ( !LookupPrivilegeValue(NULL, / / lookup privilege on local system lpszPrivilege, / / privilege to lookup &luid) ) / / receives LUID of privilege { printf( "LookupPrivilegeValue error: %u\n" , GetLastError() ); return FALSE; } tp.PrivilegeCount = 1 ; tp.Privileges[ 0 ].Luid = luid; if ( bEnablePrivilege ) tp.Privileges[ 0 ].Attributes = SE_PRIVILEGE_ENABLED; else tp.Privileges[ 0 ].Attributes = 0 ; / / Enable the privilege or disable all privileges. if ( !AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL) ) { printf( "AdjustTokenPrivileges error: %u\n" , GetLastError() ); return FALSE; } if ( GetLastError() = = ERROR_NOT_ALL_ASSIGNED ) { printf( "The token does not have the specified privilege. \n" ); return FALSE; } return TRUE; } BOOL InjectCode(DWORD dwPID) / / 关键注入部分 { HMODULE hMod = NULL; THREAD_PARAM param = { 0 ,}; HANDLE hProcess = NULL; HANDLE hThread = NULL; LPVOID pRemoteBuf[ 2 ] = { 0 ,}; hMod = GetModuleHandleA( "kernel32.dll" ); param.pFunc[ 0 ] = GetProcAddress(hMod, "LoadLibraryA" ); param.pFunc[ 1 ] = GetProcAddress(hMod, "GetProcAddress" ); / / 获取两个函数的地址并储存进入自定义的线程参数结构体中 if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, / / dwDesiredAccess FALSE, / / bInheritHandle dwPID)) ) / / dwProcessId { printf( "OpenProcess() fail : err_code = %d\n" , GetLastError()); / / 根据PID获取进程句柄 return FALSE; } / / Allocation for THREAD_PARAM / / 在对应的进程当中分配内存来存储data if ( !(pRemoteBuf[ 0 ] = VirtualAllocEx(hProcess, / / hProcess NULL, / / lpAddress sizeof(THREAD_PARAM), / / dwSize MEM_COMMIT, / / flAllocationType PAGE_READWRITE)) ) / / flProtect { printf( "VirtualAllocEx() fail : err_code = %d\n" , GetLastError()); return FALSE; } if ( !WriteProcessMemory(hProcess, / / hProcess pRemoteBuf[ 0 ], / / lpBaseAddress (LPVOID)¶m, / / lpBuffer sizeof(THREAD_PARAM), / / nSize NULL) ) / / [out] lpNumberOfBytesWritten { printf( "WriteProcessMemory() fail : err_code = %d\n" , GetLastError()); / / 写入我们的代码要用的数据 return FALSE; } / / Allocation for ThreadProc() if ( !(pRemoteBuf[ 1 ] = VirtualAllocEx(hProcess, / / hProcess NULL, / / lpAddress sizeof(g_InjectionCode), / / dwSize MEM_COMMIT, / / flAllocationType PAGE_EXECUTE_READWRITE)) ) / / flProtect { printf( "VirtualAllocEx() fail : err_code = %d\n" , GetLastError()); / / 为我们的注入代码分配空间 return FALSE; } if ( !WriteProcessMemory(hProcess, / / hProcess pRemoteBuf[ 1 ], / / lpBaseAddress (LPVOID)&g_InjectionCode, / / lpBuffer sizeof(g_InjectionCode), / / nSize NULL) ) / / [out] lpNumberOfBytesWritten { printf( "WriteProcessMemory() fail : err_code = %d\n" , GetLastError()); / / 写入我们的shellcode return FALSE; } if ( !(hThread = CreateRemoteThread(hProcess, / / hProcess / / 这里创建远程线程执行我们的shellcode NULL, / / lpThreadAttributes 0 , / / dwStackSize (LPTHREAD_START_ROUTINE)pRemoteBuf[ 1 ], pRemoteBuf[ 0 ], / / lpParameter 0 , / / dwCreationFlags NULL)) ) / / lpThreadId { printf( "CreateRemoteThread() fail : err_code = %d\n" , GetLastError()); return FALSE; } WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); return TRUE; } int main( int argc, char * argv[]) { DWORD dwPID = 0 ; if ( argc ! = 2 ) { printf( "\n USAGE : %s <pid>\n" , argv[ 0 ]); return 1 ; } / / change privilege if ( !SetPrivilege(SE_DEBUG_NAME, TRUE) ) return 1 ; / / code injection dwPID = (DWORD)atol(argv[ 1 ]); InjectCode(dwPID); return 0 ; } |
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。