Module: Pwnlib::Shellcraft::Generators::Amd64::Common

Extended by:
Helper
Defined in:
lib/pwnlib/shellcraft/generators/amd64/common/common.rb,
lib/pwnlib/shellcraft/generators/amd64/common/mov.rb,
lib/pwnlib/shellcraft/generators/amd64/common/nop.rb,
lib/pwnlib/shellcraft/generators/amd64/common/ret.rb,
lib/pwnlib/shellcraft/generators/amd64/common/popad.rb,
lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb,
lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb,
lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb,
lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb,
lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb

Overview

For non os-related methods.

Instance Method Summary collapse

Methods included from Helper

extended

Instance Method Details

#infloopObject



15
16
17
18
19
# File 'lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb', line 15

def infloop(*args)
  context.local(arch: :amd64) do
    cat X86::Common.infloop(*args)
  end
end

#memcpy(dst, src, n) ⇒ Object

Like memcpy in glibc.

Copy n bytes from src to dst.

Examples:

shellcraft.memcpy('rax', 'rbx', 0x1000)

Parameters:

  • dst (String, Symbol, Integer)

    Destination.

  • src (String, Symbol, Integer)

    Source to be copied.

  • n (Integer)

    The number of bytes to be copied.



25
26
27
28
29
30
# File 'lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb', line 25

def memcpy(dst, src, n)
  cat "/* memcpy(#{pretty(dst)}, #{pretty(src)}, #{pretty(n)}) */"
  cat 'cld'
  cat Common.setregs({ rdi: dst, rsi: src, rcx: n })
  cat 'rep movsb'
end

#mov(dst, src, stack_allowed: true) ⇒ Object

Move src into dst without newlines and null bytes.

Examples:

context.arch = 'amd64'
shellcraft.mov('rdi', 'ax')
#=> "  movzx edi, ax\n"
context.arch = 'amd64'
puts shellcraft.mov('rax', 10)
#   push 9 /* mov eax, '\n' */
#   pop rax
#   inc eax
#=> nil
context.arch = 'amd64'
puts shellcraft.mov('rax', 10, stack_allowed: false)
#   mov eax, 0x1010101
#   xor eax, 0x101010b /* 0xa == 0x1010101 ^ 0x101010b */
#=> nil

Parameters:

  • dst (String, Symbol)

    Register's name.

  • src (String, Symbol, Integer)

    Register's name or immediate value.

  • stack_allowed (Boolean) (defaults to: true)

    If equals to false, generated assembly code would not use stack-related operations. But beware of without stack-related operations the generated code length is longer.

Raises:

  • (ArgumentError)


38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/pwnlib/shellcraft/generators/amd64/common/mov.rb', line 38

def mov(dst, src, stack_allowed: true)
  raise ArgumentError, "#{dst} is not a register" unless register?(dst)

  dst = get_register(dst)
  if register?(src)
    src = get_register(src)
    if dst.size < src.size && !dst.bigger.include?(src.name)
      raise ArgumentError, "cannot mov #{dst}, #{src}: dst is smaller than src"
    end

    # Downgrade our register choice if possible.
    # Opcodes for operating on 32-bit registers are always (?) shorter.
    dst = get_register(dst.native32) if dst.size == 64 && src.size <= 32
  else
    context.local(arch: 'amd64') { src = evaluate(src) }
    raise ArgumentError, format('cannot mov %s, %d: dst is smaller than src', dst, src) unless dst.fits(src)

    orig_dst = dst
    dst = get_register(dst.native32) if dst.size == 64 && bits_required(src) <= 32

    # Calculate the packed version.
    srcp = pack(src & ((1 << dst.size) - 1), bits: dst.size)

    # Calculate the unsigned and signed versions.
    srcu = unpack(srcp, bits: dst.size, signed: false)
    # N.B.: We may have downsized the register for e.g. mov('rax', 0xffffffff)
    #       In this case, srcp is now a 4-byte packed value, which will expand to "-1", which isn't correct.
    srcs = orig_dst.size == dst.size ? unpack(srcp, bits: dst.size, signed: true) : src
  end
  if register?(src)
    if src == dst || dst.bigger.include?(src.name)
      cat "/* moving #{src} into #{dst}, but this is a no-op */"
    elsif dst.size > src.size
      cat "movzx #{dst}, #{src}"
    else
      cat "mov #{dst}, #{src}"
    end
  elsif src.is_a?(Numeric) # Constant or immi
    xor = ->(reg) { "xor #{reg.xor}, #{reg.xor}" }
    if src.zero?
      # Special case for zeroes.
      # XORing the 32-bit register clears the high 32 bits as well.
      cat "xor #{dst}, #{dst} /* #{src} */"
    elsif stack_allowed && [32, 64].include?(dst.size) && src == 10
      cat "push 9 /* mov #{dst}, '\\n' */"
      cat "pop #{dst.native64}"
      cat "inc #{dst}"
    elsif stack_allowed && [32, 64].include?(dst.size) && (-2**7 <= srcs && srcs < 2**7) && okay(srcp[0])
      # It's smaller to PUSH and POP small sign-extended values than to directly move them into various
      # registers.
      #
      # 6aff58           push -1; pop rax
      # 48c7c0ffffffff   mov rax, -1
      cat "push #{pretty(src)}"
      cat "pop #{dst.native64}"
    elsif okay(srcp)
      # Easy case. This implies that the register size and value are the same.
      cat "mov #{dst}, #{pretty(src)}"
    elsif srcu < 2**8 && okay(srcp[0]) && dst.sizes.include?(8) # Move 8-bit value into register.
      cat xor[dst]
      cat "mov #{dst.sizes[8]}, #{pretty(src)}"
    elsif srcu == srcu & 0xff00 && okay(srcp[1]) && dst.ff00
      # Target value is a 16-bit value with no data in the low 8 bits, we can use the 'AH' style register.
      cat xor[dst]
      cat "mov #{dst.ff00}, #{pretty(src)} >> 8"
    elsif srcu < 2**16 && okay(srcp[0, 2]) # Target value is a 16-bit value, use a 16-bit mov.
      cat xor[dst]
      cat "mov #{dst.sizes[16]}, #{pretty(src)}"
    else # All else has failed.  Use some XOR magic to move things around.
      a, b = xor_pair(srcp, avoid: "\x00\n")
      a = hex(unpack(a, bits: dst.size))
      b = hex(unpack(b, bits: dst.size))
      if dst.size != 64
        # There's no XOR REG, IMM64 but we can take the easy route for smaller registers.
        cat "mov #{dst}, #{a}"
        cat "xor #{dst}, #{b} /* #{hex(src)} == #{a} ^ #{b} */"
      elsif stack_allowed
        # However, we can PUSH IMM64 and then perform the XOR that way at the top of the stack.
        cat "mov #{dst}, #{a}"
        cat "push #{dst}"
        cat "mov #{dst}, #{b}"
        cat "xor [rsp], #{dst} /* #{hex(src)} == #{a} ^ #{b} */"
        cat "pop #{dst}"
      else
        raise ArgumentError, "Cannot put #{pretty(src)} into '#{dst}' without using stack."
      end
    end
  end
end

#nopObject

A no-op instruction.



11
12
13
# File 'lib/pwnlib/shellcraft/generators/amd64/common/nop.rb', line 11

def nop
  cat 'nop'
end

#popadObject

Pop all of the registers onto the stack which i386 popad does.



12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/pwnlib/shellcraft/generators/amd64/common/popad.rb', line 12

def popad
  cat <<-EOS
pop rdi
pop rsi
pop rbp
pop rbx /* add rsp, 8 */
pop rbx
pop rdx
pop rcx
pop rax
  EOS
end

#pushstr(str, append_null: true) ⇒ Object

Push a string to stack.

Examples:

context.arch = 'amd64'
puts shellcraft.pushstr('pusheen')
#   /* push "pusheen\x00" */
#   mov rax, 0x101010101010101
#   push rax
#   mov rax, 0x101010101010101 ^ 0x6e656568737570
#   xor [rsp], rax
#=> nil

Parameters:

  • str (String)

    String to be pushed.

  • append_null (Boolean) (defaults to: true)

    If need to append a null byte in the end of str.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb', line 27

def pushstr(str, append_null: true)
  # This will not affect callee's +str+.
  str += "\x00" if append_null && !str.end_with?("\x00")
  return if str.empty?

  padding = str[-1].ord >= 128 ? "\xff" : "\x00"
  cat "/* push #{str.inspect} */"
  group(8, str, underfull_action: :fill, fill_value: padding).reverse_each do |word|
    sign = u64(word, endian: 'little', signed: true)
    sign32 = u32(word[0, 4], bits: 32, endian: 'little', signed: true)
    if [0, 0xa].include?(sign) # simple forbidden byte case
      cat "push #{pretty(sign + 1)}"
      cat 'dec byte ptr [rsp]'
    elsif sign >= -0x80 && sign <= 0x7f && okay(word[0]) # simple byte case
      cat "push #{pretty(sign)}"
    elsif sign >= -0x80000000 && sign <= 0x7fffffff && okay(word[0, 4])
      # simple 32bit without forbidden byte
      cat "push #{pretty(sign)}"
    elsif okay(word)
      cat "mov rax, #{pretty(sign)}"
      cat 'push rax'
    elsif sign32.positive? && word[4, 4] == "\x00" * 4
      # The high 4 byte of word are all zeros, so we can use +xor dword ptr [rsp]+.
      a = u32(xor_pair(word[0, 4]).first, endian: 'little', signed: true)
      cat "push #{pretty(a)} ^ #{pretty(sign)}"
      cat "xor dword ptr [rsp], #{pretty(a)}"
    else
      a = u64(xor_pair(word).first, endian: 'little', signed: false)
      cat "mov rax, #{pretty(a)}"
      cat 'push rax'
      cat "mov rax, #{pretty(a ^ sign)} /* #{pretty(a)} ^ #{pretty(sign)} */"
      cat 'xor [rsp], rax'
    end
  end
end

#pushstr_array(reg, array) ⇒ Object



15
16
17
18
19
# File 'lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb', line 15

def pushstr_array(*args)
  context.local(arch: :amd64) do
    cat X86::Common.pushstr_array(*args)
  end
end

#ret(return_value = nil) ⇒ Object

Instruction return.

Examples:

context.arch = 'amd64'
shellcraft.ret
#=> "  ret"
shellcraft.ret(:rdi)
#=> "  mov rax, rdi\n  ret\n"

Parameters:

  • return_value (String, Symbol, Integer) (defaults to: nil)

    Set the return value. Can be name of a register or an immediate value. nil for not set return value.



25
26
27
28
# File 'lib/pwnlib/shellcraft/generators/amd64/common/ret.rb', line 25

def ret(return_value = nil)
  cat Common.mov('rax', return_value) if return_value
  cat 'ret'
end

#setregs(reg_context, stack_allowed: true) ⇒ Object



15
16
17
18
19
# File 'lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb', line 15

def setregs(*args, **kwargs)
  context.local(arch: :amd64) do
    cat X86::Common.setregs(*args, **kwargs)
  end
end