Add ARM execve chain support and a ret2libc spawn_shell generator (x86/x86_64/ARM)#199
Open
robbiegal wants to merge 1 commit into
Open
Add ARM execve chain support and a ret2libc spawn_shell generator (x86/x86_64/ARM)#199robbiegal wants to merge 1 commit into
robbiegal wants to merge 1 commit into
Conversation
ARM --chain execve: ArchitectureArm previously raised "does not have
support for execve chain generation". This adds ARM gadget categories
(pop/ldm pop-pc LOAD_REG, WRITE_MEM, LOAD_MEM, SYSCALL) and the svc 0
SYS ending, plus a generator that discovers terminal pop-pc / ldm
dispatch gadgets, fills r0/r1/r2/r7 with a re-loading-aware register
cover planner, writes the command into .data, and emits clearly-labelled
placeholders when a gadget (svc 0, an r0 pop, ...) is missing.
New --chain spawn_shell for x86, x86_64 and ARM: a ret2libc
system("/bin/sh") generator emitting the correct per-arch calling
convention -- x86 cdecl (stack arg), x86_64 System V (rdi + a movaps
stack-alignment ret), ARM AAPCS (r0 + pc dispatch). Options: cmd
(default /bin/sh, any path), address (libc system), string (&cmd),
align (x86_64).
system() resolution: address= > a system symbol defined in the binary
(rebased) > verified system@plt > import hint > placeholder. _findPltStub
disassembles .plt/.plt.sec/.plt.got and returns the stub whose indirect
jump provably dereferences system's GOT slot (x86_64 jmp [rip+disp],
x86 jmp [abs], ARM add ip,pc,#imm[,#rot] ; ... ; ldr pc,[ip,#imm]!),
verified against the relocation, so it never emits a silently-wrong
address; x86 PIE jmp [ebx+off] and stripped PLTs fall back to the hint.
"/bin/sh" resolution: string= > an existing copy already in the binary
(no write) > write the string into a writable scratch section, .bss
preferred, via write-what-where gadgets > placeholder. _findWritableSection
reads ELF section headers directly (SHF_WRITE|SHF_ALLOC, not TLS,
sh_size >= needed) so writability and free space are verified natively;
it avoids getSection('.bss') because .bss is SHT_NOBITS (raw is None,
size comes from sh_size).
Correctness fixes:
- The scratch-buffer pointer was double-imageBase-subtracted (pointer !=
written bytes); it now uses the same rebase_N(offset) convention as the
write.
- x86_64 _paddingNeededFor's ^pop (...)$ regex dropped pop r8/pop r9
(2-char regs), under-padding chains; broadened to \w{2,3} (also hardens
execve/mprotect).
- _useBinaryForRebase registers the binary with the gadgets' own
(fileName, section) identity, so a later gadget registration reuses the
entry instead of emitting a duplicate IMAGE_BASE.
- The written string is always NUL-terminated: the shared
_nulTerminateAndPad appends the NUL unconditionally before word-aligning
(ARM previously skipped it for cmd lengths that were a multiple of 4).
Help text (--help epilog in options.py, README.md, interactive ropchain
help in console.py) lists spawn_shell, the .bss write and cmd=, and marks
execve as ARM-capable. Tests: testcases/test_chain_arm.py and
testcases/test_chain_spawn_shell.py; full suite 58/58.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Owner
|
Thank you very much for the PR. I will test it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two additions to the ROP-chain generator:
--chain execve—ArchitectureArmpreviously raised "ArchitectureArm does not have support for execve chain generation at the moment." It now generates a realexecve("/bin/sh", NULL, NULL)chain.--chain spawn_shellfor x86, x86_64 and ARM — a ret2libcsystem("/bin/sh")generator, with verifiedsystem@pltresolution and, when the command string isn't already in the binary, a write-what-where.bssfallback.Both degrade gracefully (clearly-labelled placeholders + log messages) when a binary lacks the required gadgets, consistent with the existing generators.
spawn_shellusageOptions:
cmd(default/bin/sh, any path),address(libcsystem),string(&cmd),align(x86_64 — toggle the stack-alignmentret, default on).Per-architecture layout:
[&system][ret][&"/bin/sh"][pop rdi; ret][&"/bin/sh"][ret][&system]rdi; extraretkeeps the stack 16-byte aligned for glibcmovaps[pop {r0,..,pc}][&"/bin/sh"→r0][pad][&system→pc]r0,systemreached via thepcslotAddress resolution (graceful precedence)
system():address=(absolute, not rebased) → definedsystemsymbol (.symtab/.dynsym, rebased) → verifiedsystem@plt→ import hint + placeholder."/bin/sh":string=(absolute) → existing copy already in the binary (rebased, no write) → writecmdinto a writable scratch section,.bsspreferred (rebased) → placeholder.system@pltis resolved by disassembling.plt/.plt.sec/.plt.gotand returning the stub whose indirect jump provably dereferencessystem's GOT slot (x86_64jmp [rip+disp], x86jmp [abs], ARMadd ip, pc, #imm[,#rot] ; … ; ldr pc, [ip, #imm]!). The match is verified against the relocation target, so a wrong address is never emitted silently; x86 PIEjmp [ebx+off](GOT base unknown at rest) and stripped PLTs fall back to the hint + placeholder.The
.bsswrite is chosen by reading the ELF section headers directly (SHF_WRITE | SHF_ALLOC, non-TLS,sh_size ≥ needed), preferring.bssthen.datathen any other writable section — so writability and free space are verified natively..bssisSHT_NOBITS, so its size is taken fromsh_sizerather than fromgetSection('.bss')(whose.size/.byteswould crash on a NOBITS section). The written string is always NUL-terminated, and the pointer to it is emitted with the samerebase_N(offset)convention as the write so they always coincide.ARM execve support
ArchitectureArmhad no gadget categories (so every ARM gadget was uncategorised) and nosvc 0ending. This PR adds:LOAD_REG(pop {…pc}/ldm sp!, {…pc}),WRITE_MEM,LOAD_MEM,SYSCALL.svc 0SYS ending (LE + BE).RopChainARMExecve) that discovers terminalpop-pc/ldmdispatch gadgets, setsr0/r1/r2/r7with a re-loading-aware register-cover planner, writes the command into.data, and emits TODO placeholders for any missing piece (e.g.svc 0, anr0pop).Files changed
ropper/ropchain/ropchain.py_findSymbolAddress,_findImportGotSlot,_findPltStub(PLT disassembly),_findExistingString,_findWritableSection(native.bss/writable-section selection),_nulTerminateAndPad,_useBinaryForRebase,_rebaseLine, and the ret2libc building blocks_resolveSystemAddress/_resolveBinshPointer.ropper/arch.pyArchitectureArmgadget categories +svc 0SYS ending;ArchitectureArmBEendianness ending.ropper/ropchain/arch/ropchainarm.pyRopChainARMbase,RopChainARMExecve,RopChainARMSpawnShell.ropper/ropchain/arch/ropchainx86.pyRopChainX86SpawnShell+ arch hooks (_writeCmdToMemory); registered inavailableGenerators.ropper/ropchain/arch/ropchainx86_64.pyRopChainSpawnShellX86_64+ arch hooks;_paddingNeededForregex fix (below).ropper/ropchain/arch/__init__.pyRopChain.__subclasses__()discovery.ropper/console.pyropchainhelp text forspawn_shell(incl.cmd=and the.bsswrite) and ARMexecve.ropper/options.pyspawn_shelladded to the--help"available rop chain generators" list;execvemarked[Linux x86, x86_64, ARM].README.md--helpgenerator list.testcases/test_chain_arm.pytestcases/test_chain_spawn_shell.pyspawn_shelltests across all three arches: PLT resolution ground-truthed by an independent disassembly path,.bssselection, NUL-termination, and pointer/write-address consistency.Fixes made during development
imageBase.getSection().offsetis already image-base-relative, but it was routed through a helper that subtractsimageBaseagain, so the written string and the pointer to it disagreed. The pointer is now emitted with the samerebase_N(offset)convention as the write._paddingNeededFordroppedpop r8/pop r9. The^pop (...)$regex matched exactly three characters, silently omitting padding for the 2-character extended registers and letting the following chain word be consumed. Broadened to\w{2,3}(also hardens the existingexecve/mprotectgenerators).IMAGE_BASE._useBinaryForRebaseregistered the binary with a synthetic section identity that didn't match the gadgets', emitting two identicalIMAGE_BASE/rebase_Nlines for one file. It now reuses the gadgets' own(fileName, section)identity, so a single entry is emitted./bin/cat) was written without a terminator — masked only by a zero-filled.bss. All three writers now share_nulTerminateAndPad, which appends the NUL unconditionally before word-aligning.Tests
spawn_shell's PLT resolution is ground-truthed against the relocation table onls-x86,ls-x86_64andls-arm: every resolved stub dereferences exactly its symbol's GOT slot.