etch-a-sketch
- Category: rev
“I drew you a picture, hope you like it <3”
The picture the binary actually prints is a solid rectangle of #. Nothing is hidden or encrypted — the drawing is right there in .rodata, it is just being rendered with a brush so fat that every stroke smears into its neighbours.
Solution:
1. A pen plotter with no input
The binary is small, unstripped, and reads nothing:
$ nm -C etchasketch | grep -vE ' (U|w) '
0000000000004020 D brush_r
0000000000004060 b canvas
0000000000001149 t dab
00000000000011e5 t line
00000000000012df T main
0000000000002020 r points
main walks points, calls line between consecutive entries, then prints canvas as #/space. The canvas bounds fall out of the loop conditions — 0x77 and 0x51, so 120x82:
1186: cmp DWORD PTR [rbp-0x10],0x0 ; px >= 0
118c: cmp DWORD PTR [rbp-0x10],0x77 ; px <= 119
1192: cmp DWORD PTR [rbp-0x14],0x0 ; py >= 0
1198: cmp DWORD PTR [rbp-0x14],0x51 ; py <= 81
11ad: shl rax,0x4 ; py*16
11b1: sub rax,rdx ; - py = py*15
11b4: shl rax,0x3 ; * 8 = py*120
11c6: mov BYTE PTR [rax],0x1 ; canvas[py*120 + px] = 1
2. points is a packed pen path
Each element is one 32-bit word holding two int16, and main splits them with movzx/cwde — low half is x, high half is y:
12fd: mov rax,QWORD PTR [rbp-0x8]
1309: lea rax,[rip+0xd10] ; points
1310: mov eax,DWORD PTR [rdx+rax*1] ; one packed point
1316: movzx eax,WORD PTR [rbp-0x18]
131a: cmp ax,0xfffe ; x == -2 -> end of drawing
131e: je 1358
1324: test ax,ax ; x < 0 -> pen up
1327: js 1341
134c: cmp QWORD PTR [rbp-0x8],0x144 ; at most 325 points
So (-1,-1) lifts the pen and (-2,*) terminates. The array is 0x1300 bytes — 1216 points — but the loop caps at index 0x144 and the first -2 sentinel sits at exactly index 324, so only the first drawing is ever used. (There is a second sentinel at 964; the points between them have coordinates like x=31233, i.e. dead junk that would have been clipped away anyway.)
3. The brush is the whole challenge
dab does not set one pixel. It stamps a square of side 2*brush_r+1 centred on the point:
1153: mov eax,DWORD PTR [rip+0x2ec7] ; brush_r
1159: mov DWORD PTR [rbp-0xc],eax
115c: mov eax,DWORD PTR [rbp-0xc]
115f: neg eax ; for (dy = -r; dy <= r; dy++)
1166: mov eax,DWORD PTR [rbp-0xc]
1169: neg eax ; for (dx = -r; dx <= r; dx++)
And brush_r is a plain initialised global:
$ objdump -s -j .data --start-address=0x4020 --stop-address=0x4024 etchasketch
4020 0a000000 ....
r = 10, so every plotted pixel paints 21x21 = 441 cells on a 120x82 canvas. Strokes that are 16 apart cannot help but merge:
#########################################################################
###########################################################################################################
###########################################################################################################
###########################################################################################################
... etc ...
4. Replot at radius 0
Nothing else needs touching — same path, same Bresenham, radius 0:
# # # ############# #####
# ## ## # #
# # # # # #
# ## # # # #
# # # # # #
# ## # # #
# # # # #
### # # ##
# # # ## # ### ### # #
### # # ## ## # # # # #
# # # # ## # # # # #
# ## # # # # # # # #
# # # # # # # # # #
# ## # # # # # # # #
# # # # # # # # # #
# ## # # # # # # # #
# # ############# # ##### # # # #
#
#
#
### #############
#
#
# ### ### ########### ########### ############# ######### # #######
## # # # # # # # # # # #
# # # # # # # # # # #
# # # # # # # # ##
# # # ########### ######### # ############# #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # ########### ########### ##### ########### #
########### ##### ######### ########### ######### ##
# # # # # # # # ##
# # # # # # # # ##
# # # # # # # # #
# # # ############# # ############# #
# # # # # # #
# # # # # # #
# # # # # # #
########### ######### ########### ########### ########### #####
#
#
#
Three rows of hand-drawn glyphs on a 16-pixel grid: K 1 7 { m y _ / m a s t e r / p i e c e }.
Full script: solve.py
Flag: K17{my_masterpiece}