Writing Small .NET PE’s

渗透技巧 10个月前 admin
232 0 0

Intro

Like last year I saw the awesome announcement of the Binary Golf challenge!

I do a lot of work in .NET and I was curious if we could create something that even approached being small and met the challenge requirements. This writeup is just me delving into that briefly but not really in depth!

Why though?

We are seeking to acquire as fast as possible those neglected arts of old which a true interpreter of code must possess, and hope in time to make full announcement and presentation of the utmost interest, are we not?

Meeting the Requirements

First off lets see what the code would look like. There are a few a approaches but they are mostly very similar. The snippet below is what I settled on.

using System;
using System.IO;

class P
{
    static void Main()
    {
        File.Copy(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName,"4");
        Console.Write("4");
    }
}

You can minimize this right but it actually doesn’t matter (just saying)!

using System;using System.IO;class P{static void Main(){File.Copy(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName,"4");Console.Write('4');}}

If we compile this the result is kind of horrible!

Writing Small .NET PE's

Makes you wonder what the hell the compiler is up to really!

Common Intermediate Language (CIL)

This got me thinking, would it be better if I wrote my application directly in the .NET IL (side-note CIL and MSIL are the same thing). For those who may not be aware when you write code in C# the compiler, CSC, translates the code into CIL. This CIL code is a low-level, platform-agnostic representation of your original C# code. Later, at runtime, the Just-In-Time (JIT) compiler translates the CIL into machine code, which can be directly executed by the CPU. The JIT compiler optimizes the machine code for the specific system architecture where the program is running.

    C# Source Code            CSC Compiler
 ┌────────────────────┐   ┌────────────────────┐
 │     .cs files      │ ► │     .dll/.exe      │
 └────────────────────┘   └────────────────────┘ 
                                   ▼
    CPU Execution             JIT Compiler
 ┌────────────────────┐   ┌────────────────────┐
 │      Output        │ ◄ │   Machine Code     │
 └────────────────────┘   └────────────────────┘

I don’t really have any experience doing this but since the code is so simple it was not hard to find some basic online documentation about the IL (like this) and glue it together!

This is the code I came up with:

.assembly extern mscorlib { }
.assembly P { }
.class private auto ansi beforefieldinit P extends [mscorlib]System.Object
{
    .method private hidebysig static void Main() cil managed 
    {
        .entrypoint
        .maxstack 2
        call class [System]System.Diagnostics.Process [System]System.Diagnostics.Process::GetCurrentProcess()
        callvirt instance class [System]System.Diagnostics.ProcessModule [System]System.Diagnostics.Process::get_MainModule()
        callvirt instance string [System]System.Diagnostics.ProcessModule::get_FileName()
        ldstr "4"
        call void [mscorlib]System.IO.File::Copy(string, string)
        ldc.i4.s 52
        call void [mscorlib]System.Console::Write(char)
        ret
    }
}

Note that we eliminate the constructor as it does not seem to be necessary for our specific code sample.

The code is actually reasonably readable, the one instruction I want to highlight here is ldc.i4.s because it may not be obvious. This instruction is pushing a small integer onto the stack (4 in this case). I tried to experiment a bit if we could eliminate one of the two 4‘s in the code but my efforts resulted only in larger IL.

Results

We can compile the IL with ilasm like so:

C:\> ilasm binGolf.il /X64 /BASE=0xb33f0000 /output=binGolf.exe

64 bit target must be specified for machine type /ARM64, /ITANIUM or /X64. Target set to 64 bit.

Microsoft (R) .NET Framework IL Assembler.  Version 4.8.9105.0
Copyright (c) Microsoft Corporation.  All rights reserved.
Assembling 'binGolf.il'  to EXE --> 'binGolf.exe'
Source file is ANSI

binGolf.il(9) : warning : Reference to undeclared extern assembly 'System'. Attempting autodetect
Assembled method P::Main
Creating PE file

Emitting classes:
Class 1:        P

Emitting fields and methods:
Global
Class 1 Methods: 1;

Emitting events and properties:
Global
Class 1
Writing PE file
Operation completed successfully

The PE still works as expected!

C:\> dir
 Volume in drive C has no label.
 Volume Serial Number is 827F-D80E

 Directory of C:\Users\b33f\Desktop\BinGlof\ILAsm

25/06/2023  19:59    <DIR>          .
25/06/2023  19:20    <DIR>          ..
25/06/2023  19:21             5,120 binGolf-481.exe
25/06/2023  19:59             2,048 binGolf.exe
25/06/2023  17:56               752 binGolf.il
               3 File(s)          7,920 bytes
               2 Dir(s)  1,485,571,768,320 bytes free

C:\> binGolf.exe
4
C:\Users\b33f\Desktop\BinGlof\ILAsm>dir
 Volume in drive C has no label.
 Volume Serial Number is 827F-D80E

 Directory of C:\Users\b33f\Desktop\BinGlof\ILAsm

25/06/2023  20:01    <DIR>          .
25/06/2023  19:20    <DIR>          ..
25/06/2023  19:59             2,048 4
25/06/2023  19:21             5,120 binGolf-481.exe
25/06/2023  19:59             2,048 binGolf.exe
25/06/2023  17:56               752 binGolf.il
               4 File(s)          9,968 bytes
               2 Dir(s)  1,485,571,756,032 bytes free

The result is much better than the original, coming out at 0x800. When looking at the PE in a hex editor you can see that actually the PE could be smaller.

Writing Small .NET PE's

Here we see, highlighted, that there is null padding between the .text and .reloc section. Additionally, the .reloc section contains 2 relocations -> Image Base Relocation (8-bytes) && x2 16-bit relocation entries ==> 12-bytes total (or 0xC), while taking up a full 0x200 bytes ?.. We can verify that in the section table.

Writing Small .NET PE's

The issue is that Optinal Header -> File Alignment has a value of 0x200. While values less than 0x200 (512) are technically out-of-spec this should normally work.

I tried to force ilasm to set this value but it refused, I also attempted to manually change the PE memory directly by:

  • Rewriting the virtual and raw size of the section in the section header

  • Updating the size of the base relocation section in the optional header

  • Manually setting the image size in the optional header

  • Removing the excess bytes

Perhaps I did something wrong, it looked ok yet it did not run. There may be some peculiarities about about .NET PE’s that I am not aware of tbh, this isn’t something I have looked at.

Ironically, RTFM was the solution, ilasm has a flag to strip relocations /STRIPRELOC. This does give us a bit of extra space:

C:\> ilasm binGolf.il /X64 /BASE=0xb33f0000 /STRIPRELOC /output=binGolfRTFM.exe

64 bit target must be specified for machine type /ARM64, /ITANIUM or /X64. Target set to 64 bit.

Microsoft (R) .NET Framework IL Assembler.  Version 4.8.9105.0
Copyright (c) Microsoft Corporation.  All rights reserved.
Assembling 'binGolf.il'  to EXE --> 'binGolfRTFM.exe'
Source file is ANSI

binGolf.il(9) : warning : Reference to undeclared extern assembly 'System'. Attempting autodetect
Assembled method P::Main
Creating PE file

Emitting classes:
Class 1:        P

Emitting fields and methods:
Global
Class 1 Methods: 1;

Emitting events and properties:
Global
Class 1
Writing PE file
Operation completed successfully

C:\> dir
 Volume in drive C has no label.
 Volume Serial Number is 827F-D80E

 Directory of C:\Users\b33f\Desktop\BinGlof\ILAsm

25/06/2023  20:32    <DIR>          .
25/06/2023  19:20    <DIR>          ..
25/06/2023  19:59             2,048 4
25/06/2023  19:21             5,120 binGolf-481.exe
25/06/2023  19:59             2,048 binGolf.exe
25/06/2023  17:56               752 binGolf.il
25/06/2023  20:32             1,548 binGolfRTFM.exe
               5 File(s)         11,516 bytes
               2 Dir(s)  1,485,562,277,888 bytes free

However looking at the binary data we notice something interesting, the artifacts of the relocation section are not totally gone. Strangely the section count has been decremented but the .reloc section still shows up in the header.

Writing Small .NET PE's

And we can still see the remnants of the relocation data as well..

Writing Small .NET PE's

Still, the result is pretty good for a first / fast attempt! Our final size is 0x60c (1548), down from 0x1400 (5120).

What now?

There is a long tradition of hacking together very out-of-spec files and PE’s, you can see a great example here:

Writing Small .NET PE's

The most excellent part about this is the way the DOS header and stub are repurposed to save space ?‍?!

Of course the author (Sotirov, just saying lol) does many things we can’t do with our IL (or IL compiler) but I would like to revisit this later if I have time to see if it can be further optimized!

Binary

I have base64’d the binary below. Please note that since there are no relocations it requires that 0xb33f0000 is available as a base address.

PS C:\Users\b33f> [Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\Users\b33f\Desktop\BinGlof\ILAsm\binGolfRTFM.exe"))
TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAAZIYBAOIGmWQAAAAAAAAAAPAAIwALAgsAAAQAAAAAAAAAAAAALiMAAAAgAAAAAD+zAAAAAAAgAAAAAgAABAAAAAAAAAAEAAAAAAAAAABAAAAAAgAAAAAAAAMAQIUAAEAAAAAAAABAAAAAAAAAAAAQAAAAAAAAIAAAAAAAAAAAAAAQAAAAAAAAAAAAAADYIgAAUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAABAAAAAAAAAAAAAAABAgAABIAAAAAAAAAAAAAAAudGV4dAAAADoDAAAAIAAAAAQAAAACAAAAAAAAAAAAAAAAAAAgAABgLnJlbG9jAAAAAAAAAEAAAAAAAAAABgAAAAAAAAAAAAAAAAAAQAAAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIwAAAAAAAAAAAAAAAAAASAAAAAIABQCQIAAARAIAAAEAAAABAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATMAIAIQAAAAAAAAAoAQAACm8CAAAKbwMAAApyAQAAcCgEAAAKHzQoBQAACioAAABCU0pCAQABAAAAAAAMAAAAdjQuMC4zMDMxOQAAAAAFAGwAAADkAAAAI34AAFABAAC0AAAAI1N0cmluZ3MAAAAABAIAAAgAAAAjVVMADAIAABAAAAAjR1VJRAAAABwCAAAoAAAAI0Jsb2IAAAAAAAAAAgAAAUcEAAAJAAAAAPolMwAWAAABAAAABQAAAAIAAAABAAAABQAAAAEAAAACAAAAAACRAAEAAAAAAAYAEQAKAAoAKwAYAAoARQAYAAYAeQBvAAYAgwAKAAAAAAABAAAAAAABAAEAAAAQAKoAAAAFAAEAAQBgIAAAAACRAKwAIwABABEAMwABABEAUwAGABkAYgALACEAfgAPACkAiwAVAAAAAAAAAAAAAAAAAAAAAAAAAKoAAAAAAAAAAAAAAAAAAAAAAKEAAAAAAAQAAAAAAAAAAAAAABoACgAAAAAAAAAAPE1vZHVsZT4AU3lzdGVtAE9iamVjdABTeXN0ZW0uRGlhZ25vc3RpY3MAUHJvY2VzcwBHZXRDdXJyZW50UHJvY2VzcwBQcm9jZXNzTW9kdWxlAGdldF9NYWluTW9kdWxlAGdldF9GaWxlTmFtZQBTeXN0ZW0uSU8ARmlsZQBDb3B5AENvbnNvbGUAV3JpdGUAYmluR29sZlJURk0uZXhlAG1zY29ybGliAFAATWFpbgAAAAAAAzQAAAAAAP0C2A0z2xFKhztvC2tSSLAABAAAEgkEIAASDQMgAA4FAAIBDg4EAAEBAwi3elxWGTTgiQMAAAEAAAAAAAAjAAAAAAAAAAAAAB4jAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIwAAAAAAAAAAAAAAAAAAAABfQ29yRXhlTWFpbgBtc2NvcmVlLmRsbAAAAAAASKEAID+zAAAAAP/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAwAAAAwowAA

You can also review the binary using this CyberChef link.

 

原文始发于KnifeCoat:Writing Small .NET PE’s

版权声明:admin 发表于 2023年6月27日 上午8:59。
转载请注明:Writing Small .NET PE’s | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...