[BUUCTF]Youngter-drive - TLS反调试技术初探

原题目地址 https://buuoj.cn/challenges#Youngter-drive

flag{ThisisthreadofwindowshahaIsESE}

1.exeinfope

image

  1. upx解压

    image

  2. 再查壳

image

  1. IDA32关键代码

    //.data
    int dword_418008 = 29;
    //.text
    int __cdecl main_0(int argc, const char **argv, const char **envp)
    {
      HANDLE v4; // [esp+D0h] [ebp-14h]
      HANDLE hObject; // [esp+DCh] [ebp-8h]
    
      printWelcome();
      ::hObject = CreateMutexW(0, 0, 0);
      strcpy_0(Destination, &Source);
      hObject = CreateThread(0, 0, StartAddress, 0, 0, 0);
      v4 = CreateThread(0, 0, sub_41119F, 0, 0, 0);
      CloseHandle(hObject);
      CloseHandle(v4);
      while ( dword_418008 != -1 )
        ;
      printFlag();
      CloseHandle(::hObject);
      return 0;
    }
    int sub_411BD0()//printWelcome();
    {
      printf(
        "1111111111111111111111111111111111111111111111111111111111111111111111111111111\n"
        "*******************************************************************************\n"
        "**************             ****************************************************\n"
        "**************   ********   *********************                 *************\n"
        "**************   *********  *********************   ***************************\n"
        "**************   *********  *********************   ***************************\n"
        "**************   *********  *********************   ***************************\n"
        "**************   *******   **********************   ***************************\n"
        "**************   ****   *************************   ***************************\n"
        "**************   *    ***************************                **************\n"
        "**************   ***    *************************   ***************************\n"
        "**************   ******   ***********************   ***************************\n"
        "**************   ********   *********************   ***************************\n"
        "**************   **********   *******************   ***************************\n"
        "**************   ***********    *****************                 *************\n"
        "*******************************************************************************\n"
        "1111111111111111111111111111111111111111111111111111111111111111111111111111111\n");
      printf("input flag:\n");
      return scanf("%36s", Source);
    }
    void __stdcall sub_411B10(int a1)
    {
      while ( 1 )
      {
        WaitForSingleObject(hObject, 0xFFFFFFFF);
        if ( dword_418008 > -1 )
        {
          Sleep(100u);
          --dword_418008;
        }
        ReleaseMutex(hObject);
      }
    }
    int sub_411880()
    {
      int i; // [esp+D0h] [ebp-8h]
    
      for ( i = 0; i < 29; ++i )
      {
        if ( Source[i] != off_418004[i] )
          exit(0);
      }
      return printf("\nflag{%s}\n\n", Destination);
    }
    

    关键数据

    .rdata:0041574C aToiziztoryatou db 'TOiZiZtOrYaToUwPnToBsOaOapsyS',0
    .rdata:0041574C                                         ; DATA XREF: .data:off_418004↓o
    .rdata:0041576A                 align 10h
    .rdata:00415770 aQwertyuiopasdf db 'QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm',0
    .rdata:00415770                                         ; DATA XREF: .data:off_418000↓o
    .rdata:004157A5                 align 10h
    

    查询到一些可疑的东西

    image

    用IDA动调试试

    image

    (内存错误)
    进入ntdll.dll的反编译域

    image

    IDA报错

    image

    IDA动调失败,这个时候回看当时的伪代码区

    image

    image

    直接禁止调试,意不意外!

  2. 关键代码补充

    void __stdcall StartAddress_0(int a1)
    {
      while ( 1 )
      {
        WaitForSingleObject(hObject, 0xFFFFFFFF);
        if ( dword_418008 > -1 )
        {
          sub_41112C(Source, dword_418008);
          --dword_418008;
          Sleep(0x64u);
        }
        ReleaseMutex(hObject);
      }
    }
    // positive sp value has been detected, the output may be wrong!
    //sub_41112C
    char *__cdecl sub_411940(int a1, int a2)
    {
      char *result; // eax
      char v3; // [esp+D3h] [ebp-5h]
    
      v3 = *(_BYTE *)(a2 + a1);
      if ( (v3 < 97 || v3 > 122) && (v3 < 65 || v3 > 90) )
        exit(0);
      if ( v3 < 97 || v3 > 122 )
      {
        result = off_418000[0];
        *(_BYTE *)(a2 + a1) = off_418000[0][*(char *)(a2 + a1) - 38];
      }
      else
      {
        result = off_418000[0];
        *(_BYTE *)(a2 + a1) = off_418000[0][*(char *)(a2 + a1) - 96];
      }
      return result;
    }
    

    可见基本加密函数,重写此函数:

    void encode()
    {
        for (i; i <= -1; i--)
        {
            if ((Source[i] < 'a' || Source[i] > 'z') && (Source[i] < 'A' || Source[i] > 'Z'))
                exit(0);
            // if (Source[i] >= 'A' || Source[i] <= 'Z')
            if (Source[i] < 'a' || Source[i] > 'z')
            {
                //input range:65~90
                //output range:off_418000[27~52]
                Source[i] = off_418000[Source[i] - 38];
            }
            // if (Source[i] >= 'a' || Source[i] <= 'z')
            else
            {
                //input range:97~122
                //output range:1~26
                Source[i] = off_418000[Source[i] - 96];
            }
        }
    }
    

    简直就是***钻,这里用了一个ascii映射到字符串进行编码
    因而可以很简单的写出其反编码函数:

  3. 反编码

    
    int LocateChar(char c)
    {
        for (int i = 0; i < strlen(off_418000); i++)
        {
            if (off_418000[i] == c)
            {
                return i;
            }
        }
        return -1;
    }
    void decode()
    {
        printf("attempt from dict:");
        for (int i = 29; i >0; i--)
        {
            printf("%c", decode(off_418004[i]));
            Source[i] = decode(off_418004[i]);
        }
        encode();
        printf("\n");
        for (int i = 0; i < strlen(off_418004); i++)
        {
            int pos = LocateChar(off_418004[i]);
            if (pos != -1)
            {
                if (pos >= 1 && pos <= 26)
                {
                    flag[i] = (char)(pos + 96);
                }
                else
                {
                    flag[i] = (char)(pos + 38);
                }
            }
            else
            {
                flag[i] = '?';
            }
        }
        printf("attempt:%s", flag);
    }
    void PrintDict()
    {
        for (int j = 0; j < 128; j++)
        {
            int e = 0;
            if ((j < 'a' || j > 'z') && (j < 'A' || j > 'Z'))
                continue;
            // if (Source[i] >= 'A' || Source[i] <= 'Z')
            if (j < 'a' || j > 'z')
            {
                //input range:65~90
                //output range:off_418000[27~52]
                e = off_418000[j - 38];
            }
            // if (Source[i] >= 'a' || Source[i] <= 'z')
            else
            {
                //input range:97~122
                //output range:1~26
                e = off_418000[j - 96];
            }
            printf("if(c == %d) return %d;\n", e, j);
        }
    }
    char decode(char c)
    {
        if (c == 119) return 65;
        if (c == 101) return 66;
        if (c == 114) return 67;
        if (c == 116) return 68;
        if (c == 121) return 69;
        if (c == 117) return 70;
        if (c == 105) return 71;
        if (c == 111) return 72;
        if (c == 112) return 73;
        if (c == 97) return 74;
        if (c == 115) return 75;
        if (c == 100) return 76;
        if (c == 102) return 77;
        if (c == 103) return 78;
        if (c == 104) return 79;
        if (c == 106) return 80;
        if (c == 107) return 81;
        if (c == 108) return 82;
        if (c == 122) return 83;
        if (c == 120) return 84;
        if (c == 99) return 85;
        if (c == 118) return 86;
        if (c == 98) return 87;
        if (c == 110) return 88;
        if (c == 109) return 89;
        if (c == 0) return 90;
        if (c == 87) return 97;
        if (c == 69) return 98;
        if (c == 82) return 99;
        if (c == 84) return 100;
        if (c == 89) return 101;
        if (c == 85) return 102;
        if (c == 73) return 103;
        if (c == 79) return 104;
        if (c == 80) return 105;
        if (c == 65) return 106;
        if (c == 83) return 107;
        if (c == 68) return 108;
        if (c == 70) return 109;
        if (c == 71) return 110;
        if (c == 72) return 111;
        if (c == 74) return 112;
        if (c == 75) return 113;
        if (c == 76) return 114;
        if (c == 90) return 115;
        if (c == 88) return 116;
        if (c == 67) return 117;
        if (c == 86) return 118;
        if (c == 66) return 119;
        if (c == 78) return 120;
        if (c == 77) return 121;
        if (c == 113) return 122;
        return -1;
    }
    
  4. 然而这样计算的逆向值:dhGsGsDhCeJdHfAiXdHwKhJhJIKEk并不能通过检验,说明decode函数出现问题。
    然而根据加密函数,问题并不出在deocde里。
    根据原主函数:

    //...
      hObject = CreateThread(0, 0, StartAddress, 0, 0, 0);
      v4 = CreateThread(0, 0, sub_41119F, 0, 0, 0);
    //...
    

    注意到

    void __stdcall StartAddress_0(int a1)
    {
      while ( 1 )
      {
        WaitForSingleObject(hObject, 0xFFFFFFFF);
        if ( i > -1 )
        {
          sub_41112C(Source, i);
          --i;
          Sleep(0x64u);
        }
        ReleaseMutex(hObject);
      }
    }
    
    void __stdcall sub_411B10(int a1)
    {
      while ( 1 )
      {
        WaitForSingleObject(hObject, 0xFFFFFFFF);
        if ( i > -1 )
        {
          Sleep(0x64u);
          --i;
        }
        ReleaseMutex(hObject);
      }
    }
    

    可以看到这两个异步线程都做了一件事:

        if ( i > -1 )
        {
          Sleep(0x64u);
          --i;
        }
    

    Sleep函数暂且不表,这里两个线程都对参数dword_418008(后来的i)进行了自减操作。因而解密函数做一个小改动:

    void decode()
    {
        printf("attempt from dict:");
        for (int i = 0; i <=strlen(off_418004); i++)
        {
            if (i % 2 == 0)
            {
                printf("%c", off_418004[i]);
                Source[i] = off_418004[i];
            }
            else
            {
                printf("%c", decode(off_418004[i]));
                Source[i] = decode(off_418004[i]);
            }
        }
    }
    

    可以看到这里对索引i进行了判断,如果为偶数就保留,否则就解密。
    另外为了避免逻辑,我直接用分析过程中的encode函数生成了dict字典,并且直接格式化成了代码格式。粘贴过来就能按照字典解密,免去了复杂的数学逻辑计算(计算机的事就交给计算机去完成吧)

  5. 获取flag:flag{ThisisthreadofwindowshahaIsESZ}

    另外这里有个小坑:在比对的时候只比对前29位,因此爆破得到正确flag

    正确flag:flag{ThisisthreadofwindowshahaIsESE}

    #include <stdio.h>
    #include "defs.h"
    int i = 29;
    char off_418000[] = { "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm" };
    char Source[52] = { 0 };
    char off_418004[] = { "TOiZiZtOrYaToUwPnToBsOaOapsyS" };
    char flag[52] = { 0 };
    
    void encode();
    int LocateChar(char c);
    void decode();
    void PrintDict();
    
    char decode(char c);
    
    int main(int argc, const char** argv, const char** envp)
    {
        decode();
    	return 0;
    }
    void encode()
    {
        for (i = 29 ; i > -1; i--)
        {
            if ((Source[i] < 'a' || Source[i] > 'z') && (Source[i] < 'A' || Source[i] > 'Z'))
                continue;
            // if (Source[i] >= 'A' || Source[i] <= 'Z')
            if (Source[i] < 'a' || Source[i] > 'z')
            {
                //input range:65~90
                //output range:off_418000[27~52]
                Source[i] = off_418000[Source[i] - 38];
            }
            // if (Source[i] >= 'a' || Source[i] <= 'z')
            else
            {
                //input range:97~122
                //output range:1~26
                Source[i] = off_418000[Source[i] - 96];
            }
        }
        printf("\nencoded %s", Source);
    }
    int LocateChar(char c)
    {
        for (int i = 0; i < strlen(off_418000); i++)
        {
            if (off_418000[i] == c)
            {
                return i;
            }
        }
        return -1;
    }
    void decode()
    {
        printf("attempt from dict:");
        for (int i = 0; i <=strlen(off_418004); i++)
        {
            if (i % 2 == 0)
            {
                printf("%c", off_418004[i]);
                Source[i] = off_418004[i];
            }
            else
            {
                printf("%c", decode(off_418004[i]));
                Source[i] = decode(off_418004[i]);
            }
        }
    }
    void PrintDict()
    {
        for (int j = 0; j < 128; j++)
        {
            int e = 0;
            if ((j < 'a' || j > 'z') && (j < 'A' || j > 'Z'))
                continue;
            // if (Source[i] >= 'A' || Source[i] <= 'Z')
            if (j < 'a' || j > 'z')
            {
                //input range:65~90
                //output range:off_418000[27~52]
                e = off_418000[j - 38];
            }
            // if (Source[i] >= 'a' || Source[i] <= 'z')
            else
            {
                //input range:97~122
                //output range:1~26
                e = off_418000[j - 96];
            }
            printf("if(c == %d) return %d;\n", e, j);
        }
    }
    char decode(char c)
    {
        if (c == 119) return 65;
        if (c == 101) return 66;
        if (c == 114) return 67;
        if (c == 116) return 68;
        if (c == 121) return 69;
        if (c == 117) return 70;
        if (c == 105) return 71;
        if (c == 111) return 72;
        if (c == 112) return 73;
        if (c == 97) return 74;
        if (c == 115) return 75;
        if (c == 100) return 76;
        if (c == 102) return 77;
        if (c == 103) return 78;
        if (c == 104) return 79;
        if (c == 106) return 80;
        if (c == 107) return 81;
        if (c == 108) return 82;
        if (c == 122) return 83;
        if (c == 120) return 84;
        if (c == 99) return 85;
        if (c == 118) return 86;
        if (c == 98) return 87;
        if (c == 110) return 88;
        if (c == 109) return 89;
        if (c == 0) return 90;
        if (c == 87) return 97;
        if (c == 69) return 98;
        if (c == 82) return 99;
        if (c == 84) return 100;
        if (c == 89) return 101;
        if (c == 85) return 102;
        if (c == 73) return 103;
        if (c == 79) return 104;
        if (c == 80) return 105;
        if (c == 65) return 106;
        if (c == 83) return 107;
        if (c == 68) return 108;
        if (c == 70) return 109;
        if (c == 71) return 110;
        if (c == 72) return 111;
        if (c == 74) return 112;
        if (c == 75) return 113;
        if (c == 76) return 114;
        if (c == 90) return 115;
        if (c == 88) return 116;
        if (c == 67) return 117;
        if (c == 86) return 118;
        if (c == 66) return 119;
        if (c == 78) return 120;
        if (c == 77) return 121;
        if (c == 113) return 122;
        return -1;
    }
    
  6. 附录:对程序禁止调试的过程的分析:

    按下Ctrl+E跳转到入口点:

    image

    选择真正的OEP:

    image

可见前面有一个新的入口,称为TlsCallBack_0

Thread Local Storage

线程本地存储

All threads of a process share its virtual address space. The local variables of a function are unique to each thread that runs the function. However, the static and global variables are shared by all threads in the process. With thread local storage (TLS), you can provide unique data for each thread that the process can access using a global index. One thread allocates the index, which can be used by the other threads to retrieve the unique data associated with the index.

进程的所有线程共享其虚拟地址空间。函数的局部变量对于运行该函数的每个线程都是唯一的。但是,静态和全局变量由进程中的所有线程共享。使用线程本地存储 (TLS),您可以为进程可以使用全局索引访问的每个线程提供唯一数据。一个线程分配索引,其他线程可以使用它来检索与索引关联的唯一数据。

The constant TLS_MINIMUM_AVAILABLE defines the minimum number of TLS indexes available in each process. This minimum is guaranteed to be at least 64 for all systems. The maximum number of indexes per process is 1,088.

常量 TLS_MINIMUM_AVAILABLE 定义了每个进程中可用的 TLS 索引的最小数量。对于所有系统,此最小值保证至少为 64。每个进程的最大索引数为 1,088。

When the threads are created, the system allocates an array of LPVOID values for TLS, which are initialized to NULL. Before an index can be used, it must be allocated by one of the threads. Each thread stores its data for a TLS index in a TLS slot in the array. If the data associated with an index will fit in an LPVOID value, you can store the data directly in the TLS slot. However, if you are using a large number of indexes in this way, it is better to allocate separate storage, consolidate the data, and minimize the number of TLS slots in use.

创建线程时,系统会为 TLS 分配一组 LPVOID 值,这些值被初始化为 NULL。在使用索引之前,它必须由其中一个线程分配。每个线程都将其数据存储在数组的 TLS 插槽中以获取 TLS 索引。如果与索引关联的数据适合 LPVOID 值,您可以将数据直接存储在 TLS 插槽中。但是,如果您以这种方式使用大量索引,则最好分配单独的存储空间,合并数据,并尽量减少使用中的 TLS 槽数。

The following diagram illustrates how TLS works. For a code example illustrating the use of thread local storage, see Using Thread Local Storage.

下图说明了 TLS 的工作原理。有关说明线程本地存储使用的代码示例,请参阅使用线程本地存储

Diagram that shows how the T L S process works.

The process has two threads, Thread 1 and Thread 2. It allocates two indexes for use with TLS, gdwTlsIndex1 and gdwTlsIndex2. Each thread allocates two memory blocks (one for each index) in which to store the data, and stores the pointers to these memory blocks in the corresponding TLS slots. To access the data associated with an index, the thread retrieves the pointer to the memory block from the TLS slot and stores it in the lpvData local variable.

该进程有两个线程,线程 1 和线程 2。它分配两个索引用于 TLS,gdwTlsIndex1gdwTlsIndex2。 每个线程分配两个内存块(每个索引一个)来存储数据,并将指向这些内存块的指针存储在相应的 TLS 插槽中。 为了访问与索引关联的数据,线程从 TLS 插槽中检索指向内存块的指针并将其存储在 lpvData 局部变量中。

It is ideal to use TLS in a dynamic-link library (DLL). For an example, see Using Thread Local Storage in a Dynamic Link Library.

在动态链接库 (DLL) 中使用 TLS 是理想的。 有关示例,请参阅在动态链接库中使用线程本地存储

引自:[Thread Local Storage](Thread Local Storage - Win32 apps | Microsoft Docs)

简而言之,TLS提供了一种为每个线程单独创建一个独立的、私有的存储空间的方式。TLS表存储内存块并供私有线程使用

在《恶意代码分析实战》里16.3干扰调试器的功能节中介绍了使用TLS回调

你可能会认为程序加载到调试器后,会在第一条指令处暂停程序的运行,但事实并非一直如此。大部分调试器从程序PE头部指定的入口点处开始。TLS回调被用来在程序入口点执行之前运行代码,因此这些代码可以在调试器中秘密地运行。如果你依赖使用调试器,那么你可能会漏掉一些恶意代码的行为,因为TLS回调在恶意代码刚被加载到调试器时就会运行。

TLS是Windows的一个存储类,其中数据对象不是一个自动的堆栈变量,而是代码中运行的每个线程的一个本地变量。大致而言,TLS允许每个线程维护一个用TLS声明的专有变量。在应用程序实现TLS的情况下,可执行程序的PE头部会包含一个``.tls`段,如图16-1(图略)所示。TLS提供了初始化和终止TLS数据对象的回调函数。Windows系统在执行程序正常的入口点之前运行这些回调函数。

使用PEview查看``.tls段,可以发现TLS回调函数。通常情况下,正常程序不使用.tls段,如果在可执行程序中看到.tls`段,你应该立即怀疑它使用了反调试技术。

使用IDA Pro可以很容易地分析TLS回调函数。一旦IDA Pro完成对应用程序的分析,你可以通过按Ctrl+E组合键看到二进制的入口点,该组合键的作用是显示应用程序所有的入口点,其中包括TLS回调,如图16-2所示。其中每个TLS回调函数都拥有一个前缀字符串TlsCallback。在IDA Pro中,可以通过双击函数名来浏览回调函数。

虽然有时调试器会在中断程序入口点之前运行TLS回调,但TLS回调也可以在调试器中处理。可以通过修改调试器的配置避免这个问题。例如,如果你使用IDA Pro调试器,通过选择Options→Debugging Options→Events,然后设置System break-point作为第一个暂停的位置,这样就可以让OllyDbg在TLS回调执行前暂停。如图16-3所示。

提示:OllyDbg 2.0比 OllyDbg 1.1 拥有更强的设置断点功能。例如,它可以在TLS回调开始处设置断点。同样,WinDbg 总能在TLS回调之前,在系统断点处中断。

由于TLS回调已广为人知,因此同过去相比,恶意代码使用它的次数已经明显减少。因为为数不多的合法程序使用TLS回调,所以可执行程序中的.t1s段显得特别突出。

我们跟踪进入TlsCallback_0查看其函数功能:

int __thiscall TlsCallback_0_0(void *this, int a2, int a3, int a4)
{
  sub_411226(this);
  return sub_4111A4();
}
//  sub_411226(this);
BOOL sub_411460()
{
  size_t v0; // eax
  BOOL i; // [esp+D0h] [ebp-24Ch]
  HANDLE hSnapshot; // [esp+DCh] [ebp-240h]
  PROCESSENTRY32W pe; // [esp+E8h] [ebp-234h] BYREF

  pe.dwSize = 556;
  hSnapshot = j_CreateToolhelp32Snapshot(2u, 0);
  for ( i = j_Process32FirstW(hSnapshot, &pe); i; i = j_Process32NextW(hSnapshot, &pe) )
  {
    v0 = wcslen(pe.szExeFile);
    wcslwr_s(pe.szExeFile, v0 + 1);
    if ( !wcscmp(pe.szExeFile, L"ollyice.exe") )
    {
      printf("///////WARNING///////\n");
      exit(0);
    }
    if ( !wcscmp(pe.szExeFile, L"ollydbg.exe") )
    {
      printf("///////\nWARNING\n///////\n");
      exit(0);
    }
    if ( !wcscmp(pe.szExeFile, L"peid.exe") )
    {
      printf("///////\nWARNING\n///////\n");
      exit(0);
    }
    if ( !wcscmp(pe.szExeFile, L"ida.exe") )
    {
      printf("///////\nWARNING\n///////\n");
      exit(0);
    }
    if ( !wcscmp(pe.szExeFile, L"idaq.exe") )
    {
      printf("///////\nWARNING\n///////\n");
      exit(0);
    }
  }
  return CloseHandle(hSnapshot);
}
//  return sub_4111A4();
int sub_411790()
{
  int result; // eax

  result = NtCurrentPeb()->BeingDebugged;
  if ( result )
  {
    printf("///////\nWARNING\n///////\n");
    exit(0);
  }
  return result;
}

这里的代码是如此清晰,以至于我真想给出题人树一个大拇指(bushi。
很明显,这里进行两个判断,首先是

  for ( i = j_Process32FirstW(hSnapshot, &pe); i; i = j_Process32NextW(hSnapshot, &pe) )
  {
    v0 = wcslen(pe.szExeFile);
    wcslwr_s(pe.szExeFile, v0 + 1);
    if ( !wcscmp(pe.szExeFile, L"ollyice.exe") )
    {
      printf("///////WARNING///////\n");
      exit(0);
    }
    if ( !wcscmp(pe.szExeFile, L"ollydbg.exe") )
    {
      printf("///////\nWARNING\n///////\n");
      exit(0);
    }
    if ( !wcscmp(pe.szExeFile, L"peid.exe") )
    {
      printf("///////\nWARNING\n///////\n");
      exit(0);
    }
    if ( !wcscmp(pe.szExeFile, L"ida.exe") )
    {
      printf("///////\nWARNING\n///////\n");
      exit(0);
    }
    if ( !wcscmp(pe.szExeFile, L"idaq.exe") )
    {
      printf("///////\nWARNING\n///////\n");
      exit(0);
    }
  }

用于检测存在的进程列表里包不包含一些常见的调试程序,即

{
    "ollyice.exe",
	"ollydbg.exe",
//OD调试器
	"peid.exe",
//peid.exe:pe文件嗅探器
	"ida,exe",
	"idaq.exe"
//IDA调试器
//idag idaq 是在32位系统下,idaq使用qt做的界面,两者没有多大区别。
//idag64 idaq64用在64位系统下。}

接下来检测调试状态:

result = NtCurrentPeb()->BeingDebugged;
  if ( result )
  {
    printf("///////\nWARNING\n///////\n");
    exit(0);
  }

其中函数NtCurrentPeb()

// Gets a pointer to the PEB for x86, x64, ARM, ARM64, IA64, Alpha AXP, MIPS, and PowerPC.

// This relies on MS-compiler intrinsics.
// It has only been tested on x86/x64/ARMv7.

inline PEB* NtCurrentPeb() {
#ifdef _M_X64
	return (PEB*)(__readgsqword(0x60));
#elif _M_IX86
	return (PEB*)(__readfsdword(0x30));
#elif _M_ARM
	return *(PEB**)(_MoveFromCoprocessor(15, 0, 13, 0, 2) + 0x30);
#elif _M_ARM64
	return *(PEB**)(__getReg(18) + 0x60); // TEB in x18
#elif _M_IA64
	return *(PEB**)((size_t)_rdteb() + 0x60); // TEB in r13
#elif _M_ALPHA
	return *(PEB**)((size_t)_rdteb() + 0x30); // TEB pointer returned from callpal 0xAB
#elif _M_MIPS
	return *(PEB**)((*(size_t*)(0x7ffff030)) + 0x30); // TEB pointer located at 0x7ffff000 (PCR in user-mode) + 0x30
#elif _M_PPC
	// winnt.h of the period uses __builtin_get_gpr13() or __gregister_get(13) depending on _MSC_VER
	return *(PEB**)(__gregister_get(13) + 0x30); // TEB in r13
#else
#error "This architecture is currently unsupported"
#endif
}

PEB:

The PEB is the user-mode portion of Microsoft Windows process control structures.

PEB 是 Microsoft 管理过程控制Windows用户模式部分。

If the !peb extension with no argument gives you an error in kernel mode, you should use the !process extension to determine the PEB address for the desired process. Make sure your process context is set to the desired process, and then use the PEB address as the argument for !peb.

如果 没有参数的 !peb 扩展在内核模式下出现错误,则应该使用 !process 扩展来确定所需进程的 PEB 地址。 确保进程 上下文设置为 所需的进程,然后使用 PEB 地址作为 !peb 的参数

The exact output displayed depends on the Windows version and on whether you are debugging in kernel mode or user mode. The following example is taken from a kernel debugger attached to a Windows Server 2003 target:

显示的确切输出取决于Windows版本以及你是在内核模式还是用户模式下进行调试。 以下示例取自附加到 Windows Server 2003 目标的内核调试器:

kd> !peb
PEB at 7ffdf000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            No
    ImageBaseAddress:         4ad00000
    Ldr                       77fbe900
    Ldr.Initialized:          Yes
    Ldr.InInitializationOrderModuleList: 00241ef8 . 00242360
    Ldr.InLoadOrderModuleList:           00241e90 . 00242350
    Ldr.InMemoryOrderModuleList:         00241e98 . 00242358
            Base TimeStamp                     Module
        4ad00000 3d34633c Jul 16 11:17:32 2002 D:\WINDOWS\system32\cmd.exe
        77f40000 3d346214 Jul 16 11:12:36 2002 D:\WINDOWS\system32\ntdll.dll
        77e50000 3d3484ef Jul 16 13:41:19 2002 D:\WINDOWS\system32\kernel32.dll
....
    SubSystemData:     00000000
    ProcessHeap:       00140000
    ProcessParameters: 00020000
    WindowTitle:  'D:\Documents and Settings\Administrator\Desktop\Debuggers.lnk'
    ImageFile:    'D:\WINDOWS\system32\cmd.exe'
    CommandLine:  '"D:\WINDOWS\system32\cmd.exe" '
    DllPath:      'D:\WINDOWS\system32;D:\WINDOWS\system32;....
    Environment:  00010000
        ALLUSERSPROFILE=D:\Documents and Settings\All Users
        APPDATA=D:\Documents and Settings\UserTwo\Application Data
        CLIENTNAME=Console
....
        windir=D:\WINDOWS

[!peb - MSDN](peb (WinDbg) - Windows drivers | Microsoft Docs)

引自看雪:

Windows是一个强大的操作系统。为了方便开发者,微软将进程中的每个线程都设置了一个独立的结构数据。这个结构体内存储着当前线程大量的信息。这个结构被称为TEB(线程环境块)。通过TEB结构内的成员属性向下扩展,可以得到很多线程信息。这其中还包含大量的未公开数据。

TEB结构的其中一个成员为PEB(进程环境块),顾名思义,这个结构中存储着整个进程的信息。通过对PEB中成员的向下扩展可以找到一个存储着该进程所有模块数据的链表。

[原创]PEB结构:获取模块kernel32基址技术及原理分析-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

从而获取此PEB表之后可以获取其中的参数。系统将调试器状态装入PEB,从而让程序可以确定是否正在被调试。

通过读取PEB,可以进行反调试操作。

  1. 消除TLS的影响
    如前文所述,TLS有一个区段表。根据PE结构,在``_IMAGE_DATA_DIRECTORY`目录里应该存在一个项:

    image

    如图,此处在0x09位置有一个TLS结构表。用LordPE打开:

    image

    选择“目录”

    image

    选择“TLS表”,点开后面的详情(...)按钮

    image

    将回调表VA设置为0

    image

    点击“保存”之后“确定”,将PE文件保存,打开IDA
    可能会加载一段时间。因为我们没有将TLS区段删掉

image

可见这里已经没有TLS的入口了,动调
image

image

....

反正这个时候TLS已经解决了,毕竟这玩意我在正常模式也打不开

image

估计这是upx脱壳之后导致的什么奇怪问题。

whatever

完结撒花

posted @ 2021-09-27 20:54  二氢茉莉酮酸甲酯  阅读(252)  评论(0编辑  收藏  举报