ETCHASKETCH
Challenge
A single stripped ELF binary, etchasketch. Running it draws something to the screen with a thick brush — too thick to actually read. The picture, not the program, is the flag.
Finding the drawing data
The binary itself only does two things: hold a hardcoded array of points, and draw lines/dabs between them with a fixed brush radius. Rather than dealing with the rendering at runtime, the points array can just be read straight out of the ELF's data section with pyelftools.
The array lives at vaddr 0x2020: 325 packed int16 x/y pairs, terminated by a sentinel value of -2 (which starts a new stroke / lifts the pen). The actual drawing routine is a standard line() (Bresenham) plus a circular dab() brush stamped along it — but the real binary uses a brush radius of 10px, which at this point/canvas scale turns the whole picture into an unreadable blob.
Reimplementing dab()/line() in Python with the brush radius forced down to 0 (a single pixel per step, i.e. just the bare line art) turns the blob back into a readable image.
Solution
import struct
from elftools.elf.elffile import ELFFile
from PIL import Image
def load_points(path):
with open(path, 'rb') as f:
elf = ELFFile(f)
for seg in elf.iter_segments():
if seg['p_vaddr'] <= 0x2020 < seg['p_vaddr'] + seg['p_filesz']:
data = seg.data()
off = 0x2020 - seg['p_vaddr']
break
points = []
i = off
while True:
x, y = struct.unpack_from('<hh', data, i)
i += 4
if x == -2 and y == -2:
break
points.append((x, y))
return points
def line(img, x0, y0, x1, y1):
dx, dy = abs(x1 - x0), -abs(y1 - y0)
sx = 1 if x0 < x1 else -1
sy = 1 if y0 < y1 else -1
err = dx + dy
while True:
if 0 <= x0 < img.width and 0 <= y0 < img.height:
img.putpixel((x0, y0), (0, 0, 0))
if x0 == x1 and y0 == y1:
break
e2 = 2 * err
if e2 >= dy:
err += dy; x0 += sx
if e2 <= dx:
err += dx; y0 += sy
def render(path, out):
points = load_points(path)
img = Image.new('RGB', (512, 512), (255, 255, 255))
prev = None
for p in points:
if prev is not None:
line(img, *prev, *p)
prev = p
img.save(out)
if __name__ == "__main__":
import sys
render(sys.argv[1], "flag.png")