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 :).
Sprint zaczniemy od małego wstępu "co to jest Pwndbg" i "co mogę z tym zrobić", a następnie podzielimy się na zadania wylistowane poniżej (ofc można dodać również i swoje).
$ echo 'int main() { puts("Hello world"); )}' > a.c
$ gcc a.c
$ gdb --quiet ./a.out
pwndbg> break main
pwndbg> continue
Przydatne komendy:
break <symbol albo adres> - ustawia breakpoint na danym adresie
run [<argumenty>] - uruchamia procesu z danymi argumentami (jeśli nie ustawiliśmy breakpointa, to proces może się skończyć)
starti [<argumenty>] - uruchamia proces i zatrzymuje się na jego pierwszej instrukcji
continue - kontynuuje wykonywanie programu
si - skrót od "step instruction", czyli: wykonaj dokładnie jedną instrukcję (wchodząc również do funkcji które są wywoływane)
ni - skrót od "next instruction", czyli: wykonaj jedną instrukcję, ale jeśli jest ona wywołaniem funkcji (np. instrukcja "call") to wykonaj całe wnętrze i zatrzymaj się dopiero na następnej instrukcji w funkcji w której jesteśmy
info break - wyświetl ustawione breakpointy
delete <numer-breakpointa> - usuwa dany breakpoint; jeśli nie podamy numeru, to usunie wszystkie
print <wyrazenie> - oblicz i wypisz dane wyrażenie
x/10i $rip - komenda GDB "examine", posiadająca różne formaty; tu: wyświetlająca 10 instrukcji rozpoczynając od adresu z rejestru RIP (IP == Instruction Pointer), który wskazuje na instrukcję którą program będzie właśnie wykonywać
Polecam również GDB cheatsheet.
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.
Taski / kto co robi (poniżej jest dłuższy opis tasków):
memfrob
– artcztelescope --reversed
patch <adres> <dane lub instrukcje>
memfrob
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.
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:
-*- coding: utf-8 -*-
itp.)#!/usr/bin/env python
), który jest zbędny, gdyż nigdy nie są uruchamiane bezpośrednioByć 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: https://github.com/pwndbg/pwndbg/issues/1039
To wszystko można przetestować lokalnie na architekturze x64 używając QEMU user emulation. Aby to zrobić musimy:
sudo apt install qemu-user
)sudo apt install gcc-11-aarch64-linux-gnu
)echo 'int main() {}' > main.c
)qemu-aarch64 -L <sciezka-do-/lib-aarch64> ./a.out
-g 1234
QEMU wystawi gdbserver na danym porcie (tu: 1234)target remote :1234
aby podłączyć się do tego gdbservera i debugować emulowany procestelescope --reversed
TL;DR: https://github.com/pwndbg/pwndbg/issues/1050
patch <adres> <dane lub instrukcje>
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:
pwndbg/commands/patch.py
- możemypwntools
(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:
pwnlib.context.context.arch = ...
gdy ustawiane jest pwndbg.arch.current
patch 0x1234 0x90
- powinno wpisać pod 0x1234
bajt 0x90
(czyli instrukcję nop
na x86/x64)patch 0x1234 9090
- powinno wpisać dwie instrukcje nop
0x
?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 patchePotencjalne problemy:
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 ...
nextret
, nextsyscall
, stepsyscall
, etc.search ...
mprotect
vmmap
probeleak
, leakfind
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?
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ć: https://github.com/pwndbg/pwndbg/issues/1008 (lub zamknąć jeśli to nieaktualne).
Chodzi o to, żeby gdy jesteśmy na instrukcji call printf
wyświetlać ładnie argumenty tego printfa.
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 programGDB 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