Введение в PEDA и Pwntools. Часть 2
Продолжим наше знакомство с необходимым инструментарием..
Pwntools
Pwntools – это python библиотека позволяющая эффективно и быстро писать эксплойты. Очень часто её используют в CTF. Сейчас мы разберёмся как с ней работать.
Прежде всего нужно её поставить. Хотя разработчики и пишут, что лучше использовать python3, у меня на 3-ем питоне библиотека не взлетела.
Получилось поставить так:
root@kali:~# apt-get update
root@kali:~# apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev build-essential ipython
root@kali:~# pip2 install --upgrade pip
root@kali:~# pip2 install pwntools
Как использовать Pwntools
Существует три способа использования:
- Интерактивно через python/iPython консоль
- Непосредственно в скрипте python
- Pwntools-утилитой командной строки
Способ 1. Интерактивный через консоль
Часто при написании эксплойта есть необходимость что-то по-побыстрому проверить не создавая полноценный скрипт. Консоль iPython отличный способ изучить Pwntools API:
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/build# ipython
Python 2.7.17 (default, Oct 19 2019, 23:36:22)
Type "copyright", "credits" or "license" for more information.
IPython 5.8.0 -- An enhanced Interactive Python.
? -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help -> Python's own help system.
object? -> Details about 'object', use 'object??' for extra details.
In [1]: from pwn import *
In [2]:
В iPython есть функция автодополнения и встроенная система для поиска ключевых слов в документации. К примеру, если мы хотим посмотреть что делает функция p32, просто используем символ ? :
In [4]: p32?
Signature: p32(*a, **kw)
Docstring:
p32(number, sign, endian, ...) -> str
Packs an 32-bit integer
Arguments:
number (int): Number to convert
endianness (str): Endianness of the converted integer ("little"/"big")
sign (str): Signedness of the converted integer ("unsigned"/"signed")
kwargs (dict): Arguments passed to context.local(), such as
``endian`` or ``signed``.
Returns:
The packed number as a string
File: /usr/local/lib/python2.7/dist-packages/pwnlib/context/__init__.py
Type: function
In [5]: p32(0x41424344)
Out[5]: 'DCBA'
In [6]:
Способ 2. В скрипте python
Для начала, воспользуемся данным шаблоном:
#!/usr/bin/python
from pwn import *
def main():
pass
if __name__ == '__main__':
main()
Попробуем из скрипта вызвать Pwntools API:
#!/usr/bin/python
from pwn import *
def main():
p = process("/bin/sh")
p.interactive()
if __name__ == '__main__':
main()
Запустим скрипт:
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/scripts# python 2_shellsample.py
[+] Starting local process '/bin/sh': pid 39116
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
$
Способ 3. Инструменты командной строки Pwntools
Pwntools устанавливает pwn скрипт в директорию /usr/local/bin. Для получения списка команд, просто запустите pwn -h:
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/scripts# pwn -h
usage: pwn [-h]
{asm,checksec,constgrep,cyclic,debug,disasm,disablenx,elfdiff,elfpatch,errno,hex,phd,pwnstrip,scramble,shellcraft,template,unhex,update}
...
Pwntools Command-line Interface
positional arguments:
{asm,checksec,constgrep,cyclic,debug,disasm,disablenx,elfdiff,elfpatch,errno,hex,phd,pwnstrip,scramble,shellcraft,template,unhex,update}
asm Assemble shellcode into bytes
checksec Check binary security settings
constgrep Looking up constants from header files. Example:
constgrep -c freebsd -m ^PROT_ '3 + 4'
cyclic Cyclic pattern creator/finder
debug Debug a binary in GDB
disasm Disassemble bytes into text format
disablenx Disable NX for an ELF binary
elfdiff Compare two ELF files
elfpatch Patch an ELF file
errno Prints out error messages
hex Hex-encodes data provided on the command line or stdin
phd Pwnlib HexDump
pwnstrip Strip binaries for CTF usage
scramble Shellcode encoder
shellcraft Microwave shellcode -- Easy, fast and delicious
template Generate an exploit template
unhex Decodes hex-encoded data provided on the command line
or via stdin.
update Check for pwntools updates
optional arguments:
-h, --help show this help message and exit
Полная версия документации доступна тут: https://docs.pwntools.com/en/stable/commandline.html
Взаимодействие с исполняемыми файлами
Существует множество векторов атак, давайте сосредоточимся на удалённо запущенных исполняемых файлах, к которым у нас есть доступ по сети.
Для начала, давайте посмотрим, как мы можем взаимодействовать с локальной копией файла, который на вход принимает строку из stdin и возвращает результат в stdout.
Локальная копия исполняемого файла
Попробуем запустить следующий файл:
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/build# ./2_interactive
Welcome to the Super Secure Shell
Password: HelloWorld?
Incorrect password!
Вот исходный код запускаемого файла:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void give_shell() {
system("/bin/sh");
}
int main() {
// Disable buffering on stdin and stdout to make network connections better.
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
char * password = "TheRealPassword";
char user_password[200];
puts("Welcome to the Super Secure Shell");
printf("Password: ");
scanf("%199s", user_password);
if (strcmp(password, user_password) == 0) {
puts("Correct password!");
give_shell();
}
else {
puts("Incorrect password!");
}
}
Главная задача программы – сравнить введёные данные и строку пароля . Если они совпадают, то запускается шелл:
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/build# ./2_interactive
Welcome to the Super Secure Shell
Password: TheRealPassword
Correct password!
# id
uid=0(root) gid=0(root) groups=0(root)
#
Теперь мы осознали, что нужно отправить в качестве входных данных. Давайте напишем простой эксплойт используя Pwntools:
#!/usr/bin/python
from pwn import *
def main():
# Start a local process
p = process("../build/2_interactive")
# Get rid of the prompt
data1 = p.recvrepeat(0.2)
log.info("Got data: %s" % data1)
# Send the password
p.sendline("TheRealPassword")
# Check for success or failure
data2 = p.recvline()
log.info("Got data: %s" % data2)
if "Correct" in data2:
# Hand interaction over to the user if successful
log.success("Success! Enjoy your shell!")
p.interactive()
else:
log.failure("Password was incorrect.")
if __name__ == "__main__":
main()
Потратьте немного времени для того чтобы разобраться, как работает скрипт. Обратите внимание на строку process(“../build/2_interactive”). Она запускает новый процесс и позволяет взаимодействовать с процессом как с сокетом. Давайте запустим скрипт, чтобы убедиться что всё работает:
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/scripts# python 3_interactive.py
[+] Starting local process '../build/2_interactive': pid 39170
[*] Got data: Welcome to the Super Secure Shell
Password:
[*] Got data: Correct password!
[+] Success! Enjoy your shell!
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
$ ls -al
total 36
drwxr-xr-x 2 root root 4096 Jan 5 17:12 .
drwxr-xr-x 6 root root 4096 Jan 5 17:12 ..
-rw-r--r-- 1 root root 98 Jan 5 17:12 1_template.py
-rw-r--r-- 1 root root 136 Jan 5 17:12 2_shellsample.py
-rw-r--r-- 1 root root 628 Jan 5 17:12 3_interactive.py
-rw-r--r-- 1 root root 663 Jan 5 17:12 4_networked.py
-rw-r--r-- 1 root root 412 Jan 5 17:12 5_gdb.py
-rw-r--r-- 1 root root 369 Jan 5 17:12 6_gdbsol.py
-rw-r--r-- 1 root root 363 Jan 5 17:12 7_gdbremote.py
$
[*] Interrupted
[*] Stopped process '../build/2_interactive' (pid 39170)
Моделирование сетевого приложения
Для начала, мы создадим новую скрин сессию, для запуска нашего сервера при помощи утилиты socat:
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/scripts# screen bash
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/scripts#
Теперь запустим сам сервер, порт 1330:
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/scripts# socat TCP4-listen:1330,reuseaddr,fork EXEC:./2_interactive
Пока тут всё. Вернёмся к нашей первой bash сессии при помощи комбинации клавиш: CTRL-A-D.
Для того, чтобы проверить что сервер работает на 1330 порту, мы можем запустить netcat:
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/scripts# nc localhost 1330
Welcome to the Super Secure Shell
Password: Hello
Incorrect password!
Теперь модифицируем первый скрипт, который работал с бинарными файлами. Просто закомментируем строку запуска процесса process(), и допишем remote(“localhost”, 1330):
#!/usr/bin/python
from pwn import *
def main():
# Start a local process
#p = process("../build/2_interactive")
p = remote("localhost", 1330)
# Get rid of the prompt
data1 = p.recvrepeat(0.2)
log.info("Got data: %s" % data1)
# Send the password
p.sendline("TheRealPassword")
# Check for success or failure
data2 = p.recvline()
log.info("Got data: %s" % data2)
if "Correct" in data2:
# Hand interaction over to the user if successful
log.success("Success! Enjoy your shell!")
p.interactive()
else:
log.failure("Password was incorrect.")
if __name__ == "__main__":
main()
Проверяем:
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/scripts# python 4_networked.py
[+] Opening connection to localhost on port 1330: Done
[*] Got data: Welcome to the Super Secure Shell
Password:
[*] Got data: Correct password!
[+] Success! Enjoy your shell!
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
$ ls -al
total 36
drwxr-xr-x 2 root root 4096 Jan 5 17:12 .
drwxr-xr-x 6 root root 4096 Jan 5 17:12 ..
-rw-r--r-- 1 root root 98 Jan 5 17:12 1_template.py
-rw-r--r-- 1 root root 136 Jan 5 17:12 2_shellsample.py
-rw-r--r-- 1 root root 628 Jan 5 17:12 3_interactive.py
-rw-r--r-- 1 root root 663 Jan 5 17:12 4_networked.py
-rw-r--r-- 1 root root 412 Jan 5 17:12 5_gdb.py
-rw-r--r-- 1 root root 369 Jan 5 17:12 6_gdbsol.py
-rw-r--r-- 1 root root 363 Jan 5 17:12 7_gdbremote.py
$
[*] Closed connection to localhost port 1330
Отладка при помощи GDB
Pwntools включает в себя GDB функционал, для его изучения лучше пройтись по офф документации: http://docs.pwntools.com/en/stable/gdb.html
Давайте попробуем:
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/build# ./3_reversing
Name: This is a Name
Token: AAAA
Submitted Name: This is a Name
Submitted Token: 0x41414141
Incorrect credentials.
Чтобы лучше понять что происходит, можно использовать утилиту ltrace:
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/build# apt install ltrace
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/build# ltrace ./3_reversing
__libc_start_main(0x4007b1, 1, 0x7ffcb8734588, 0x4008f0 <unfinished ...>
setvbuf(0x7f38691b7a00, 0, 2, 0) = 0
setvbuf(0x7f38691b8760, 0, 2, 0) = 0
printf("Name: "Name: ) = 6
read(0This is a Name
, "This is a Name\n", 63) = 15
printf("Token: "Token: ) = 7
read(0AAAA
, "AAAA", 4) = 4
printf("Submitted Name: %s\n", "This is a Name\n"Submitted Name: This is a Name
) = 32
printf("Submitted Token: 0x%x\n", 0x41414141Submitted Token: 0x41414141
) = 28
strcmp("Santo & Johnny", "This is a Name\n") = -1
puts("Incorrect credentials."Incorrect credentials.
) = 23
+++ exited (status 0) +++
Используя следующий скрипт, мы можем вывести id процесса перед взаимодействием с ним:
#!/usr/bin/python
from pwn import *
def main():
# Start a new process
p = process("../build/3_reversing")
# Name and Token
name = "Test Name".ljust(63, "\x00")
token = 0x41414141
# Print pid
raw_input(str(p.proc.pid))
# Send name and token
p.send(name)
p.send(p32(token))
# Start an interactive session
p.interactive()
if __name__ == "__main__":
main()
Теперь запустим ещё одну SSH сессию и запустим наш скрипт:
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/scripts# python 5_gdb.py
[+] Starting local process '../build/3_reversing': Done
5652
Вернёмся в предыдущую bash сессию и запустим GDB. Теперь мы можем присоедениться прямо к процессу:
root@kali:~/linux-exploitation-course/lessons/3_intro_to_tools/scripts# gdb
GNU gdb (Debian 8.2.1-2) 8.2.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
gdb-peda$ attach 5652
Attaching to process 5652
Reading symbols from /vagrant/lessons/3_intro_to_tools/build/3_reversing...(no debugging symbols found)...done.
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.23.so...done.
done.
Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/ld-2.23.so...done.
done.
[----------------------------------registers-----------------------------------]
RAX: 0xfffffffffffffe00
RBX: 0x0
RCX: 0x7f2156e17680 (<__read_nocancel+7>: cmp rax,0xfffffffffffff001)
RDX: 0x3f ('?')
RSI: 0x7ffc16545500 --> 0x0
RDI: 0x0
RBP: 0x7ffc16545550 --> 0x4008f0 (<__libc_csu_init>: push r15)
RSP: 0x7ffc165454e8 --> 0x400844 (<main+147>: mov edi,0x40098a)
RIP: 0x7f2156e17680 (<__read_nocancel+7>: cmp rax,0xfffffffffffff001)
R8 : 0x7f2157304700 (0x00007f2157304700)
R9 : 0x6
R10: 0x37b
R11: 0x246
R12: 0x400680 (<_start>: xor ebp,ebp)
R13: 0x7ffc16545630 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7f2156e17677 <read+7>: jne 0x7f2156e17689 <read+25>
0x7f2156e17679 <__read_nocancel>: mov eax,0x0
0x7f2156e1767e <__read_nocancel+5>: syscall
=> 0x7f2156e17680 <__read_nocancel+7>: cmp rax,0xfffffffffffff001
0x7f2156e17686 <__read_nocancel+13>: jae 0x7f2156e176b9 <read+73>
0x7f2156e17688 <__read_nocancel+15>: ret
0x7f2156e17689 <read+25>: sub rsp,0x8
0x7f2156e1768d <read+29>: call 0x7f2156e354e0 <__libc_enable_asynccancel>
[------------------------------------stack-------------------------------------]
0000| 0x7ffc165454e8 --> 0x400844 (<main+147>: mov edi,0x40098a)
0008| 0x7ffc165454f0 --> 0x0
0016| 0x7ffc165454f8 --> 0x4141414100000000 ('')
0024| 0x7ffc16545500 --> 0x0
0032| 0x7ffc16545508 --> 0x0
0040| 0x7ffc16545510 --> 0x0
0048| 0x7ffc16545518 --> 0x0
0056| 0x7ffc16545520 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x00007f2156e17680 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84
84 ../sysdeps/unix/syscall-template.S: No such file or directory.
gdb-peda$
Программа остановлена где то в библиотеке libc. Мы можем установить наши точки останова. Давайте предположим, что мы уже методом реверса определили куда передаются входные данные – в функцию check_creds():
gdb-peda$ disas check_creds
Dump of assembler code for function check_creds:
0x0000000000400776 <+0>: push rbp
0x0000000000400777 <+1>: mov rbp,rsp
0x000000000040077a <+4>: sub rsp,0x10
0x000000000040077e <+8>: mov QWORD PTR [rbp-0x8],rdi
0x0000000000400782 <+12>: mov DWORD PTR [rbp-0xc],esi
0x0000000000400785 <+15>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000400789 <+19>: mov rsi,rax
0x000000000040078c <+22>: mov edi,0x400974
0x0000000000400791 <+27>: call 0x400650 <strcmp@plt>
0x0000000000400796 <+32>: test eax,eax
0x0000000000400798 <+34>: je 0x4007aa <check_creds+52>
0x000000000040079a <+36>: cmp DWORD PTR [rbp-0xc],0xdeadc0de
0x00000000004007a1 <+43>: jne 0x4007aa <check_creds+52>
0x00000000004007a3 <+45>: mov eax,0x0
0x00000000004007a8 <+50>: jmp 0x4007af <check_creds+57>
0x00000000004007aa <+52>: mov eax,0x1
0x00000000004007af <+57>: leave
0x00000000004007b0 <+58>: ret
End of assembler dump.
gdb-peda$
Давайте установим несколько точек останова на функции и пошагово продолжим выполнение. Таким образом мы сможем определить, какие данные нужно подать на вход, чтобы пройти проверку:
gdb-peda$ br check_creds
Breakpoint 1 at 0x40077a
gdb-peda$ c
Continuing.
gdb-peda$
Вернитесь к терминалу где запущен скрипт, нажмите Enter для отправки данных. В терминале где открыт GDB, отладчик должен прервать выполнение в точке останова. Продолжайте отладку чтобы получить нужные значения.