Hey, pwntools developer here!

Here’s some additional pro tips you may want to integrate into your blog

payload = str(foo) payload += pack(bar) payload += baz

Can be collapsed down into a single statement with the flat() function.

payload = flat(foo, bar, baz)

Integers are passed to pack, strings are untouched, and everything else gets __pack__() invoked on it.

Additionally, it looks like you’re aligning your payload to a specific boundary. There is another routine, fit() , which handles this automagically and makes the padding a valid cyclic() offset. This way, if you mess up offsets, you will have e.g. "faab" instead of "AAAA" .

pad = cyclic_find("faab") # 120

The way that this works with fit() would look like:

payload = fit({ pad: "BBBB" # Overwriting saved RIP with BBBB })

fit() just calls flat() on everything passed to it, so you can pass arrays of things to be set at a given offset.

payload = fit({ 0: shellcode, # PLACING SHELLCODE IN BEGINNING OF BUFF pad: [mov_rax_15_ret, # SET RAX TO SIGRETURN SYSCALL NUMBER syscall_ret, # CALL SIGRETURN frame, # PLACE FAKE FRAME ON STACK leak] # RETURN2SHELLCODE })

For attaching with GDB, you might want to look at the Pwntools function gdb.attach() and gdb.debug() !

Finally, a small nit: You don’t have to specify kernel= for SigreturnFrame unless the target arch is i386 . Since it’s amd64 , this is unnecessary.

Thanks for the article, and thanks for using Pwntools!