代码注入

  1. 代码注入是一种向目标进程插入独立运行代码并使之运行的技术,它一般调用CreateRomoteThread() API以远程线程形式运行插入的代码,所以也被称为线程注入。
    在这里插入图片描述
  2. 首先向目标进程target.exe插入代码与数据,在此过程中,代码以线程过程(Thread Procedure)形式插入,而代码中使用的数据则以线程参数的形式传入,也就是说,代码与数据是分别注入的。

DLL注入与代码注入

  1. 下面这段代码用来弹出Windows消息框:
DWORD WINAPI ThreadProc(LPVOID lParam)
{
    MessageBoxA(NULL, "www.reversecore.com", "ReverseCore", MB_OK);

    return 0;
}
  1. 若想使用DLL注入技术,则需要把上述代码放入某个DLL文件,然后再将整个DLL文件注入目标进程,采用该技术完成注入后,运行OllyDbg调试器,查看上述ThreadProc()代码区域。
    在这里插入图片描述
  • 10009290与1000929C分别指向DLL数据节区中的字符串(“ReverseCore"、“www.reversecore.com”),上面2条PUSH指令将MessageBoxA() API中要使用的字符串地址存储到栈,1000100E地址处由一条CALL DWORD PTR DS:[100080F0]指令,该指令即使调用user32!MessageBoxA() API的命令,转到100080F0地址处查看。
    在这里插入图片描述
  • 100080F0地址就是DLL的IAT区域,在其上方可用看到其他API的地址,向这样,DLL代码中使用的所有数据均位于DLL的数据区域,采用DLL注入技术,整个DLL会被插入目标进程,代码与数据共存于内存,所以代码能够正常运行,以此不同,代码注入仅向目标进程注入必要的代码,要想使注入的代码正常运行,还必须将代码中使用的数据一同注入。
  1. 使用代码注入的原因
  • 占用内存少:如果要注入的代码与数据较少,那么就不需要将它们做成DLL的形式再注入,此时直接采用代码注入的方式同样能够获得与DLL注入相同的效果,且占用内存会更少。
  • 难以查找痕迹:采用DLL注入方式会在目标进程的内存中留下相关痕迹,很容易让人判断出目标进程是否被执行注入操作,但采用代码注入方式几乎不会留下任何痕迹。

例子

  1. 运行notepad.exe,然后使用Process Explorer查看notepad.exe进程的PID。
    在这里插入图片描述
  2. 运行如下命令
C:\Users\12586\Downloads\example\03\27\bin>CodeInjection.exe 1196
  • notepad.exe进程中弹出一个消息框。
    在这里插入图片描述
  1. CodeInjection.cpp
  • 看一下main()函数
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;
}
  • main()函数用来调用InjectCode()函数,传入的函数参数为目标进程的PID。
  • ThreadProc()函数即为要注入目标进程的代码。
typedef struct _THREAD_PARAM 
{
    FARPROC pFunc[2];               // LoadLibraryA(), GetProcAddress()
    char    szBuf[4][128];          // "user32.dll", "MessageBoxA", "www.reversecore.com", "ReverseCore"
} THREAD_PARAM, *PTHREAD_PARAM;

typedef HMODULE (WINAPI *PFLOADLIBRARYA)
(
    LPCSTR lpLibFileName
);

typedef FARPROC (WINAPI *PFGETPROCADDRESS)
(
    HMODULE hModule,
    LPCSTR lpProcName
);

typedef int (WINAPI *PFMESSAGEBOXA)
(
    HWND hWnd,
    LPCSTR lpText,
    LPCSTR lpCaption,
    UINT uType
);

DWORD WINAPI ThreadProc(LPVOID lParam)
{
    PTHREAD_PARAM   pParam      = (PTHREAD_PARAM)lParam;
    HMODULE         hMod        = NULL;
    FARPROC         pFunc       = NULL;

    // LoadLibrary()
    hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]);    // "user32.dll"
    if( !hMod )
        return 1;

    // GetProcAddress()
    pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]);  // "MessageBoxA"
    if( !pFunc )
        return 1;

    // MessageBoxA()
    ((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);

    return 0;
}
  • 稍微整理下:
hMod = LoadLibraryA("user32.dll");
pFunc = GetProcAddress(hMod, "MessageBoxA");
pFunc = (NULL,"www.revercore.com","ReverseCore",MB_OK);
  • 代码注入技术的核心内容是注入可独立运行的代码,为此,需要同时注入代码与数据,并且要保证代码能够准确引用注入的数据。
  • 上述代码中的ThreadProc()函数可以看到,函数中并未直接调用相关API,也未直接定义使用字符串,它们都是通过THREAD_PARAM结构体以线程参数的形式传递使用。
  • ThreadProc()函数中使用THREAD_PARAM结构体接收2个API地址与4个字符串数据,其中2个API分别为LoadLibraryA()与GetProcAddress(),只要有了这2个API,就能调用所有库函数。
00401000  /. 55             PUSH EBP
00401001  |. 8BEC           MOV EBP,ESP
00401003  |. 56             PUSH ESI
00401004  |. 8B75 08        MOV ESI,DWORD PTR SS:[EBP+8]; [EBP+8] = IParam = address of THREAD_PARAM
00401007  |. 8B0E           MOV ECX,DWORD PTR DS:[ESI]
00401009  |. 8D46 08        LEA EAX,DWORD PTR DS:[ESI+8] ; EAX = "user32.dll"
0040100C  |. 50             PUSH EAX
0040100D  |. FFD1           CALL ECX     ; kernel32.LoadLibraryA()
0040100F  |. 85C0           TEST EAX,EAX
00401011  |. 75 0A          JNZ SHORT 0040101D                       ;  0040101D
00401013  |> B8 01000000    MOV EAX,1
00401018  |. 5E             POP ESI
00401019  |. 5D             POP EBP
0040101A  |. C2 0400        RETN 4
0040101D  |> 8D96 88000000  LEA EDX,DWORD PTR DS:[ESI+88]
00401023  |. 52             PUSH EDX  ; EDX = "MessageBoxA"
00401024  |. 50             PUSH EAX  ; EAX = hMod
00401025  |. 8B46 04        MOV EAX,DWORD PTR DS:[ESI+4]
00401028  |. FFD0           CALL EAX ;kernel32.GetProcAddress
0040102A  |. 85C0           TEST EAX,EAX
0040102C  |.^74 E5          JE SHORT 00401013                        ;  00401013
0040102E  |. 6A 00          PUSH 0
00401030  |. 8D8E 88010000  LEA ECX,DWORD PTR DS:[ESI+188]
00401036  |. 51             PUSH ECX ; ECX = "ReverseCore"
00401037  |. 81C6 08010000  ADD ESI,108
0040103D  |. 56             PUSH ESI ; ESI = "www.reversecore.com"
0040103E  |. 6A 00          PUSH 0
00401040  |. FFD0           CALL EAX ;user32.MessageBoxA()
00401042  |. 33C0           XOR EAX,EAX
00401044  |. 5E             POP ESI
00401045  |. 5D             POP EBP
00401046  \. C2 0400        RETN 4
  • 所有重要数据都是从线程参数IParam[EBP+8]接收使用的,也就说,ThreadProc()函数是可以独立运行的代码。
  1. InjectCode()函数
BOOL InjectCode(DWORD dwPID)
{
    HMODULE         hMod            = NULL;
    THREAD_PARAM    param           = {0,};
    HANDLE          hProcess        = NULL;
    HANDLE          hThread         = NULL;
    LPVOID          pRemoteBuf[2]   = {0,};
    DWORD           dwSize          = 0;

    hMod = GetModuleHandleA("kernel32.dll");

    // set THREAD_PARAM
    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");

    // Open Process
    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS,   // dwDesiredAccess
                                  FALSE,                // bInheritHandle
                                  dwPID)) )             // dwProcessId
    {
        printf("OpenProcess() fail : err_code = %d\n", GetLastError());
        return FALSE;
    }

    // Allocation for THREAD_PARAM
    dwSize = sizeof(THREAD_PARAM);
    if( !(pRemoteBuf[0] = VirtualAllocEx(hProcess,          // hProcess
                                      NULL,                 // lpAddress
                                      dwSize,               // 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)&param,                 // lpBuffer
                            dwSize,                         // nSize
                            NULL) )                         // [out] lpNumberOfBytesWritten
    {
        printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
        return FALSE;
    }

    // Allocation for ThreadProc()
    dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;
    if( !(pRemoteBuf[1] = VirtualAllocEx(hProcess,          // hProcess
                                      NULL,                 // lpAddress
                                      dwSize,               // 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)ThreadProc,             // lpBuffer
                            dwSize,                         // nSize
                            NULL) )                         // [out] lpNumberOfBytesWritten
    {
        printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
        return FALSE;
    }

    if( !(hThread = CreateRemoteThread(hProcess,            // hProcess
                                       NULL,                // lpThreadAttributes
                                       0,                   // dwStackSize
                                       (LPTHREAD_START_ROUTINE)pRemoteBuf[1],     // dwStackSize
                                       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;
}
  • injectCode()函数的set THREAD_PARAM部分用来设置THREAD_PARAM结构体变量,它们会注入目标进程,并且以参数形式传递给ThreadProc()线程。
  • 接下来调用了一系列API函数,API函数归纳如下:
OpenProcess();
//data:THREAD_PARAM
VirtualAllocEx();
WriteProcessMemory();
//code:ThreadProc()
VirtualAllocEx();
WriteProcessMemory();

CreateRemoteThread();
  • 上述代码主要用在目标进程中分别为data与code分配内存,并将它们注入目标进程,最后调用CreateRemoteThread() API,执行远程线程。

代码注入调试练习

  1. 用Ollydbg开始调试notepad.exe文件,按F9运行键,是notepad.exe处于Runing状态。
    在这里插入图片描述
  2. 设置OllyDbg的选项后,即可从注入的线程代码开始调试。
    在这里插入图片描述
  • 每当notepad.exe进程中生成新线程,调试器就暂停在线程函数开始的代码位置。
  1. 使用Process Explorer查看notepad.exe的进程PID。
    在这里插入图片描述
  • 在命令窗口输入PID作参数,运行CodeInjection.exe。
C:\Users\12586\Downloads\example\03\27\bin>CodeInjection.exe 13716
  1. 按F9继续运行调试器,代码注入成功后,调试器就会暂停在被注入的线程代码的开始位置,由此开始调试。
    在这里插入图片描述
  • 004E0004地址处的MOV ESI,DWORD PTR SS:[EBP+8],[EBP+8]地址就是ThreadProc()函数的IParam参数,而参数IParam则指向被一同注入的THREAD_PARAM结构体。
    在这里插入图片描述
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CTF(Capture The Flag)竞赛中,常见的一个攻击技术是PHP代码注入(PHP Code Injection)。这种攻击利用了应用程序对用户输入的不充分验证和过滤,使得攻击者能够将恶意的PHP代码注入到应用程序中,从而执行任意代码或实施其他攻击。 以下是一些常见的PHP代码注入漏洞场景和防范措施: 1. 用户输入的直接执行:如果应用程序直接将用户输入作为PHP代码执行,而没有进行充分的验证和过滤,攻击者可以通过提交恶意代码来执行任意操作。防范措施是使用合适的输入验证和过滤,例如使用白名单来限制允许的操作或使用安全的函数来处理用户输入。 2. 变量覆盖:如果应用程序在解析用户输入时,没有正确处理变量覆盖的情况,攻击者可以通过构造特殊的输入来覆盖应用程序中的变量,并执行恶意操作。防范措施是在处理用户输入之前,将其赋值给新的变量,并确保不会被覆盖已有的变量。 3. 文件包含漏洞:如果应用程序在包含文件时没有进行充分的验证和过滤,攻击者可以通过构造特殊的文件路径来包含恶意的PHP代码文件。防范措施是使用白名单来限制允许包含的文件路径,并对用户输入进行适当的过滤和验证。 4. 数据库查询注入:虽然不是直接的PHP代码注入,但数据库查询注入漏洞可能导致执行恶意的SQL语句,从而进一步执行PHP代码。防范措施是使用参数化查询或预处理语句,避免直接将用户输入拼接到SQL查询中。 总之,要防范PHP代码注入漏洞,开发者应该始终进行充分的输入验证和过滤,使用安全的函数和方法处理用户输入,避免直接执行或拼接用户输入作为代码执行。同时,定期更新和修复应用程序中使用的框架、库和组件,以确保使用的是最新的安全版本。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值