2013-03-12 / Malware Analysis

This is my exploration of a trojan horse sent to open@duckduckgo.com. The email, which claimed to hold "My eTicket", contained a malicious call to action and .zip file.

I began by attempting to locate the origin of the email. The relevant portion of the headers is excerpted below. These headers cannot be relied upon for identification (they are trivially forged). However, the email does not claim to be from delta.com, leading me to suspect the original domain is genuine - why pretend to be from an arbitrary domain?

Received: from unknown (HELO ) (89.77.209.23)
  by 0 with SMTP; 5 Mar 2013 22:11:01 -0000
From: "DELTA" <PFlFnLybzQlsh@lorusso.com>
To: open@duckduckgo.com
Message-ID: <20130305231057.D5B526351B50D849E929.5F438C@MARTA-F97BA78A4>
Subject: Your eTicket
MIME-Version: 1.0
Content-Type: multipart/related;
  boundary="----=_Part_369841052092"
Taking a look at lorusso.com, it seems legitimate. Following with a WHOIS query, it still looks good.
Domain Name: LORUSSO.COM
Registrar: TUCOWS DOMAINS INC.
Whois Server: whois.tucows.com
Referral URL: http://domainhelp.opensrs.net
Name Server: NS10.IXWEBHOSTING.COM
Name Server: NS9.IXWEBHOSTING.COM
Status: clientTransferProhibited
Status: clientUpdateProhibited
Updated Date: 17-oct-2011
Creation Date: 25-oct-1998
Expiration Date: 24-oct-2017

Administrative Contact:
   Lorusso, David  dave@lorusso.com
   1200 Mahogany Lane
   Cedar Park, TX 78613
   US
   +1.5123319487
Technical Contact:
   Lorusso, David  dave@lorusso.com
   1200 Mahogany Lane
   Cedar Park, TX 78613
   US
   +1.5123319487

Because I do not believe the email was sent by the owner of lorusso.com, this leaves two possibilities: a negligently open mail relay service, or a compromised system. To determine if the former is the case, I attempt to send my own email through his mail provider. To locate the resource, I first query the mail exchange record for the domain and it's corresponding address record.

$ dig mx lorusso.com +short
10 mail909.ixwebhosting.com.

$ dig mail909.ixwebhosting.com +short
76.162.254.111
76.162.254.117
76.162.254.118
76.162.254.109
76.162.254.110

$ telnet 76.162.254.111 25
Trying 76.162.254.117...
Connected to 76.162.254.117.
Escape character is '^]'.
220 ironport4.opentransfer.com ESMTP
helo dylanstestserver.com
250 ironport4.opentransfer.com
mail from: dylansserver.com
250 sender <dylansserver.com> ok
rcpt to: dylan@dylansserver.com
550 #5.1.0 Address rejected dylan@dylansserver.com
quit
221 ironport4.opentransfer.com
Connection closed by foreign host.

My request to forward mail through the server is denied appropriately. Without running an intrusive network scan of lorusso.com, at this point there is nothing left to do except to alert the technical contact of the domain.

Now to the payload. Inside a GNU/Linux VM I identify the file type, log a checksum, and unpack, recursively. I'm lucky - it's packed, but there is no obfuscation of the executable by its format.

$ file eTicket.zip
eTicket.zip: Zip archive data, at least v2.0 to extract

$ md5sum eTicket.zip
5f3aeef467f263e56b7a53f28497523c  eTicket.zip

$ unzip eTicket.zip
Archive:  eTicket.zip
  inflating: eTicket and Receipt for ID5376594563456459762374628734628769348628756826398467263596245663284682369498268354892634986234876248528374698137404568798057347573204312462656.pdf.exe

$ file eTicket\ and\ Receipt\ for\ ID5376594563456459762374628734628769348628756826398467263596245663284682369498268354892634986234876248528374698137404568798057347573204312462656.pdf.exe
eTicket and Receipt for ID5376594563456459762374628734628769348628756826398467263596245663284682369498268354892634986234876248528374698137404568798057347573204312462656.pdf.exe: PE32 executable (GUI) Intel 80386, for MS Windows, UPX compressed

$ md5sum eTicket\ and\ Receipt\ for\ ID5376594563456459762374628734628769348628756826398467263596245663284682369498268354892634986234876248528374698137404568798057347573204312462656.pdf.exe
a98d8bf1d8b68477867ebae47f0d5086  eTicket and Receipt for ID5376594563456459762374628734628769348628756826398467263596245663284682369498268354892634986234876248528374698137404568798057347573204312462656.pdf.exe

$ upx -d eTicket\ and\ Receipt\ for\ ID5376594563456459762374628734628769348628756826398467263596245663284682369498268354892634986234876248528374698137404568798057347573204312462656.pdf.exe 

$ file eTicket\ and\ Receipt\ for\ ID5376594563456459762374628734628769348628756826398467263596245663284682369498268354892634986234876248528374698137404568798057347573204312462656.pdf.exe 
eTicket and Receipt for ID5376594563456459762374628734628769348628756826398467263596245663284682369498268354892634986234876248528374698137404568798057347573204312462656.pdf.exe: PE32 executable (GUI) Intel 80386, for MS Windows

$ md5sum eTicket\ and\ Receipt\ for\ ID5376594563456459762374628734628769348628756826398467263596245663284682369498268354892634986234876248528374698137404568798057347573204312462656.pdf.exe
82c3c81779564d999787a3a15203fb33  eTicket and Receipt for ID5376594563456459762374628734628769348628756826398467263596245663284682369498268354892634986234876248528374698137404568798057347573204312462656.pdf.exe

Seeing the portable executable, I know the honeypot system I need. Before preparing it, I take quick peek inside the file:

$ strings eTicket\ and\ Receipt\ for\ ID5376594563456459762374628734628769348628756826398467263596245663284682369498268354892634986234876248528374698137404568798057347573204312462656.pdf.exe
...
KERNEL32.DLL
gdi32.dll
user32.dll
UnregisterWaitEx
GetEnvironmentStringsA
GetCommandLineW
CreateDirectoryExA
ExitProcess
GetNumberFormatW
GetCommandLineA
EnumTimeFormatsA
GetPrivateProfileStructW
GetTextExtentPoint32W
GetRgnBox
SetColorSpace
DeviceCapabilitiesExW
DeleteDC
PolyTextOutA
GetBkColor
GetFontLanguageInfo
CreateFontW
GetKerningPairsA
GdiDeleteSpoolFileHandle
GetMapMode
GdiArtificialDecrementDriver
CreateFontIndirectExA
StartDocW
SetROP2
UpdateColors
OffsetViewportOrgEx
GetOutlineTextMetricsA
DeviceCapabilitiesExA
CopyEnhMetaFileA
Polyline
SetAbortProc
ExtEscape
SetBrushOrgEx
GetFontResourceInfoW
StartPage
GetDIBColorTable
EudcUnloadLinkW
OffsetWindowOrgEx
IntersectClipRect
SetMapMode
CreateFontIndirectW
GetTextFaceA
GetRelAbs
DescribePixelFormat
GetLogColorSpaceW
BeginPath
GetPath
GetCharacterPlacementW
GdiPlayPrivatePageEMF
SelectPalette
CloseMetaFile
CreateRectRgn
EnumFontsA
SetLayout
EudcLoadLinkW
InvertRgn
EnumFontFamiliesExW
SetRelAbs
EnumFontFamiliesExA
GetStretchBltMode
GetCharacterPlacementA
CreateFontIndirectA
GdiPlayScript
CreateDCA
ExcludeClipRect
SetMetaFileBitsEx
GetDeviceCaps
StartFormPage
GetWorldTransform
CombineTransform
FlattenPath
GdiPlayPageEMF
CreatePolyPolygonRgn
GetBkMode
SelectFontLocal
PolyPolyline
CreateDCW
GetTextExtentPoint32A
SelectObject
EnumFontFamiliesA
RemoveFontResourceExW
SetSystemPaletteUse
GetPaletteEntries
GetCharWidthFloatA
Escape
DeleteObject
UpdateICMRegKeyA
GetFontUnicodeRanges
CreateCompatibleBitmap
ExtCreatePen
GetObjectW
GetTextExtentPointI
GdiComment
GetWindowExtEx
SelectBrushLocal
GetCharWidthFloatW
FloodFill
EndPath
LPtoDP
WidenPath
RemoveFontResourceW
CopyEnhMetaFileW
GetMetaFileA
PolyPolygon
PaintRgn
CreatePalette
GetGlyphIndicesA
GdiGetSpoolFileHandle
GetDIBits
SetTextCharacterExtra
PolylineTo
SetMetaRgn
GetKerningPairsW
ExtCreateRegion
GetCharWidthA
SetColorAdjustment
GetLayout
SetMagicColors
SetICMProfileW
GetSystemPaletteEntries
SetDIBits
DeleteEnhMetaFile
CreatePatternBrush
SetWindowOrgEx
GetTextExtentPointA
UnrealizeObject
PolyTextOutW
ResetDCW
CreateFontIndirectExW
GetTextExtentExPointW
CreateCompatibleDC
GetLogColorSpaceA
GetTextExtentPointW
CreateDIBPatternBrushPt
CreatePolygonRgn
GdiPlayJournal
ColorCorrectPalette
RemoveFontMemResourceEx
GetStockObject
PatBlt
FrameRgn
UpdateICMRegKeyW
GetCharABCWidthsA
CreatePen
CombineRgn
GetEnhMetaFileW
GetDCOrgEx
GetBoundsRect
LineDDA
PlayEnhMetaFile
RemoveFontResourceA
GetSystemPaletteUse
GdiPlayDCScript
CreateColorSpaceW
GetBitmapBits
GetDCPenColor
GetBrushOrgEx
GetCharWidthI
GetBitmapDimensionEx
GetObjectType
RemoveFontResourceExA
SelectClipRgn
TranslateCharsetInfo
CreateEnhMetaFileW
GetObjectA
SetStretchBltMode
GetFontAssocStatus
SetDCBrushColor
SetRectRgn
Polygon
SetMapperFlags
EnumEnhMetaFile
SetDIBColorTable
GetDeviceGammaRamp
StartDocA
CreatePenIndirect
StretchBlt
VkKeyScanExA
InvalidateRect
ToUnicodeEx
GetMenuDefaultItem
AdjustWindowRect
ReleaseCapture
EnumDisplayDevicesW
DdeDisconnect
TranslateMDISysAccel
SetClipboardViewer
DrawTextA
LoadMenuW
CharNextW
GetLastActivePopup
CopyRect
PrivateExtractIconsW
IsWindow
GetTabbedTextExtentA
InvalidateRgn
GetClipboardFormatNameA
IMPQueryIMEW
TranslateMessage
CreateMenu
SetWindowsHookExA
DefWindowProcA
GetDialogBaseUnits
GetWindowRgn
OpenDesktopW
LockWindowUpdate
...

There are some interesting calls made, but I don't know enough about Windows internals to tell much from it. Instead, I'll move on to dynamic analysis. To create a safe, monitored environment, I will use another guest operating system, loaded with the following utilities:

  • VirtualBox appliance, Windows XP SP3
  • Internet Explorer 8 (updated)
  • Windows Security Essentials (updated)
  • RegShot (for registry and filesystem snapshots)
  • Windows Process Monitor (for live monitoring of system calls)

The host also requires configuration. I create a virtual network device (and an ethernet bridge) that can be attached to the virtual machine, watched and firewalled.

sudo modprobe vboxnetflt

sudo brctl addbr br0
sudo brctl addif br0 eth0

sudo modprobe tun
sudo ip tuntap mode tap
sudo link set up tap0
sudo brctl addif br0 tap0

mkdir monitor && cd monitor
sudo tcpdump -itap0 -vvvA -s0 -G 60 -W 1 -Uw baseline_
tcpdump -vvvA -r baseline_00

With a distinct, tapped interface, I listen for baseline network connections including ARP and UDP inside the LAN. This will help me eliminate noise from the network I/O of the infected system. Simultaneously, I create a new virtual machine snapshot to return to later. Meanwhile, inside the guest I take registry and filesystem snapshots with RegShot, as well as open the Process Monitor, filtering out friendly services. With a healthy signature obtained, I start a new listening process:

sudo tcpdump -itap0 -vvvA -s0 -C 128 -W 10 -Uw capture_

Environment prepared, I download and execute the trojan. The file disappears after triggering, and the process and network monitors flood with calls and packets. After about 3 minutes, I pause the VM, and begin the log analysis.

My first step now is to peek inside with my editor. There are a lot of HTTP requests, furthermore, a lot of requests that seem to passing parameters used for ad tracking.

$ strings capture_00 | grep http | wc -l
281
$ strings capture_00 | grep http | grep CLICK | wc -l
137
$ strings capture_00 | grep http | grep -v CLICK | grep impression | wc -l
73
$ strings capture_00 | grep Host | sort -u
Host: 113594url.directdisplayad.com
Host:239.255.255.250:1900
Host: 88.198.7.221
Host: ajax.googleapis.com
Host: cache.adfeedstrk.com
Host: cds.q2q3h3t3.hwcdn.net
Host: connect.facebook.net
Host: edge.sharethis.com
Host: fonts.googleapis.com
Host: html5shiv.googlecode.com
Host: j.maxmind.com
Host: redirect.ad-feeds.net
Host: vjlvchretllifcsgynuq.com
Host: wd.sharethis.com
Host: w.sharethis.com
Host: www.directorslive.com
Host: xlotxdxtorwfmvuzfuvtspel.com

A bit more searching and it's clear that the malware is using my computer to send out hundreds of forged ad impressions every minute. It's also hitting something else interesting - j.maxmind.com is a geolocation service. It's possible that it's fetching this information to send back to a command and control sever. This of course points towards the next concern - that the program has also installed additional hooks such as a keylogger, which it could use to send keystrokes (including financial information) to its owner.

None of the outgoing packets look very interesting, but there's no way of predicting when it might try to make contact. Instead, I'll try to look for evidence of additional tampering locally. To do this, I look at the registry and file system diff, alongside the process monitor.

----------------------------------
Files deleted: 2
----------------------------------
C:\WINDOWS\Tasks\Microsoft Antimalware Scheduled Scan.job
C:\WINDOWS\Tasks\MpIdleTask.job
$ egrep -i 'Control.*firewall' registry.changes
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile\AuthorizedApplications
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile\AuthorizedApplications\List
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\AuthorizedApplications
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\AuthorizedApplications\List
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Setup\InterfacesUnfirewalledAtUpdate
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile\AuthorizedApplications
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile\AuthorizedApplications\List
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\AuthorizedApplications
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\AuthorizedApplications\List
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Setup\InterfacesUnfirewalledAtUpdate
HKLM\SYSTEM\ControlSet001\Enum\Root\LEGACY_SHAREDACCESS\0000\DeviceDesc: "Windows Firewall/Internet Connection Sharing (ICS)"
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile\EnableFirewall: 0x00000001
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile\DisableNotifications: 0x00000000
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile\DoNotAllowExceptions: 0x00000000
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile\AuthorizedApplications\List\%windir%\Network Diagnostic\xpnetdiag.exe: "%windir%\Network Diagnostic\xpnetdiag.exe:*:Enabled:@xpsp3res.dll,-20000"
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile\AuthorizedApplications\List\%windir%\system32\sessmgr.exe: "%windir%\system32\sessmgr.exe:*:enabled:@xpsp2res.dll,-22019"
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\EnableFirewall: 0x00000001
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\DisableNotifications: 0x00000000
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\DoNotAllowExceptions: 0x00000000
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\AuthorizedApplications\List\%windir%\Network Diagnostic\xpnetdiag.exe: "%windir%\Network Diagnostic\xpnetdiag.exe:*:Enabled:@xpsp3res.dll,-20000"
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\AuthorizedApplications\List\%windir%\system32\sessmgr.exe: "%windir%\system32\sessmgr.exe:*:enabled:@xpsp2res.dll,-22019"
HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Setup\InterfacesUnfirewalledAtUpdate\All: 0x00000001
HKLM\SYSTEM\CurrentControlSet\Enum\Root\LEGACY_SHAREDACCESS\0000\DeviceDesc: "Windows Firewall/Internet Connection Sharing (ICS)"
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile\EnableFirewall: 0x00000001
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile\DisableNotifications: 0x00000000
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile\DoNotAllowExceptions: 0x00000000
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile\AuthorizedApplications\List\%windir%\Network Diagnostic\xpnetdiag.exe: "%windir%\Network Diagnostic\xpnetdiag.exe:*:Enabled:@xpsp3res.dll,-20000"
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile\AuthorizedApplications\List\%windir%\system32\sessmgr.exe: "%windir%\system32\sessmgr.exe:*:enabled:@xpsp2res.dll,-22019"
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\EnableFirewall: 0x00000001
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\DisableNotifications: 0x00000000
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\DoNotAllowExceptions: 0x00000000
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\AuthorizedApplications\List\%windir%\Network Diagnostic\xpnetdiag.exe: "%windir%\Network Diagnostic\xpnetdiag.exe:*:Enabled:@xpsp3res.dll,-20000"
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\AuthorizedApplications\List\%windir%\system32\sessmgr.exe: "%windir%\system32\sessmgr.exe:*:enabled:@xpsp2res.dll,-22019"
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Setup\InterfacesUnfirewalledAtUpdate\All: 0x00000001

No more security scans! These deleted files ensure that the automatic malware scans will no longer run. Also modified and removed are firewall control settings. There don't seem to be any obvious changes to core system code such as DLLs that would be used by a keylogger - but I could easily be missing something. With the process monitor I see the file and registry modifications in real time, but nothing else jumps out at me.

Here is the baseline tcpdump, the capture tcpdump and the full filesystem and registry diff.

Proxies are servers that act as intermediaries between clients and other servers. Requests made to the proxy server are made to the content or service provider by the proxy server on behalf of the client.

Before permitting a request on behalf of a client, a proxy server can apply arbitrary rules to filter or authorize traffic. Validated requests can also be altered, as well as content returned to the client.

Proxies can be used for anonymity, bypassing geographical filters, logging, authorization, filtering and caching.

Reverse proxies work on behalf on content or service providers to cache dynamically generated content and load balance applications. Reverse proxies can also change content served, but generally do not except sometimes compression.

The Tor (The Onion Router) is a layered proxy system intended for anonymity and bypassing harmful filtering. One example of its use is to bypass the Great Firewall of China, a politically purposed firewall administrated by the People's Republic of China, censoring politically sensitive material. The Tor proxy randomly bounces traffic through a network of global volunteer relays. Tor traffic is routed through two nodes before reaching the destination server. The first Tor relay knows the clients IP, but is not privy to the encrypted data. The intermediary servers are unaware of the origin or data. Exit relays know transmitted data, but not the origin.

This presentation requires a preconfigured Squid server.

(ports are arbitrary everywhere, but standardized by the Internet Assigned Numbers Authority (IANA), see /etc/services)

# Session/Application layer SOCKet Secure (SOCKS) 5 proxy
#

# Establish a TCP stream to dylansserver.com on port 22
# Start a SOCKS5 proxy server on port 8080
#
# ssh        # secure shell
# -f         # background command
# -N         # don't execute a remote command
#
ssh -fND 8080 dylan@dylansserver.com

# Make an HTTP GET request using local port 8080
#
curl --socks5 localhost:8080 whatismyip.org

# Use root privileges to watch TCP packets
#
# sudo       # execute as root user
# iptables   # dump traffic on a network
# -vvv       # very very verbose
# -A         # print each packet in ASCII
# -i lo      # only print packets using the local network interface
# -s 0       # don't truncate packets
# port 8080  # only print packets using port 8080
#
sudo tcpdump -vvvA -ilo -s0 port 8080

# Watch HTTP GET requests sent over proxy
#
sudo tcpdump -vvvA -ilo -s0 port 8080 | grep -A 10 GET

# Make an HTTP request over proxy
#
curl --socks5 localhost:8080 whatismyip.org
curl --socks5 127.0.0.1:8080 whatismyip.org
curl --socks5-hostname localhost:8080 whatismyip.org # DNS on proxy

# Use proxy with browser
#
v ~/.config/luakit/globals.lua


# Network layer tranparent Squid proxy
# 
# (connections are whitelisted in /etc/squid/squid.conf,
# `acl client <IP>
# http_access allow client`)
#

# Set kernel ip_forward parameter
sudo sysctl net/ipv4/ip_forward=1

# Route traffic through remote proxy on local machine
#
# iptables               # administration tool for IPv4 packet filtering and NAT
# -t nat                 # refer to "nat" packet matching table
# -A OUTPUT              # add to "OUTPUT" table
# -p tcp                 # match only TCP packets
# --dport 80             # match only packets destined for port 80
# -jDNAT                 # jump to "DNAT" target
# --to 50.16.219.8:3128  # destination network address
#
sudo iptables -tnat -AOUTPUT -ptcp --dport 80 -jDNAT --to 50.16.219.8:3128

# -jDNAT          # jump to "REDIRECT" target
# --to-port 3128  # new destination port
#
sudo iptables -tnat -AOUTPUT -ptcp --dport 80 -jREDIRECT --to-port 3128

# Make an HTTP request over proxy
#
curl whatismyip.org

# What's an error message look like now?
#
curl 'thisisnotawebsite!@#$%^&*()_'

# Use proxy with browser
#

# Watch it happen
#
ssh dl -t "sudo tail -f /var/log/squid/access.log"

This is how I connected to the Penn. State wireless network (2.0) with GNU/Linux and wpa_supplicant. Here is the network information from PSU:

SSID: psu
Security: WPA2-Enterprise
Encryption: AES
Authentication Type: EAP-TTLS
Authentication Protocol: PAP
Certificate Authority: Thawte Premium Server CA

The following configuration block needs to be entered into /etc/wpa_supplicant.conf:

network={
    ssid="psu"
    identity="your_access_id_here"
    password="your_password_here"
    key_mgmt=WPA-EAP
    eap=TTLS
    phase2="auth=PAP"
    ca_cert="/etc/ssl/certs/Thawte_Premium_Server_CA.pem"
}
Check that your wireless card is up with `ifconfig` and that you are in range of an access point with `iwlist scan`. You can now connect using wpa_supplicant and dhcpcd. Change the following line to use your wireless driver and interface.
wpa_supplicant -B -D wext -i wlan0 -c /etc/wpa_supplicant.conf
dhcpcd wlan0

Looking around my iPhone file system, I found the SMS database at /var/mobile/Library/SMS/sms.db. I decided to write a script to poll for new text mesages and notify me on my monitor. First things first, I scp'd the file over to my computer for inspection. Apple uses SQLite for its databases, which was new to me, so after an apt-cache search sqlite, apt-get install sqlite3, I opened up the database in my terminal.

It took me some stumbling to disect the tables; the SQLite syntax is quite different from MySQL. I eventually found the .schema command to dump the table structure.

sqlite> .schema
CREATE TABLE _SqliteDatabaseProperties (key TEXT, value TEXT, UNIQUE(key));
CREATE TABLE group_member (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, group_id INTEGER, address TEXT, country TEXT);
CREATE TABLE message (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, address TEXT, date INTEGER, text TEXT, flags INTEGER, replace INTEGER, svc_center TEXT, group_id INTEGER, association_id INTEGER, height INTEGER, UIFlags INTEGER, version INTEGER, subject TEXT, country TEXT, headers BLOB, recipients BLOB, read INTEGER, smsc_ref INTEGER, dr_date INTEGER);
CREATE TABLE msg_group (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, type INTEGER, newest_message INTEGER, unread_count INTEGER, hash INTEGER);
CREATE TABLE msg_pieces (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, message_id INTEGER, data BLOB, part_id INTEGER, preview_part INTEGER, content_type TEXT, height INTEGER, version INTEGER, flags INTEGER, content_id TEXT, content_loc TEXT, headers BLOB);
CREATE INDEX message_flags_index ON message(flags);
CREATE INDEX message_group_index ON message(group_id, ROWID);
CREATE INDEX pieces_message_index ON msg_pieces(message_id);
CREATE TRIGGER delete_message AFTER DELETE ON message WHEN NOT read(old.flags) BEGIN UPDATE msg_group SET unread_count = (SELECT unread_count FROM msg_group WHERE ROWID = old.group_id) - 1 WHERE ROWID = old.group_id; END;
CREATE TRIGGER delete_newest_message AFTER DELETE ON message WHEN old.ROWID = (SELECT newest_message FROM msg_group WHERE ROWID = old.group_id) BEGIN UPDATE msg_group SET newest_message = (SELECT ROWID FROM message WHERE group_id = old.group_id AND ROWID = (SELECT max(ROWID) FROM message WHERE group_id = old.group_id)) WHERE ROWID = old.group_id; END;
CREATE TRIGGER delete_pieces AFTER DELETE ON message BEGIN DELETE from msg_pieces where old.ROWID == msg_pieces.message_id; END;
CREATE TRIGGER insert_newest_message AFTER INSERT ON message WHEN new.ROWID >= IFNULL((SELECT MAX(ROWID) FROM message WHERE message.group_id = new.group_id), 0) BEGIN UPDATE msg_group SET newest_message = new.ROWID WHERE ROWID = new.group_id; END;
CREATE TRIGGER insert_unread_message AFTER INSERT ON message WHEN NOT read(new.flags) BEGIN UPDATE msg_group SET unread_count = (SELECT unread_count FROM msg_group WHERE ROWID = new.group_id) + 1 WHERE ROWID = new.group_id; END;
CREATE TRIGGER mark_message_read AFTER UPDATE ON message WHEN NOT read(old.flags) AND read(new.flags) BEGIN UPDATE msg_group SET unread_count = (SELECT unread_count FROM msg_group WHERE ROWID = new.group_id) - 1 WHERE ROWID = new.group_id; END;
CREATE TRIGGER mark_message_unread AFTER UPDATE ON message WHEN read(old.flags) AND NOT read(new.flags) BEGIN UPDATE msg_group SET unread_count = (SELECT unread_count FROM msg_group WHERE ROWID = new.group_id) + 1 WHERE ROWID = new.group_id; END;

Trying some queries, I discovered that I had over 100 messages marked "unread" in the database, in spite of having no unread messages on my phone. I suspect this is due to buggy interactions from other packages I had installed. I needed to start with a clean slate, but `update message set read=1` was giving me the error "Error: no such function: read". Eventually I realized this was referring to a trigger. Unfortunately, SQLite does not give any way to temporarily disable a trigger, so I was forced to manually drop the triggers, run my update statement, and recreate the triggers. This can be done locally and copied back over (will require a reboot), or on the phone after installing the sqlite3 frontend there.

sqlite> drop trigger mark_message_read;
sqlite> drop trigger mark_message_unread;
sqlite> update message set read=1;
sqlite> CREATE TRIGGER mark_message_read AFTER UPDATE ON message WHEN NOT read(old.flags) AND read(new.flags) BEGIN UPDATE msg_group SET unread_count = (SELECT unread_count FROM msg_group WHERE ROWID = new.group_id) - 1 WHERE ROWID = new.group_id; END;
sqlite> CREATE TRIGGER mark_message_unread AFTER UPDATE ON message WHEN read(old.flags) AND NOT read(new.flags) BEGIN UPDATE msg_group SET unread_count = (SELECT unread_count FROM msg_group WHERE ROWID = new.group_id) + 1 WHERE ROWID = new.group_id; END;

With the table up to date, the next step was simple:

while true;
  do ssh mobile@10.0.0.2 sqlite3 "Library/SMS/sms.db 'select text from message where read=0;'";
  sleep 3s;
done;

This BASH command assumes that your SSH key is already trusted by the mobile user on the phone. After asking a friend to send me a test text, I was happy to see the text message output. The next step was to find the name of the sender. I found the contact database at '/var/mobile/Library/AddressBook/AddressBook.sqlitedb'.

The database structure took me a lot of puzzling. Contact numbers are stored in a table separate from contact name information. Most painfully, SMS sender addresses are prepended with a plus sign in the message table; It took me some time to realize why I was getting zero results when joining the contact table. Also worth noting is that message.flags is set to 2 for received messages.

SELECT contact.first, contact.last, message.text
  FROM message LEFT JOIN (
    SELECT ABMultiValue.value,ABPerson.first,ABPerson.last
      FROM ABPerson JOIN ABMultiValue
      ON ABmultiValue.record_id=ABPerson.ROWID ) contact
    ON contact.value=SUBSTR(message.address,2,LENGTH(message.address) )
    where message.read=0 AND message.flags=2;

All that remained was to wrap the text message into a pretty NotifyOSD notification.

#!/bin/bash

while true;
  do query=`ssh mobile@10.0.0.2 \
           "sqlite3 /var/mobile/Library/SMS/sms.db '
            ATTACH DATABASE \"/var/mobile/Library/AddressBook/AddressBook.sqlitedb\" AS sms;
            SELECT contact.first, contact.last, message.text
              FROM message LEFT JOIN ( 
                SELECT ABMultiValue.value,ABPerson.first,ABPerson.last 
                  FROM ABPerson JOIN ABMultiValue 
                  ON ABmultiValue.record_id=ABPerson.ROWID ) contact 
                ON contact.value=SUBSTR(message.address,2,LENGTH(message.address) ) 
                where message.read=0 AND message.flags=2;
             ;'"`
    if [ -n "$query" ]
    then
    IFS=$'\n'
    for line in $query
      do
        record=(`echo $line | tr "|" "\n"`)
        echo "${record[0]} ${record[1]} said '${record[2]}'"
        python -c "import pynotify; notification = pynotify.Notification('${record[0]} ${record[1]}','${record[2]}', '/home/dylan/scripts/images/sms.png'); notification.show();";
    done;
    unset query;
    fi;
  sleep 10s;
done;

The weak point here is that the script depends on knowing the LAN address of the phone, which is likely to change as you move on and off of the network. The script is also vulnerable to injection attacks. It also doesn't handle multiple unread messages or images. But it's a start.

2011-03-25 / Migrating Domains

I set up dylanstestserver.com as a test domain. It's a pain to type out, so I planned on switching to dylansserver.com. By the time I got around to this though, the first domain has already been indexed by Google. My deployment process was also engrained with the original domain. This is how I made the migration without losing standing in search engines.

First I added a new virtualhost to Apache's httpd.conf.

I then checked out a new copy of the live branch into a new folder in the webroot.

Next I added a new A record to my DNS, pointing from the new domain to my elastic IP.

Then I restarted Apache and checked that the new address worked. Then I replaced the .htaccess in the original DocumentRoot with a new directive.

v .htaccess
ggcG
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\.dylansserver\.com$
RewriteRule (.*) http://www.dylansserver.com/$1 [R=301,L]

I moved the repository to it's new folder and changed it's post-recieve hook to reflect the new working directory. I also needed to edit my local gitosis-admin/gitosis.conf (and push the changes) to grant myself access to the new repository. Finally, I updated my local repository to point to the new destination.

git remote set-url origin git@dylansserver.com:dylansserver