Didier Stevens

Tuesday 3 July 2007

The BlockSite Firefox Add-on

Filed under: Reverse Engineering — Didier Stevens @ 8:00

The Firefox add-on BlockSite by Erik van Kempen allows you to maintain a blacklist of sites you want to block for surfing. I extended his add-on with a whitelist: in stead of specifying the sites you want to block, you can decide to specify the sites you want to allow, and all other sites will be blocked. Erik has integrated my code in his add-on:

Version 0.5 — December 30, 2006 — 34 KB

[+] Whitelist/Blacklist feature (by Didier Stevens): Choose if the list is a blacklist or a whitelist.
[~] Password protection still pending (unfortunately), most probably in next major release

Reverse engineering a Firefox add-on is really simple. The file format for add-ons, XPI, is in fact a ZIP file. After unzipping the XPI file, you’ll find a JAR file (again, this is also based on ZIP). Unzip the JAR file and then you can analyze the JavaScript and XUL files.

You can also load an unpacked Firefox add-on in Firefox to test and debug it, how is explained here.

Wednesday 20 June 2007

UserAssist Q&A

Filed under: Reverse Engineering — Didier Stevens @ 6:29

I was a speaker at the local ISSA chapter last Monday. My talk explained how to use my UserAssist tool for forensic analysis. The audience had great questions for me at the Q&A, some of which I want to share here.

Does switching to the “Classic Start Menu” prevent the logging of data in the UserAssist registry keys?
No, it doesn’t. When you use the classic start menu (the start menu from Windows NT & 2000, without a frequently used programs pane), Windows explorer still continues to monitor and log the programs you execute. When you switch back to the “modern” start menu, you’ll see several of the programs you recently used in the frequently used programs pane.

Does disabling the Active Desktop prevent the logging of data in the UserAssist registry keys?
No, it doesn’t. In fact, I use the following litmus test to know if starting a program is recorded in the UserAssist keys: did a user perform the action through Windows explorer? If a user did, then the action is logged.
The only trick I know to permanently disable the UserAssist keys is this one:

  • add a new subkey “Settings” under the “UserAssist” key
  • add a new DWORD value “NoLog” and set it to one.

My UserAssist tool allows you to toggle this setting via a simple menu command.

One audience member asked me if I was really sure that using a mandatory user profile (NTUSER.MAN) implied that the UserAssist registry keys where not persisted.

I promised him that I would test it, and I must admit that I was wrong.
A mandatory user profile is a profile that the user can change, but the changes are not saved when the user logs out.
This is how I tested the UserAssist tool with a mandatory user profile:

  1. a domain controller
  2. a member workstation
  3. a domain user with the profile path set to a share on the DC
  4. renaming NTUSER.DAT to NTUSER.MAN
  5. log on to the workstation with the domain user account
  6. start some programs
  7. analyse the profiles

I discovered that the NTUSER.MAN file in the local copy of the profile (file NTUSER.MAN in c:\document and settings\user on the workstation) had been modified, and that the UserAssist keys listed the program I had executed. As expected, the NTUSER.MAN file on the DC in the roaming user profile was not modified. And when I logged on to the workstation a second time, the local profile was overwritten with the mandatory profile, as expected.

So you can use the NTUSER.MAN file in a forensic investigation, but some restrictions apply:

  1. use the local copy, not the file hosted on the DC (in fact, you should compare the UserAssist entries from both files, because some entries in the UserAssist keys might come from the original NTUSER.MAN file)
  2. make sure to grab a copy before the user logs on again, otherwise the file will be overwritten (you could try to recover it)
  3. entries in the UserAssist keys will pertain to the last session of the user, it is not a complete history of all the sessions (and remember restriction 1, comparing the profiles)

Raymond Chen has started a series of blog posts about the Start Menu’s frequently used programs. Keep in mind that he discusses the rules that govern the display and ranking of programs on the start menu, and not actually the rules for collecting the data (i.e. UserAssist keys). What he calls points is not the same as the counter in a UserAssist entry.

Monday 12 February 2007

Reverse Engineering Mentoring

Filed under: Reverse Engineering — Didier Stevens @ 18:19

I started mentoring someone on Reverse Engineering. We use a Wiki to communicate, feel free to join as a mentor or mentee.

Tuesday 19 December 2006

Teaching a SpiderMonkey a new trick

Filed under: Reverse Engineering — Didier Stevens @ 9:28

Have you read NJ Verenini post on Websense’s Blog were he explains how to use SpiderMonkey to deobfuscate Javascript? As SpiderMonkey has no document object, Verenini shows a way to define your own document object to support document.write().

I’ve adapted the SpiderMonkey source code to include the document object. Not that my method is better than Verenini’s, I just wanted to play with SpiderMonkey.
An upcoming “Virus Lab” post will explain how I use this adapted SpiderMonkey, but for now I want to explain how I proceeded to modify SpiderMonkey.

If you’re not familiar with the SpiderMonkey source-code (like me), were do you start? I want to implement a document object with a write method. Is there something similar in JavsScript? Take a look at the Math object.

js
js>Math
[object Math]

The Math object has several methods, like sin:
js> Math.sin(3.1415926/2)
0.9999999999999997

document does not exist:
js> document
2: ReferenceError: document is not defined

The trick is to add a document object that has the same behaviour as the Math object (i.e. same members), and if this works, we adapt the document object by removing all Math members and adding a write method.

Greping for Math in the source code reveals that the object is defined in jsmath.c and jsmath.h. This is good, the Math object is defined in it’s own source files. So we will make our own source files for document based on Math: copy jsmath.[ch] to jsdocument.[ch]. Then edit jsdocument.[ch] and replace Math with document (there are some execeptions, like math.h).

Then we add jsdocument.[ch] to the makefile.
Greping for jsmath.h reveals that it’s included in jsapi.c. A quick search for
Math in jsapi.c reveals this code:
js_InitMathClass(cx, obj) &&
{js_InitMathClass, ATOM_OFFSET(Math)},

We add our own code:

js_InitDocumentClass(cx, obj) &&
{js_InitDocumentClass, ATOM_OFFSET(Document)},

Now when we build, we’ll get an error because we use a Document ATOM that we didn’t define. A bit of searching in the source code shows that atoms are defined in jsatom.[ch]. We search for Math and add extra code for Document.
And now the build succeeds!

js
js> document
[object document]
js> document.sin(3.1415926/2)
0.9999999999999997

Now we have to remove all members and add our own write method, but this is for another post, where I’ll publish my modified spidermonkey (it’s GPLed).

Reversing with the commented source code is not so difficult as reversing binaries, especially the patching process. If you want to add a new feature, look for an existing similar feature and do an “intelligent” copy-paste of the source code.

Once upon a time, long ago, I read the Dragon Book, and this also explains how I was able to quickly understand how to modify SpiderMonkey.

Sunday 19 November 2006

OllyStepNSearch v0.6.1

Filed under: My Software,Reverse Engineering — Didier Stevens @ 9:31

I’ve released a bugfix for my OllyDbg plugin OllyStepNSearch.

Thanks to Ngan Truong for finding and reporting bugs in the help function. My program worked with an uninitialized pointer, shame on me.

Monday 6 November 2006

Challenger

Filed under: My Software,Reverse Engineering — Didier Stevens @ 6:58

Challenger is a small program I’ve used in reverse-engineering challenges (without success ;-) ). It performs dictionary and brute-force attacks on the reverse-engineering challenge program.

The programs used in reverse-engineering challenges are usually console programs. You start the program, it asks for the password (standard output), you type the password (standard input), the program responds and ends.

level1.png

Challenger automates this process: it runs the program against a list of passwords (dictionary) or it tries out all combinations (brute-force).

Challenger is also a console program taking command-line arguments.

  • /executable:program is the only required argument, you use it to specify the program to be challenged
  • /arguments:parameters is needed when the program to be challenged also takes command-line arguments. You cannot provide them with the /executable argument, you need to use the /arguments argument. This parameter is optional
  • /log:file allows you to write all results to a file. Results are always displayed on the console, with /log:log.txt, all results are also appended to file log.txt
  • /dictionary:file is used to perform a dictionary attack and specify the file containing the words to test as a password
  • /bruteforce:password is used to specify the starting password of a brute-force attack. By default, Challenger will execute a brute-force attack, starting with password a.
  • /characters:characters allows you to specify the characters used in a brute-force attack. By default, this is abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
  • /search:keyword allows you to specify a keyword that will stop the attack. Once this keyword is detected in the output of the challenged program, Challenger will stop the attack. Searching for the keyword is case-sensitive. Challenger will go on indefinitely if no keyword is provided, it will only report each time the challenged program produces output it has not produced before. If you now what the challenged program outputs when you provide the correct password, you use this search argument to look for it. If you don’t know it, you just let Challenger run and review its output
  • /timeout:milliseconds allows you to specify the timeout for the challenged program. By default, this is 100 ms: if the challenged program runs longer than 100 ms, Challenger will stop it.
  • /heartrate:count allows you to define how often Challenger writes status info to the log. By default, it’s every 1000 passwords tested

Here is an example where I use my program on F-secure’s Khallenge level 1 program with a tiny wordlist from Openwall. Since I don’t know the output produced by the program when a correct password is entered, I don’t use the search argument: challenger /executable:level1.exe /dictionary:lower.lst /log:log.txt

Here is the result:

Start     > 2/11/2006 21:49:45
Start     > Challenger v1.0.0.0 (https://DidierStevens.com)

Config    > dictionary

Config    > file: lower.lst

Config    > executable: level1.exe

Config    > arguments:

Config    > timeout: 100

Config    > heartbeat: 1000

Config    > search: not enabled

Config    > log: log.txt

New output> a -> ASSEMBLY'06 REVERSE ENGINEERING CHALLENGE
  *** LEVEL 1 ***  Challenge Copyright (c) 2006 F-Secure Corporation
For more information, please see http://www.f-secure.com/weblog/asm.htm
Enter the password:
Try another one.
Heartbeat > 2/11/2006 21:49:58 counter: 1000 password: anonymity

Heartbeat > 2/11/2006 21:50:11 counter: 2000 password: barge

Heartbeat > 2/11/2006 21:50:23 counter: 3000 password: brass

Heartbeat > 2/11/2006 21:50:34 counter: 4000 password: cement

Heartbeat > 2/11/2006 21:50:45 counter: 5000 password: compendia

Heartbeat > 2/11/2006 21:50:57 counter: 6000 password: cuisine

Heartbeat > 2/11/2006 21:51:10 counter: 7000 password: disavow

Heartbeat > 2/11/2006 21:51:21 counter: 8000 password: emergency

Heartbeat > 2/11/2006 21:51:34 counter: 9000 password: feeble

Heartbeat > 2/11/2006 21:51:45 counter: 10000 password: g

Heartbeat > 2/11/2006 21:51:58 counter: 11000 password: handbarrow

Heartbeat > 2/11/2006 21:52:11 counter: 12000 password: identical

Heartbeat > 2/11/2006 21:52:23 counter: 13000 password: ion

Heartbeat > 2/11/2006 21:52:35 counter: 14000 password: lev

Heartbeat > 2/11/2006 21:52:47 counter: 15000 password: meatball

Heartbeat > 2/11/2006 21:53:00 counter: 16000 password: naivete

New output> obvious -> ASSEMBLY'06 REVERSE ENGINEERING CHALLENGE
  *** LEVEL 1 ***  Challenge Copyright (c) 2006 F-Secure Corporation
For more information, please see http://www.f-secure.com/weblog/asm.htm
Enter the password:
Yup, thats it!
To continue, send an email to:   level1-solution_was_obvious@khallenge.com
Heartbeat > 2/11/2006 21:53:13 counter: 17000 password: orthograph

Heartbeat > 2/11/2006 21:53:26 counter: 18000 password: pestle

Heartbeat > 2/11/2006 21:53:39 counter: 19000 password: presume

Heartbeat > 2/11/2006 21:53:51 counter: 20000 password: recount

Heartbeat > 2/11/2006 21:54:04 counter: 21000 password: sandy

Heartbeat > 2/11/2006 21:54:16 counter: 22000 password: sis

Heartbeat > 2/11/2006 21:54:29 counter: 23000 password: stomp

Heartbeat > 2/11/2006 21:54:42 counter: 24000 password: tenor

Heartbeat > 2/11/2006 21:54:54 counter: 25000 password: tunisia

Heartbeat > 2/11/2006 21:55:07 counter: 26000 password: venerate

Heartbeat > 2/11/2006 21:55:19 counter: 27000 password: withhold

For the first password (a), the challenge program outputs “Try another one.”. The challenge program outputs this for every password in the list, until the password “obvious” is tested. When obvious is entered as the password, the output of the challenge program is “Yup, thats it!”, allong with the e-mail address. Since no /search argument was provided, the Challenger program continues until the wordlist is exhausted.

The “New output>” line lists the exact output produced by the tested program, except that all newlines are replaced by a space character to make it fit on one line (for clarity, I’ve added the newlines back in this example).


Had I known that the level 1 program outputed “Yup, thats it!” when the correct password is entered, I could have issued this command: challenger /executable:level1.exe /dictionary:lower.lst /log:log.txt /search:Yup

And the program would stop once the correct password was found:

Found > counter: 16663 password: obvious ASSEMBLY’06 REVERSE …

It’s also possible to start a brute-force attack, like this: challenger /executable:level1.exe

This will start with password ‘a’ and try all alphanumeric combinations.

During the reversing of the level 3 challenge of F-Secure’s Khallenge, I discovered that only characters 2, 4, 6 and 8 were used in the password. So I used my Challenger program to try all combinations, while I continued reversing:

challenger /executable:level3.exe /bruteforce:2 /characters:2468 /log:log.txt

Output:

Start     > 2/11/2006 22:09:25

Start     > Challenger v1.0.0.0 (https://DidierStevens.com)

Config    > brute force

Config    > start: 2

Config    > characters: 2468

Config    > executable: level3.exe

Config    > arguments:

Config    > timeout: 100

Config    > heartbeat: 1000

Config    > search: not enabled

Config    > log: log2.txt

New output> 2 -> ASSEMBLY'06 REVERSE ENGINEERING CHALLENGE
  *** LEVEL 3 ***  Challenge
Copyright (c) 2006 F-Secure Corporation
For more information, please see http://www.f-secure.com/weblog/asm.htm
Enter password:
Nope.

Heartbeat > 2/11/2006 22:09:40 counter: 1000 password: 66428

Heartbeat > 2/11/2006 22:09:53 counter: 2000 password: 264868

Heartbeat > 2/11/2006 22:10:06 counter: 3000 password: 464628

Heartbeat > 2/11/2006 22:10:20 counter: 4000 password: 664268

Heartbeat > 2/11/2006 22:10:34 counter: 5000 password: 862828

Heartbeat > 2/11/2006 22:10:48 counter: 6000 password: 2262468

Heartbeat > 2/11/2006 22:11:02 counter: 7000 password: 2462228
...

But I found the correct password through reversing before my Challenger program found it with brute-force: the password was so long that my program would take too long…

Challenger is written in C# with Microsoft Visual C# 2005 Express Edition.

Download:

Challenger_V1_0_0.zip (https)

MD5: FC71CAA3F99CB6EE9094098D60B7E4C3

Monday 30 October 2006

OllyStepNSearch v0.6.0

Filed under: Reverse Engineering — Didier Stevens @ 10:06

I’ve released a new version of my OllyDbg plugin called OllyStepNSearch.

The new features are:

  • an options dialog
  • Disable After Break option
  • Search in Information Pane
  • a new help function

And this time, there is also a demo movie here on YouTube, a hires (XviD) version can be found here.

Monday 2 October 2006

Reversing an anonymous proxy

Filed under: Reverse Engineering — Didier Stevens @ 10:08

Unipeak is a free anonymous proxy, it encodes the URLs like this:

http://www.unipeak.com/gethtml.php?_u_r_l_=aHR0cDovL3d3dy5nb29nbGUuY29t (this is http://www.google.com).

Suppose you had to reverse engineer the encoding scheme, how could you proceed? You are in a comfortable position, because you can execute a Chosen Plaintext Attack.

First we need to find out if the encoding scheme is reversible, because it could also be a hash or another key used to access the cache of the proxy (if it’s a caching proxy).

So we add a letter ‘a’ to the encoded URL and see what Unipeak replies:

http://www.unipeak.com/gethtml.php?_u_r_l_=aHR0cDovL3d3dy5nb29nbGUuY29ta

and we see the Google website.

So it’s not a hash, it’s reversible.

We add another ‘a’:

http://www.unipeak.com/gethtml.php?_u_r_l_=aHR0cDovL3d3dy5nb29nbGUuY29taa

and now we get an error message:

unable to connect to http://www.google.comi:80/

It’s definitely reversible.

Searching with Google via Unipeak gives another URL:

http://www.unipeak.com/gethtml.php?_u_r_l_=aHR0cDovL3d3dy5nb29nbGUuY29tOjgwL3NlYXJjaA%3D%3D&hl=en&q=unipeak&btnG=Google+Search

This URL starts with the same sequence as our first URL, so it’s probably a simple encoding scheme where the characters are processed from left to right.

So let’s start another experiment, we enter this URL: aaaaaaaaaa

The encoded URL is:

http://www.unipeak.com/gethtml.php?_u_r_l_=YWFhYWFhYWFhYQ==

Very interesting, we also get a repeating pattern, but the cycle is 4 characters long (YWFh).

Ok, now let’s use a trick: we enter a series of characters Us. The character U is special, its ASCII encoding written in binary is 01010101. Thus UU is 0101010101010101, UUU is 010101010101010101010101, …

Entering UUUUUUUUUU gives us:

http://www.unipeak.com/gethtml.php?_u_r_l_=VVVVVVVVVVVVVQ==

Another nice sequence!

This is a strong indication that the encoding is done at the bit level: the input is seen as a stream of bits, the bits are grouped in groups of X bits (where X is unknown). Each group is transformed to another sequence of bits by a function F, and the same function F is used for each group. We can also assume that X is even, otherwise we wouldn’t get a sequence of identical characters, but a sequence of identical pairs.

We perform some extra tests to prove (or disprove) our hypothesis.

We encode sequences of different lengths and compare the length of the cleartext and the cyphertext: the ratio is about 3 to 4, 3 input characters generate 4 output characters (BTW, the fact that we get a cycle of 4 characters for aaaaa… is also a strong indication for this ratio).

So X can be 3, 6, 9, 12, … . Except we assume X is even: 6, 12, …

Let’s test X = 6.

We try URL 000, this gives us MDAw (http://www.unipeak.net/gethtml.php?_u_r_l_=MDAw)
Now 000 is 30 30 30 (in hexadecimal ASCII)

or 00110000 00110000 00110000 in binary, grouped in 8 bits (1 byte)
or 001100 000011 000000 110000 in binary, but grouped in 6 bits (X = 6)

Now increment the first group:

001101 000011 000000 110000

or 00110100 00110000 00110000 in binary, grouped in 8 bits (1 byte)

or 34 30 30 (in hexadecimal ASCII)

or 400

So 000 becomes 400 when you increment the first group of 6 bits.

Testing URL 400 gives NDAw: changing the first 6 bits changes only the first character!

We do the same for the remaining groups:

000 -> 0@0 -> MEAw

000 -> 00p -> MDBw

000 -> 001 -> MDAx
So X is indeed 6, because changing a group of 6 bits at a time changes only one encoded character.

And we can also assume that function F is linear, because incrementing the input with 1 increments the output with 1 (M -> N, D -> E, A -> B and w -> x).

Now we could try every possible permutation of 6 bits, and see what the corresponding encoded character is.

We would discover that F maps 0..63 to ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

And this is a very common encoding scheme: base64

Monday 18 September 2006

A Windows Live CD plugin for my UserAssist utility

Filed under: Reverse Engineering — Didier Stevens @ 15:24

I’ve published a BartPE plugin for my UserAssist utility, you can download it here (https, MD5 D43E519B7BCE90F31EB54884E7AA75C1 DE9D576C0F5FF8D33E039A5064BD8AFF). And I’m posting another movie.
Windows Live CDs are a popular troubleshooting and forensic investigation tool, they allow you to boot a (Windows) PC from a CD. Bart Lagerweij developed BartPE, a tool to create a Windows Live CD (a Windows “pre-install” environment CD), and several people build their own tools based on his work. The Ultimate Boot CD for Windows is based on BartPE.

Bart’s PE has an open architecture, you can integrate your own tools by making a dedicated plugin. My UserAssist utility uses the Microsoft .NET Framework 2.0, which is not supported by BartPE. You need to add Colin Finck’s Microsoft .NET Framework 2.0 plugin to the Ultimate Boot CD for Windows plugins to use my plugin.

You add plugins to the Ultimate Boot CD for Windows with the Plugins dialog:

plugins.PNG

Afterwards you create your own Ultimate Boot CD for Windows (you have to provide your own licensed Windows XP SP2 CD).

The UserAssist utility is located in the Programs/Forensics menu (when you boot from the CD):

screenshot.png

The UserAssist utility displays the activity of the current user at startup. This is of course not useful for a Live CD, because the profile of the current user of a Live CD is not persisted.

You will have to load the NTUSER.DAT registry hive of the user you want to investigate in RegEdit and export it to a reg file, before you can import it in UserAssist (I plan to add a feature to UserAssist to automate this task).

userassist.PNG

I’ve tested my plugin with the Ultimate Boot CD for Windows, not with BartPE.
There’s a movie here on YouTube, or hires (XviD) here showing you how to do this for user Employee.

Sunday 27 August 2006

Hiding the password

Filed under: Reverse Engineering — Didier Stevens @ 13:35

Where and how do you store credentials used by applications? There is no easy solution in Windows. We all agree that storing cleartext passwords in the source code or a configuration file is a bad idea. We should encrypt these passwords. But what do we do with the decryption key? Is the decryption algorithm easy to break?

I wondered how difficult it would be to extract the password from McAfee’s EPO agent installation program. It turns out to be rather easy, it took me about 3 hours of debugging with OllyDbg. And then I developed a OllyDbg plugin to automate the task.

McAfee’s EPO provides centralized anti-virus management. It connects to EPO agents installed on each managed machine. One can install the EPO agent centrally via the EPO manager or locally by copying the EPO agent installation program to the machine and executing it with local admin credentials. McAfee provides a solution when the install has to be done by a user without administrative privileges. The necessary credentials are stored in the EPO agent installation program.

Here’s how I proceeded to extract the password from the EPO agent installation program.

First I create 2 EPO agent installation programs with different passwords (password and P@ssw0rd) and I compare the files with JojoDiff.

jdiff-w32 -lr FramePkg-1.exe FramePkg-2.exe:

1        1 EQL 25792
25793    25793 MOD 64
25856    25856 EQL 1487303

The files are identical, except for 64 bytes.

I extract the 64 bytes from FramePkg-1.exe with my binary tools:

middle FramePkg-1.exe 25792 64 password.bin

I examine password.bin with XVI32 and discover it’s ASCII:

jXoAADpNAADvOY9WkCYp0xOk6ON8lFjm4af+X4+8IVL6vuLPafhTAuyfdv52BG4e

This must be the encrypted password (P@ssw0rd).

I debug FramePkg-1.exe with OllyDbg, looking for the password (encrypted and cleartext). It takes an hour to discover that FramePkg-1.exe extracts several files to a temporary folder and starts another program it extracted: FrmInst.exe
This program takes several arguments, one of which is the encrypted password:

/CreateService="C:mydirsEPOAgentEPOAgentFramePkg-1.exe"
/LOGDIR=C:DOCUME~1ADMINI~1LOCALS~1TempNAILogs
/Cleanup2="C:DOCUME~1ADMINI~1LOCALS~1Tempunz6.tmp"
/EmbeddedUsername="administrator"
/EmbeddedDomain="."
/EmbeddedPassword="jXoAADpNAADvOY9WkCYp0xOk6ON8lFjm4af+X4+8IVL6vuLPafhTAuyfdv52BG4e"
/Install=Agent

I debug FrmInst.exe with these arguments, and after 2 hours I find register EBP pointing to ASCII string P@ssw0rd. This is at address 0x004101BD.

pssw0rd.PNG

This confirms my suspicion: the password is safe from a normal user, but someone with assembly debugging skills can recover the password within a few hours. No big surprise, but you know, there are people who can only be convinced when you deliver a proof to backup your claim.

This dreary debugging process inspired me to develop a OllyDbg plugin called OllyStepNSearch to automated the debugging process. It will automatically step through the debugged program until a register points to the string you specified. You can download it, but it’s still beta.

I used it to debug the EPO agent to look for P@ssw0rd. It’s slow (about 45 minutes), but it runs unattended.

« Previous PageNext Page »

Theme: Rubric. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 84 other followers