Module: Pwnlib::Asm

Included in:
Pwn
Defined in:
lib/pwnlib/asm.rb

Overview

Convert assembly code to machine code and vice versa. Use two open-source projects keystone/capstone to asm/disasm.

Constant Summary collapse

DEFAULT_VMA =

Default virtaul memory base address of architectures.

This address may be different by using different linker.

{
  i386: 0x08048000,
  amd64: 0x400000,
  arm: 0x8000
}.freeze
ARCH_EM =

Mapping context.arch to ::ELFTools::Constants::EM::EM_*.

{
  aarch64: 'AARCH64',
  alpha: 'ALPHA',
  amd64: 'X86_64',
  arm: 'ARM',
  cris: 'CRIS',
  i386: '386',
  ia64: 'IA_64',
  m68k: '68K',
  mips64: 'MIPS',
  mips: 'MIPS',
  powerpc64: 'PPC64',
  powerpc: 'PPC',
  s390: 'S390',
  sparc64: 'SPARCV9',
  sparc: 'SPARC'
}.freeze

Class Method Summary collapse

Class Method Details

.asm(code, vma: 0) ⇒ String

Convert assembly code to machine code.

Examples:

assembly = shellcraft.amd64.linux.sh
context.local(arch: 'amd64') { asm(assembly) }
#=> "jhH\xB8/bin///sPj;XH\x89\xE71\xF6\x99\x0F\x05"

context.local(arch: 'i386') { asm(shellcraft.sh) }
#=> "jhh///sh/binj\vX\x89\xE31\xC9\x99\xCD\x80"

Parameters:

  • code (String)

    The assembly code to be converted.

  • vma (Integer) (defaults to: 0)

    Virtual memory address.

Returns:

  • (String)

    The result.

Raises:

Difference with Python pwntools:

  • Not support asm(‘mov eax, SYS_execve’).



126
127
128
129
# File 'lib/pwnlib/asm.rb', line 126

def asm(code, vma: 0)
  require_message('keystone_engine', install_keystone_guide)
  KeystoneEngine::Ks.new(ks_arch, ks_mode).asm(code, vma)[0]
end

.disasm(data, vma: 0) ⇒ String

Disassembles a bytestring into human readable assembly.

disasm depends on another open-source project - capstone, error will be raised if capstone is not intalled.

Examples:

context.arch = 'i386'
print disasm("\xb8\x5d\x00\x00\x00")
#   0:   b8 5d 00 00 00  mov eax, 0x5d

context.arch = 'amd64'
print disasm("\xb8\x17\x00\x00\x00")
#   0:   b8 17 00 00 00 mov     eax, 0x17
print disasm("jhH\xb8/bin///sPH\x89\xe71\xd21\xf6j;X\x0f\x05", vma: 0x1000)
#  1000:   6a 68                          push    0x68
#  1002:   48 b8 2f 62 69 6e 2f 2f 2f 73  movabs  rax, 0x732f2f2f6e69622f
#  100c:   50                             push    rax
#  100d:   48 89 e7                       mov     rdi, rsp
#  1010:   31 d2                          xor     edx, edx
#  1012:   31 f6                          xor     esi, esi
#  1014:   6a 3b                          push    0x3b
#  1016:   58                             pop     rax
#  1017:   0f 05                          syscall

Parameters:

  • data (String)

    The bytestring.

  • vma (Integer) (defaults to: 0)

    Virtual memory address.

Returns:

  • (String)

    Disassemble result with nice typesetting.

Raises:



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/pwnlib/asm.rb', line 81

def disasm(data, vma: 0)
  require_message('crabstone', install_crabstone_guide) # will raise error if require fail.
  cs = Crabstone::Disassembler.new(cs_arch, cs_mode)
  insts = cs.disasm(data, vma).map do |ins|
    [ins.address, ins.bytes, ins.mnemonic.to_s, ins.op_str.to_s]
  end
  max_dlen = format('%x', insts.last.first).size + 2
  max_hlen = insts.map { |ins| ins[1].size }.max * 3
  max_ilen = insts.map { |ins| ins[2].size }.max
  insts.reduce('') do |s, ins|
    hex_code = ins[1].map { |c| format('%02x', c) }.join(' ')
    inst = if ins[3].empty?
             ins[2]
           else
             format("%-#{max_ilen}s %s", ins[2], ins[3])
           end
    s + format("%#{max_dlen}x:   %-#{max_hlen}s %s\n", ins[0], hex_code, inst)
  end
end

.make_elf(data, vma: nil, to_file: false) {|path| ... } ⇒ String, Object

Builds an ELF file from executable code.

Examples:

bin = make_elf(asm(shellcraft.sh))
bin[0, 4]
#=> "\x7FELF"
path = make_elf(asm(shellcraft.cat('/proc/self/maps')), to_file: true)
puts `#{path}`
# 08048000-08049000 r-xp 00000000 fd:01 27671233                           /tmp/pwn20180129-3411-7klnng.elf
# f77c7000-f77c9000 r--p 00000000 00:00 0                                  [vvar]
# f77c9000-f77cb000 r-xp 00000000 00:00 0                                  [vdso]
# ffda6000-ffdc8000 rwxp 00000000 00:00 0                                  [stack]
# no need 'to_file' parameter if block is given
make_elf(asm(shellcraft.cat('/proc/self/maps'))) do |path|
  puts `#{path}`
  # 08048000-08049000 r-xp 00000000 fd:01 27671233                           /tmp/pwn20180129-3411-7klnng.elf
  # f77c7000-f77c9000 r--p 00000000 00:00 0                                  [vvar]
  # f77c9000-f77cb000 r-xp 00000000 00:00 0                                  [vdso]
  # ffda6000-ffdc8000 rwxp 00000000 00:00 0                                  [stack]
end

Parameters:

  • data (String)

    Assembled code.

  • vma (Integer?) (defaults to: nil)

    The load address for the ELF file. If nil is given, default address will be used. See DEFAULT_VMA.

  • to_file (Boolean) (defaults to: false)

    Returns ELF content or the path to the ELF file. If true is given, the ELF will be saved into a temp file.

Yield Parameters:

  • path (String)

    The path to the created ELF file.

Yield Returns:

  • (Object)

    Whatever you want.

Returns:

  • (String, Object)

    Without block

    • If to_file is false (default), returns the content of ELF.

    • Otherwise, a file is created and the path is returned.

    With block given, an ELF file will be created and its path will be yielded. This method will return what the block returned, and the ELF file will be removed after the block yielded.

Raises:

Difference with Python pwntools:

  • Unlike pwntools-python uses cross-compiler to compile code into ELF, we create ELFs in pure Ruby implementation. Therefore, we have higher flexibility and less binary dependencies.



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/pwnlib/asm.rb', line 183

def make_elf(data, vma: nil, to_file: false)
  to_file ||= block_given?
  vma ||= DEFAULT_VMA[context.arch.to_sym]
  vma &= -0x1000
  # ELF header
  # Program headers
  # <data>
  headers = create_elf_headers(vma)
  ehdr = headers[:elf_header]
  phdr = headers[:program_header]
  entry = ehdr.num_bytes + phdr.num_bytes
  ehdr.e_entry = entry + phdr.p_vaddr
  ehdr.e_phoff = ehdr.num_bytes
  phdr.p_filesz = phdr.p_memsz = entry + data.size
  elf = ehdr.to_binary_s + phdr.to_binary_s + data
  return elf unless to_file

  path = Dir::Tmpname.create(['pwn', '.elf']) do |temp|
    File.open(temp, 'wb', 0o750) { |f| f.write(elf) }
  end
  block_given? ? yield(path).tap { File.unlink(path) } : path
end