2

Created a Windows executable with options /hotpatch and /FUNCTIONPADMIN:195 which adds 195 bytes of padding to the beginning of main().

// Windows x86 null-free WinExec Calc.exe shellcode.

char shellcode[195] = 
"\x89\xe5\x83\xec\x20\x31\xdb\x64\x8b\x5b\x30\x8b\x5b\x0c\x8b\x5b"
"\x1c\x8b\x1b\x8b\x1b\x8b\x43\x08\x89\x45\xfc\x8b\x58\x3c\x01\xc3"
"\x8b\x5b\x78\x01\xc3\x8b\x7b\x20\x01\xc7\x89\x7d\xf8\x8b\x4b\x24"
"\x01\xc1\x89\x4d\xf4\x8b\x53\x1c\x01\xc2\x89\x55\xf0\x8b\x53\x14"
"\x89\x55\xec\xeb\x32\x31\xc0\x8b\x55\xec\x8b\x7d\xf8\x8b\x75\x18"
"\x31\xc9\xfc\x8b\x3c\x87\x03\x7d\xfc\x66\x83\xc1\x08\xf3\xa6\x74"
"\x05\x40\x39\xd0\x72\xe4\x8b\x4d\xf4\x8b\x55\xf0\x66\x8b\x04\x41"
"\x8b\x04\x82\x03\x45\xfc\xc3\xba\x78\x78\x65\x63\xc1\xea\x08\x52"
"\x68\x57\x69\x6e\x45\x89\x65\x18\xe8\xb8\xff\xff\xff\x31\xc9\x51"
"\x68\x2e\x65\x78\x65\x68\x63\x61\x6c\x63\x89\xe3\x41\x51\x53\xff"
"\xd0\x31\xc9\xb9\x01\x65\x73\x73\xc1\xe9\x08\x51\x68\x50\x72\x6f"
"\x63\x68\x45\x78\x69\x74\x89\x65\x18\xe8\x87\xff\xff\xff\x31\xd2"
"\x52\xff\xd0";


int main(int argc, char* argv[])
{
    char (*jmp)() = (char*)&main - sizeof(shellcode);
    jmp();
}

The shellcode was written into the binary image 195 bytes prior to the start of main().
Upon disassembling, it shows there are 13 bytes of extra padding (0xCC):

  00401000: CC CC CC CC CC CC CC CC CC CC CC CC CC 89 E5 83  IIIIIIIIIIIII%†Ÿ
  00401010: EC 20 31 DB 64 8B 5B 30 8B 5B 0C 8B 5B 1C 8B 1B   1Ud<[0<[.<[.<.
  00401020: 8B 1B 8B 43 08 89 45 FC 8B 58 3C 01 C3 8B 5B 78  <.<C.%E<X<.A<[x
  00401030: 01 C3 8B 7B 20 01 C7 89 7D F8 8B 4B 24 01 C1 89  .A<{ .€%}o<K$.A%
  00401040: 4D F4 8B 53 1C 01 C2 89 55 F0 8B 53 14 89 55 EC  M“<S..A%Ud<S.%U
  00401050: EB 32 31 C0 8B 55 EC 8B 7D F8 8B 75 18 31 C9 FC  ‰21A<U<}o<u.1
  00401060: 8B 3C 87 03 7D FC 66 83 C1 08 F3 A6 74 05 40 39  <<Ø.}fŸA.¢Ýt.@9
  00401070: D0 72 E4 8B 4D F4 8B 55 F0 66 8B 04 41 8B 04 82  Dr„<M“<Udf<.A<.,
  00401080: 03 45 FC C3 BA 78 78 65 63 C1 EA 08 52 68 57 69  .EA§xxecAˆ.RhWi
  00401090: 6E 45 89 65 18 E8 B8 FF FF FF 31 C9 51 68 2E 65  nE%e.Š,˜˜˜1Qh.e
  004010A0: 78 65 68 63 61 6C 63 89 E3 41 51 53 FF D0 31 C9  xehcalc%aAQS˜D1
  004010B0: B9 01 65 73 73 C1 E9 08 51 68 50 72 6F 63 68 45  1.essA‚.QhProchE
  004010C0: 78 69 74 89 65 18 E8 87 FF FF FF 31 D2 52 FF D0  xit%e.ŠØ˜˜˜1OR˜D
_main:
  004010D0: 66 90              xchg        ax,ax     ; part of hot patching method.
  004010D2: 55                 push        ebp
  004010D3: 8B EC              mov         ebp,esp
  004010D5: 51                 push        ecx
  004010D6: B8 D0 10 40 00     mov         eax,offset _main
  004010DB: 2D C3 00 00 00     sub         eax,0C3h
  004010E0: 89 45 FC           mov         dword ptr [ebp-4],eax
  004010E3: FF 55 FC           call        dword ptr [ebp-4]
  004010E6: 33 C0              xor         eax,eax
  004010E8: 8B E5              mov         esp,ebp
  004010EA: 5D                 pop         ebp
  004010EB: C3                 ret

If the hot patch padding is always a multiple of 16, can any extra bytes be safely used by the shellcode?

Running the executable, starts the Windows Calculator:
shellcode_calc_exe

vengy
  • 1,548
  • 10
  • 18
  • 2
    MSVC, like most compilers, aligns functions by 16, so yeah it's normal that it would still arrange for that to be the case. You just set the *minimum* padding between functions, I guess? I didn't read the doc you linked for that MSVC option. – Peter Cordes Aug 28 '21 at 01:04
  • I missed the MIN (minimum) part of that option /FUNCTIONPADMIN. So, 195 bytes of padding is the minimum but is actually rounded up to 208. Thanks! – vengy Aug 28 '21 at 01:11

1 Answers1

3

If the hot patch padding is always a multiple of 16, can any extra bytes be safely used by the shellcode?

No, because the size of the previous function may vary.

f function has the offset 0x00401000, and g is located afterwards. With /FUNCTIONPADMIN g will have the offset 0x00401000 + <f size> + <at least 195>. <at least 195> is the smallest possible value to make g aligned on 16 bytes boundary. If <f size> is 13, then <at least 195> equals to 195, and there's no extra padding.

Current MSVC pads with 0xCC which is one-byte int 3 instruction. This can be used for checking if the padding is the right size, and if it isn't used already by another hotpatch.

Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79
  • 1
    Note that a `0xcc` byte could be the last byte of some other instruction at the end of a function. Perhaps a `jmp somewhere` rel32 or `jmp [rax + r9*8]` direct or indirect tailcall. Although `0xcc` in the high byte of a rel32 jump would mean jumping very far, and `0xcc` in the SIB byte would be `ff 24 cc jmp QWORD PTR [rsp+rcx*8]`; barely plausible for indexing into shadow space. (With `CC` in the modrm byte, `ff cc` is `dec esp`, so that's implausible even with a REX prefix). `0xcc` as a disp8 isn't a multiple of 8, so it's unlikely, and also unlikely as thie high byte of a disp32. – Peter Cordes Dec 01 '21 at 15:36
  • 1
    The last byte of many functions is `0xc3 ret`; Windows x64 doesn't use `ret imm16`, and `0xcc__` would be a huge amount of arg space for a callee to be popping anyway. So yes it's possible for `0xcc` to be the last actually-used byte of a function, but it's unlikely in normal compiler-generated code I think. – Peter Cordes Dec 01 '21 at 15:38
  • Well, there could be some case when the last byte is `0xcc`. Maybe if function ends with unconditional jump backwards, and rel16 is long enough to have 0xcc in high byte. Calling `[[noreturn]]` functions or taking `__assume(false)` cause the compiler to omit `ret`, but this usually ends of being similar to a tailcall. – Alex Guteniev Dec 01 '21 at 16:31
  • 1
    Or even rel8 jump backwards where rel8 is exactly 0xCC – Alex Guteniev Dec 01 '21 at 16:36
  • `jmp rel16` truncates EIP to 16 bits, so it's not really usable outside of 16-bit mode (although it *is* encodeable in 32-bit mode). But yes, a backward `jmp rel8` into the same function is 100% plausible, great point. – Peter Cordes Dec 01 '21 at 16:37