Class: PWN::Plugins::REPL::AIConsole::Keyboard

Inherits:
Object
  • Object
show all
Defined in:
lib/pwn/plugins/repl/ai_console.rb

Overview

Read a duplicated terminal rather than stdin, which belongs to tools.

Constant Summary collapse

KEYS =
{
  "\e[A" => :up, "\e[B" => :down, "\e[C" => :right, "\e[D" => :left,
  "\eOA" => :up, "\eOB" => :down, "\eOC" => :right, "\eOD" => :left,
  "\e[H" => :home, "\e[F" => :end, "\eOH" => :home, "\eOF" => :end,
  "\e[1~" => :home, "\e[4~" => :end, "\e[3~" => :delete,
  "\e[5~" => :page_up, "\e[6~" => :page_down,
  "\e[13;2u" => :newline, "\e[27;2;13~" => :newline, "\e[200~" => :paste_start, "\e[201~" => :paste_end
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(source) ⇒ Keyboard

Returns a new instance of Keyboard.



116
117
118
119
# File 'lib/pwn/plugins/repl/ai_console.rb', line 116

def initialize(source)
  @source = source
  @buffer = +''.b
end

Instance Method Details

#call ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/pwn/plugins/repl/ai_console.rb', line 121

def call
  if @source.wait_readable(@buffer.empty? ? 0.04 : 0)
    chunk = @source.read_nonblock(4096, exception: false)
    return :eof if chunk.nil? && @buffer.empty?

    @buffer << chunk if chunk.is_a?(String)
  end
  return if @buffer.empty?

  match = KEYS.keys.find { |key| @buffer.start_with?(key) }
  return KEYS.fetch(@buffer.slice!(0, match.bytesize)) if match

  cursor = cursor_key
  return cursor unless cursor == :pending
  return if KEYS.keys.any? { |key| key.start_with?(@buffer) } && @source.wait_readable(0.04)

  decode_character
end

#cursor_key ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
# File 'lib/pwn/plugins/repl/ai_console.rb', line 140

def cursor_key
  match = @buffer.match(/\A\e\[([0-9;]*)([ABCDHF])/)
  return :pending unless match
  return :pending if KEYS.keys.any? { |key| key.start_with?(match[0]) && key != match[0] }

  @buffer.slice!(0, match[0].bytesize)
  modifier = match[1].split(';')[1].to_i
  return if modifier > 1

  { 'A' => :up, 'B' => :down, 'C' => :right, 'D' => :left, 'H' => :home, 'F' => :end }[match[2]]
end

#decode_character ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/pwn/plugins/repl/ai_console.rb', line 152

def decode_character
  lead = @buffer.getbyte(0)
  size = case lead
         when 0..127 then 1
         when 194..223 then 2
         when 224..239 then 3
         when 240..244 then 4
         else 0
         end
  prefix = @buffer.bytes.take(size)
  valid = size.positive? && prefix.drop(1).all? { |byte| byte.between?(128, 191) }
  second = prefix[1]
  valid &&= !(second && ((lead == 224 && second < 160) || (lead == 237 && second > 159) ||
                        (lead == 240 && second < 144) || (lead == 244 && second > 143)))
  if valid
    return if @buffer.bytesize < size

    return @buffer.slice!(0, size).force_encoding(Encoding::UTF_8)
  end
  @buffer.slice!(0, 1)
  '�'
end