Hacker News new | past | comments | ask | show | jobs | submit login
How to look at the stack with gdb (jvns.ca)
187 points by yesenadam on May 19, 2021 | hide | past | favorite | 37 comments



Nice write up. Some little tips:

- you’re on 64-bit, so you probably want x/gx (or x/16gx, etc.) to print out 64-bit words (the g means “giant”). That’ll make the addresses on the stack more useful. (x/ax works too, and will attempt to resolve addresses to symbols if possible, at the cost of making the output not aligned in columns).

- the stack overflow is detected by using a canary value, basically a random series of bytes sitting after the string on the stack (your canary for example is 0x00 0x80 0xf7 0x8a 0x8a 0xbb 0x58 0xb6). This gets checked at the end of the function; on a mismatch, the function calls __stack_chk_fail which prints an error and aborts the program. The canary is pretty clever: it starts with a null byte so that it won’t be leaked by normal string functions, and the true canary is stored somewhere else in memory (not th stack) so it can’t be easily leaked or corrupted.


> The canary is pretty clever: it starts with a null byte so that it won’t be leaked by normal string functions

Doesn't that also mean it won't be overwritten by string functions, masking certain bugs? Would it be better to make the nul byte the second one, so that only one byte can be leaked, but certain program bugs that wouldn't otherwise will be exposed?


strcpy and friends don't check for a null byte in the destination to find the end of the buffer, that wouldn't work very well because often you want to copy into a buffer that has been initialized as all zeros. Or copy a new string into a buffer that already has a shorter string, etc.


While that's a better way to do that, if the canary had a null at the beginning it would effectively render string off-by-one errors useless, since now they can't even be exploited to crash a program. Again, this should not be used as an excuse to ignore string off-by-one errors since these errors might be triggered in other architectures where the canary isn't guaranteed to start with a null.


> the true canary is stored somewhere else in memory

Specifically, on x86-64 Linux it's stored at fs:0x28.


I can wrap my head around how memory works, but gdb just feels... hard to use. The commands are esoteric and hard to remember and the syntax can be arcane. Plugins like pwndbg add color highlighting and other new features, but on the whole I wish the UX was better. I guess old GNU tools are always like that.


Pwndbg author here, glad that you like it!

If you think there are any ways we can improve, please don't hesitate to create an Issue on Github for new features or bug fixes.


I don’t know if `pwndbg’ does this (or some other add-on maybe) but something I’ve haven’t seen since macsbug on a classic Mac: when stepping through a disassembly at a conditional branch, the PC line will state whether the branch will be taken and the target address. Saves all that tedious mucking about for the status register.


Not only does it do this, it does all kinds of forward emulation, thanks to Unicorn.

https://github.com/pwndbg/pwndbg/blob/dev/caps/disasm_taken_...

This also works for stepping through e.g. ROP gadgets during exploitation.


Hi! Great plugin, though I haven't explored it very much at all. I hope to do so over the next few weeks as I get started with the basics of binary exploits. Once I have some more experience I'll let you know. I think my issue is more with base GDB, not your project.


> I can wrap my head around how memory works, but gdb just feels... hard to use

I really like it. I find it simple in the same way I find C a simple language. There isn't that many commands that you use frequently and the names are quite intuitive and can be shortened to a single letter if there are no conflicts. Now, on Windows, WinDbg I never really groked. I found it hard to use.


I would not call gdb or for that matter, most GNU tools, as 'simple'.


I've forgotten who this was (someone in the Rust community?), but I recently heard someone describe GNU tools as simple to use and hard to learn. I'm still in the learning phase, but it makes sense to me.


Assembly itself is pretty simple, but hard to use.


That's a good point. However, it's arguably only hard to use to do modern software development which is a complex use case for a low level language. At least in all my time doing C (>10yrs) I can reduce most of my gdb usage to analyzing core dumps by doing a few simple things: print backtraces per thread, moving up/down the stack and printing variable values. The rare situations I've used it for 'stepping' through code, is also just a few commands. The code itself is where the complexity is at that point that gdb itself is the least of my concerns.


i would ;)



I tend to find lldb a lot easier to use for the same reasons. There isnt a 1:1 mapping of gdb commands to lldb but the cheatsheet really helps and it doesn’t take that long to get a hang of it.


I used gdbpeda, which is similar to pwndbg. There is a learning curve. But if you like GDB, check out radare2. It's the GDB of disassemblers :)


Old tools stay around because they are good tools.

See windbg also.


use a GUI if you aren't already. I hated using GDB, vscode has thoroughly changed that.


That's fine until you have to debug over ssh.


If gdbserver is an option, VS Code seems to have support for it.


I do debug over ssh :)

the vscode remote ssh extension has you covered.


Fairly basic, but still fun. Julia Evans has the best explanations of systems level stuff often shared in a comic strip form. Plus the article has a reference to Nightmare which is a fun binary exploitation ctf game/course that most programmers should probably have a go at playing. https://github.com/guyinatuxedo/nightmare



The most useful gdb trick I know is `gdb -tui`. Having a UI makes stuff so much more discoverable.


You forgot to tell about 'ref'(refresh) that you have to do very often while using gdb -tui (each time an output "corrupt" the terminal) That said, yes, TUI is very useful indeed.


On a tangent, I've been building a software stack from scratch, and I recently learned how to print out a call stack in machine code: https://wiki.osdev.org/Stack_Trace


Woah, that is fascinating. Hmm that whole wiki looks fascinating. Thank you!


Hmmh, the article recommends -O0, while you're better off using -Og with gcc "because some compiler passes that collect debug information are disabled at -O0."


I'm curious what OS and version of GCC she used for this writeup - I tried to follow along, but I got a seg fault when I tried to overflow the stack on Debian Buster using GCC 8.3.


Now I know what is meant by "full stack developer"..


as opposed to a stack overflow developer? :)


best of both worlds: a Full Stack Overflow Developer.


Now I am imagining a full 400 ft Starship stack overflowing with LOX and liquid methane.

Debugging such condition would be ... unsafe.


>gdb -tui (master)$ gdb -tui C:\Strawberry\c\bin\gdborig.exe: TUI mode is not supported

ja ja.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: