TL;DR
When exploiting a buffer overflow on ARM Linux using ret2libc and calling system(3), the parameter string passed to system() might be nullified by protections. This guide shows how to identify this issue and fix it by carefully crafting your payload to ensure the correct address is used.
Identifying the Problem
- Observe the Crash: After attempting a ret2libc exploit with
system(), you likely see a crash. Debugging reveals that the pointer passed as an argument tosystem()is NULL (0x00000000). - Check Protections: Use tools like
checksecorpwntools's elf_infoto identify stack canaries, ASLR status and other protections. While not directly causing this issue, these protections impact exploit development. - Disassemble the Target: Examine the assembly code around the vulnerable buffer overflow. Look for how arguments are passed to functions (registers vs. stack). On ARM, arguments are typically passed in registers like
r0,r1,r2, andr3. - Confirm Parameter Nullification: Step through the execution with a debugger (GDB) to see exactly when and why the parameter string becomes NULL. This often happens if there’s an early return or unexpected control flow before the parameter is fully copied onto the stack.
Fixing the Exploit
The core issue is that the address of your shell command (e.g., /bin/sh) isn’t being correctly passed to system(). Here’s how to fix it:
- Find system() Address: Use tools like
objdumporpwntoolsto locate the address ofsystem()in libc.objdump -T /lib/arm-linux-gnueabihf/libc.so.6 | grep ' system$' - Find a Gadget for Register Loading: You need an ARM gadget that allows you to load the address of your shell command string into
r0(the first argument register). Look for gadgets like:pop {pc}– Simple return-to-address gadget.- Gadgets loading from stack or memory into registers.
Use tools like
ROPgadgetto find suitable gadgets.ROPgadget --binary ./vulnerable_program | grep 'pop {pc}' - Craft the Payload: Construct your payload carefully. The general structure will be:
- Overflow buffer to overwrite return address.
- Address of gadget that loads into
r0. - Address of shell command string on stack.
- Address of
system()function.
- Stack Alignment: Ensure your shell command string is correctly aligned on the stack for the gadget to access it properly. This might involve padding the buffer before placing the address.
- Example Payload (Conceptual): Assuming
system()is at 0x7674cdb8 and your shell command string is at 0xbffff7e0, and a pop {pc} gadget is at 0x12345678:payload = b'A'*offset + p32(0x12345678) + # Address of pop {pc} gadget p32(0xbffff7e0) + # Address of /bin/sh string on stack p32(0x7674cdb8) # Address of system() functionNote: This is a simplified example. The exact payload will depend on the target architecture, protections, and available gadgets.
- Debugging: Use GDB to step through your exploit and verify that
r0contains the correct address of your shell command string before callingsystem().break *0x7674cdb8 # Break at system() function info registers # Check r0 value
Additional Considerations
- ASLR: If ASLR is enabled, you’ll need to leak a libc address before calculating the addresses of
system()and other functions. - Stack Canaries: If stack canaries are present, bypass them using techniques like canary leaking or overwriting with known values.

