The problem was actually in Python 3 that couldn't output a raw bytes in a straightforward way like in Python 2.
Also, my theory about add esp, 8; pop ebx; ret gadget is correct :) This is an alternative way to build a ROP chain (the garbage value is not referenced by add_sh function, so I can do that without a danger to fall into a segmentation fault, it ...
First off (only because you mentioned "realistic"), this is not exactly a realistic scenario for a 64-bit system. The main reason is that the address space is much much larger, which makes it much more difficult to predict and land within your NOP sled. For a more detailed explanation, see this answer. Also, no-execute-stack (NX) is almost universally ...
If you fix this issue, I believe you will be successful.
gef➤ ropper --search pop
0x0804901e: pop ebx; ret;
0x080491c4: pop edi; pop ebp; ret;
These gadgets don't "mess up" the stack in this case; they clean it up a bit after the overflow so that you are able to return to the next function in the chain. The gadget you picked instead is close,...