分类 信息安全 下的文章

0x00 环境

主机:Windows 10
IDE:VS 2019 (x86)

0x01 介绍

傀儡进程指将目标进程的映射文件替换为指定的映射文件,替换后的进程称之为傀儡进程。在早期的木马程序中使用较广。实现傀儡进程必须要选择合适的时机,要在目标进程刚加载进内存后还未开始运行之前替换。

0x02 基本步骤

1.使用CreateProcess()函数创建挂起进程
2.使用GetThreadContext()函数获取进程上下文(寄存器状态)
3.清空目标进程(如果傀儡进程的大小必目标进程小的话这部可以省略)
4.VirtualAllocEx()重新分配空间,大小为傀儡进程大小
5.WriteProcessMemory()向分配的空间写入傀儡进程
6.恢复上下文,由于目标进程和傀儡进程的入口点一般不相同,因此在恢复之前,需要更改一下其中的线程入口点,需要用到SetThreadContext函数。将挂起的进程用ResumeThread函数释放运行。

0x03 具体代码

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <malloc.h>

int main(int argc, char* argv[])
{
    STARTUPINFOA stSi = { 0 };
    PROCESS_INFORMATION stPi = { 0 };
    stSi.cb = sizeof(stSi);
    LPCWSTR Filename = (LPCWSTR)L"目标进程完整目录";

    //1.创建挂起的目标进程
    if (!CreateProcess(Filename,
        NULL,
        NULL,
        NULL,
        NULL,
        CREATE_SUSPENDED,
        NULL,
        NULL,
        &stSi,
        &stPi
    ))
    {
        printf("%d", GetLastError());
        return FALSE;
    }

    PTCHAR address = L"傀儡进程完整目录";
    HANDLE hFile = CreateFile(address, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 
                               NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("Open EXE File Filed");
        printf("%d", GetLastError());
        return -1;
    }
    DWORD dwSize = GetFileSize(hFile, NULL);
    LPBYTE pAllocPE = NULL; 
    PBYTE pBuf = (PBYTE)malloc(dwSize);
    DWORD dwBytesRead = 0;
    ReadFile(hFile, (LPVOID)pBuf, dwSize, &dwBytesRead, NULL);
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuf;
    PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(pBuf + pDosHeader->e_lfanew);


    //2.获取进程上下文
    CONTEXT stThreadContext;
    stThreadContext.ContextFlags = CONTEXT_FULL;
    if (GetThreadContext(stPi.hThread, &stThreadContext) == 0)
    {
        printf("CreateProcess failed (%d).\n", GetLastError());
        return FALSE;
    }

    ////3.清空目标进程

    //BOOL UnMapTargetProcess(HANDLE hProcess, CONTEXT * stThreadContext)
    //{
    //    DWORD dwProcessBaseAddr = 0;
    //    if (ReadProcessMemory(stPi.hProcess, (LPCVOID)(stThreadContext.Ebx + 8), &dwProcessBaseAddr, sizeof(PVOID), NULL) == 0)//读取目标进程对应地址
    //    {
    //        return FALSE;
    //    }
    //    HMODULE hNtModule = GetModuleHandle(_T("ntdll.dll"));//获取dll的句柄
    //    if (hNtModule == NULL)
    //    {
    //        return FALSE;
    //    }
    //    //手动寻找NtUnmapViewOfSection()的指针
    //    ZwUnmapViewOfSection  pfnZwUnmapViewOfSection = GetProcAddress(hNtModule, "ZwUnmapViewOfSection");
    //    if (pfnZwUnmapViewOfSection == NULL)
    //    {
    //        return FALSE;
    //    }
    //    return (pfnZwUnmapViewOfSection(stPi.hProcess, (PVOID)dwProcessBaseAddr) == 0);
    //}

    //4.重新分配空间
    void* lpAddr = VirtualAllocEx(stPi.hProcess, (LPVOID)pNtHeaders->OptionalHeader.ImageBase,
        pNtHeaders->OptionalHeader.SizeOfImage,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE);//用Imagebase为起始地址避免了重定位。
    if (lpAddr == NULL)
    {
        printf("VirtualAlloc failed (%d).\n", GetLastError());
        return FALSE;
    }

    //5.写入傀儡进程
        // 替换PE头
    BOOL bRet = WriteProcessMemory(stPi.hProcess,
        lpAddr,
        (LPCVOID)pBuf,//指向要写的数据的指针。
        pNtHeaders->OptionalHeader.SizeOfHeaders,
        NULL);
    if (!bRet)
    {
        return FALSE;
    }
    // 替换节
    LPVOID lpSectionBaseAddr = (LPVOID)((DWORD)pBuf
        + pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS));
    PIMAGE_SECTION_HEADER pSectionHeader;
    DWORD dwIndex = 0;
    for (; dwIndex < pNtHeaders->FileHeader.NumberOfSections; ++dwIndex)
    {
        pSectionHeader = (PIMAGE_SECTION_HEADER)lpSectionBaseAddr;
        bRet = WriteProcessMemory(stPi.hProcess,
            (LPVOID)((DWORD)lpAddr + pSectionHeader->VirtualAddress),
            (LPCVOID)((DWORD)pBuf + pSectionHeader->PointerToRawData),
            pSectionHeader->SizeOfRawData,
            NULL);
        if (!bRet)
        {
            return FALSE;
        }
        lpSectionBaseAddr = (LPVOID)((DWORD)lpSectionBaseAddr + sizeof(IMAGE_SECTION_HEADER));
    }
    //6.恢复现场并运行傀儡进程
        // 替换PEB中基地址
    DWORD dwImageBase = pNtHeaders->OptionalHeader.ImageBase;
    bRet = WriteProcessMemory(stPi.hProcess, (LPVOID)(stThreadContext.Ebx + 8), (LPCVOID)&dwImageBase, sizeof(PVOID), NULL);
    if (!bRet)
    {
        return FALSE;
    }
    // 替换入口点
    stThreadContext.Eax = dwImageBase + pNtHeaders->OptionalHeader.AddressOfEntryPoint;
    bRet = SetThreadContext(stPi.hThread, &stThreadContext);
    if (!bRet)
    {
        return FALSE;
    }
    ResumeThread(stPi.hThread);

    printf("PID: %d", stPi.dwProcessId);
    free(pBuf);
    return 0;
}

0x04 参考链接及反思

本文有参考以下链接文章:
傀儡进程技术实现
常见进程注入的实现及内存dump分析——Process Hollowing(冷注入)

反思:
1.本次学习了一些WIN API,不过对于未文档话的函数NtUnmapViewOfSection调用还是有点问题。
2.创建出来的目标进程的PID在任务管理器中看不到感觉有点奇怪。

0x00 环境及此文背景

待调试ELF文件
IDA 7.0
主机:Windows 10
虚拟机:Kali Linux
达成效果:在Window上利用IDA远程动态调试linux里的ELF文件
成文背景:在做攻防世界的一道基础逆向题目时遇到了一个ELF文件想看看文件的进行流程,发现Linux里面多是有gdb,radare2等命令行神器,习惯了windows上的GUI突然要用用命令行有点不熟悉,所以就有了此文。

0x01 具体步骤

1.把idapro安装目录中dbgsrv目录下的linux_server或者linux_server64放到linux中(根据自己要调试的程序选择哪个版本的,放置的路径随意)
01.jpg
linux_server:用于调试32位Linux应用程序。
linux_server64:用于调试64位Linux应用程序。

2.打开终端,提权运行对应服务程序
使用cd命令打开服务程序所在目录,chmod a+x linux_server64 进行提权,然后运行 linux_server64。具体linux下chmod +x的意思?为什么要进行chmod +x可以看这篇文章
服务程序路径地址.jpg
03.jpg
看到箭头所指的状态即Linux配置完毕。现在到Windows界面进行配置。

下面的步骤很重要

3.在IDA中按F9选择Remote Linux debugger,然后在Debug application setup中设置相关相关参数。
04.jpg

05.jpg

3.1. Application和Input file的路径为linux中要调试程序的完整路径,Directory直接输入属性中的父文件夹(除去程序的完整路径)

3.2 Parameters和Password 放空 Hostname要回到Linux终端中使用ifconfig命令查看IP地址。
06.jpg

4.到这里就基本上完成了,在IDA下断点,然后在按开始键程序就在Linux里跑起来了,后面就可以自己调试了。
07.jpg

0x02 参考文章

ida动态调试elf(无坑详细)
IDA动态调试ELF
《IDA+Pro权威指南(第二版)》

0x00前言

很惭愧,在刚放寒假立下的FLAG现在才来实践,不过好在假期还没结束还有时间,由于在学校的时候有接触过一点所以就直接从第5节开始了,b站链接在这里,本文的目的也是为了自己更好的学习,所以不会写的很详细只会写一些自己认为比较有用且需要重点记忆的内容,,学长推荐的教材是《Windows程序设计(第5版 珍藏版)》,有机会我希望能够仔细拜读一下。我也是个初学者,如有任何错误,敬请各位读者斧正。

0x01进程与进程创建(5-6)

1,进程与线程关系:首先举个例子,进程就可以比喻成房屋,线程可以比喻成房屋里的人,人可以做到我们想要的事情,房屋(进程)可以给人(线程)提供场所(空间)和装备(资源),想要做成一件事情就一定要有人去完成(一个进程里至少要有一个线程),当然要做大事一个人力量是不够的,这个时候就可以叫兄弟帮忙了(多线程),在一个房屋内一起写项目(多个线程共用进程资源与空间)。这个例子简单的讲述了进程与线程的关系,虽然有可能有问题的地方,不过还是比较通俗易懂了。
2,进程内存空间地址分配:首先看张图1.jpg从这张图中可以很直观的看出一个进程在内存空间里面有着4GB的虚拟内存空间,不过这4GB并不全部都是拿来放本进程可以拿来使用的,我们可以分为高2G和低2G,高2G是内核区域,内核区域所有进程是一模一样的。所以在不同进程之间主要有区别的是在用户模式区。! 这里 !有着比较详细的资料后期必仔细拜读。
3,(1)进程的创建:任何的进程都是由进程创建的,我们在桌面上双击程序所创建的进程都是由(explorer.exe)这个进程创建的,而explorer.exe这个进程是由系统创建的。 (2)创建过程:我们还是直接看图来的更直观一点主要是懒2.jpg 在第五步映射DLL之前有一个傀儡进程的操作最好去原视频看看(32:02),具体实现原理等后期学到在补坑。
4,CreateProcess()函数:这个函数和LoadLibrary()一样是有区分不同的编码方式。(其实大多数的win32API都有这个设定)

#ifdef UNICODE
#define CreateProcess  CreateProcessW
#else
#define CreateProcess  CreateProcessA
#endif // !UNICODE

以下是这个函数的定义

BOOL
WINAPI
CreateProcessW(
    _In_opt_ LPCWSTR lpApplicationName,
    _Inout_opt_ LPWSTR lpCommandLine,
    _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ BOOL bInheritHandles,
    _In_ DWORD dwCreationFlags,
    _In_opt_ LPVOID lpEnvironment,
    _In_opt_ LPCWSTR lpCurrentDirectory,
    _In_ LPSTARTUPINFOW lpStartupInfo,
    _Out_ LPPROCESS_INFORMATION lpProcessInformation
    );

不要看上面定义的参数那么多,目前我们只需要了解前俩个和后俩个参数,下面让我们慢慢学习。先说前俩个参数,他们要接受的都是TCHAR类型的数组,唯一的区别是lpApplicationName要的是不可变(const)参数,而lpCommandLine需要的是可变参数。对于后俩个参数,lpStartupInfo所要接受的是一个数组代码如下

typedef struct _STARTUPINFOW {
    DWORD   cb;
    LPWSTR  lpReserved;
    LPWSTR  lpDesktop;
    LPWSTR  lpTitle;
    DWORD   dwX;
    DWORD   dwY;
    DWORD   dwXSize;
    DWORD   dwYSize;
    DWORD   dwXCountChars;
    DWORD   dwYCountChars;
    DWORD   dwFillAttribute;
    DWORD   dwFlags;
    WORD    wShowWindow;
    WORD    cbReserved2;
    LPBYTE  lpReserved2;
    HANDLE  hStdInput;
    HANDLE  hStdOutput;
    HANDLE  hStdError;
} STARTUPINFOW, *LPSTARTUPINFOW;
````

这个数组主要的信息是父进程给所产生的新进程写入的信息,不过修改次数组内容并不会对新进程产生影响。最后一个参数也是传入一个数组代码如下

typedef struct _PROCESS_INFORMATION {

HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;

} PROCESS_INFORMATION, PPROCESS_INFORMATION, LPPROCESS_INFORMATION;


这个数组里是线程和进程的ID及句柄,具体什么是句柄下次再说。  
**最后有个反调试的操作**,就是对lpStartupInfo数组内容进行判别,直接双击打开和通过OD等调试器打开里面初始化的参数内容是不一样的,就此可以进行简单的反调试。

## 0x02句柄表(7-8)

1,内核对象:首先我们来了解一下什么是内核对象,像进程、线程、文件、互斥体、事件等在内核都有一个对应的结构体,这些结构体由内核负责管理。我们管这样的对象叫做内核对象。我们可以看CreateXXX函数的定义,其中有类型为**LPSECURITY_ATTRIBUTES**的参数,则这个函数创建的对象为内核对象。  
2,句柄表:***只有每个进程有一个句柄表***。设置句柄表是为了避免使用地址来访问内核对象,因为涉及R0的东西一旦出现地址出差就会没有访问权限从而导致电脑蓝屏,句柄就很好的隔离开内核层与用户层。句柄表通过进程的内核结构体中的一个指针指向。
3,句柄表的继承:用我的话来说就是双重保险,第一层是创建内核对象时在LPSECURITY_ATTRIBUTES里有一个参数可以选择**本内核对象**是否允许继承。第二层是在创建子进程时使用CreateProcess()函数时第五个参数也可以选择是否继承**句柄表**。  
4,句柄值:因为我们之前称的句柄表都是进程中私有的,仅在本进程可以使用,这样就照成了很多麻烦,所以就出现了全局句柄表的值(句柄值)也就是我们常说的PID。通过PID就可以实现在不同进程中互相调用的效果。
5,工作路径和模块路径:模块路径就是我们传统意义上EXE所在的位置的整个路径,而工作路径就是由父进程所编写的路径。工作路径的作用就是在工作路径内的文件想要访问或打开,可以不需要写完整路径,只要写文件名和拓展名即可。  

## 0x03 线程(9-12)

1,创建线程:使用CreateThread()函数创建线程
2,线程控制函数:Sleep()程序执行到此次暂停多少秒(让自己停下来);SuspendThread()传入线程句柄可以使该线程挂起暂停(让别人停下来)与之对应的就是ResumeThread()恢复线程状态使之继续进行。  
3,线程状态等待:WaitForSingleObject()等待单个线程结束(INFINITE)或者在某一个时间后接着进行【WaitForMultipleObjects()和这个类似就是可以等待多个线程】;GetExitCodeThread() 线程一段代码结束后会有一个返回值可以用这个函数获取,可以根据自己的需求进行修改返回值。  
4,多线程操作中不同线程间寄存器数值的存放函数 使用SetThreadContext()修改 和 GetThreadContext()进行读取。[Dll注入新姿势:SetThreadContext注入](https://www.anquanke.com/post/id/86786)   
5,多线程安全问题:前提条件:1.有全局变量,2.对全局变量进行读操作。  
解决办法:临界区(对临界资源(全局变量)进行访问的代码段)通过线程锁 EnterCriticalSection(**令牌地址**) 和 LeaveCriticalSection(**令牌地址**)这俩个函数之间的代码就是临界区,令牌记得需要用InitializeCriticalSection(**令牌地址**)初始化。  
6,互斥体:内核级的令牌,在不同进程的线程中起作用;互斥体起作用的条件(任意一个即可):有信号或者线程的拥有者。可以通过创建互斥体函数(CreateMutex())的返回值用来放置多开程序。  

## 0x04 事件(13)

1,通知类型:在使用CreateEvent()的对第二个参数进行设定可以选择是用通知模式还是互斥模式(此次互斥模式和上文的互斥体类似,简单的来说就是*有我没你,有你没我*)。通知模式下WaitForSingleObject()之类线程等待函数不会改变事件状态(信号有无),简单的说就是可以通知多个线程。  
2,线程同步:同步的前提是互斥,互斥是无序的(某个线程连着好几次使用同一个临界资源)。互斥 + 有序 = 同步。方法(1)可以使用互斥体不过会浪费CPU时间片造成资源浪费。方法(2)使用俩个事件进行相互通讯,SetEvent(另外一个事件)挂起自己通知别人。  

## 0x05 窗口的本质(14)

1,HWND与HANDLE的区别:HWND是HANDLE的一种,但是HANDLE并不是HWND,HANDLE是操作系统一系列内核对象的句柄,HWND仅是窗口对象的句柄。  

2,绘制窗口的步骤:(1)设置设备对象(即在哪画,NULL默认是桌面)。  
(2)获取设备对象上下文(HDC),因为我们都是先画到设备对象的内存里所以需要中国设备对象上下文。  
(3)创建图形对象,可以使用GetStockObject()函数获取系统默认图形对象。  
(4)关联上下文和图形对象SelectObject()。  
(5)开始画(使用LineTo(),Rectangle()等函数,起始点默认为(0,0),可以用MoveToEx()修改画线初始点)。  
(6)释放资源(一般Create类函数用DeleteObject()类函数,Get类函数用ReleaseDC()类函数)。

## 0x05 消息机制(15)

1,每个线程只有一个消息队列,消息队列是存放在R0层线程对象的结构体里的。  
2,鼠标的消息如何到达消息队列:首先在R0层每个窗口都有一个窗口对象,这个窗口对象(WinObject)里面记录了窗口的起始位置,大小,层级关系,所在线程等等;其次鼠标点击后会把鼠标所在位置,左键还是右键等打包成一个结构体(消息);最后通过位置找到消息所属的窗口对象指向的线程加入到消息队列里。  
3,每个线程可以有多个窗口,一个窗口只能属于一个线程。

## 0x06 结束