Gallopsled/pwntools

Wrong assembly generated by shellcraft.execve('ecx', [], 0)

Closed this issue · 1 comments

Output:

    /* execve(path='ecx', argv=[], envp=0) */
    /* push argument array [] */
    /* push '\x00' */
    push 1
    dec byte ptr [esp]
    xor ecx, ecx
    push ecx /* null terminate */
    mov ecx, esp

    mov ebx, ecx // <- ecx has be polluted
    xor edx, edx
    /* call execve() */
    push SYS_execve /* 0xb */
    pop eax
    int 0x80

Because shellcraft.execve('ecx', [], 0) will call push_array first, which modifies ecx.

Same situation occurs when architecture is amd64, which pollutes rsi during pushing the empty array.

This is a bug, but I don't think it's something that can be easily fixed (and right now we lack sufficient resources to handle e.g. #1189 in a timely manner).

For this to work the way you intend (i.e., have the register value of ecx as the first syscall argument), we'd have to encode a bunch of register dependency logic into the pushstr and pushstr_array. There's not a nice way to do it.

Generally, pwntools tries to just "be magic" and have things work the way you'd expect. This one unfortunately isn't one of those cases, and you have to do a little bit of work.

I'd recommend moving the value manually to ebx, and invoking with execve('ebx', ...).

>>> print shellcraft.mov('ebx', 'ecx') + shellcraft.execve('ebx', [], {})
    mov ebx, ecx
    /* execve(path='ebx', argv=[], envp={}) */
    /* push argument array [] */
    /* push '\x00' */
    push 1
    dec byte ptr [esp]
    xor edx, edx
    push edx /* null terminate */
    mov edx, esp
    /* push argument array [] */
    /* push '\x00' */
    push 1
    dec byte ptr [esp]
    xor ecx, ecx
    push ecx /* null terminate */
    mov ecx, esp
    /* setregs noop */
    /* call execve() */
    push SYS_execve /* 0xb */
    pop eax
    int 0x80

Alternately, you can specify argv and envp such that the offending code is not emitted.

>>> print shellcraft.execve('ecx', 0, 0)
    /* execve(path='ecx', argv=0, envp=0) */
    mov ebx, ecx
    xor ecx, ecx
    xor edx, edx
    /* call execve() */
    push SYS_execve /* 0xb */
    pop eax
    int 0x80

Finally, you can always invoke the syscall manually:

>>> print shellcraft.syscall(constants.SYS_execve, 'ecx', 0, 0)
    /* call execve('ecx', 0, 0) */
    push SYS_execve /* 0xb */
    pop eax
    mov ebx, ecx
    xor ecx, ecx
    cdq /* edx=0 */
    int 0x80

Ultimately this is a bug, but not one that we can fix without major rework, as i386 has a very limited number of registers, and ecx is a volatile register in most ABIs. Hopefully the workarounds I suggested will help.