Hi! Witaj na stronie sprintu Pwndbg. Poniżej możesz przeczytać opisy przykładowych rzeczy, które można by dodać lub usprawnić w Pwndbg :).
- Repo projektu: https://github.com/pwndbg/pwndbg/
Zadania mają różną trudność i wymagają różnej wiedzy. Prostsze zadania mogą pomóc w rozeznaniu się w strukturze projektu lub różnych schematach, np. jak wygląda "komenda".
PS: Na samym dole dodałem kilka przykładów róznych API w Pwndbg, które mogą się przydać oraz informacji o samym GDB.
Komenda memfrob
robi XORa danej pamięci z kluczem '*'
.
Powinniśmy to ująć w jej opisie, tak, żeby memfrob --help
lub help memfrob
jasno opisywało co to robi.
- Komendę memfrob możemy znaleźć szukając w projekcie funkcji
memfrob
naturalnie :)
Stare wersje Pwndbg wspierały Pythona 2, ale od pewnej wersji usunęliśmy support dla Pythona 2, gdyż obecnie żadne nowe distro nie buduje GDB z Pythonem 2.
W kodzie Pwndbg pozostały jednak rzeczy, które potrzebne były jedynie w Pythonie 2, a teraz możemy się ich pozbyć.
Na przykład:
- komentarze odnośnie kodowania plików (
-*- coding: utf-8 -*-
itp.) - w sumie to nie związane z Py2, ale niektóre pliki mają też shebang (
#!/usr/bin/env python
), który jest zbędny, gdyż nigdy nie są uruchamiane bezpośrednio
Być może coś jeszcze?
Aarch64 to 64-bitowa architektura ARM (czyli to, co mamy m.in. w telefonach czy Macbookach M1/M2). Na tej architekturze jest dość dużo rejestrów - X0-X30 - i obecnie chyba źle wyświetlamy jeden z nich.
Tu więcej info: pwndbg/pwndbg#1039
To wszystko można przetestować lokalnie na architekturze x64 używając QEMU user emulation. Aby to zrobić musimy:
- zainstalować paczkę z QEMU user (
sudo apt install qemu-user
) - zainstalować kompilator gcc na aarch64, aby cross-kompilować przykładowy program napisany w C do AARCH64 (
sudo apt install gcc-11-aarch64-linux-gnu
) - stworzyć przykładowy program (
echo 'int main() {}' > main.c
) - skompilować go
- wtedy możemy uruchomić ten program pod qemu:
qemu-aarch64 -L <sciezka-do-/lib-aarch64> ./a.out
- dodając flagę
-g 1234
QEMU wystawi gdbserver na danym porcie (tu: 1234) - wtedy możemy w GDB zrobić
target remote :1234
aby podłączyć się do tego gdbservera i debugować emulowany proces
- TL;DR: Trzeba wysłać PR z patchem, który jest w pwndbg/pwndbg#1047
- Oraz pomyśleć, czy nie nazwać tego argumentu inaczej
- Warto zbadać co się stanie jeśli będziemy klikać dalej - czy pokażą się kolejne adresy? Czy powinniśmy renderować poprzednie czy kolejne wartości? Do przedyskutowania
TL;DR: pwndbg/pwndbg#1050
Przydałaby się komenda do "patchowania" kodu, tak, żeby można było w łatwy sposób zmienić kod programu na inny - na przykład usunąć "ifa" z programu, albo zrobić tak, żeby dana funkcja zawsze zwracała zero (lub inną wartość).
Przykładowo, patch 0x1234 nop; nop; nop
(na x86/x64) powinno wpisać pod adres 0x1234
zasemblowane instrukcje nop; nop; nop
, czyli wartość 0x90
trzy razy, bo instrukcja nop
, która jest tak zwanym "no operation", tzn. nic nie robi, odpowiada właśnie bajtowi 0x90 na architekturach x86/x64.
Przydatne rzeczy:
- musimy stworzyć nową komendę w pliku
pwndbg/commands/patch.py
- możemy Do tego zadania przyda się bibliotekapwntools
(którą trzeba dodać jako zależność)
Kod plus minus:
# prawdopodobnie trzeba string arch jakoś zmapować z nazw Pwndbg na nazwy architektur używane przez Pwntools
import pwnlib
arch = pwndbg.arch.current
data = pwnlib.asm.asm(assembly_code, arch=arch)
pwndbg.memory.write(address, data)
Potencjalne rozszerzenia:
- Zamiast pobierać architekturę za każdym razem podczas wykonywania komendy, moglibyśmy ustawiać
pwnlib.context.context.arch = ...
gdy ustawiane jestpwndbg.arch.current
- Można rozszerzyć interfejs tak, aby pozwolić wprowadzać bajty instrukcji heksadecymalnie, zamiast tylko poprzez instrukcje asemblera; na przykład:
patch 0x1234 0x90
- powinno wpisać pod0x1234
bajt0x90
(czyli instrukcjęnop
na x86/x64)patch 0x1234 9090
- powinno wpisać dwie instrukcjenop
- Do zastanowienia się: czy powinniśmy supportować hexy bez prefixu
0x
?
- Do zastanowienia się: czy powinniśmy supportować hexy bez prefixu
- Można zapisywać listę wszystkich dodanych patchy i wtedy:
patch --list
- listowałoby zaaplikowane patchepatch --disable <id...>
- wyłączałoby dane patchepatch --enable <id...>
- włączałoby dane patchepatch --revert <id...>
- pozwołiłoby zrevertować dany patchpatch --revert-all
- cofałoby wszystkie patche
- Powinniśmy pozwalać na export patchy do pliku
- Dodanie testów :)
Potencjalne problemy:
- Co z rebase'owaniem binarki (ASLR/PIE)?
- obecnie Pwndbg ma dość mało testów
- testy na CI są uruchamiane przez
PWNDBG_GITHUB_ACTIONS_TEST_RUN=1 sudo --preserve-env ./tests.sh
i są zaimplementowane w./tests/
Potrzebujemy testów m.in. dla wielu komend:
hexdump ...
- nawigacja po programie -
nextret
,nextsyscall
,stepsyscall
, etc. - wyszukanie wartości w programie:
search ...
- zmieniania uprawnień do pamięci -
mprotect
- listowanie layoutu pamięci -
vmmap
- również dla targetów z QEMU user emulation
- również dla targetów z QEMU kernel
- komend wyświetlających pamięć w ciekawy sposób -
probeleak
,leakfind
- (bardzo trudne) funkcjonalności związane ze stertą (heap) z Pull Requesta, dla różnych wersji glibc i architektur
W skrócie, można by dodać, żeby niektóre komendy, jak na przykład dq
, dw
, dd
, db
pozwalały na zdumpowanie danych w formie kodu, tak, żeby można było łatwo wkleić je np. jako tablicę w C?
- TL;DR: pwndbg/pwndbg#1018
- Przy okazji być może dałoby się zrefaktoryzować te parsery komend dq itd., żeby nie było tyle powtórzen tego samego kodu?
Ktoś zgłosił bug, w którym jak ustawimy rejestr RIP (Instruction Pointer) na niezmapoawny adres, to Pwndbg się crashuje. Trzeba by to sprawdzić, zdebugować i naprawić: pwndbg/pwndbg#1008 (lub zamknąć jeśli to nieaktualne).
Chodzi o to, żeby gdy jesteśmy na instrukcji call printf
wyświetlać ładnie argumenty tego printfa.
- Issue na to jest stworzone w pwndbg/pwndbg#939
- Mamy też PR z poprzedniego sprintu z EuroPython 2022, który zaczął to dodawać: pwndbg/pwndbg#939
- Trzeba sprawdzić, czy obsługuje to wszystkie przypadki, czy nie jest zbugowane, czy się nie crashuje gdy coś się nie zgadza
- No i dodać testy
GDB (GNU Debugger) to konsolowy debugger na Linuxa, który obsługuje debugowanie programów napisanych w przeróżnych językach, na wiele różnych targetów - architektur czy ABI. Można tu np. zobaczyć wewnątrz GDB wykonanie komend:
show arch
show osabi
show language
- warto wiedzieć, że "wybrany" język wpływa na to jakie wyrażenia możemy używać w GDB debugując dany program
GDB pod spodem korzysta pod spodem z wywołania systemowego ptrace
, które pozwala na śledzenie procesów na Linuxie oraz na czytanie i modyfikację ich stanu (pamięci i rejestrów procesora).
Wcześniej wymienione arch
, osabi
czy language
, to właściwie "parametry" GDB. Takich parametrów jest sporo i można je wszystkie wyświetlić komendą show
, a co niektórych zmieniać wartość poprzez komendę set <parametr> <wartosc>
.
# Reading memory
# NOTE: may raise gdb.error(...) if `addr` is not readable or mapped in the target
pwndbg.memory.write(addr, data) # write `data` bytes into memory, e.g. b'asdf'
data = pwndbg.memory.read(addr, count) # read `count` bytes from memory, returns e.g. b'asdf'
# Useful constants
pwndbg.arch.endian # endianess used by the architecture
pwndbg.arch.ptrsize # size of a pointer on a given architecture
pwndbg.arch.ptrmask #
# Getting useful info about process
pwndbg.arch.current # name of target architecture (string)
pwndbg.regs.current # list of target registers
pwndbg.heap.current # currently used heap
pwndbg.vmmap.get() # get list of memory maps mapped into the process
pwndbg.auxv.get() # get ELF Auxiliary Vectors info
pwndbg.file.get(filepath) # download a file from the target's filesystem
pwndbg.symbol.get(address) # fetch text name for a symbol at given address
pwndbg.disasm.get(address, n) # disassebmle N instructions at address
pwndbg.chain.get(addr, limit, offset, ...) # recursively dereference address
pwndbg.string.get(addr, ...) # returns a printable C-string from address