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

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:



107
108
109
110
# File 'lib/pwnlib/asm.rb', line 107

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:



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/pwnlib/asm.rb', line 62

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:



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/pwnlib/asm.rb', line 164

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