Форум программистов, компьютерный форум, киберфорум
The trick
Войти
Регистрация
Восстановить пароль
Оценить эту запись

Загрузчик, шеллкод, без рантайма... (часть 2)

Запись от The trick размещена 14.09.2016 в 19:28

Первая часть ->.
После извлечения файлов вызывается функция ExecuteProcess которая запускает выполнение команд используя функцию ShellExecuteEx:
Visual Basic
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
' // Execution command process
Function ExecuteProcess() As Boolean
    Dim index       As Long:                Dim bItem       As BinExecListItem
    Dim pPath       As Long:                Dim pErrMsg     As Long
    Dim shInfo      As SHELLEXECUTEINFO:    Dim pTempString As Long
    Dim pItem       As Long:                Dim status      As Long
 
    ' // Set pointer and size
    shInfo.cbSize = Len(shInfo)
    pItem = pExecutesTable
    
    ' // Go thru all items
    For index = 0 To ProjectDesc.execListDescriptor.dwNumberOfItems - 1
    
        ' // Copy item
        CopyMemory bItem, ByVal pItem, ProjectDesc.execListDescriptor.dwSizeOfItem
        
        ' // Set pointer to next item
        pItem = pItem + ProjectDesc.execListDescriptor.dwSizeOfItem
        
        ' // Normalize path
        pPath = NormalizePath(pStringsTable + bItem.ofstFileName, 0)
        
        ' // Fill SHELLEXECUTEINFO
        shInfo.lpFile = pPath
        shInfo.lpParameters = pStringsTable + bItem.ofstParameters
        shInfo.fMask = SEE_MASK_NOCLOSEPROCESS Or SEE_MASK_FLAG_NO_UI
        shInfo.nShow = SW_SHOWDEFAULT
        
        ' // Performing...
        status = ShellExecuteEx(shInfo)
        
        ' // If error occurs show notification (retry, abort, ignore)
        Do Until status
            
            If pErrMsg Then SysFreeString pErrMsg: pErrMsg = 0
            
            ' // Ignore error
            If bItem.dwFlags And EF_IGNOREERROR Then
                Exit Do
            End If
                        
            pTempString = GetString(MID_ERROREXECUTELINE)
            pErrMsg = StrCat(pTempString, pPath)
            
            SysFreeString pTempString: pTempString = 0
            
            Select Case MessageBox(0, pErrMsg, 0, MB_ICONERROR Or MB_SYSTEMMODAL Or MB_CANCELTRYCONTINUE)
            Case MESSAGEBOXRETURN.IDCONTINUE: Exit Do
            Case MESSAGEBOXRETURN.IDTRYAGAIN
            Case Else: GoTo CleanUp
            End Select
 
            status = ShellExecuteEx(shInfo)
            
        Loop
        
        ' // Wait for process terminaton
        WaitForSingleObject shInfo.hProcess, INFINITE
        CloseHandle shInfo.hProcess
        
    Next
    
    ' // Success
    ExecuteProcess = True
    
CleanUp:
 
    If pTempString Then SysFreeString pTempString
    If pErrMsg Then SysFreeString pErrMsg
    If pPath Then SysFreeString pPath
    
End Function
Эта функция похожа на предыдущую за исключением того что здесь используется функция ShellExecuteEx вместо извлечения. Обратите внимание что каждая операция выполняется синхронно, т.е. каждый вызов процедуры ShellExecuteEx ждет окончания выполнения команды.
Если предыдущая функция выполнилась успешно тогда вызывается функция RunProcess которая подготовливает данные для исполнения главного исполняемого файла из памяти:
Visual Basic
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
32
33
34
' // Run exe from project in memory
Function RunProcess() As Boolean
    Dim bItem       As BinStorageListItem:  Dim Length      As Long
    Dim pFileData   As Long
    
    ' // Get descriptor of executable file
    CopyMemory bItem, ByVal pStoragesTable + ProjectDesc.storageDescriptor.dwSizeOfItem * _
                      ProjectDesc.storageDescriptor.iExecutableIndex, Len(bItem)
    
 
    ' // Alloc memory within top memory addresses
    pFileData = VirtualAlloc(ByVal 0&, bItem.dwSizeOfFile, MEM_TOP_DOWN Or MEM_COMMIT, PAGE_READWRITE)
    If pFileData = 0 Then Exit Function
    
    ' // Copy raw exe file to this memory
    CopyMemory ByVal pFileData, ByVal pFilesTable + bItem.ofstBeginOfData, bItem.dwSizeOfFile
    
    ' // Free decompressed project data
    HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pProjectData
    pProjectData = 0
    
    ' // Run exe from memory
    RunExeFromMemory pFileData, bItem.dwFlags And FF_IGNOREERROR
    
    ' ----------------------------------------------------
    ' // An error occurs
    ' // Clean memory
    
    VirtualFree ByVal pFileData, 0, MEM_RELEASE
    
    ' // If ignore error then success
    If bItem.dwFlags And FF_IGNOREERROR Then RunProcess = True
    
End Function
Эта процедура выделяет память в верхних областях виртуального адресного пространства (поскольку большинство EXE файлов грузятся по довольно низким адресам (обычно 0x00400000). После этого очишается память данных проекта поскольку если EXE файл запустится, то эта память не будет освобождена, затем вызывается функция RunExeFromMemory которая делает следующий шаг в загрузке EXE из памяти. Если по какой-либо причине загрузка EXE файла не состоялась то освобождается выделенная память и управление передается функции Main. Итак, для того чтобы загрузить EXE файл нам нужно освободить память загрузчика, т.е. выгрузить загрузчик. Нам нужно только оставить маленькуий кусочек кода который будет загружать EXE файл и запускать его. Для этого я решил использовать шеллкод, хотя можно использовать и DLL. Шеллкод - это маленький базонезависимый код (код который не ссылается к внешним данным). Но в любом случае нам придется обеспечить доступ к API функциям из шеллкода. Мы не можем вызывать API функции непосредственно из шеллкода поскольку наш главный исполняемый файл будет выгружен и любое обращение к таблице импорта вызовет креш. Второе ограничение - это то что инструкция call использует относительное смещение (это наиболее частый случай). Из этого следует что нам нужно инициализировать некие "трамплины" которые будут перебрасывать нас на API функции. Я решил делать это посредством сплайсинга. Я просто заменяю первые 5 байт функции пусттышки на ассемблерную инструкцию jmp которая ссылается на необходимую API функцию:
Visual Basic
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
' // Run EXE file by memory address
Function RunExeFromMemory( _
                ByVal pExeData As Long, _
                ByVal IgnoreError As Boolean) As Boolean
    Dim Length  As Long:    Dim pCode       As Long
    Dim pszMsg  As Long:    Dim pMsgTable   As Long
    Dim index   As Long:    Dim pCurMsg     As Long
    
    ' // Get size of shellcode
    Length = GetAddr(AddressOf ENDSHELLLOADER) - GetAddr(AddressOf BEGINSHELLLOADER)
    
    ' // Alloc memory within top addresses
    pCode = VirtualAlloc(ByVal 0&, Length, MEM_TOP_DOWN Or MEM_COMMIT, PAGE_EXECUTE_READWRITE)
    
    ' // Copy shellcode to allocated memory
    CopyMemory ByVal pCode, ByVal GetAddr(AddressOf BEGINSHELLLOADER), Length
    
    ' // Initialization of shellcode
    If Not InitShellLoader(pCode) Then GoTo CleanUp
    
    ' // Splice CallLoader function in order to call shellcode
    Splice AddressOf CallLoader, pCode + GetAddr(AddressOf LoadExeFromMemory) - GetAddr(AddressOf BEGINSHELLLOADER)
    
    ' // Check ignore errors
    If Not IgnoreError Then
        
        ' // Alloc memory for messages table
        pMsgTable = VirtualAlloc(ByVal 0&, 1024, MEM_TOP_DOWN Or MEM_COMMIT, PAGE_READWRITE)
        If pMsgTable = 0 Then GoTo CleanUp
        
        ' // Skip pointers
        pCurMsg = pMsgTable + EM_END * 4
        
        For index = 0 To EM_END - 1
        
            ' // Load message string
            pszMsg = GetString(MSG_LOADER_ERROR + index)
            If pszMsg = 0 Then GoTo CleanUp
            
            Length = SysStringLen(pszMsg)
 
            lstrcpyn ByVal pCurMsg, ByVal pszMsg, Length + 1
            
            ' // Store pointer
            CopyMemory ByVal pMsgTable + index * 4, pCurMsg, Len(pCurMsg)
            
            ' // Next message offset
            pCurMsg = pCurMsg + (Length + 1) * 2
            
            SysFreeString pszMsg
            
        Next
        
    End If
    
    ' // Call shellcode
    CallLoader pExeData, pCode, pMsgTable
    
CleanUp:
    
    If pMsgTable Then
        VirtualFree ByVal pMsgTable, 0, MEM_RELEASE
    End If
    
    If pCode Then
        VirtualFree ByVal pCode, 0, MEM_RELEASE
    End If
    
End Function
Как видно из кода он вычисляет размер шеллкода используя разницу между крайними функциями - ENDSHELLLOADER и BEGINSHELLLOADER. Эти функции должны окружать наш шеллкод и иметь разный прототип поскольку VB6 компилятор может объединять идентичные функции. Затем выделяется память для самого шеллкода и он копируется в эту область памяти. После этого вызывается функция InitShellLoader которая сплайсит все функции в шеллкоде:
Visual Basic
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
' // Shellcode initialization
Function InitShellLoader( _
                 ByVal pShellCode As Long) As Boolean
    Dim hLib    As Long:        Dim sName   As Long
    Dim sFunc   As Long:        Dim lpAddr  As Long
    Dim libIdx  As Long:        Dim fncIdx  As Long
    Dim libName As MessagesID:  Dim fncName As MessagesID
    Dim fncSpc  As Long:        Dim splAddr As Long
    
    ' // +----------------------------------------------------------------+
    ' // |                  Fixing of API addresses                       |
    ' // +----------------------------------------------------------------+
    ' // | In order to call api function from shellcode i use splicing of |
    ' // |    our VB functions and redirect call to corresponding api.    |
    ' // |     I did same in the code that injects to other process.      |
    ' // +----------------------------------------------------------------+
    
    splAddr = GetAddr(AddressOf tVirtualAlloc) - GetAddr(AddressOf BEGINSHELLLOADER) + pShellCode
    
    ' // Get size in bytes between stub functions
    fncSpc = GetAddr(AddressOf tVirtualProtect) - GetAddr(AddressOf tVirtualAlloc)
 
    ' // Use 3 library: kernel32, ntdll и user32
    For libIdx = 0 To 2
    
        ' // Get number of imported functions depending on library
        Select Case libIdx
        Case 0: libName = API_LIB_KERNEL32: fncIdx = 13
        Case 1: libName = API_LIB_NTDLL:    fncIdx = 1
        Case 2: libName = API_LIB_USER32:   fncIdx = 1
        End Select
        
        ' // Get library name from resources
        sName = GetString(libName): If sName = 0 Then Exit Function
        
        ' // Get module handle
        hLib = GetModuleHandle(ByVal sName): If hLib = 0 Then Exit Function
        SysFreeString sName
        
        ' // Go thru functions
        Do While fncIdx
        
            libName = libName + 1
            ' // Get function name
            sName = GetString(libName): If sName = 0 Then Exit Function
            
            ' // Because of GetProcAddress works with ANSI string translate it to ANSI
            sFunc = ToAnsi(sName): If sFunc = 0 Then Exit Function
            
            ' // Get function address
            lpAddr = GetProcAddress(hLib, sFunc)
            SysFreeString sName: SysFreeString sFunc
            
            ' // Error
            If lpAddr = 0 Then Exit Function
            
            ' // Splice stub
            Splice splAddr, lpAddr
            
            ' // Next stub
            splAddr = splAddr + fncSpc
            fncIdx = fncIdx - 1
            
        Loop
        
    Next
    
    ' // Modify CallByPointer
    lpAddr = GetAddr(AddressOf CallByPointer) - GetAddr(AddressOf BEGINSHELLLOADER) + pShellCode
    
    ' // pop eax    - 0x58
    ' // pop ecx    - 0x59
    ' // push eax   - 0x50
    ' // jmp ecx    - 0xFFE1
    
    CopyMemory ByVal lpAddr, &HFF505958, 4
    CopyMemory ByVal lpAddr + 4, &HE1, 1
 
    ' // Success
    InitShellLoader = True
    
End Function
 
' // Splice function
Sub Splice( _
            ByVal Func As Long, _
            ByVal NewAddr As Long)
    ' // Set memory permissions
    VirtualProtect ByVal Func, 5, PAGE_EXECUTE_READWRITE, 0
    CopyMemory ByVal Func, &HE9, 1                      ' // JMP
    CopyMemory ByVal Func + 1, NewAddr - Func - 5, 4    ' // Relative address
End Sub
Вначале код вычисляет смещение первого "трамплина" (в нашем случае это функция tVirtualAlloc) относительно начала шеллкода, и вычисляет расстояние (в байтах) между функциями "трамплинами". Когда компилятор VB6 компилирует стандартный модуль он размещает функции в том же порядке в котором они определены в модуле. Необходимое условие - обеспечить уникальное возвращаемое значение для каждой функции. Затем код проходит по всем необходимым библиотекам (kernel32, ntdll, user32 - в этом порядке) и их функциям. Первая запись в ресурсах строк соответствует имени библиотеки за котором идут имена функций в этой библиотеке. Когда строка имени функции из ресурсов получена она транслируется в ANSI формат и вызывается функция GetProcAddress. Затем вызывается функция Splice которая собирает "трамплин" к необходимой функции из шеллкода. В конце модифицируется функция CallByPointer для того чтобы обеспечить прыжок из шеллкода на точку входа EXE файла. Далее функция RunExeFromMemory патчит функцию CallLoader для того чтобы обеспечить вызов шеллкода из загрузчика. После этой операции функция формирует таблицу сообщений об ошибках (если нужно) которая представляет из себя просто набор указателей на стоки сообщений. И наконец вызывается пропатченная CallLoader которая прыгает на функцию шеллкода LoadExeFromMemory которая больше не расположена внутри загрузчика, а находится в верхних адресах АП процесса.

Внутри шеллкода.

Итак, я сделал несколько функций внутри шеллкода:
  • LoadExeFromMemory - стартовая функция шеллкода;
  • GetImageNtHeaders - возвращает структуру IMAGE_NT_HEADERS и ее адрес по базовому адресу;
  • GetDataDirectory - возвращает структуру IMAGE_DATA_DIRECTORY и ее адрес по базовому адресу и каталоговому индексу;
  • EndProcess - показать сообщение об ошибке (если есть такое) и завершить процесс;
  • ProcessSectionsAndHeaders - выделить память под все заголовки (DOS, NT, секции) и все секции. Скопировать данные в секции;
  • ReserveMemory - зарезервировать необходимую память под EXE;
  • ProcessRelocations - настроить адреса иесли EXE был загружен не по базовому адресу;
  • ProcessImportTable - сканировать таблицу импорта EXE файла, загрузить необходимые библиотеки и заполнить таблицу адресов импорта (IAT);
  • SetMemoryPermissions - настроить разрешения памяти для каждой секции;
  • UpdateNewBaseAddress - обновить новый базовый адрес в системных структурах PEB и LDR.
Из-за того что нельзя использовать функцию VarPtr, я сделалпохожую функцию используя функцию lstrcpyn - IntPtr. Итак, функция LoadExeFromMemory извлекает вначале заголовок NT и проверяет архитектуру процессора, является ли PE файл исполняемым и является ли он 32-битным приложением. Если проверка прошла успешно тогда шеллкод выгружает загрузчик из памяти используя функцию ZwUnmapViewOfSection. Если функция выполняется успешно EXE образ загрузчика больше не находится в памяти и занимаемая им память освобождается. Отныне мы не можем напрямую вызывать API функции, теперь мы должны использовать наши "трамплины":
Visual Basic
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
' // Parse exe in memory
Function LoadExeFromMemory( _
                 ByVal pRawData As Long, _
                 ByVal pMyBaseAddress As Long, _
                 ByVal pErrMsgTable As Long) As Boolean
    Dim NtHdr   As IMAGE_NT_HEADERS
    Dim pBase   As Long
    Dim index   As Long
    Dim iError  As ERROR_MESSAGES
    Dim pszMsg  As Long
    
    ' // Get IMAGE_NT_HEADERS
    If GetImageNtHeaders(pRawData, NtHdr) = 0 Then
        iError = EM_UNABLE_TO_GET_NT_HEADERS
        EndProcess pErrMsgTable, iError
        Exit Function
    End If
    
    ' // Check flags
    If NtHdr.FileHeader.Machine <> IMAGE_FILE_MACHINE_I386 Or _
       (NtHdr.FileHeader.Characteristics And IMAGE_FILE_EXECUTABLE_IMAGE) = 0 Or _
       (NtHdr.FileHeader.Characteristics And IMAGE_FILE_32BIT_MACHINE) = 0 Then Exit Function
 
    ' // Release main EXE memory. After that main exe is unloaded from memory.
    ZwUnmapViewOfSection GetCurrentProcess(), GetModuleHandle(ByVal 0&)
 
    ' // Reserve memory for EXE
    iError = ReserveMemory(pRawData, pBase)
    If iError Then
        EndProcess pErrMsgTable, iError
        Exit Function
    End If
    
    ' // Place data
    iError = ProcessSectionsAndHeaders(pRawData, pBase)
    If iError Then
        EndProcess pErrMsgTable, iError
        Exit Function
    End If
    
    ' // Update new base address
    iError = UpdateNewBaseAddress(pBase)
    If iError Then
        EndProcess pErrMsgTable, iError
        Exit Function
    End If
    
    ' // Import table processing
    iError = ProcessImportTable(pBase)
    If iError Then
        EndProcess pErrMsgTable, iError
        Exit Function
    End If
    
    ' // Relocations processing
    iError = ProcessRelocations(pBase)
    If iError Then
        EndProcess pErrMsgTable, iError
        Exit Function
    End If
    
    ' // Set the memory attributes
    iError = SetMemoryPermissions(pBase)
    If iError Then
        EndProcess pErrMsgTable, iError
        Exit Function
    End If
    
    ' // Release error message table
    If pErrMsgTable Then
        tVirtualFree pErrMsgTable, 0, MEM_RELEASE
    End If
    
    ' // Call entry point
    CallByPointer NtHdr.OptionalHeader.AddressOfEntryPoint + pBase
    
    ' // End process
    EndProcess
    
End Function
Затем шеллкод вызывает функцию ReserveMemory показанную ниже. Эта функция извлекает заголовок NT из загружаемого EXE и пытается зарезервировать регион памяти по адресу указанному в поле ImageBase размера SizeOfmage. Если регион по какой-то причине не был выделен функция проверяет имеет ли EXE файл таблицу релокаций. Если так, тогда функция пытается выделять память по любому адресу. Информация о релокациях позволяет загрузить EXE по любому адресу отличному от ImageBase. Она содержит все места в EXE файле где он использует абсолютную адресацию. Мы можем потом подкорректировать эти адреса используя разницу между реальным базовым адресом и адресом указанным в поле ImageBase:
Visual Basic
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
32
33
34
35
36
37
38
39
40
41
42
43
' // Reserve memory for EXE
Function ReserveMemory( _
                 ByVal pRawExeData As Long, _
                 ByRef pBase As Long) As ERROR_MESSAGES
    Dim NtHdr       As IMAGE_NT_HEADERS
    Dim pLocBase    As Long
    
    If GetImageNtHeaders(pRawExeData, NtHdr) = 0 Then
        ReserveMemory = EM_UNABLE_TO_GET_NT_HEADERS
        Exit Function
    End If
    
    ' // Reserve memory for EXE
    pLocBase = tVirtualAlloc(ByVal NtHdr.OptionalHeader.ImageBase, _
                          NtHdr.OptionalHeader.SizeOfImage, _
                          MEM_RESERVE, PAGE_EXECUTE_READWRITE)
    If pLocBase = 0 Then
        
        ' // If relocation information not found error
        If NtHdr.FileHeader.Characteristics And IMAGE_FILE_RELOCS_STRIPPED Then
        
            ReserveMemory = EM_UNABLE_TO_ALLOCATE_MEMORY
            Exit Function
            
        Else
            ' // Reserve memory in other region
            pLocBase = tVirtualAlloc(ByVal 0&, NtHdr.OptionalHeader.SizeOfImage, _
                                 MEM_RESERVE, PAGE_EXECUTE_READWRITE)
            
            If pLocBase = 0 Then
            
                ReserveMemory = EM_UNABLE_TO_ALLOCATE_MEMORY
                Exit Function
                
            End If
 
        End If
        
    End If
    
    pBase = pLocBase
    
End Function
Если при вызове функции произошла ошибка то показывается сообщение о ней и приложение завершается. В противном случае вызывается функция ProcessSectionsAndHeaders. Эта функция размещает все заголовки в выделенную память, извлекает информацию о всех секциях и копирует все данные в выделенную для них память. Если какая-либо секция имеет неинициализированные данные то этот регион заполняется нулями:
Visual Basic
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
' // Allocate memory for sections and copy them data to there
Function ProcessSectionsAndHeaders( _
                 ByVal pRawExeData As Long, _
                 ByVal pBase As Long) As ERROR_MESSAGES
 
    Dim iSec    As Long
    Dim pNtHdr  As Long
    Dim NtHdr   As IMAGE_NT_HEADERS
    Dim sec     As IMAGE_SECTION_HEADER
    Dim lpSec   As Long
    Dim pData   As Long
    
    pNtHdr = GetImageNtHeaders(pRawExeData, NtHdr)
    If pNtHdr = 0 Then
        ProcessSectionsAndHeaders = EM_UNABLE_TO_GET_NT_HEADERS
        Exit Function
    End If
    
    ' // Alloc memory for headers
    pData = tVirtualAlloc(ByVal pBase, NtHdr.OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE)
    If pData = 0 Then
        ProcessSectionsAndHeaders = EM_UNABLE_TO_ALLOCATE_MEMORY
        Exit Function
    End If
    
    ' // Copy headers
    tCopyMemory pData, pRawExeData, NtHdr.OptionalHeader.SizeOfHeaders
    
    ' // Get address of beginnig of sections headers
    pData = pNtHdr + Len(NtHdr.Signature) + Len(NtHdr.FileHeader) + NtHdr.FileHeader.SizeOfOptionalHeader
    
    ' // Go thru sections
    For iSec = 0 To NtHdr.FileHeader.NumberOfSections - 1
    
        ' // Copy section descriptor
        tCopyMemory IntPtr(sec.SectionName(0)), pData, Len(sec)
        
        ' // Alloc memory for section
        lpSec = tVirtualAlloc(sec.VirtualAddress + pBase, sec.VirtualSize, MEM_COMMIT, PAGE_READWRITE)
        If lpSec = 0 Then
            ProcessSectionsAndHeaders = EM_UNABLE_TO_ALLOCATE_MEMORY
            Exit Function
        End If
        
        ' If there is initialized data
        If sec.SizeOfRawData Then
        
            ' // Take into account  file alignment
            If sec.SizeOfRawData > sec.VirtualSize Then sec.SizeOfRawData = sec.VirtualSize
            
            ' // Copy initialized data to section
            tCopyMemory lpSec, pRawExeData + sec.PointerToRawData, sec.SizeOfRawData
            lpSec = lpSec + sec.SizeOfRawData
            sec.VirtualSize = sec.VirtualSize - sec.SizeOfRawData
            
        End If
 
        ' // Fill remain part with zero
        tFillMemory lpSec, sec.VirtualSize, 0
        
        ' // Next section
        pData = pData + Len(sec)
        
    Next
    
End Function
Затем функция LoadExeFromMemory вызывает функцию UpdateNewBaseAddress которая обновляет новый базовый адрес в user-mode системных структурах. Windows создает специальную структуру называемую PEB (Process Environment Block) для каждого процесса. Это очень полезная структура которая позволяет получить очень много информации о процессе. Множество API функций берут информацию из этой структуры. Для примера GetModuleHandle(NULL) берет возвращаемое значение из PEB.ImageBaseAddress или GetModuleHandle("MyExeName") извлекает информацию из списка загруженных модулей - PEB.Ldr. Нам нужно обновить эту информацию согласно новому базовому адресу для того чтобы API функции возвращали корректное значение. Вот небольшая часть структуры PEB:
Visual Basic
1
2
3
4
5
6
7
8
Type PEB
    NotUsed                         As Long
    Mutant                          As Long
    ImageBaseAddress                As Long
    LoaderData                      As Long ' // Pointer to PEB_LDR_DATA
    ProcessParameters               As Long
    ' // ....
End Type
Нам интересно только поле ImageBaseAddress и LoaderData. Первое поле содержит базовый адрес EXE файла. Второе поле содержит указатель на структуру PEB_LDR_DATA которая описывает все загруженные модули в процессе:
Visual Basic
1
2
3
4
5
6
7
8
Type PEB_LDR_DATA
    Length                          As Long
    Initialized                     As Long
    SsHandle                        As Long
    InLoadOrderModuleList           As LIST_ENTRY
    InMemoryOrderModuleList         As LIST_ENTRY
    InInitializationOrderModuleList As LIST_ENTRY
End Type
Эта структура содержит три двухсвязных списка что описывают каждый модуль. Список InLoadOrderModuleList содержит ссылки на элементы в порядке загрузки, т.е. ссылки в этом списке расположены в порядке загрузки (первый модуль в начале). Список InMemoryOrderModuleList тоже самое только в порядке расположения в памяти, а InInitializationOrderModuleList в порядке инициализации. Нам нужно получить первый элемент списка InLoadOrderModuleList который является указателем на структуру LDR_MODULE:
Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Type LDR_MODULE
    InLoadOrderModuleList           As LIST_ENTRY
    InMemoryOrderModuleList         As LIST_ENTRY
    InInitOrderModuleList           As LIST_ENTRY
    BaseAddress                     As Long
    EntryPoint                      As Long
    SizeOfImage                     As Long
    FullDllName                     As UNICODE_STRING
    BaseDllName                     As UNICODE_STRING
    Flags                           As Long
    LoadCount                       As Integer
    TlsIndex                        As Integer
    HashTableEntry                  As LIST_ENTRY
    TimeDateStamp                   As Long
End Type
Эта структура описывает один модуль. Первый элемент списка InLoadOrderModuleList является описателем главного исполняемого файла. Нам нужно изменить поле BaseAddress на новый базовый адрес и сохранить изменения. Итак, для того чтобы получить адрес структуры PEB мы можем использовать функцию NtQueryInformationProcess которая извлекает множество полезной информации о процессе (узнать подробнее можно в книге 'Windows NT/2000 Native API Reference' by Gary Nebbett). Структура PEB может быть получена из структуры PROCESS_BASIC_INFORMATION которая описывает базовую информацию о процессе:
Visual Basic
1
2
3
4
5
6
7
8
Type PROCESS_BASIC_INFORMATION
    ExitStatus                      As Long
    PebBaseAddress                  As Long
    AffinityMask                    As Long
    BasePriority                    As Long
    UniqueProcessId                 As Long
    InheritedFromUniqueProcessId    As Long
End Type
Поле PebBaseAddress содержит адрес структуры PEB.
Для того чтобы извлечь структуру PROCESS_BASIC_INFORMATION нам нужно передать в качестве параметра класса информации значение ProcessBasicInformation. Поскольку размер структуры может меняться в различных версиях Windows я использую кучу для извлечения структуры PROCESS_BASIC_INFORMATION. Если размер не подходит код увеличивает размер памяти для структуры PROCESS_BASIC_INFORMATION и повторяет заново пока структура не будет извлечена:
Visual Basic
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
Function UpdateNewBaseAddress( _
                 ByVal pBase As Long) As ERROR_MESSAGES
    Dim pPBI    As Long:                        Dim PBIlen  As Long
    Dim PBI     As PROCESS_BASIC_INFORMATION:   Dim cPEB    As PEB
    Dim ntstat  As Long
    Dim ldrData As PEB_LDR_DATA
    Dim ldrMod  As LDR_MODULE
    
    ntstat = tNtQueryInformationProcess(tGetCurrentProcess(), ProcessBasicInformation, IntPtr(PBI.ExitStatus), Len(PBI), PBIlen)
    
    Do While ntstat = STATUS_INFO_LENGTH_MISMATCH
        
        PBIlen = PBIlen * 2
        
        If pPBI Then
            tHeapFree tGetProcessHeap(), HEAP_NO_SERIALIZE, pPBI
        End If
        
        pPBI = tHeapAlloc(tGetProcessHeap(), HEAP_NO_SERIALIZE, PBIlen)
        ntstat = tNtQueryInformationProcess(tGetCurrentProcess(), ProcessBasicInformation, pPBI, PBIlen, PBIlen)
        
    Loop
    
    If ntstat <> STATUS_SUCCESS Then
        UpdateNewBaseAddress = EM_PROCESS_INFORMATION_NOT_FOUND
        GoTo CleanUp
    End If
    
    If pPBI Then
        ' // Copy to PROCESS_BASIC_INFORMATION
        tCopyMemory IntPtr(PBI.ExitStatus), pPBI, Len(PBI)
    End If
 
    ' // Get PEB
    tCopyMemory IntPtr(cPEB.NotUsed), PBI.PebBaseAddress, Len(cPEB)
    
    ' // Modify image base
    cPEB.ImageBaseAddress = pBase
    
    ' // Restore PEB
    tCopyMemory PBI.PebBaseAddress, IntPtr(cPEB.NotUsed), Len(cPEB)
    
    ' // Fix base address in PEB_LDR_DATA list
    tCopyMemory IntPtr(ldrData.Length), cPEB.LoaderData, Len(ldrData)
    
    ' // Get first element
    tCopyMemory IntPtr(ldrMod.InLoadOrderModuleList.Flink), ldrData.InLoadOrderModuleList.Flink, Len(ldrMod)
    
    ' // Fix base
    ldrMod.BaseAddress = pBase
    
    ' // Restore
    tCopyMemory ldrData.InLoadOrderModuleList.Flink, IntPtr(ldrMod.InLoadOrderModuleList.Flink), Len(ldrMod)
    
CleanUp:
    
    ' // Free memory
    If pPBI Then
        tHeapFree tGetProcessHeap(), HEAP_NO_SERIALIZE, pPBI
    End If
    
End Function
После обновления базового адреса в системных структурах шеллкод вызывает функцию ProcessImportTable которая загружает необходимые библиотеки для работы EXE файла. Вначале извлекается директория IMAGE_DIRECTORY_ENTRY_IMPORT которая содержит RVA массива структур IMAGE_IMPORT_DESCRIPTOR:
Visual Basic
1
2
3
4
5
6
7
Type IMAGE_IMPORT_DESCRIPTOR
    Characteristics                 As Long
    TimeDateStamp                   As Long
    ForwarderChain                  As Long
    pName                           As Long
    FirstThunk                      As Long
End Type
Каждая такая структура описывает одну DLL. Поле pName содержит RVA ASCIIZ строки с именем библиотеки. Поле Characteristics содержит RVA таблицы импортируемых функций, а поле FirstThunk содержит RVA таблицы адресов импорта (IAT). Таблица имен представляет из себя массив структур IMAGE_THUNK_DATA. Эта структура представляет из себя 32 битное значение в котором если установлен старший бит остальные биты представляют из себя ординал функции (импорт по ординалу), иначе остальные биты содержат RVA имени функции предваренной значением Hint. Если же структура IMAGE_THUNK_DATA содержит 0 то значит список имен закончен. Если все поля структуры IMAGE_IMPORT_DESCRIPTOR равны 0 это означает что список структур также окончен.
Visual Basic
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
' // Process import table
Function ProcessImportTable( _
                 ByVal pBase As Long) As ERROR_MESSAGES
    Dim NtHdr           As IMAGE_NT_HEADERS:        Dim datDirectory    As IMAGE_DATA_DIRECTORY
    Dim dsc             As IMAGE_IMPORT_DESCRIPTOR: Dim hLib            As Long
    Dim thnk            As Long:                    Dim Addr            As Long
    Dim fnc             As Long:                    Dim pData           As Long
        
    If GetImageNtHeaders(pBase, NtHdr) = 0 Then
        ProcessImportTable = EM_UNABLE_TO_GET_NT_HEADERS
        Exit Function
    End If
    
    ' // Import table processing
    If NtHdr.OptionalHeader.NumberOfRvaAndSizes > 1 Then
        
        If GetDataDirectory(pBase, IMAGE_DIRECTORY_ENTRY_IMPORT, datDirectory) = 0 Then
            ProcessImportTable = EM_INVALID_DATA_DIRECTORY
            Exit Function
        End If
 
        ' // If import table exists
        If datDirectory.Size > 0 And datDirectory.VirtualAddress > 0 Then
        
            ' // Copy import descriptor
            pData = datDirectory.VirtualAddress + pBase
            tCopyMemory IntPtr(dsc.Characteristics), pData, Len(dsc)
            
            ' // Go thru all descriptors
            Do Until dsc.Characteristics = 0 And _
                     dsc.FirstThunk = 0 And _
                     dsc.ForwarderChain = 0 And _
                     dsc.pName = 0 And _
                     dsc.TimeDateStamp = 0
                
                If dsc.pName > 0 Then
                
                    ' // Load needed library
                    hLib = tLoadLibrary(dsc.pName + pBase)
                    
                    If hLib = 0 Then
                        ProcessImportTable = EM_LOADLIBRARY_FAILED
                        Exit Function
                    End If
 
                    If dsc.Characteristics Then fnc = dsc.Characteristics + pBase Else fnc = dsc.FirstThunk + pBase
                    
                    ' // Go to names table
                    tCopyMemory IntPtr(thnk), fnc, 4
                    
                    ' // Go thru names table
                    Do While thnk
                    
                        ' // Check import type
                        If thnk < 0 Then
                            ' // By ordinal
                            Addr = tGetProcAddress(hLib, thnk And &HFFFF&)
                        Else
                            ' // By name
                            Addr = tGetProcAddress(hLib, thnk + 2 + pBase)
                        End If
                        
                        ' // Next function
                        fnc = fnc + 4
                        tCopyMemory IntPtr(thnk), fnc, 4
                        tCopyMemory dsc.FirstThunk + pBase, IntPtr(Addr), 4
                        dsc.FirstThunk = dsc.FirstThunk + 4
                        
                    Loop
                    
                End If
                
                ' // Next descriptor
                pData = pData + Len(dsc)
                tCopyMemory IntPtr(dsc.Characteristics), pData, Len(dsc)
                
            Loop
            
        End If
        
    End If
                 
End Function
Функция ProcessRelocation вызывается после обработки импорта. Эта функция настраивает все абсолютные ссылки (если таковые имеются). Извлекается каталог IMAGE_DIRECTORY_ENTRY_BASERELOC который содержит RVA массива структур IMAGE_BASE_RELOCATION. Каждый элемент этого масива содержит настройки в пределах 4Кб относительно адреса VirtualAddress:
Visual Basic
1
2
3
4
Type IMAGE_BASE_RELOCATION
    VirtualAddress                  As Long
    SizeOfBlock                     As Long
End Type
Поле SizeOfBlock содержит размер элемента в байтах. Массив 16-битных значений дескрипторов расположен после каждой структуры IMAGE_BASE_RELOCATION. Мы можем вычислить количество этих значений по формуле: (SizeOfBlock - Len(IMAGE_BASE_RELOCATION)) \ Len(Integer). Каждый элемент массива дескрипторов имеет следующуюю структуру:

Нажмите на изображение для увеличения
Название: REloc_rus.png
Просмотров: 456
Размер:	8.7 Кб
ID:	3966

Верхние 4 бита содержат тип настройки. Нам интересна только настройка IMAGE_REL_BASED_HIGHLOW которая означает что нам нужно добавить разницу (RealBaseAddress - ImageBaseAddress) к значению Long которое расположено по адресу VirtualAddress + 12 младших бит дескриптора. Массив струкутр IMAGE_BASE_RELOCATION заканчивается структурой где все поля заполнены нулями:
Visual Basic
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
' // Process relocations
Function ProcessRelocations( _
                 ByVal pBase As Long) As ERROR_MESSAGES
    Dim NtHdr           As IMAGE_NT_HEADERS:        Dim datDirectory    As IMAGE_DATA_DIRECTORY
    Dim relBase         As IMAGE_BASE_RELOCATION:   Dim entriesCount    As Long
    Dim relType         As Long:                    Dim dwAddress       As Long
    Dim dwOrig          As Long:                    Dim pRelBase        As Long
    Dim delta           As Long:                    Dim pData           As Long
    
    ' // Check if module has not been loaded to image base value
    If GetImageNtHeaders(pBase, NtHdr) = 0 Then
        ProcessRelocations = EM_UNABLE_TO_GET_NT_HEADERS
        Exit Function
    End If
    
    delta = pBase - NtHdr.OptionalHeader.ImageBase
    
    ' // Process relocations
    If delta Then
        
        ' // Get address of relocation table
        If GetDataDirectory(pBase, IMAGE_DIRECTORY_ENTRY_BASERELOC, datDirectory) = 0 Then
            ProcessRelocations = EM_INVALID_DATA_DIRECTORY
            Exit Function
        End If
        
        If datDirectory.Size > 0 And datDirectory.VirtualAddress > 0 Then
        
            ' // Copy relocation base
            pRelBase = datDirectory.VirtualAddress + pBase
            tCopyMemory IntPtr(relBase.VirtualAddress), pRelBase, Len(relBase)
            
            Do While relBase.VirtualAddress
            
                ' // To first reloc chunk
                pData = pRelBase + Len(relBase)
                
                entriesCount = (relBase.SizeOfBlock - Len(relBase)) \ 2
                
                Do While entriesCount > 0
                    
                    tCopyMemory IntPtr(relType), pData, 2
                    
                    Select Case (relType \ 4096) And &HF
                    Case IMAGE_REL_BASED_HIGHLOW
                        
                        ' // Calculate address
                        dwAddress = relBase.VirtualAddress + (relType And &HFFF&) + pBase
                        
                        ' // Get original address
                        tCopyMemory IntPtr(dwOrig), dwAddress, Len(dwOrig)
                        
                        ' // Add delta
                        dwOrig = dwOrig + delta
                        
                        ' // Save
                        tCopyMemory dwAddress, IntPtr(dwOrig), Len(dwOrig)
                        
                    End Select
                    
                    pData = pData + 2
                    entriesCount = entriesCount - 1
                    
                Loop
                
                ' // Next relocation base
                pRelBase = pRelBase + relBase.SizeOfBlock
                tCopyMemory IntPtr(relBase.VirtualAddress), pRelBase, Len(relBase)
                
            Loop
            
        End If
        
    End If
 
End Function
После настройки релокаций шеллкод вызывает функцию SetMemoryPermissions которая настраивает разрешения памяти согласно полю Characteristics структуры IMAGE_SECTION_HEADER. Для этого просто вызывается функция VirtualProtect с определенными атрибутами памяти:
Visual Basic
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
' // Set memory permissions
Private Function SetMemoryPermissions( _
                 ByVal pBase As Long) As ERROR_MESSAGES
    Dim iSec    As Long:                    Dim pNtHdr  As Long
    Dim NtHdr   As IMAGE_NT_HEADERS:        Dim sec     As IMAGE_SECTION_HEADER
    Dim Attr    As MEMPROTECT:              Dim pSec    As Long
    Dim ret     As Long
    
    pNtHdr = GetImageNtHeaders(pBase, NtHdr)
    If pNtHdr = 0 Then
        SetMemoryPermissions = EM_UNABLE_TO_GET_NT_HEADERS
        Exit Function
    End If
 
    ' // Get address of first section header
    pSec = pNtHdr + 4 + Len(NtHdr.FileHeader) + NtHdr.FileHeader.SizeOfOptionalHeader
    
    ' // Go thru section headers
    For iSec = 0 To NtHdr.FileHeader.NumberOfSections - 1
    
        ' // Copy section descriptor
        tCopyMemory IntPtr(sec.SectionName(0)), pSec, Len(sec)
        
        ' // Get type
        If sec.Characteristics And IMAGE_SCN_MEM_EXECUTE Then
            If sec.Characteristics And IMAGE_SCN_MEM_READ Then
                If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
                    Attr = PAGE_EXECUTE_READWRITE
                Else
                    Attr = PAGE_EXECUTE_READ
                End If
            Else
                If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
                    Attr = PAGE_EXECUTE_WRITECOPY
                Else
                    Attr = PAGE_EXECUTE
                End If
            End If
        Else
            If sec.Characteristics And IMAGE_SCN_MEM_READ Then
                If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
                    Attr = PAGE_READWRITE
                Else
                    Attr = PAGE_READONLY
                End If
            Else
                If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
                    Attr = PAGE_WRITECOPY
                Else
                    Attr = PAGE_NOACCESS
                End If
            End If
        End If
        
        ' // Set memory permissions
        If tVirtualProtect(sec.VirtualAddress + pBase, sec.VirtualSize, Attr, IntPtr(ret)) = 0 Then
            SetMemoryPermissions = EM_UNABLE_TO_PROTECT_MEMORY
            Exit Function
        End If
        
        ' // Next section
        pSec = pSec + Len(sec)
        
    Next
    
End Function
В конце концов очищается таблица сообщений об ошибках (если нужно) и вызывается точка входа загруженного EXE. В предыдущей версии загрузчика я выгружал шеллкод тоже, но некоторые EXE не вызывают ExitProcess следовательно это могло вызывать креши. Загрузчик готов.
Хотя мы написал загрузчик без использвания рантайма, компилятор VB6 добавляет его все-равно поскольку все OBJ файлы имеют ссылки на MSVBVM60 во время компиляции. Нам придется удалить рантайм из таблицы импорта загрузчика вручную. Для этого я сделал специальную утилиту - Patcher которая ищет рантайм в таблице импорта и таблице связанного импорта и удаляет его оттуда. Эта утилита также была полезна для драйвера режима ядра. Я не буду описывать ее работу поскольку она использует те же концепции PE-формата что я уже описал здесь. В общем и целом мы сделали рабочий EXE который не использует MSVBVM60 на целевой машине.
Для того чтобы использовать загрузчик нужно скомпилировать его затем с помощью патчера пропатчить его. После этог можно использовать его в компиляторе.

Я надеюсь вам понравилось. Спасибо за внимание!
С уважением,
Кривоус Анатолий (The trick).
Вложения
Тип файла: zip VBLoader.zip (176.4 Кб, 166 просмотров)
Размещено в Без категории
Показов 3414 Комментарии 0
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2021, vBulletin Solutions, Inc.