Jev0n 发布的文章

本篇文章主要是为了记录一些我个人觉得不错的书本和网络文档
(待填坑)

逆向分析

0x01 语言基础

一,编程语言

1,《C Primer Plus》
2,《C++ Primer Plus》(本书更适合此前没有一点编程基础的小伙伴)
3,《C++ Primer》(相对与Plus版本更抽象一些,比较不适合初学者)
4,《C和指针》(不仅仅是将指针的,比较不错有一定的提升)
5,《汇编语言(第3版)--王爽》(虽然讲的的16位的汇编,不过用于入门比较不错,可以用“emu8086”这个软件来做里面的实验)
6,《Python编程-入门到实践》(同时也比比较推荐Python-100-Days这个项目)
7,《x86汇编语言-从实模式到保护模式》

二,数据结构和算法

1,《数据结构与算法图解》(比较简单适合入门)
2,《大话数据结构》

0x02 逆向基础

1,《有趣的二进制》
2,《逆向工程核心原理》
3,《加密与解密》
4,《C++反汇编与逆向分析》

0x03 操作系统基础

1,《Windows核心编程》
2,《深入理解计算机系统》

0x04 内核基础

1,滴水海哥内核
2,《Windows内核原理与实现》
3,《windows内核编程》(Windows Kernel Programming - Pavel Yosifovich)

前端开发

HTML&CSS

1,初识HTML(5)+CSS(3)
2,《Head First HTML与CSS》

JavaScript

1,现代 JavaScript 教程
2, 《JavaScript高级程序设计》

人工智能

入门

1,《Python神经网络编程》
2,《深度学习入门-基于Python的理论与实现》(鱼书)

0x00 前言

因为我本机已经安装了Git,所以我就放在了虚拟机里再次演示,虚拟机的环境为Win7 x86,具体的安装步骤于Win10大体上是一至的。如还有不懂请善用好搜索引擎。

0x01 Git简介

Git是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。简单的说就是在团队协作里管理代码的工具。

0x02 Git安装步骤

1.下载地址:下载点我,可能下载有点慢,这里提供蓝凑云链接,截至2020年5月最新版本为2.26.2

2.首先一路Next到这个界面,选择所需要安装的模块,不知道的可以全部选上。
1.jpg

3.这里是选择是否要添加开始菜单文件夹。
2.jpg

4.接下来是选择编辑器(这部关系到后期的体验建议选择比较好用的编辑器如VS Code),如选择默认的Vim编辑器后期学习成本比较大,万一选错可以看这篇文章后面在修改。
3.jpg

5.然后就一路默认,等待安装完成,桌面上会出现这个图标即安装成功
4.jpg

0x03 云端托管平台

1.GitHub
这个平台对于大家来说应该是比较熟悉了,有关的注册都比较详细这里就放一个,我就不仔细介绍了。唯一的缺点就是要一点英语功底,不过换个方式想还能提升英语水平一举两得。(逃
5.png

2.Coding
本平台不仅仅是代码托管平台,更是一个协作平台,支持多种协作搬砖方式,而且帮助文档比较详细,对于新手团队来说会比较好上手,有5个人免费额度,可以尝试,我们小组此次程序设计实践就是用此平台,相关注册的方式这里放个官方链接,比较详细了。

0x01 环境

系统:Windows 10
编译环境:MSVC x86
关键函数:QueueUserAPC 函数

0x02 线程 APC 队列简介

就是在正常函数之外的另外一个函数,类似于SEH在进程遇到异常的地位,默认情况下,创建线程时不会创建这个队列,当调用 QueueUserAPC函数时,就会为这个线程创建这个队列。创建 APC 队列的函数,一般使用 Wait 函数族或者 SleepEx 函数等带有 bAlertable 参数的函数进入一种假"警惕"的状态, 进入 Alertable 状态的线程,系统调度器会在线程函数本身处于"警惕"(暂停等待状态)时,调用线程 APC 队列中的函数。

0x03 原理及步骤

1.原理

在一个进程中,执行到SleepEx()或WaitForSingleObjectEx()时,系统就会产生一个“中断”,当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数。我们就可以利用QueueUserAPC()这个API,以此去执行我们设定的代码,进而完成DLL注入的目的。

2.步骤

0.通过目标窗口名获取PID
1.利用快照枚举所有的线程找到与PID相等的TID
2.开辟远程内存空间,写入Dll的路径
3.插入我们的DLL即可

0x04 代码

#include <Windows.h>
#include <Tlhelp32.h>
#include <iostream>

// 用于保存线程 TID 的指针
ULONG *pThreadId = (ULONG * )malloc(sizeof(ULONG));

// 根据进程名称获取 PID
// ProcessName 进程名称
ULONG GetProcessIdByProcessName(char* ProcessName)
{
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    //拍快照
    if (INVALID_HANDLE_VALUE == hSnapshot)
    {
        return 0;
    }

    PROCESSENTRY32 pi;
    pi.dwSize = sizeof(PROCESSENTRY32); //初始化成员
    BOOL bRet = Process32First(hSnapshot, &pi);
    while (bRet)
    {
        char Name[MAX_PATH] = { 0 };
        sprintf_s(Name, "%ls", pi.szExeFile);
        if (strcmp(Name, ProcessName) == 0)
        {
            return pi.th32ProcessID; // 返回PID
        }

        bRet = Process32Next(hSnapshot, &pi);
    }

    return 0;
}

// 根据 PID 获取获取相应的线程 TID
// ProcessId    进程 PID
// pThreadId   数组用于保存线程TID
// ThreadIdLen 用于返回数组实际长度
BOOL GetThreadIdByProcessId(ULONG ProcessId, ULONG* pThreadId, ULONG* ThreadIdLen)
{
    HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
    THREADENTRY32 te32 = { sizeof(THREADENTRY32) };
    ULONG Number = 0;

    // 把所有线程拍一个快照
    hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if (hThreadSnap == INVALID_HANDLE_VALUE)
        return(FALSE);

    // 在使用 Thread32First 前初始化 THREADENTRY32 的结构大小.
    te32.dwSize = sizeof(THREADENTRY32);

    // 现在获取系统线程列表, 并显示与指定进程相关的每个线程的信息
    do {
        // 比对是否为该进程线程
        if (te32.th32OwnerProcessID == ProcessId)
        {
            // 是的话保存到线程数组中
            *pThreadId = te32.th32ThreadID;
            break;
        }
    } while (Thread32Next(hThreadSnap, &te32));

    if (!Number)
        return FALSE;

    return TRUE;
}

// APC注入
// ProcessName 要注入的进程名称
// DllName     要注入的DLL名称
BOOL ApcInjectDll(char* ProcessName, char* DllName)
{
    ULONG ProcessId = 0;
    ULONG IsFlag = TRUE;
    HANDLE hProcess = NULL, hThread = NULL;
    ULONG DllNameLen = strlen(DllName) + 1;
    PVOID pBaseAddr = NULL;
    ULONG dwRet = 0;
    PVOID pLoadLibraryA = NULL;
    ULONG ThreadIdsLen = 0;

    do
    {
        // 根据进程名称获取 PID
        ProcessId = GetProcessIdByProcessName(ProcessName);
        if (0 >= ProcessId)
        {
            printf("GetProcessIdByProcessName Error: %d\n", GetLastError());
            IsFlag = FALSE;
            break;
        }
        // 根据 PID 获取获取所有相应的线程 TID
        IsFlag = GetThreadIdByProcessId(ProcessId, pThreadId, &ThreadIdsLen);
        if (!IsFlag)
        {
            printf("GetAllThreadIdByProcessId Error: %x\n", GetLastError());
            IsFlag = FALSE;
            break;
        }
        // 打开注入线程
        hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
        if (hProcess == NULL)
        {

            printf("OpenProcess Error: %x\n", GetLastError());
            IsFlag = FALSE;
            break;
        }
        // 在注入的进程中申请空间
        pBaseAddr = VirtualAllocEx(hProcess, NULL, DllNameLen, MEM_COMMIT | MEM_RESERVE,
                PAGE_EXECUTE_READWRITE);
        if (pBaseAddr == NULL)
        {
            printf("VirtualAllocEx Error: %x\n", GetLastError());
            IsFlag = FALSE;
            break;
        }
        // 向申请的空间中写入 Dll 路径数据
        WriteProcessMemory(hProcess, pBaseAddr, DllName, DllNameLen, &dwRet);
        if (DllNameLen != dwRet)
        {
            printf("WriteProcessMemory Error: %x\n", GetLastError());
            IsFlag = FALSE;
            break;
        }

        hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, *pThreadId);
        if (hThread != NULL)
        {
            // 插入 APC
            if (!QueueUserAPC((PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)pBaseAddr))
            {
                printf("QueueUserAPC Error: %x\n", GetLastError());
            }
            // 关闭线程句柄
            CloseHandle(hThread);
            hThread = NULL;
            printf("插入 APC 成功!\n");
        }

    } while (FALSE);

    CloseHandle(hProcess);
    return IsFlag;
}

int main()
{
    // 注入我们想要执行的DLL
    ApcInjectDll((char*)"TestExe.exe", (char*)"./Dll1.dll");
    system("pause");
    return 0;
}

0x05 总结及相关文章

总的来说APC注入对目标进程是有要求的,需要对方进入“警惕”状态下的,文章中的代码仅向目标进程的一个线程进行注入,如果有需要可以添加提权和枚举所有线程的操作。其次也在其他网站上看到APC队列的由来好像是和驱动文件有关系,所以在内核层面应该会有不一样的APC注入。
学习文章:
UserMode下的APC插入
APC异步过程调用
常见注入手法第二讲,APC注入(注意此方法中的示范代码并不能成功注入,个人认为是因为MFC消息机制等原因,原理是相似的)

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权威指南(第二版)》