Skip to main content

shellcode-me

Solved by: grb

This is what the challenge looks like, there's a binary file attached with it.

Challenge

As usual, lets open up the binary under IDA.

main function

So, the main function here allocates an RWX memory, copy the shellcode into that memory, and executes it. Lets try to extract the shellcode.

EB 3C 5E 89 F3 B1 28 89  F7 4F AC 34 ED 47 AA FE
C9 75 F7 B1 28 89 DE 89 F7 4F AC 34 55 47 AA FE
C9 75 F7 31 C0 40 40 40 40 31 D2 B2 28 31 C9 89
D9 31 DB FE C3 CD 80 31 C0 40 31 DB CD 80 E8 BF
FF FF FF 8E 82 80 9D 84 99 96 86 82 89 88 B2 8E
8C 83 8A 86 8C 83 8A B2 9E 84 80 9D 81 88 B2 8C
87 8C B2 94 8C 8A 88 9E 94 8C 90 00

After we extracted the shellcode, we could use any disassembler you want, I used one from shellstorm.

disassembled

This is the disassembled code. First, the code immediately jump to offset 0x3e, after it jumped, it goes back to the the next instruction after the first jump, which is the pop rsi instruction, this way, the program get the address/pointer of the data that sits next to the call 2 instruction. As you might notice, the instructions after the call 2 instruction doesnt very make any sense, because it actually is an encrypted data. If you might notice, there's 2 chunk of very similar code.

chunk 1

chunk 2

Both of them are essentially a XOR looping operation. In the first chunk, the bytes are XOR-ed with 0xED, and in the second chunk, the bytes are XOR-ed with 0x55. We can try to replicate this operation in python with this code.

# Extracted from the bytes after the "call 2" instruction
enc = bytes([
0x8E,0x82,0x80,0x9D,0x84,0x99,0x96,0x86,0x82,0x89,0x88,0xB2,0x8E,0x8C,0x83,0x8A,
0x86,0x8C,0x83,0x8A,0xB2,0x9E,0x84,0x80,0x9D,0x81,0x88,0xB2,0x8C,0x87,0x8C,0xB2,
0x94,0x8C,0x8A,0x88,0x9E,0x94,0x8C,0x90,0x00
])

blob = bytearray(enc[:-1]) # drop trailing null for processing
# first pass: XOR with 0xED
for i in range(len(blob)):
blob[i] ^= 0xED
# second pass: XOR with 0x55
for i in range(len(blob)):
blob[i] ^= 0x55

print(blob.decode())

And when we run the code above...

PO- PO- PO- wait what?

PO- PO- PO- wait... what? Hmmm.... Let's try to only run the first XOR pass.

# Extracted from the bytes after the "call 2" instruction
enc = bytes([
0x8E,0x82,0x80,0x9D,0x84,0x99,0x96,0x86,0x82,0x89,0x88,0xB2,0x8E,0x8C,0x83,0x8A,
0x86,0x8C,0x83,0x8A,0xB2,0x9E,0x84,0x80,0x9D,0x81,0x88,0xB2,0x8C,0x87,0x8C,0xB2,
0x94,0x8C,0x8A,0x88,0x9E,0x94,0x8C,0x90,0x00
])

blob = bytearray(enc[:-1]) # drop trailing null for processing
# first pass: XOR with 0xED
for i in range(len(blob)):
blob[i] ^= 0xED

print(blob.decode())

And when we run the code above...

PWNED!!!

There we go, PO- PO- PO- PWNED!!!