Aperi’CTF 2019 - Pay2Load
Challenge details
Event | Challenge | Category | Points | Solves |
---|---|---|---|---|
Aperi’CTF 2019 | Pay2Load | Forensic | 175 | 3 |
We’re given a chall data.bin file.
Task description:
Our company was recently targeted by a ransomware campaign.
Despite our deep packet analysis probes, the attacker managed to infiltrate and compromise one of our servers by exploiting a 0day vulnerability.
Fortunately, the forensic team was able to recover a suspicious payload that might have been used during the attack.
Analyze this payload!
Note: the targeted server is running Linux.
TL;DR
The challenge consists in analyzing a file containing an alphanumeric payload. After some analysis, we finally discover that this payload is a shellcode for Linux i386 architecture that prints a flag.
File analysis
At first glance, the file doesn’t seem to be a known file format, but let’s check it anyway:
$ file files/data.bin
files/data.bin: ASCII text, with very long lines, with no line terminators
$ binwalk files/data.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
Data analysis
Let’s analyze the data using Python to see if we find any interesting information about its content:
data = 'PYj0X40PPPPPQaJP50000RX4DjzY0DOPVX500005O000PTY01VX5000B5000pPTYAAA01VX50000500t0PTYAA01VX500005Ow10PTY01A01VX5000B5000pPTYAAA01VX58H7E5NVrXPTY01A01A01VX501015qabzPVX510B05absXPVX5B1015vSXJPVX5004B5cXZuPVX504E05XXvoPVX50B445XqXXPVX5B0005rTUXPVX502005M8XSPVX500BB5aXccPVX500BB500stPTYAAA01VX5000050t10PTYA01VX500005w400PTY01Tx'
occurrences = {}
for char in set(data):
occurrences[char] = data.count(char)
print('Max char value: 0x{0:02x}'.format(max(map(ord, set(data)))))
print('Min char value: 0x{0:02x}'.format(min(map(ord, set(data)))))
print('Number of different chars: {:d}'.format(len(set(data))))
print('Character occurrences:')
for char in sorted(occurrences, key=occurrences.get, reverse=True):
print('{:s}: {:d} ({:f}%)'.format(char, occurrences[char], (occurrences[char] / len(data) * 100)))
Output:
Max char value: 0x7a
Min char value: 0x30
Number of different chars: 41
Character occurrences:
0: 84 (25.846154%)
5: 37 (11.384615%)
X: 32 (9.846154%)
P: 26 (8.000000%)
1: 19 (5.846154%)
V: 19 (5.846154%)
A: 15 (4.615385%)
B: 11 (3.384615%)
...
As shown above, the payload seems to use only printable characters:
Decimal | Hex | Char |
---|---|---|
48 | 0x30 | 0 |
122 | 0x7a | z |
This charset could be used for base64 encoding, but we’re not able to decode the data as is:
>>> binascii.a2b_base64(data)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
binascii.Error: Invalid base64-encoded string: number of data characters (325) cannot be 1 more than a multiple of 4
According to the top 10 character occurrences, the string seems to contain many alphanumeric characters, but let’s analyze the data using binvis.io!
On the left is the entropy visualization - black is the zero entropy, with colors ranging from blue to hot pink for maximum entropy.
On the right is the Hilbert curve visualization that allows us to visualize the usage of common byte classes:
Color | Byte class |
---|---|
█ | 0x00 |
█ | Low |
█ | Ascii |
█ | High |
█ | 0xFF |
The data is only composed of ASCII characters…
Reading the task description again, we’re informed that this string has been used to compromise a server which is running Linux.
If we take some time to think about what type of string can be used to compromise an operating system, we can establish this short list:
- Basic injection payload (e.g., SQLi, XSS, command, etc.)
- Shellcode
File
As described above, the string doesn’t seem to use a known format, and therefore shouldn’t have been used for basic injection such as SQL or command injection.
Let’s check if it’s a valid shellcode!
Shellcode analysis
The well-known Online x86 / x64 Assembler and Disassembler developped by Taylor Hornby can be used to disassemble a shellcode, let’s try it!
First, we need to convert the string to its hexadecimal representation:
print(''.join(['\\x{:02x}'.format(ord(c)) for c in data]))
Output:
\x50\x59\x6a\x30\x58\x34\x30\x50\x50\x50\x50\x50\x51\x61\x4a\x50\x35\x30\x30\x30\x30\x52\x58\x34\x44\x6a\x7a\x59\x30\x44\x4f\x50\x56\x58\x35\x30\x30\x30\x30\x35\x4f\x30\x30\x30\x50\x54\x59\x30\x31\x56\x58\x35\x30\x30\x30\x42\x35\x30\x30\x30\x70\x50\x54\x59\x41\x41\x41\x30\x31\x56\x58\x35\x30\x30\x30\x30\x35\x30\x30\x74\x30\x50\x54\x59\x41\x41\x30\x31\x56\x58\x35\x30\x30\x30\x30\x35\x4f\x77\x31\x30\x50\x54\x59\x30\x31\x41\x30\x31\x56\x58\x35\x30\x30\x30\x42\x35\x30\x30\x30\x70\x50\x54\x59\x41\x41\x41\x30\x31\x56\x58\x35\x38\x48\x37\x45\x35\x4e\x56\x72\x58\x50\x54\x59\x30\x31\x41\x30\x31\x41\x30\x31\x56\x58\x35\x30\x31\x30\x31\x35\x71\x61\x62\x7a\x50\x56\x58\x35\x31\x30\x42\x30\x35\x61\x62\x73\x58\x50\x56\x58\x35\x42\x31\x30\x31\x35\x76\x53\x58\x4a\x50\x56\x58\x35\x30\x30\x34\x42\x35\x63\x58\x5a\x75\x50\x56\x58\x35\x30\x34\x45\x30\x35\x58\x58\x76\x6f\x50\x56\x58\x35\x30\x42\x34\x34\x35\x58\x71\x58\x58\x50\x56\x58\x35\x42\x30\x30\x30\x35\x72\x54\x55\x58\x50\x56\x58\x35\x30\x32\x30\x30\x35\x4d\x38\x58\x53\x50\x56\x58\x35\x30\x30\x42\x42\x35\x61\x58\x63\x63\x50\x56\x58\x35\x30\x30\x42\x42\x35\x30\x30\x73\x74\x50\x54\x59\x41\x41\x41\x30\x31\x56\x58\x35\x30\x30\x30\x30\x35\x30\x74\x31\x30\x50\x54\x59\x41\x30\x31\x56\x58\x35\x30\x30\x30\x30\x35\x77\x34\x30\x30\x50\x54\x59\x30\x31\x54\x78
Let’s submit this shellcode to the disassembler with x86 architecture:
0: 50 push eax
1: 59 pop ecx
2: 6a 30 push 0x30
4: 58 pop eax
5: 34 30 xor al,0x30
7: 50 push eax
8: 50 push eax
9: 50 push eax
a: 50 push eax
b: 50 push eax
c: 51 push ecx
d: 61 popa
e: 4a dec edx
f: 50 push eax
10: 35 30 30 30 30 xor eax,0x30303030
15: 52 push edx
16: 58 pop eax
17: 34 44 xor al,0x44
19: 6a 7a push 0x7a
1b: 59 pop ecx
1c: 30 44 4f 50 xor BYTE PTR [edi+ecx*2+0x50],al
20: 56 push esi
21: 58 pop eax
22: 35 30 30 30 30 xor eax,0x30303030
27: 35 4f 30 30 30 xor eax,0x3030304f
2c: 50 push eax
2d: 54 push esp
2e: 59 pop ecx
2f: 30 31 xor BYTE PTR [ecx],dh
31: 56 push esi
32: 58 pop eax
33: 35 30 30 30 42 xor eax,0x42303030
38: 35 30 30 30 70 xor eax,0x70303030
3d: 50 push eax
3e: 54 push esp
3f: 59 pop ecx
40: 41 inc ecx
41: 41 inc ecx
42: 41 inc ecx
43: 30 31 xor BYTE PTR [ecx],dh
45: 56 push esi
46: 58 pop eax
47: 35 30 30 30 30 xor eax,0x30303030
4c: 35 30 30 74 30 xor eax,0x30743030
51: 50 push eax
52: 54 push esp
53: 59 pop ecx
54: 41 inc ecx
55: 41 inc ecx
56: 30 31 xor BYTE PTR [ecx],dh
58: 56 push esi
59: 58 pop eax
5a: 35 30 30 30 30 xor eax,0x30303030
5f: 35 4f 77 31 30 xor eax,0x3031774f
64: 50 push eax
65: 54 push esp
66: 59 pop ecx
67: 30 31 xor BYTE PTR [ecx],dh
69: 41 inc ecx
6a: 30 31 xor BYTE PTR [ecx],dh
6c: 56 push esi
6d: 58 pop eax
6e: 35 30 30 30 42 xor eax,0x42303030
73: 35 30 30 30 70 xor eax,0x70303030
78: 50 push eax
79: 54 push esp
7a: 59 pop ecx
7b: 41 inc ecx
7c: 41 inc ecx
7d: 41 inc ecx
7e: 30 31 xor BYTE PTR [ecx],dh
80: 56 push esi
81: 58 pop eax
82: 35 38 48 37 45 xor eax,0x45374838
87: 35 4e 56 72 58 xor eax,0x5872564e
8c: 50 push eax
8d: 54 push esp
8e: 59 pop ecx
8f: 30 31 xor BYTE PTR [ecx],dh
91: 41 inc ecx
92: 30 31 xor BYTE PTR [ecx],dh
94: 41 inc ecx
95: 30 31 xor BYTE PTR [ecx],dh
97: 56 push esi
98: 58 pop eax
99: 35 30 31 30 31 xor eax,0x31303130
9e: 35 71 61 62 7a xor eax,0x7a626171
a3: 50 push eax
a4: 56 push esi
a5: 58 pop eax
a6: 35 31 30 42 30 xor eax,0x30423031
ab: 35 61 62 73 58 xor eax,0x58736261
b0: 50 push eax
b1: 56 push esi
b2: 58 pop eax
b3: 35 42 31 30 31 xor eax,0x31303142
b8: 35 76 53 58 4a xor eax,0x4a585376
bd: 50 push eax
be: 56 push esi
bf: 58 pop eax
c0: 35 30 30 34 42 xor eax,0x42343030
c5: 35 63 58 5a 75 xor eax,0x755a5863
ca: 50 push eax
cb: 56 push esi
cc: 58 pop eax
cd: 35 30 34 45 30 xor eax,0x30453430
d2: 35 58 58 76 6f xor eax,0x6f765858
d7: 50 push eax
d8: 56 push esi
d9: 58 pop eax
da: 35 30 42 34 34 xor eax,0x34344230
df: 35 58 71 58 58 xor eax,0x58587158
e4: 50 push eax
e5: 56 push esi
e6: 58 pop eax
e7: 35 42 30 30 30 xor eax,0x30303042
ec: 35 72 54 55 58 xor eax,0x58555472
f1: 50 push eax
f2: 56 push esi
f3: 58 pop eax
f4: 35 30 32 30 30 xor eax,0x30303230
f9: 35 4d 38 58 53 xor eax,0x5358384d
fe: 50 push eax
ff: 56 push esi
100: 58 pop eax
101: 35 30 30 42 42 xor eax,0x42423030
106: 35 61 58 63 63 xor eax,0x63635861
10b: 50 push eax
10c: 56 push esi
10d: 58 pop eax
10e: 35 30 30 42 42 xor eax,0x42423030
113: 35 30 30 73 74 xor eax,0x74733030
118: 50 push eax
119: 54 push esp
11a: 59 pop ecx
11b: 41 inc ecx
11c: 41 inc ecx
11d: 41 inc ecx
11e: 30 31 xor BYTE PTR [ecx],dh
120: 56 push esi
121: 58 pop eax
122: 35 30 30 30 30 xor eax,0x30303030
127: 35 30 74 31 30 xor eax,0x30317430
12c: 50 push eax
12d: 54 push esp
12e: 59 pop ecx
12f: 41 inc ecx
130: 30 31 xor BYTE PTR [ecx],dh
132: 56 push esi
133: 58 pop eax
134: 35 30 30 30 30 xor eax,0x30303030
139: 35 77 34 30 30 xor eax,0x30303477
13e: 50 push eax
13f: 54 push esp
140: 59 pop ecx
141: 30 31 xor BYTE PTR [ecx],dh
143: 54 push esp
144: 78 .byte 0x78
Except the last byte, this seems to be a valid shellcode consisting essentially of pushing xored data onto the stack.
If we analyze it a little more deeply, we notice that the following instruction set operates on the last byte of the shellcode:
17: 34 44 xor al,0x44
19: 6a 7a push 0x7a
1b: 59 pop ecx
1c: 30 44 4f 50 xor BYTE PTR [edi+ecx*2+0x50],al
Basically, it’s used to replace the last byte of the shellcode:
al = 0xFF
al ^= 0x44
byte = 0x78
hex(byte ^ al)
Output:
0xc3
Which, according to the Intel instruction set reference corresponds to the ret instruction opcode:
Opcode | Instruction | Description |
---|---|---|
C3 | RET | Near return to calling procedure |
If we pay attention to what is pushed on the stack, we actually get another shellcode:
\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\x31\xc9\x51\x68\x21\x21\x7d\x0a\x68\x63\x30\x64\x65\x68\x68\x33\x6c\x6c\x68\x6c\x33\x5f\x53\x68\x6e\x37\x34\x62\x68\x7b\x50\x52\x31\x68\x41\x50\x52\x4b\x89\xe1\xba\x1d\x00\x00\x00\xcd\x80\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80
Which can be dissassembled to:
0: b8 04 00 00 00 mov eax,0x4 ; write syscall
5: bb 01 00 00 00 mov ebx,0x1 ; STDOUT
a: 31 c9 xor ecx,ecx ; ecx = 0
c: 51 push ecx ; '\x00' (null terminated string)
d: 68 21 21 7d 0a push 0xa7d2121 ; '!!}\n'
12: 68 63 30 64 65 push 0x65643063 ; 'c0de'
17: 68 68 33 6c 6c push 0x6c6c3368 ; 'h3ll'
1c: 68 6c 33 5f 53 push 0x535f336c ; 'l3_S'
21: 68 6e 37 34 62 push 0x6234376e ; 'n74b'
26: 68 7b 50 52 31 push 0x3152507b ; '{PR1'
2b: 68 41 50 52 4b push 0x4b525041 ; 'APRK'
30: 89 e1 mov ecx,esp ; ecx = flag
32: ba 1d 00 00 00 mov edx,0x1d ; edx = len(flag)
37: cd 80 int 0x80 ; write(STDOUT, flag, len(flag))
39: b8 01 00 00 00 mov eax,0x1 ; exit syscall
3e: bb 00 00 00 00 mov ebx,0x0 ; SUCCESS
43: cd 80 int 0x80 ; exit(0)
We can also get the flag without disassembling the shellcode:
cat <<-'EOF' >flag.c
int main(void) {
char shellcode[] = "PYj0X40PPPPPQaJP50000RX4DjzY0DOPVX500005O000PTY01VX5000B5000pPTYAAA01VX50000500t0PTYAA01VX500005Ow10PTY01A01VX5000B5000pPTYAAA01VX58H7E5NVrXPTY01A01A01VX501015qabzPVX510B05absXPVX5B1015vSXJPVX5004B5cXZuPVX504E05XXvoPVX50B445XqXXPVX5B0005rTUXPVX502005M8XSPVX500BB5aXccPVX500BB500stPTYAAA01VX5000050t10PTYA01VX500005w400PTY01Tx";
(*(void (*)())shellcode)();
}
EOF
gcc -m32 -z execstack -o flag flag.c # we're returning to the shellcode which is pushed on the stack.
./flag
The final flag is APRK{PR1n74bl3_Sh3llc0de!!}
Happy Hacking!