티스토리 뷰

1편 : https://suspected.tistory.com/279

[개요]
본 글에서는 1편에서 소개했듯이 특정 커스텀 인코딩 루틴 분석 및 디코딩 스크립트를 제작하는 과정에 대해 설명한다. 

[분석]
본 글에서는 1편에서 소개했듯이 특정 커스텀 인코딩 루틴 분석 및 디코딩 스크립트를 제작하는 과정에 대해 설명한다. 

아래 코드는 0x18001B330 함수 호출 전 모습이다. 인자로는 v8 변수(인코딩된 데이터)와 v11 변수(데이터 길이 값)을 전달 받는다.

메모리 복사, 디코딩 루틴


아래 코드는 디코딩 루틴(0x18001B330)이다. 아래 함수를 디버깅해서 문자열을 추출하는 방법도 있지만, 시간 소요가 크다는 단점이 있어 IDAPython을 이용해 스크립트를 작성한다. 코드가 많아보이지만 주요 기능은 별로 안된다.

__int64 __fastcall sub_18001B330(_QWORD *a1, __int64 a2)
{
  __int64 v2; // r14
  _QWORD *v3; // rbx
  unsigned __int64 v4; // r12
  char *v5; // rsi
  __int64 v6; // rdi
  __int64 v7; // r15
  bool v8; // cf
  _QWORD *v9; // rax
  unsigned __int64 v10; // rdi
  unsigned __int64 v11; // rax
  __int64 v12; // rsi
  __int64 v13; // rdx
  bool v14; // cf
  _QWORD *v15; // rax
  __int64 v16; // r8
  __int64 v17; // r9
  __int128 v18; // kr00_16
  __int128 *v19; // rax
  __int64 v20; // rcx
  char v22[16]; // [rsp+20h] [rbp-50h]
  int v23; // [rsp+30h] [rbp-40h]
  __int128 v24; // [rsp+38h] [rbp-38h]
  __int128 v25; // [rsp+48h] [rbp-28h]
  __int64 v26; // [rsp+58h] [rbp-18h]

  v2 = a2;
  v3 = a1;
  v26 = a2;
  *(_QWORD *)&v25 = 0i64;
  *((_QWORD *)&v25 + 1) = 7i64;
  LOWORD(v24) = 0;
  sub_1800081A0((unsigned __int64 *)&v24, (__int64)&unk_180061E08, 0i64);
  v4 = v3[2];
  if ( !(v4 & 1) && v4 > 0x20 )
  {
    LODWORD(v26) = 0;
    WORD2(v26) = 0;
    v23 = 0;
    v5 = v22;
    v6 = 0i64;
    v7 = 16i64;
    while ( 1 )
    {
      v8 = v3[3] < 8ui64;
      if ( v3[3] >= 8ui64 )
        break;
      LOWORD(v26) = *(_WORD *)((char *)v3 + v6);
      v9 = v3;
      if ( !v8 )
        goto LABEL_7;
LABEL_8:
      *(_DWORD *)((char *)&v26 + 2) = *(unsigned __int16 *)((char *)v9 + v6 + 2);
      sub_18001BC10(&v26, L"%x", &v23);
      *v5 = v23;
      v6 += 4i64;
      ++v5;
      if ( !--v7 )
      {
        v10 = 32i64;
        v11 = 0i64;
        while ( 1 )
        {
          v12 = v11 - 16;
          if ( v11 < 0x10 )
            v12 = v11;
          v13 = 2 * v10;
          v14 = v3[3] < 8ui64;
          if ( v3[3] >= 8ui64 )
            break;
          LOWORD(v26) = *(_WORD *)((char *)v3 + v13);
          v15 = v3;
          if ( !v14 )
            goto LABEL_15;
LABEL_16:
          *(_DWORD *)((char *)&v26 + 2) = *(unsigned __int16 *)((char *)v15 + v13 + 2);
          sub_18001BC10(&v26, L"%x", &v23);
          v17 = (unsigned int)(char)(v23 ^ v7 ^ v22[v12]);
          v18 = v25;
          if ( (unsigned __int64)v25 >= *((_QWORD *)&v25 + 1) )
          {
            sub_18001B8D0(&v24, *((_QWORD *)&v25 + 1), v16, v17);
          }
          else
          {
            *(_QWORD *)&v25 = v25 + 1;
            v19 = &v24;
            if ( *((_QWORD *)&v18 + 1) >= 8ui64 )
              v19 = (__int128 *)v24;
            *((_WORD *)v19 + v18) = v17;
            *((_WORD *)v19 + v18 + 1) = 0;
          }
          LOBYTE(v7) = v23;
          v10 += 2i64;
          v11 = v12 + 1;
          if ( v10 >= v4 )
          {
            *(_OWORD *)v2 = v24;
            *(_OWORD *)(v2 + 16) = v25;
            return v2;
          }
        }
        LOWORD(v26) = *(_WORD *)(v13 + *v3);
LABEL_15:
        v15 = (_QWORD *)*v3;
        goto LABEL_16;
      }
    }
    LOWORD(v26) = *(_WORD *)(v6 + *v3);
LABEL_7:
    v9 = (_QWORD *)*v3;
    goto LABEL_8;
  }
  *(_QWORD *)(v2 + 16) = 0i64;
  *(_QWORD *)(v2 + 24) = 7i64;
  *(_WORD *)v2 = 0;
  sub_1800081A0((unsigned __int64 *)v2, (__int64)&unk_180061E08, 0i64);
  if ( *((_QWORD *)&v25 + 1) >= 8ui64 )
  {
    v20 = v24;
    if ( (unsigned __int64)(2i64 * *((_QWORD *)&v25 + 1) + 2) >= 0x1000 )
    {
      v20 = *(_QWORD *)(v24 - 8);
      if ( (unsigned __int64)(v24 - v20 - 8) > 0x1F )
        invalid_parameter_noinfo_noreturn();
    }
    sub_1800295B0(v20);
  }
  return v2;
}


아래부터는 위 함수 내 커스텀 알고리즘으로 인/디코딩된 루틴을 단계별로 설명한다.

첫 번째로, 1877b~~~27ca와 같은 인코딩된 문자열을 입력 받아 2개씩 끊어 Byte 형태로 변환을 우선 수행한다. 
두 번째로, Byte 형태로 변환된 값 중 앞에서부터 16Byte, 나머지 Byte를 분리한다.

알고리즘 1,2


세 번째로, 위에서 나눈 것을 토대로 a ^ b ^ c 형태로 XOR을 하는 루틴이다.
우선, 16byte 왼쪽 부분부터 1씩 증가하는 a, 나머지 Byte 영역 부분부터 1씩 증가하는 c로 치환한다. b는 초기 값으로 0을 넣는다.
stage는 인코딩된 데이터(Byte 형태)의 갯수만큼 반복문을 수행한다. stage 1은 위에서 말한 바와 같이 왼쪽 16byte 영역의 첫 번째 요소인 0x18(a), 나머지 영역의 첫 번째 요소인 0x3d(c), b=0으로 치환 후 XOR을 수행한다.
stag 2부터는 a와 c는 한 칸씩 이동해 값을 불러오는 것이 동일하다. b같은 경우에는 이전(stage 1이 되겠죠?) c의 값을 b에 저장 후 XOR 연산을 수행한다. 아래 그림을 참고하면 이해가 쉬울 것이다. 

stage를 계속 수행하다가 인코딩된 데이터(Byte 형태)의 갯수만큼 작동하고 끝이난다. 결과는 "%PDF-1.7..4 0 obj"

알고리즘 3


위 과정까지가 알고리즘 수행의 기본 루틴이고 아래는 알고리즘의 핵심 조건문 루틴이다. 3번에서 수행한 인코딩된 문자열 길이는 짧기 때문에 a영역이 16바이트가 되기 전에 끝났다.

하지만, 아래와 같이 긴 인코딩 문자열을 입력 받아 수행할 때는 a영역의 offset이 16바이트를 초과하면 어떤 요소에 접근해야 하는지에 대한 조건문이다. 아래 그림을 토대로 설명하면, stage 16까지 잘 수행하다가 stage 17을 수행하면 b와 c는 정상적으로 다음 요소를 순차적으로 저장한다. 그러나 a는 다음 요소가 c의 영역이기 때문에 a의 요소가 16이 넘어가는 순간 다시 0부터 다시 시작한다. 

아래 stage 17과 같이 a가 0x89부터 다시 시작한다. 나머지는 순차적으로 값을 그대로 얻어와 XOR을 수행한다. 
결과는 아래와 같다. 17번째 요소에 5가 들어가있는 것을 확인할 수 있다. "0602000000A4000052534131000400000100010005DA37C671C00B2A04759D5A143C015F4D0B38F0F83D6E4E19B309
D570ADB6EEA7CACB5A59A489B9E4B8D801B76A0C361E7D7798E6248722DC0349400857F68C5B21474138F0D3EE0929
AB1EBEA9EBB057E88D0CACB41D4A6029F459AD7B8A8D180B77DC4596745B9CF77DAD7B50F44B43DA8F1326E64C53
DAA51807A02751E2"

알고리즘 조건식


[구현]
위에서 분석한 알고리즘을 기반으로 이제 IDAPython을 이용해 디코딩 스크립트를 구현한다.

우리가 분석해야 하는 디코딩 함수 0x18001B330을 사용하는 함수를 확인하기 위해 Xref 기능을 사용해 확인한 결과, 중복도 있겠지만 239개의 함수가 있는 것을 확인했다.

Xref - 0x18001B330


우선 첫 번째로 0x18001B330을 call하는 부분부터 backward를 수행해 "lea rdx, a2c92cd0a32f60e" 부분으로 이동해 2번째 오퍼랜드의 값을 가져와야 한다. 이 루틴을 구현하면 239개의 인코딩된 데이터를 모두 추출해 리스트로 저장한다.

인코딩된 데이터 가져오기


팁 : 데이터는 아래와 같이 유니코드 형태로 저장되어 있어 "GetString(str_address, strtype=idc.ASCSTR_UNICODE)" 형태로 작성해줘야 그대로 추출된다. 유니코드가 아닌 형태로는 1Byte씩 접근해 주소를 옮겨가며 추출해야 하는 것 보다 훨씬 편하다.

유니코드 형태로 저장된 인코딩된 데이터


인코딩된 데이터를 리스트에 모두 저장한 후 이제 반복문을 돌려 디코딩 함수에 인코딩된 데이터, 데이터 길이 2가지를 인자로 넣어주면 끝이다.

아래 코드는 IDA(offset : 0x18001B330) 내 구현된 알고리즘을 기반으로 재구성했다. 알고리즘 형태는 위에서 설명한 것과 동일하므로 설명은 생략한다.

from idautils import *
from idaapi import *
from idc import *
import re

p = re.compile('(\d|[a-f]){20,}')

def EncodedDataCollector(addr):
    encodeddataList = []
    for addr in XrefsTo(0x18001B330, flags=0):
        count = 0
        line = addr.frm
        while True:
            line = idc.PrevHead(line)
            count += 1
            if GetMnem(line) == "lea" and GetOpType(line, 0) == o_reg and GetOpType(line, 1) == o_mem:
                str_address = GetOperandValue(line, 1)
                
                try:
                    data = GetString(str_address, strtype=idc.ASCSTR_UNICODE)
                    signal = 1
                except:
                    signal = 0
                                    
                if signal != 1:
                    break
                else:
                    encodeddataList.append(data)
                    
            if count > 10:
                break
    
    return encodeddataList
                
def confirmRegex(lists):
    attemplist = []
    expectinglen = len(lists)
    for r in lists:
        result = p.finditer(r)
        for r in result:
            zzz = r.group()
            attemplist.append(zzz)

    if expectinglen == len(attemplist):
        return 1
    else:
        return 0

def str2binbyte(encdata):
    count = 0
    bl = []
    for i in range(0, len(encdata), 2):         
        bl.append(int(encdata[i:i+2], 16))

    if type(bl) == list:
        return bl # list
    else:
        return False

def decoding_1B330(enc, enclen):
    v22=0
    v7=0
    v10_count=16-1 #count
    enc16byteleft = enc[:16]
    enc16byteRigth = enc[16:]
    decodedDataList = []
    j = 0
    for i in range(0, enclen):
        
        if j >= 16:
            j = 0
        
        v23 = enc16byteleft[j]
        v22 = enc16byteRigth[i]
        dedata = v23^v7^v22
        decodedDataList.append(dedata)

        v7 = v22
        v10_count += 1
        j+=1
        
        if v10_count >= enclen-1:
            break
    
    finaldata = ''.join(map(chr, decodedDataList))

    return finaldata

def main():
    decodingroutineaddr = 0x18001B330
    result = EncodedDataCollector(decodingroutineaddr) #list type

    if len(result) > 0:
        if(confirmRegex(result)):
            for endata in set(result):
                encbytebin = str2binbyte(endata)

                if(encbytebin != False):
                    enc_len = len(encbytebin)
                    result = decoding_1B330(encbytebin, enc_len)
                    print(result)

main()


드롭퍼(DLL)를 IDA에 로드하고 위에서 구현한 디코딩 스크립트를 실행하면 아래와 같은 문자열들이 출력된다. API 함수, C2 주소, URL 파라미터, 바이너리 등 유의미한 정보들을 추출 할 수 있었다. xref로 확인했을 때는 239개였는데, 중복을 제거했더니 200개로 줄었다.

LookupPrivilegeValueW
GetWindowTextW
FindClose
HttpAddRequestHeadersA
ToAscii
temp
%d-%02d-%02d_%02d-%02d-%02d-%03d
GetWindowTextA
texts.letterpaper.press
shlwapi.dll
GetFileSize
ConsentPromptBehaviorAdmin
GetLocalTime
SelectObject
ReadFile
KeyboardMonitor
shell32.dll
flags
CreateRemoteThread
LookupPrivilegeValueA
SHCreateDirectoryExA
S_Regsvr32
VirtualAllocEx
RegQueryValueExA
HttpSendRequestExA
SetFilePointer
GetWindowThreadProcessId
0702000000A400005253413200040000010001006D4582142BA47753E19FF39DBF232B7BAEE5141CC59AB328CA25EC21BEF955FE091F90B8FF3C3D8CD00973E3D2D7FACAD76B40A0A90BDE7468338B4F7C39DFDDE6C1574F3C48065AB364E505C322FF6B26CB67014DA28CD1FABEE32C9DB4BFD6F182AAA9DFB77EF3B26F91BC2E03EE4AB04B8A8741B83A85443DB8F28B99A3C63B206FAE6F36E19D4AFA768CF24283CFB7137FE47C403BC1E9E44CC12AB46877E7EAD66E69BC1C95E074127F1359978D8F6A8F5F57F15B220CACF213184176F9773E649A421A870340AFB025640A0EE5AFCA7DF1C7F6682FD59C9FEC241A9128D26608F5DDE2114CCA6815CA18B337C6B6FA713606A5ABE15133B061765AD8CB4181E72FC2DAA084FDB14AB05D38254556CBF2AF5DEB987F25206E657B03EAC21D2C7BD86703AEB22B27A456B60E3AD3C3C62D5DD7C06E5DB1C5D756B6F0019DFFFE3A425CDFDF9EA4E9F0085DD8064F97028A954A6D9DED2C06358EB77FF3CCC976B5E08344AC167B3971CBABBABD20E90C7BF4D1EA6F563B62B14C175D0683466190BFE8ECF4DDC69665233D21FD28825B7FFC4755A7A398E96FD94517D4B9F1EFFD99EAEB42F507F31D01594F0FF9FC03F4D5C3A40E4B64557C76CC4949BCFD96B77CD3F7A40DCB65B573E8C96A363EB2830B32ABD6827BACCF784BAC428F244861126235051C2B567FA9731699F1F6F76486CAC76DF407CBD0A52531AC87D6CC129B19D219E9CEE9E07AA3B7FAD0106F631B53B76D876F73CE642C3285ECB322473BCAD03B3647E716575FF530C57A078A2D5485BD13954E6FFA3C70C41F
.enc
InternetWriteFile
SystemTimeToFileTime
CreateMutexW
kernel32.dll
list.fdb
GetCurrentProcess
FindWindowA
GetTempFileNameA
Advapi32.dll
WriteFile
LoadLibraryA
URLDownloadToFileW
PromptOnSecureDesktop
RegCreateKeyExA
%PDF-1.7..4 0 obj
User32.dll
Desktop
DeleteFileW
InternetConnectA
RegSetValueExW
GetModuleHandleW
Process32First
GetVolumeInformationW
CopyFileW
OpenMutexW
Gdi32.dll
ExitProcess
CreateMutexA
FindNextFileA
GetStartupInfoW
URLDownloadToFileA
    :repeat
    del "%s"
    if exist "%s" goto repeat
    del "%%~f0"
RegCreateKeyExW
Software\Microsoft\Windows\CurrentVersion\RunOnce
ESTCommon.dll
MoveFileExW
GetDIBits
MessageBoxA
HttpEndRequestW
LoadLibraryW
GetTempPathW
GetTempPathA
GetObjectA
SHGetFolderPathW
DeleteObject
FindNextFileW
InternetOpenW
GetTokenInformation
RegCloseKey
CreateFileA
SHCreateDirectoryExW
CreateCompatibleBitmap
Process32FirstW
MoveFileExA
GetTickCount
CreateToolhelp32Snapshot
Documents
Process32NextW
HttpOpenRequestW
SpyRegsvr32-20210505162735
GetSystemTime
FindFirstFileW
CreateProcessW
SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
AdjustTokenPrivileges
OpenProcessToken
ReleaseDC
.zip
urlmon.dll
Downloads
FolderMonitor
OpenMutexA
VirtualProtect
.tmp
GetDesktopWindow
InternetSetOptionExA
Wininet.dll
HttpSendRequestA
0602000000A4000052534131000400000100010005DA37C671C00B2A04759D5A143C015F4D0B38F0F83D6E4E19B309D570ADB6EEA7CACB5A59A489B9E4B8D801B76A0C361E7D7798E6248722DC0349400857F68C5B21474138F0D3EE0929AB1EBEA9EBB057E88D0CACB41D4A6029F459AD7B8A8D180B77DC4596745B9CF77DAD7B50F44B43DA8F1326E64C53DAA51807A02751E2
GetVolumeInformationA
GetModuleFileNameA
GetWindowsDirectoryW
regsvr32.exe
ShowWindow
HeapFree
AppData\Local\Microsoft\Windows\INetCache\IE
GetKeyState
DeleteDC
HttpQueryInfoW
FreeLibrary
HttpEndRequestA
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
RegSetValueExA
GetDC
Process32Next
InternetSetOptionExW
CloseHandle
InternetCloseHandle
CreateCompatibleDC
RegQueryValueExW
InternetReadFile
HttpSendRequestW
powershell.exe start-process regsvr32.exe -argumentlist '/s %s' -verb runas
PathFileExistsA
WriteProcessMemory
HeapAlloc
GetForegroundWindow
 /s "
GetSystemMetrics
%s/?m=a&p1=%s&p2=%s-%s-v%s.%d
DeleteUrlCacheEntryW
CreateFileW
GetParent
GetProcessHeap
WaitForSingleObject
InternetOpenA
ESTsoftAutoUpdate
CopyFileA
MessageBoxW
cache
Iphlpapi.dll
FindWindowW
RegOpenKeyExW
GetModuleHandleA
HttpOpenRequestA
CreateProcessA
HttpQueryInfoA
VirtualAlloc
GetVersion
GetWindowsDirectoryA
GetAdaptersInfo
GetStartupInfoA
GetKeyboardState
Software\ESTsoft\Common
2.0
GetTempFileNameW
OpenProcess
list.ldb
CreatePipe
HttpAddRequestHeadersW
BitBlt
CreateThread
RegDeleteValueW
DeleteFileA
TerminateProcess
.bat
ScreenMonitor
SHGetFolderPathA
SetProcessDPIAware
RegDeleteValueA
RegOpenKeyExA
Win%d.%d.%dx64
Sleep
SeDebugPrivilege
GetNativeSystemInfo
UsbMonitor
GetModuleFileNameW
DeleteUrlCacheEntryA
GetDeviceCaps
PathFileExistsW
GetProcAddress
FindFirstFileA
--7263b57d61acd27d98a454fc484795fe0106d5
InternetConnectW
VirtualFree
HttpSendRequestExW
GetWindow
GetObjectW

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함