吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

搜索
查看: 2515|回复: 24
上一主题 下一主题
收起左侧

[原创] 一道反调试小题分析

  [复制链接]
跳转到指定楼层
楼主
舒默哦 发表于 2021-4-17 00:13 回帖奖励
本帖最后由 舒默哦 于 2021-4-17 01:47 编辑

这道题在网上下的,也不知道谁发的,文件我发上来了,想要分析的朋友可以下载下来搞搞,再来看我的帖子,对照下看是否有出入。


正文




这是32位的一个控制台程序,启动后界面如下:



主函数如下:





当程序走到wsprintfA函数会引起异常,v10[0]里面存的是sub_401360函数地址。
异常来自这个函数,在OD里跟进这个函数查看:
执行到401372产生了异常:


再次点击运行时,程序产生第二次异常卡住了。正常打开程序后,输入随机16位字符,也会走这个地方并且产生异常,程序并不会卡死,会提醒输入的flag错误。
那么。暂时可以推断该程序是注册了一个VEH或者注册了一个最顶层异常处理函数,因为在主函数中没有看见结构化异常处理的相关代码。

分析反调试:


用户层异常流程:



先说明下异常在用户层的分发流程:
KiUserExceptionDispatcher->RtlDispatchException->VEH  -> 没有VEH处理则 调用SEH。没有SEH,则查看是否有最顶层异常处理函数,没有程序就会exitprocess。
注意:用户态的异常如果在非调试状态下的话仅仅只有一轮的分发,而只有在调试状态下才会进行第二轮,再次判断调试器是否要接手异常。如果注册了最顶层异常处理函数,有调试器存在的情况下,顶级异常处理函数就会被跳过,否则就会被顶级处理函数接管。

由上面产生异常时,分析得出的结论:该程序可能注册了一个VEH或者注册了一个最顶层异常处理函数。那么在哪儿注册的呢?
首先查看是否有TLS表的存在:


TLS不存在,那么可以暂时在KiUserExceptionDispatcher下断,程序产生异常后,一步一步往下跟,看看有什么情况。

运气好,这儿看到RtlDispatchException函数的函数头被HOOK了:


接下来,可以用IDA打开分析:

再次跟进Handler:

跟进sub_401550函数,发现还注册了顶层异常处理函数。






过反调试:




既然知道注册了顶层异常处理函数,那么在NtQueryInformationProcess下断,时机是产生异常时下断。NtQueryInformationProcess会断下两次。
第一次的协议号是0x22,过滤掉,第二次协议号是0x7,与调试有关的,修改返回值,整个流程就通了。


第二次调用NtQueryInformationProcess返回时候,把sf置1,跳过inc esi这条指令

随意输入16个字符,程序不会被卡住,正常退出了:

分析加密算法:



这个程序把整个加密过程放到了异常处理的流程中了,具体放到了sub_401600函数和handler函数里面了。

1、先分析输入数据的流向:



产生异常前,输入的字符串数据分别被保存ebx、edx、ecx、esi中,此外eax=0x797963。




产生异常后KiUserExceptionDispatcher->RtlDispatchException->jmp sub_401600




其中a1是_CONTEXT结构体,a2是EXCEPTION_RECORD。
a1+0xA0在_CONTEXT结构体中,恰好对应的是esi寄存器。


数据经过处理后被存放到了EXCEPTION_RECORD的ExceptionInformation中。


[C] 纯文本查看 复制代码
_EXCEPTION_RECORD 结构体如下:                

#define EXCEPTION_MAXIMUM_PARAMETERS 15 // maximum number of exception parameters
type struct _EXCEPTION_RECORD                
{                                                                
        DWORD ExceptionCode;                                //异常代码
        DWORD ExceptionFlags;                                //异常状态
        struct _EXCEPTION_RECORD* ExceptionRecord;        //下一个异常
        PVOID ExceptionAddress;                                //异常发生地址
        DWORD NumberParameters;                        //附加参数个数
        ULONG_PTR ExceptionInformation 
        [EXCEPTION_MAXIMUM_PARAMETERS];                //附加参数指针        
} 



经过一些流程后,最后调用顶层异常处理函数,把加密后的flag放到eax、ebx、edx、ecx、esi、edi寄存器里。

[C] 纯文本查看 复制代码
LONG __stdcall TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
  ExceptionInfo->ContextRecord->Eax = ExceptionInfo->ExceptionRecord->ExceptionInformation[0];
  ExceptionInfo->ContextRecord->Ebx = ExceptionInfo->ExceptionRecord->ExceptionInformation[1];
  ExceptionInfo->ContextRecord->Ecx = ExceptionInfo->ExceptionRecord->ExceptionInformation[2];
  ExceptionInfo->ContextRecord->Edx = ExceptionInfo->ExceptionRecord->ExceptionInformation[3];
  ExceptionInfo->ContextRecord->Edi = ExceptionInfo->ExceptionRecord->ExceptionInformation[4];
  ExceptionInfo->ContextRecord->Esi = ExceptionInfo->ExceptionRecord->ExceptionInformation[5];
  ExceptionInfo->ContextRecord->Eip = (DWORD)sub_401390;
  SetUnhandledExceptionFilter(lpTopLevelExceptionFilter);
  RemoveVectoredExceptionHandler(Handler);
  return -1;
}


EIP修正为sub_401390:
下面sub_401390函数的主要功能:
1、最终把输入的flag作比较,都匹配成功了则返回1,失败返回0。
2、修改返回地址,把EIP改为了main函数的地址。



最后返回main函数0x401B6E处继续执行。


2、分析加密流程



输入的flag的会经过三次加密,最终和一个字符串对比,这个字符串是 "LMlWu2Y/Tk8c33Y+T8Lv0a==";匹配成功则flag输入正确。
第一次加密:

第二次加密:

第三次加密:Handler->sub_401110
[C] 纯文本查看 复制代码
_BYTE *__cdecl sub_401110(unsigned int a1, int *__shifted(EXCEPTION_RECORD,0x14) a2)
{
  _BYTE *v3; // [esp+0h] [ebp-18h]
  unsigned int v4; // [esp+8h] [ebp-10h]
  unsigned int v5; // [esp+Ch] [ebp-Ch]
  unsigned int i; // [esp+10h] [ebp-8h]
  _BYTE *v7; // [esp+14h] [ebp-4h]
  _BYTE *v8; // [esp+14h] [ebp-4h]
  _BYTE *v9; // [esp+14h] [ebp-4h]
  _BYTE *v10; // [esp+14h] [ebp-4h]
  _BYTE *v11; // [esp+14h] [ebp-4h]

  v7 = calloc(4 * (a1 / 3) + 5, 1u);
  v3 = v7;
  for ( i = 0; i < 3 * (a1 / 3); i += 3 )
  {
    LOBYTE(v4) = *((_BYTE *)ADJ(a2)->ExceptionInformation + i + 2);
    BYTE1(v4) = *((_BYTE *)ADJ(a2)->ExceptionInformation + i + 1);
    HIWORD(v4) = *((unsigned __int8 *)ADJ(a2)->ExceptionInformation + i);
    *v7 = byte_41E8B0[(v4 >> 18) & 0x3F];
    v8 = v7 + 1;
    *v8++ = byte_41E8B0[(v4 >> 12) & 0x3F];
    *v8++ = byte_41E8B0[(v4 >> 6) & 0x3F];
    *v8 = byte_41E8B0[v4 & 0x3F];
    v7 = v8 + 1;
  }
  if ( a1 != i )
  {
    LOWORD(v5) = 0;
    HIBYTE(v5) = 0;
    if ( a1 - i == 2 )
    {
      BYTE1(v5) = *((_BYTE *)ADJ(a2)->ExceptionInformation + i + 1);
      BYTE2(v5) = *((_BYTE *)ADJ(a2)->ExceptionInformation + i);
      *v7 = byte_41E8B0[(v5 >> 18) & 0x3F];
      v9 = v7 + 1;
      *v9 = byte_41E8B0[(v5 >> 12) & 0x3F];
      v10 = v9 + 1;
      *v10 = byte_41E8B0[(v5 >> 6) & 0x3F];
    }
    else
    {
      BYTE2(v5) = *((_BYTE *)ADJ(a2)->ExceptionInformation + i);
      *v7 = byte_41E8B0[(v5 >> 18) & 0x3F];
      v11 = v7 + 1;
      *v11 = byte_41E8B0[(v5 >> 12) & 0x3F];
      v10 = v11 + 1;
      *v10 = 61;
    }
    v10[1] = 61;
  }
  return v3;
}





其中byte_41E8B0是一个字符串:



逆算法



逆算法,我直接贴代码了:
[C] 纯文本查看 复制代码
#define _CRT_SECURE_NO_WARNINGS
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <string>

typedef unsigned char   uint8;
#define _BYTE  unsigned char
#define _WORD  unsigned word
#define LOBYTE_(w)           ((BYTE)(((DWORD_PTR)(w)) & 0xff))
#define HIBYTE_(w)           ((BYTE)((((DWORD_PTR)(w)) >> 8) & 0xff))
#define BYTEn(x, n)   (*((_BYTE*)&(x)+n))
#define WORDn(x, n)   (*((_WORD*)&(x)+n))
#define BYTE0(x)   BYTEn(x,  0)         // byte 0 (counting from 0)  添加此宏定义
#define BYTE1(x)   BYTEn(x,  1)         // byte 1 (counting from 0)
#define BYTE2(x)   BYTEn(x,  2)
#define BYTE3(x)   BYTEn(x,  3)
#define BYTE4(x)   BYTEn(x,  4)

const char* byte_41E8B0 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/";
void reversecalc(char* incs)
{

    /*----------------------------------------------------------------------------------*/
    /*        逆算法第一步        ,也就是逆加密的第三步                                                   */
    /*----------------------------------------------------------------------------------*/
    std::string s = byte_41E8B0;
    //const char* str = "WlML/Y2uc8kT+Y33vL8T ==a0";
    const char* str = "LMlWu2Y/Tk8c33Y+T8Lv0a==";
    char ttt = str[0x14];
    //int incs[8] = { 0 };
    //char* tempstr = (char*)str;
    char tv[0x32] = { 0 };
    int xtemp = 0;
    int xtemp1 = 0;
    int xtemp2 = 0;
    int xtemp3 = 0;
    int i = 0;
    int j = 0;
    for (; i < strlen(str)-4; i+=4)
    {
        xtemp = s.find(str[i], 0);
        xtemp <<= 18;
        xtemp1 = s.find(str[i+1], 0);
        xtemp1 <<= 12;
        xtemp2 = s.find(str[i+2], 0);
        xtemp2 <<= 6;
        xtemp3 = s.find(str[i+3], 0);
        
        xtemp = (xtemp | xtemp1 | xtemp2 | xtemp3);

        BYTE byte_1, byte_2, byte_3, byte_4;
        unsigned int  ret;
        //int 类型 占4个字节,32位
        byte_1 = (xtemp & 0xff000000) >> 24;
        byte_2 = (xtemp & 0x00ff0000) >> 16;
        byte_3 = (xtemp & 0x0000ff00) >> 8;
        byte_4 = xtemp & 0x000000ff;
        incs[j++] = byte_2;
        incs[j++] = byte_3;
        incs[j++] = byte_4;
    }

    xtemp = s.find(str[i], 0);
    xtemp <<= 18;
    xtemp >>= 16;
    xtemp1 = s.find(str[i+1], 0);
    xtemp1 <<= 12;
    incs[j] = xtemp;



    /*----------------------------------------------------------------------------------*/
    /*        逆算法第二步        ,也就是逆加密的第二步                                        */
    /*----------------------------------------------------------------------------------*/

    EXCEPTION_RECORD a2 = { 0};
 
    for (int i = 0; i < 4; i++)
    {
        a2.ExceptionInformation[i] = *((int*)incs+i);
    }
    

    a2.ExceptionInformation[3] ^= a2.ExceptionInformation[1];
    a2.ExceptionInformation[1] ^= a2.ExceptionInformation[3];
    a2.ExceptionInformation[3] ^= a2.ExceptionInformation[1];
    a2.ExceptionInformation[2] ^= a2.ExceptionInformation[0];
    a2.ExceptionInformation[0] ^= a2.ExceptionInformation[2];
    a2.ExceptionInformation[2] ^= a2.ExceptionInformation[0];
    a2.ExceptionInformation[1] ^= a2.ExceptionInformation[0];
    a2.ExceptionInformation[0] ^= a2.ExceptionInformation[1];
    a2.ExceptionInformation[1] ^= a2.ExceptionInformation[0];



    /*----------------------------------------------------------------------------------*/
    /*        逆算法第三步        ,也就是逆加密的第一步                                           */
    /*----------------------------------------------------------------------------------*/
    const char* v2 = "1234567890123456cyy";
    int tv2[8] = { 0,0xbe517013,0x34d7f69e,0x70133315 ,0xc6dfff05 };
    int temp = 0;
    for (int i = 4; i > 0; --i)
    {
        temp = a2.ExceptionInformation[i-1];
        if (i==4)
        {
            temp ^= *(DWORD*)((char*)v2 + i * 4);//(DWORD)v2[i * 4];
        }
        else
        {
            temp ^=tv2[i+1];
        }
 
        __asm {
            mov eax, temp
            ror eax, 5
            mov temp, eax
        }
        tv2[i] = temp;
    }


    printf("%s\n", tv2 + 1);
    return;
}

int main()
{
    char a2[30] = { 0 };
    reversecalc(a2);
    system("pause");
    return 0;
}


运行之后最终得到:flag{Ex<ept10n~}

把flag{Ex<ept10n~}贴到窗口,成功了!


复盘



先找sub_401600函数的来源,是谁调用了。


sub_401600在sub_401680里引用了其地址。

再次查看sub_401680的来源,来源于sub_401000():

再追sub_401000的来源:

来到了.rdata数据区。看到const _PVFV First ,就知道是_initterm函数调用sub_401000,_initterm函数先于在main执行,这个函数的功能是初始化全局变量。
那么,程序大概是这样子:
[C] 纯文本查看 复制代码
//点号代表省略
......
int sub_401680()
{
    .......
}
.......
#pragma init_seg(".CRT$XCC")
int g_hook = sub_401680();

int main()
{
    .......
}





复盘程序的整个流程:

1、_initterm先于main执行,调用sub_401680(),完成RtlDispatchException函数的HOOK。
2、执行main函数,输入flag之后,产生异常:KiUserExceptionDispatcher->RtlDispatchException->jmp sub_401600。
3、在sub_401600函数里面,注册了VEH,在VEH处理函数里面又注册了顶层异常处理函数,然后返回RtlDispatchException里继续执行。
此时,还没有注册了顶层异常处理函数,因为还没有调用VEH。
4、RtlDispatchException调用VEH查看异常是否被处理了,此时,在VEH函数里注册了顶层异常处理函数,返回RtlDispatchException,
调用UnhandledExceptionFilter查看程序是否在被调试,被调试则交给调试器,没有调试器就调用SetUnhandledExceptionFilter()注册的函数来处理,最终修改EIP返回main()。



免费评分

参与人数 32吾爱币 +33 热心值 +28 收起 理由
aaa661179 + 1 + 1 热心回复!
solly + 1 + 1 用心讨论,共获提升!
love灰太狼灬 + 1 谢谢@Thanks!
跃龙门 + 1 我很赞同!
Razuri + 1 + 1 谢谢@Thanks!
13171595977 + 1 + 1 谢谢@Thanks!
海笑 + 1 + 1 我很赞同!
natsu27 + 1 热心回复!
cpj1203 + 1 + 1 谢谢@Thanks!
evea + 1 + 1 谢谢@Thanks!
soyiC + 1 + 1 谢谢@Thanks!
siuhoapdou + 1 + 1 谢谢@Thanks!
KylinYang + 1 + 1 我很赞同!
victos + 1 + 1 谢谢@Thanks!
lyl610abc + 3 + 1 我很赞同!
Ah0NoR + 1 + 1 谢谢@Thanks!
CoderWang119 + 1 用心讨论,共获提升!
FuSu_ChunQiu + 1 + 1 用心讨论,共获提升!
pdcba + 1 + 1 我很赞同!
yixi + 1 + 1 谢谢@Thanks!
chuxia12 + 1 感谢您的宝贵建议,我们会努力争取做得更好!
poisonbcat + 1 + 1 谢谢@Thanks!
白影33 + 2 用心讨论,共获提升!
fengbolee + 1 + 1 用心讨论,共获提升!
nmy124 + 1 + 1 谢谢@Thanks!
哥比彩砖还炫 + 1 + 1 我很赞同!
azcolf + 1 + 1 热心回复!
小朋友呢 + 2 + 1 谢谢@Thanks!
showwindows + 1 + 1 谢谢@Thanks!
无敌小车 + 1 + 1 热心回复!
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
sdaza + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
丿一叶知秋丶 发表于 2021-4-27 19:38
每到一个新的机制是,调试器都有一次处理的机会
推荐
哈哈先生No.5 发表于 2021-4-27 11:59
推荐
natsu27 发表于 2021-5-1 15:31
推荐
cpj1203 发表于 2021-4-30 01:15
慢慢研究吧!
推荐
nmy124 发表于 2021-4-19 21:22
谢谢楼主分析,试着学习,再次谢谢
7#
 楼主| 舒默哦 发表于 2021-4-17 00:14 |楼主
附件在这儿。

crackme.zip

62.45 KB, 下载次数: 23, 下载积分: 吾爱币 -1 CB

8#
sdaza 发表于 2021-4-17 05:50
马一下。
9#
alongzhenggang 发表于 2021-4-17 07:47
想要看懂理解需要哪些基础
10#
 楼主| 舒默哦 发表于 2021-4-17 10:14 |楼主
alongzhenggang 发表于 2021-4-17 07:47
想要看懂理解需要哪些基础

熟悉三环和内核的异常流程就可以了
11#
she383536296 发表于 2021-4-17 10:28
学习中,对我这样的新手来说有点难度
12#
alongzhenggang 发表于 2021-4-17 10:36
舒默哦 发表于 2021-4-17 10:14
熟悉三环和内核的异常流程就可以了

纯小白没基础的,不过谢谢,去试着学习
13#
taohuawu1998 发表于 2021-4-17 11:18
感谢分享,试着学习
14#
哥比彩砖还炫 发表于 2021-4-18 14:27
火哥牛逼!
15#
 楼主| 舒默哦 发表于 2021-4-18 17:47 |楼主

火锅是谁
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2021-5-9 23:50

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表