Module: Pwnlib::Util::Fiddling

Included in:
Pwn, Shellcraft::Generators::Helper::Runner
Defined in:
lib/pwnlib/util/fiddling.rb

Overview

Some fiddling methods.

Examples:

Call by specifying full module path.

require 'pwnlib/util/fiddling'
Pwnlib::Util::Fiddling.enhex('217') #=> '323137'

require 'pwn' and have all methods.

require 'pwn'
enhex('217') #=> '323137'

Class Method Summary collapse

Class Method Details

.b64d(s) ⇒ String

Base64-decodes a string.

Examples:

b64d('ZGVzdQ==') #=> 'desu'

Parameters:

  • s (String)

    String to be decoded.

Returns:

  • (String)

    Base64-decoded string.



266
267
268
# File 'lib/pwnlib/util/fiddling.rb', line 266

def b64d(s)
  s.unpack1('m0')
end

.b64e(s) ⇒ String

Base64-encodes a string. Do NOT contains those stupid newline (with RFC 4648).

Examples:

b64e('desu') #=> 'ZGVzdQ=='

Parameters:

  • s (String)

    String to be encoded.

Returns:

  • (String)

    Base64-encoded string.



252
253
254
# File 'lib/pwnlib/util/fiddling.rb', line 252

def b64e(s)
  [s].pack('m0')
end

.bits(s, endian: 'big', zero: 0, one: 1) ⇒ Array

Converts the argument to an array of bits.

Examples:

bits(314) #=> [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0]
bits('orz', zero: '-', one: '+').join #=> '-++-++++-+++--+--++++-+-'
bits(128, endian: 'little') #=> [0, 0, 0, 0, 0, 0, 0, 1]

Parameters:

  • s (String, Integer)

    Input to be converted into bits. If input is integer, output would be padded to byte aligned.

  • endian (String) (defaults to: 'big')

    Endian for conversion. Can be any value accepted by context (See Context::ContextType).

  • zero (defaults to: 0)

    Object representing a 0-bit.

  • one (defaults to: 1)

    Object representing a 1-bit.

Returns:

  • (Array)

    An array consisting of zero and one.



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/pwnlib/util/fiddling.rb', line 138

def bits(s, endian: 'big', zero: 0, one: 1)
  context.local(endian: endian) do
    is_little = context.endian == 'little'
    case s
    when String
      v = +'B*'
      v.downcase! if is_little
      s.unpack1(v).chars.map { |ch| ch == '1' ? one : zero }
    when Integer
      # TODO(Darkpi): What should we do to negative number?
      raise ArgumentError, 's must be non-negative' unless s >= 0

      r = s.to_s(2).chars.map { |ch| ch == '1' ? one : zero }
      r.unshift(zero) until (r.size % 8).zero?
      is_little ? r.reverse : r
    else
      raise ArgumentError, 's must be either String or Integer'
    end
  end
end

.bits_str(s, endian: 'big', zero: 0, one: 1) ⇒ String

Simple wrapper around bits, which converts output to string.

Examples:

bits_str('GG') #=> '0100011101000111'

Parameters:

  • s (String, Integer)

    Input to be converted into bits. If input is integer, output would be padded to byte aligned.

  • endian (String) (defaults to: 'big')

    Endian for conversion. Can be any value accepted by context (See Context::ContextType).

  • zero (defaults to: 0)

    Object representing a 0-bit.

  • one (defaults to: 1)

    Object representing a 1-bit.

Returns:

  • (String)

    The output of bits joined.



168
169
170
# File 'lib/pwnlib/util/fiddling.rb', line 168

def bits_str(s, endian: 'big', zero: 0, one: 1)
  bits(s, endian: endian, zero: zero, one: one).join
end

.bitswap(s) ⇒ String

Reverse the bits of each byte in input string.

Examples:

bitswap('rb') #=> 'NF'

Parameters:

  • s (String)

    Input string.

Returns:

  • (String)

    The string with bits of each byte reversed.



216
217
218
# File 'lib/pwnlib/util/fiddling.rb', line 216

def bitswap(s)
  unbits(bits(s, endian: 'big'), endian: 'little')
end

.bitswap_int(n, bits: nil) ⇒ Integer

Reverse the bits of a number, and returns the result as number.

Examples:

bitswap_int(217, bits: 8) #=> 155

Parameters:

  • n (Integer)
  • bits (Integer) (defaults to: nil)

    The bit length of n, only the lower bits bits of n would be used. Default to context.bits.

Returns:

  • (Integer)

    The number with bits reversed.



233
234
235
236
237
238
239
# File 'lib/pwnlib/util/fiddling.rb', line 233

def bitswap_int(n, bits: nil)
  context.local(bits: bits) do
    bits = context.bits
    n &= (1 << bits) - 1
    bits_str(n, endian: 'little').ljust(bits, '0').to_i(2)
  end
end

.enhex(s) ⇒ String

Hex-encodes a string.

Examples:

enhex('217') #=> '323137'

Parameters:

  • s (String)

    String to be encoded.

Returns:

  • (String)

    Hex-encoded string.



29
30
31
# File 'lib/pwnlib/util/fiddling.rb', line 29

def enhex(s)
  s.unpack1('H*')
end

.hex(n) ⇒ String

Present number in hex format, same as python hex() do.

Examples:

hex(0) #=> '0x0'
hex(-10) #=> '-0xa'
hex(0xfaceb00cdeadbeef) #=> '0xfaceb00cdeadbeef'

Parameters:

  • n (Integer)

    The number.

Returns:

  • (String)

    The hex format string.



59
60
61
# File 'lib/pwnlib/util/fiddling.rb', line 59

def hex(n)
  (n.negative? ? '-' : '') + format('0x%x', n.abs)
end

.unbits(s, endian: 'big') ⇒ String

Reverse of bits and bits_str, convert an array of bits back to string.

Examples:

unbits('0100011101000111') #=> 'GG'
unbits([0, 1, 0, 1, 0, 1, 0, 0]) #=> 'T'
unbits('0100011101000111', endian: 'little') #=> "\xE2\xE2"

Parameters:

  • s (String, Array<String, Integer, Boolean>)

    String or array of bits to be convert back to string. [0, '0', false] represents 0-bit, and [1, '1', true] represents 1-bit.

  • endian (String) (defaults to: 'big')

    Endian for conversion. Can be any value accepted by context (See Context::ContextType).

Returns:

  • (String)

    A string with bits from s.

Raises:

  • (ArgumentError)

    If input contains value not in [0, 1, '0', '1', true, false].



191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/pwnlib/util/fiddling.rb', line 191

def unbits(s, endian: 'big')
  s = s.chars if s.is_a?(String)
  context.local(endian: endian) do
    is_little = context.endian == 'little'
    bytes = s.map do |c|
      case c
      when '1', 1, true then '1'
      when '0', 0, false then '0'
      else raise ArgumentError, "cannot decode value #{c.inspect} into a bit"
      end
    end
    [bytes.join].pack(is_little ? 'b*' : 'B*')
  end
end

.unhex(s) ⇒ String

Hex-decodes a string.

Examples:

unhex('353134') #=> '514'

Parameters:

  • s (String)

    String to be decoded.

Returns:

  • (String)

    Hex-decoded string.



43
44
45
# File 'lib/pwnlib/util/fiddling.rb', line 43

def unhex(s)
  [s].pack('H*')
end

.urldecode(s, ignore_invalid: false) ⇒ String

URL-decodes a string.

Examples:

urldecode('test%20url') #=> 'test url'
urldecode('%qw%er%ty') #=> raise ArgumentError
urldecode('%qw%er%ty', ignore_invalid: true) #=> '%qw%er%ty'

Parameters:

  • s (String)

    String to be decoded.

  • ignore_invalid (Boolean) (defaults to: false)

    Whether invalid encoding should be ignore. If set to true, invalid encoding in input are left intact to output.

Returns:

  • (String)

    URL-decoded string.

Raises:

  • (ArgumentError)

    If ignore_invalid is false, and there are invalid encoding in input.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/pwnlib/util/fiddling.rb', line 95

def urldecode(s, ignore_invalid: false)
  res = +''
  n = 0
  while n < s.size
    if s[n] != '%'
      res << s[n]
      n += 1
    else
      cur = s[n + 1, 2]
      if cur =~ /[0-9a-fA-F]{2}/
        res << cur.to_i(16).chr
        n += 3
      elsif ignore_invalid
        res << '%'
        n += 1
      else
        raise ArgumentError, 'Invalid input to urldecode'
      end
    end
  end
  res
end

.urlencode(s) ⇒ String

URL-encodes a string.

Examples:

urlencode('shikway') #=> '%73%68%69%6b%77%61%79'

Parameters:

  • s (String)

    String to be encoded.

Returns:

  • (String)

    URL-encoded string.



73
74
75
# File 'lib/pwnlib/util/fiddling.rb', line 73

def urlencode(s)
  s.bytes.map { |b| format('%%%02x', b) }.join
end

.xor(s1, s2) ⇒ String

Xor two strings. If two strings have different length, the shorter one will be repeated until has the same length as another one.

Examples:

xor("\xE8\xE1\xF0\xF0\xF9", "\x80")
=> 'happy'

xor("\x80", "\xE8\xE1\xF0\xF0\xF9")
=> 'happy'

xor('plaintext', 'thekey')
=> "\x04\x04\x04\x02\v\r\x11\x10\x11"

xor('217', "\x00" * 10)
=> '2172172172'

Parameters:

  • s1 (String)

    First string.

  • s2 (String)

    Second string.

Returns:

  • (String)

    The xor-ed result.



294
295
296
297
# File 'lib/pwnlib/util/fiddling.rb', line 294

def xor(s1, s2)
  s1, s2 = s2, s1 if s1.size < s2.size
  s1.bytes.zip(''.ljust(s1.size, s2).bytes).map { |a, b| a ^ b }.pack('C*')
end

.xor_pair(data, avoid: "\x00\n") ⇒ (String, String)?

Find two strings that will xor into a given string, while only using a given alphabet.

Examples:

xor_pair("test") #=> ["\x01\x01\x01\x01", 'udru']

Parameters:

  • data (String, Integer)

    The desired string.

  • avoid (String) (defaults to: "\x00\n")

    The list of disallowed characters. Defaults to nulls and newlines.

Returns:

  • ((String, String)?)

    Two strings which will xor to the given string. If no such two strings exist, then nil is returned.



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/pwnlib/util/fiddling.rb', line 311

def xor_pair(data, avoid: "\x00\n")
  data = pack(data) if data.is_a?(Integer)
  alphabet = 256.times.reject { |c| avoid.include?(c.chr) }
  res1 = +''
  res2 = +''
  data.bytes.each do |c1|
    # alphabet.shuffle! if context.randomize
    c2 = alphabet.find { |c| alphabet.include?(c1 ^ c) }
    return nil if c2.nil?

    res1 << c2.chr
    res2 << (c1 ^ c2).chr
  end
  [res1, res2]
end