We start by connecting to the server:
nc 94.237.58.224 46436
AT28C16 EEPROMs
_____ _____
| \_/ |
A7 [| 1 24 |] VCC
A6 [| 2 23 |] A8
A5 [| 3 22 |] A9
A4 [| 4 21 |] !WE
A3 [| 5 20 |] !OE
A2 [| 6 19 |] A10
A1 [| 7 18 |] !CE
A0 [| 8 17 |] I/O7
I/O0 [| 9 16 |] I/O6
I/O1 [| 10 15 |] I/O5
I/O2 [| 11 14 |] I/O4
GND [| 12 13 |] I/O3
|_____________|
> help
Usage:
method_name(argument)
EEPROM COMMANDS:
set_address_pins(address) Sets the address pins from A10 to A0 to the specified values.
set_ce_pin(volts) Sets the CE (Chip Enable) pin voltage to the specified value.
set_oe_pin(volts) Sets the OE (Output Enable) pin voltage to the specified value.
set_we_pin(volts) Sets the WE (Write Enable) pin voltage to the specified value.
set_io_pins(data) Sets the I/O (Input/Output) pins to the specified data values.
read_byte() Reads a byte from the memory at the current address.
write_byte() Writes the current data to the memory at the current address.
help Displays this help menu.
Examples:
set_ce_pin(3.5)
set_io_pins([0, 5.1, 3, 0, 0, 3.1, 2, 4.2])
>
We are provided with a shell like environment to interact with the EEPROM. We can set the voltage at each pin manually and read/write data from/to the memory.
The first thing we do is to find the datasheet for the AT28C16 EEPROM by googling it.
The datasheet explains wha pins need to be set high or low to read data from a given address. We consider 5V sd the high voltage and 0V as the low voltage.
After reading the first byte with manual commands we receive an empty byte.
set_ce_pin(0)
set_oe_pin(0)
set_we_pin(5)
set_address_pins([0, 0, 0, 0, 0, 0, 0, 0, 0 ,0 ,0])
read_byte()
We then write a python script to automate the process of reading the memory. We use the
import pwn
import sys
def read_address(r: pwn.remote, address: int):
r.recvuntil(">")
# convert adress to binary with 11 bits
address = format(address, '011b')
# set address to 5v
pins = [0] * 11
for i in range(0, 11):
pins[i] = int(address[i]) * 5
address = ", ".join(str(x) for x in pins)
r.sendline(f"set_address_pins([{address}])")
r.recvuntil(">")
r.sendline("read_byte()")
print(r.recvline().decode("utf-8"))
def solve(r: pwn.remote):
# set pins for read
r.recvuntil(b">")
r.sendline(b"set_ce_pin(0)")
r.recvuntil(b">")
r.sendline(b"set_oe_pin(0)")
r.recvuntil(b">")
r.sendline(b"set_we_pin(5)")
# read data
for i in range(0, 2048):
read_address(r, i)
r.close()
def conn():
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} REMOTE remote-ip remote-port")
sys.exit(1)
r = pwn.remote(sys.argv[1], sys.argv[2])
return r
def main():
r = conn()
solve(r)
if __name__ == "__main__":
main()
Running this sscript unfortunately returns us only a lot of empty bytes.
So we are clearly missing something here. After fideling around with the pins for a while we try to read the datasheet again to maybe find something that could help us.
We find the following in the datasheet:
DEVICE IDENTIFICATION: An extra 32 bytes of EEPROM
memory are available to the user for device identification.
By raising A9 to 12V (± 0.5V) and using address locations
7E0H to 7FFH the additional bytes may be written to or
read from in the same manner as the regular memory
array.
So we modify our script to read from DEVICE IDENTIFICATION and get the flag:
import pwn
import sys
def read_information(r: pwn.remote, address: int):
r.recvuntil(">")
# convert adress to binary with 11 bits
address = format(address, '011b')
# set address to 5v
pins = [0] * 11
for i in range(0, 11):
pins[i] = int(address[i]) * 5
# set A9 to 12v
pins[1] = 12
address = ", ".join(str(x) for x in pins)
r.sendline(f"set_address_pins([{address}])")
r.recvuntil(">")
r.sendline("read_byte()")
line = r.recvline().decode("utf-8")
line = line.split("Read 0x")[1]
line = line.split(" ")[0]
# hex to ascii
print(bytes.fromhex(line).decode("utf-8"), end="")
def solve(r: pwn.remote):
# set pins for read
r.recvuntil(b">")
r.sendline(b"set_ce_pin(0)")
r.recvuntil(b">")
r.sendline(b"set_oe_pin(0)")
r.recvuntil(b">")
r.sendline(b"set_we_pin(5)")
# read data
for i in range(0x7E0, (0x7FF + 1)):
read_information(r, i)
r.close()
def conn():
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} REMOTE remote-ip remote-port")
sys.exit(1)
r = pwn.remote(sys.argv[1], sys.argv[2])
return r
def main():
r = conn()
solve(r)
if __name__ == "__main__":
main()
Running this script returns us the flag:
python exploit.py
HTB{AT28C16_EEPROM_s3c23t_1d!!!}