分类 信息安全 下的文章

正常解法

通过查看字符串发现有个类似迷宫(蜂巢)地图的区域,猜测得先逆向得出地图字符串如何使用

image-20201010153149078

然后再分析CheckFlag函数中用户输入R或者L之后蜂巢的走向

image-20201010153415848

最后得出一段由R和L组成的Flag

我的解法

我发现在CheckFlag函数中验证Flag不是非常严谨,每一位Flag之间没有很深的关联,并且是按位校验的,只要当前校验位不正确程序就会打印错误提示。由此我们可以在判断用户输入处下断点,进行人工爆破,例如当前输入R程序提示错误则当前位应该为L。

image-20201010154143969.png

通过前面输入处可知,Flag一共是25位而且只有每位只有两种可能性,所以人工爆破用不了多少时间

QQ截图20201010154501

通过十多分钟的尝试,爆破出了结果,最后md5一下就可以交了

image-20201010154631830

总结

我这种解法是投机取巧的,看看其他师傅的文章。
更优雅的爆破

熊猫烧香病毒分析及思维导图

xmind.png

0x00 基本介绍

熊猫烧香是一个感染性的蠕虫病毒,它能感染系统中的 exe ,com ,pif,src,html,asp 等文件,它还能中止大量的反病毒软件进程并且会删除扩展名为 gho 的文件,该文件是一系统备份工具 GHOST 的备份文件,使用户的系统备份文件丢失,被感染的用户系统中所有的.exe可执行文件图标全部被改成熊猫烧香的图标。

分析一个病毒有许多的步骤,本文仅局限于逆向分析层面,如查壳,行为分析,运行效果分析等未在本文展开,文末会放上个人认为比较好的分析文章以供学习。

0x01 环境及工具

分析环境:VMware Workstation Pro 15 + Win7 x86

工具:IDA Pro 7.0 & OD

0x02 Delphi的特性

1.函数用寄存器传参

2.【字符串地址 - 0x04】为字符串长度

0x03 逆向分析

1. 字符串比较

以“xboy”和“whboy”为key 对字符串进行解密,并与程序内原有字符串进行比较,比较成功进入三大核心模块

image-20200913140029931

2. CreateAndRunPanda模块

2.1 删除Desktop_.ini文件

Desktop_.ini.png

2.2 对文件本体进行判断

image-20200913165912948

2.3 如果为原始程序,则继续执行

先判断进程内是否存在spcolsv.exe,存在就结束进程。

image-20200913170526411

把病毒源程序复制到C:\Windows\System32\drivers\spcolsv.exe,然后启动spcolsv.exe(伪装),关闭自身源程序。

伪装程序和原本的panda.exe 内容是一模一样的,唯一不同就是在此前判断是否为spcolsv.exe程序的地方。

image-20200913170603049

2.4 如果本体为spcolsv.exe(伪装程序) 则跳转至loc_4085BA,释放之前申请存放的病毒文件信息的内存。

image-20200913171855862

1end.png

2.5 被感染后的程序末尾有五个字节都是Whboy且末尾是数字,通过文件中感染标记来区分,目前运行的是病毒本体还是被感染的文件如果是被感染文件会跳转至loc_4085D3,判断spclosv.exe是否存在,如果不存在从被感染的文件分裂出病毒程序重新执行。

感染后.png

3. InfectOtherFile模块

3.1 调用三个功能函数,三个函数大致的功能是感染本地文件,创建autorun.inf和setup.exe,利用139和445端口局域网传播。

threefunc.png

3.2 创建新线程通过查找到的盘符,遍历盘符中的各种文件并进行感染,同时规避掉一些系核心目录

VirusThread.png

3.3 sub_4094A4函数判断了是否为文件程序,诺为程序则跳转至感染区块

Infect.png

感染区块先删除GHO文件,断了被感染者备份的路。

delgho.png

判断文件后缀,诺为 EXE,RCS,FIP,MOC文件则执行一般感染函数

InfectFile.png

诺文件后缀为html,asp等网页文件格式进行网页感染函数,网页感染函数只是在网页文件尾部写入“

WebInfect.png

3.4 诺为文件夹目录,则先规避掉系统核心目录,在目录下写入隐藏的Desktop_.ini文件,并判断

writeini.png

在c:\test.txt中写入感染信息

writetxt.png

3.5 感染完文件后,代码会执行到设置计时器函数,这里定时调用TimerFunc函数、在这个函数中,会生成自启动autorun.inf以及病毒复制体setup.exe,工作原理与此前分析的生成伪装体和生成Desktop_.ini类似。

TimeFunc.png

3.6 局域网传播函数,先是创建线程回调函数,进入sub_40BCC8->sub_40BAA0 函数,进入此函数就有点类似网络编程了,先初始化套接字,创建套接字,尝试连接,关闭套接字。

threadhuidiao.png

Socket.png

最后如果尝试连接端口成功则把病毒拷贝到网络文件夹

CopyNetFileWork.png

4. VirusProtectSelf模块

4.1 四个时钟回调函数

TimerFunc.jpg

4.2 第一个回调函数(sub_40CD30)

通过发送窗口关闭消息关闭反病毒软件和任务管理器,同时通过注册表设置开机自启动和文件隐藏。

1timer.png

4.3 第二个回调函数(sub_40CE8C)

定期从 http://wangma.9966.org/down.txt 读取源码并下载文件并执行

2timer.png

4.4 第三个回调函数(sub_40CE94)

创建俩个线程,第一个线程和第二个回调线程(sub_40CE8C)执行内容一致

3timer.png

第二个线程删除网络共享文件,并且设置用户文件隐藏共享。

3.2timer.png

4.5 第四个回调函数(sub_407540)

删除服务、删除安全软件相关启动项

!4timer.png

0x04 总结

1. 特征地址及特征字符

http://wangma.9966.org/down.txt

WhBoy, 武*汉*男*生*感*染*下*载*者*

2. 优秀文章

病毒分析学习之旅--熊猫烧香

熊猫烧香病毒逆向过程及其分析思路

0x00 前言

本文是为了记载一些自己学习过程中发现的IDA使用小技巧,以便以后再次回想和复习,每个tip下面都会有诺干个链接以便拓展学习。

0x01 正文

  1. C键可以让ida再次分析程序,即实现从数据转化为代码的过程。
    [[求助]看《加密与解密》的一些疑问](https://bbs.pediy.com/thread-29374.htm) IDA-数据与代码、函数互相转换
  2. 堆栈不平衡,无法F5大法。可以先在option->general,把Stack pointer打开,然后选中最近的call,alt+k修改堆栈(把值改为函数最后的负数)。
    BUUCTF re:Youngter-drive
  3. IDA里面的反调试特征,__readgsqword(0x60)得到指向PEB64的指针,__readfsdword(0x30)得到指向PEB32的指针,TEB(0x30)=PEB。
    反调试
  4. 数据段明显为字符串,但IDA未识别出来,可使用快捷键A转换为字符串

5.【CTF】HWS计划2020安全精英夏令营培训 这个课程挺好的,基本上基础都过了一遍

6.在调试程序的时候有动态基址,可以IDA -> Edit -> Segments -> Rebase program修改IDA内的默认基址

首届四川省安恒杯CTF逆向WP

0x01 gostright

题目地址:https://jev0n.lanzous.com/ioijle9jcti

0.0 大致思路

开屏暴击,说我缺少一个DLL文件。

00.jpg
我先是没去管它直接拖到IDA里面去看一下,习惯性shift+f12看一下字符串,发现了换表的Base64。

01.jpg
用CyberChef操作了一下,感觉应该是对付反调试的。

02.jpg
然后往下一点就发现了一个'congratulation!'的字符串按X交叉引用过去,F5大法,看起来像是个迷宫之类的题目,这个时候的关键就是要找到这个迷宫。

03.jpg
接着对函数交叉引用看看是谁调用了这个checkflag,在交叉引用过程中发现出现了sp错误,这个时候就要去修复一下位置为0x402EB4

04.jpg
在Options->General处打开Stack pointer,然后发现这里有为负值,用Alt+K修复一下,然后就可以F5了

05.jpg
初步判断这个就是对迷宫进行构造的地方,最主要的就是那个两层循环

06.jpg
然后就是构造迷宫

maze.jpg
最后结合起初发现的那个包含成功提示字符串的函数,进行走迷宫,flag的body一共24个字符,每两个为一组,第一个是方向,第二个是走多少步(用大写字母顺序来判断如:B为走2步)

0.1 迷宫代码

#include<iostream>
using namespace std;

int main()
{
    int array[] = {42,43,40,41,46,47,44,45,34,35,32,33,38,39,36,37,58,59,56,
                    43,40,41,46,47,44,45,34,35,32,33,38,39,36,37,58,59,97,57,
                    40,41,46,47,44,45,34,35,32,33,38,39,36,37,58,59,56,56,62,
                    41,47,46,45,44,35,34,33,32,39,38,37,36,59,58,57,56,63,63,
                    46,46,44,45,34,35,32,33,38,39,36,37,58,59,56,57,62,63,60,
                    47,45,44,35,34,33,32,39,38,37,36,59,58,57,56,63,62,61,61,
                    44,45,34,35,32,33,38,39,36,37,58,59,56,57,62,63,60,60,50,
                    45,35,34,33,32,39,38,37,36,59,58,57,56,63,62,61,61,51,51,
                    34,34,32,33,38,39,36,37,58,59,56,57,62,63,60,60,50,50,48,
                    35,33,33,39,38,37,36,59,58,57,56,63,62,112,61,51,51,49,49,
                    32,32,38,38,36,37,58,59,56,57,62,63,60,61,50,50,48,48,54,
                    33,39,39,37,36,59,58,57,56,63,62,61,60,51,50,49,49,55,55,
                    38,38,36,37,58,59,56,57,62,63,60,61,50,51,48,49,54,54,52,
                    39,37,36,59,58,57,56,63,62,61,60,51,50,49,48,55,54,53,53,
                    36,37,58,59,56,57,62,63,60,61,50,51,48,49,54,55,52,53,10};
    int i = 0;
    for (i = 0; i < 285; ++i)
    {
        int k = i / 19 + i % 19;
        if (i % 19 == 0 && i != 0)
        {
            cout << endl;
        }
                cout << (char)(array[i] ^ k);
    }

    getchar();
    return 0;
}

0.2 总结

题目逻辑上手之后感觉还可以,起初自己比较懒加上太久没做题,总想着找空子找捷径结果就是没在规定时间内写完有点亏,然后还有就是因为我机子上缺个DLL所以也就没进行动态调试,我看网上的方法也不得行,不懂为什么。

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消息机制等原因,原理是相似的)