Self-deleting a binary using C# and Alternate Data Streams

Under normal conditions it is not possible to delete a binary on Windows while it is running. However, using WinAPIs and Alternate Data Streams we will see a binary can delete itself.

Repository: https://github.com/ricardojoserf/SharpSelfDelete



SharpSelfDelete

This is a PoC code to self-delete a binary in C#. It is specially useful for malware as under normal conditions it is not possible to delete a binary on Windows while it is running. In my case I needed it for the SharpCovertTube project, so the binary can delete itself from disk.

It uses the APIs GetModuleFileName, CreateFileW and SetFileInformationByHandle to rename the Alternate Data Stream $DATA (the default one) in the binary to a random new one and then delete the file.

img



Code

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace SharpSelfDelete
{
    internal unsafe class Program
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr CreateFileW(
            [MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
            uint dwDesiredAccess,
            uint dwShareMode,
            uint lpSecurityAttributes,
            uint dwCreationDisposition,
            uint dwFlagsAndAttributes,
            uint hTemplateFile
        );

        [DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern Int32 WSAGetLastError();

        [DllImport("Kernel32.dll", SetLastError = true)]
        private static extern int SetFileInformationByHandle(
            IntPtr hFile,
            FileInformationClass FileInformationClass,
            IntPtr FileInformation,
            Int32 dwBufferSize
        );

        [DllImport("Kernel32.dll", SetLastError = true)]
        private static extern bool CloseHandle(
            IntPtr handle
        );

        [DllImport("kernel32.dll", SetLastError = true)]
        [PreserveSig]
        public static extern uint GetModuleFileName
        (
            [In] IntPtr hModule,
            [Out] StringBuilder lpFilename,
            [In][MarshalAs(UnmanagedType.U4)] int nSize
        );

        enum FileInformationClass : int
        {
            FileBasicInfo = 0,
            FileStandardInfo = 1,
            FileNameInfo = 2,
            FileRenameInfo = 3,
            FileDispositionInfo = 4,
            FileAllocationInfo = 5,
            FileEndOfFileInfo = 6,
            FileStreamInfo = 7,
            FileCompressionInfo = 8,
            FileAttributeTagInfo = 9,
            FileIdBothDirectoryInfo = 10, // 0xA
            FileIdBothDirectoryRestartInfo = 11, // 0xB
            FileIoPriorityHintInfo = 12, // 0xC
            FileRemoteProtocolInfo = 13, // 0xD
            FileFullDirectoryInfo = 14, // 0xE
            FileFullDirectoryRestartInfo = 15, // 0xF
            FileStorageInfo = 16, // 0x10
            FileAlignmentInfo = 17, // 0x11
            FileIdInfo = 18, // 0x12
            FileIdExtdDirectoryInfo = 19, // 0x13
            FileIdExtdDirectoryRestartInfo = 20, // 0x14
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct filerenameinfo_struct
        {
            public bool ReplaceIfExists;
            public IntPtr RootDirectory;
            public uint FileNameLength;
            public fixed byte filename[255];
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct filedispositioninfo_struct
        {
            public bool DeleteFile;
        }

        const uint DELETE = (uint)0x00010000L;
        const uint SYNCHRONIZE = (uint)0x00100000L;
        const uint FILE_SHARE_READ = 0x00000001;
        const uint OPEN_EXISTING = 3;
        const int MAX_PATH = 256;

        static void Main(string[] args)
        {
            StringBuilder fname = new System.Text.StringBuilder(MAX_PATH);
            GetModuleFileName(IntPtr.Zero, fname, MAX_PATH);
            string filename = fname.ToString();
            string new_name = ":Random";
            Console.WriteLine("[+] File Name: \t\t\t\t" + filename);
            Console.WriteLine("[+] New Alternate Data Stream: \t\t" + new_name);

            // Renaming
            Console.WriteLine("[+] RENAMING FILE...");

            // Handle to current file
            IntPtr hFile = CreateFileW(filename, DELETE | SYNCHRONIZE, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
            Console.WriteLine("[+] CreateFileW File handle: \t\t" + hFile);
            int last_error = WSAGetLastError();
            if (last_error != 0 || hFile == IntPtr.Zero) {
                Console.WriteLine("[-] Error calling CreateFileW");
                System.Environment.Exit(0);
            }

            // Creating FILE_RENAME_INFO struct
            filerenameinfo_struct fri = new filerenameinfo_struct();
            fri.ReplaceIfExists = true;
            fri.RootDirectory = IntPtr.Zero;
            uint FileNameLength = (uint)(new_name.Length * 2);
            fri.FileNameLength = FileNameLength;
            int size = Marshal.SizeOf(typeof(filerenameinfo_struct)) + (new_name.Length + 1) * 2;

            IntPtr fri_addr = IntPtr.Zero;

            unsafe
            {
                // Get Address of FILE_RENAME_INFO struct
                filerenameinfo_struct* pfri = &fri;
                fri_addr = (IntPtr)pfri;
                Console.WriteLine("[+] FILE_RENAME_INFO struct address: \t0x" + fri_addr.ToString("x"));

                // Copy new file name (bytes) to filename member in FILE_RENAME_INFO struct
                byte* p = fri.filename;
                byte[] filename_arr = Encoding.Unicode.GetBytes(new_name);
                foreach (byte b in filename_arr)
                {
                    *p = b;
                    p += 1;
                }
            }
            // Rename file calling SetFileInformationByHandle
            int sfibh_res = SetFileInformationByHandle(hFile, FileInformationClass.FileRenameInfo, fri_addr, size);
            Console.WriteLine("[+] SetFileInformationByHandle result:\t" + sfibh_res);
            last_error = WSAGetLastError();
            if (sfibh_res == 0)
            {
                Console.WriteLine("[-] Error calling SetFileInformationByHandle");
                System.Environment.Exit(0);
            }

            // Close handle to finally rename file
            bool ch_res = CloseHandle(hFile);
            Console.WriteLine("[+] Close Handle Result:\t\t" + ch_res);

            // Deleting
            Console.WriteLine("[+] DELETING FILE...");

            // Handle to current file
            IntPtr hFile2 = CreateFileW(filename, DELETE | SYNCHRONIZE, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
            last_error = WSAGetLastError();
            Console.WriteLine("[+] CreateFileW File handle: \t\t" + hFile2);
            if (last_error != 0 || hFile == IntPtr.Zero)
            {
                Console.WriteLine("[-] Error calling CreateFileW");
                System.Environment.Exit(0);
            }

            // Creating FILE_DISPOSITION_INFO struct
            filedispositioninfo_struct fdi = new filedispositioninfo_struct();
            fdi.DeleteFile = true;
            IntPtr fdi_addr = IntPtr.Zero;
            int size_fdi = Marshal.SizeOf(typeof(filedispositioninfo_struct));

            unsafe
            {
                // Get Address of FILE_DISPOSITION_INFO struct
                filedispositioninfo_struct* pfdi = &fdi;
                fdi_addr = (IntPtr)pfdi;
                Console.WriteLine("[+] FILE_DISPOSITION_INFO struct addr: \t0x" + fdi_addr.ToString("x"));
            }

            // Rename file calling SetFileInformationByHandle
            int sfibh_res2 = SetFileInformationByHandle(hFile2, FileInformationClass.FileDispositionInfo, fdi_addr, size_fdi);
            Console.WriteLine("[+] SetFileInformationByHandle result:\t" + sfibh_res2);
            last_error = WSAGetLastError();
            if (sfibh_res == 0 || last_error != 0)
            {
                Console.WriteLine("[-] Error calling SetFileInformationByHandle");
                System.Environment.Exit(0);
            }

            // Close handle to finally delete file
            bool ch_res2 = CloseHandle(hFile2);
            Console.WriteLine("[+] Close Handle result:\t\t" + ch_res2);
        }
    }
}



Source

This is a port from code in one lesson of Maldev Academy, which was originally written in C, but I needed it in C#.


Written on January 29, 2024