2010-08-11

Notes on raw FileVault access, for live system forensics

Yesterday, I stupidly/accidentally deleted a source file that I hadn't yet checked in. Unfortunately, I was unable to recover the data, but I did learn how to go about doing so, given that the file was stored in my home directory, protected by FileVault.

The first thing to know is that FileVault is essentially an encrypted DMG file. The DMG is also "sparse," which means that it looks really big to the computer (to get around being limited to a fixed-size), but only stores blocks which haven't been allocated.

This sparse DMG image essentially looks like a physical disk. It's then formatted with a partitioning scheme, and the primary partition is attached to the filesystem at your home path, e.g. /Users/jon, not unlike how a normal /home partition would be mounted under Linux.

The cool thing is that the encryption/decryption happens at the physical device layer, not at the partition layer. So, if you happen to lose a file in OS X in a FileVault-protected directory, here's what you should do:

1. Open the terminal

2. Run "hdiutil info". This will spit out information about any mounted DMGs you have open:

framework       : 283
driver          : 10.6v283
================================================
image-path      : /Users/.jon/jon.sparsebundle
image-alias     : /Users/.jon/jon.sparsebundle
shadow-path     :
icon-path       : /System/Library/PrivateFrameworks/DiskImages.framework/Resources/CDiskImage.icns
image-type      : sparse bundle disk image
system-image    : TRUE
blockcount      : 1258291200
blocksize       : 512
writeable       : TRUE
autodiskmount   : TRUE
removable       : TRUE
image-encrypted : TRUE
mounting user   : root
mounting mode   : -rwx------
process ID      : 646
/dev/disk1    Apple_partition_scheme   
/dev/disk1s1    Apple_partition_map   
/dev/disk1s2    Apple_HFS    /Users/jon

You can see here that /dev/disk1 is the "physical" device associated with the DMG, and that the second partition, /dev/disk1s2 is formatted Apple_HFS and mounted at /Users/jon. Make a note of the path of this partition. It's a valid HFS+ partition, by the way; running the sleuthkit on it works just fine.

3. cd to somewhere that's outside of the mount point, e.g., /Users.

4. Run "sudo hdiutil unmount -force ", e.g., "hdiutil unmount -force /Users/jon". This unmounts the filesystem in the DMG, but still leaves the DMG open.

5. Now you can do whatever you'd like with /dev/disk1s2, or /dev/disk1. You can dd it, grep it, run recovery tools against it, whatever. You should be warned, however, that because it's a sparse image, it will be appear much bigger than what it is. But it will be decrypted.

Nota Bene: All of this information is based on my own experimentation yesterday. Please let me know if I've made any mistakes.

2010-08-09

Cross Channel Scripting (XCS) Attacks

The August issue of Communications of the ACM has an interesting article about cross channel scripting (XCS) attacks. The authors describe, among other things, how to attack NAS devices by uploading files with JavaScript filenames that then hijack admin sessions in the management web app. Cool stuff.

Peak Oil => Peak Food

Here's an AP article I saw in the Washington Post, headlined US Official: Poor nations must learn to grow food.

Y'know, the whole "teach someone to fish" argument explains this pretty well. Still, it's a bit of a reversal of long-standing U.S. policy, where we have the world's most productive farms, generate huge surpluses, and then sell/give surplus grain to other countries. The U.S. Gov't has long propped up farms through subsidies. Why wouldn't we want to keep supplying grain to other countries, then, especially in a world where we're knocking down barriers to global trade?

There are good development arguments to be made about why countries should grow their own food. However, the single-biggest reason why U.S. farms became so productive is because of the widespread use of fertilizer. Making fertilizer requires oil. In large measure, we've been eating oil.

The use of fertilizer and refinement of seeds and agricultural methods in the 20th century were a sort of Moore's Law. But the high rate of productivity improvement has probably run its course. The U.S. isn't going to be able to feed the rest of the world, especially if we're going to stop eating oil. I think this is part of that acknowledgment.

2010-08-05

DialogClass::ActivateEvent

While I labor over my post for my 3rd law of EnScript, here's an interesting problem. A former colleague of mine, Jeff, emailed me with a sample script. He wanted his user interface to be a wizard, and wanted a subsequent tab to update what it displayed based upon what the user put into the previous tab.

If the only update that needs to occur is to redisplay data from some variable, then this will happen automatically. When the user clicks "Next" on a tab, the widgets on that dialog will write their data to their corresponding variables. When the next tab is displayed, that dialog will automatically re-read its variables. If variables are being shared between widgets on the different dialogs, then everything will appear to be in sync.

However, if you need to do something more complex (a simple example being to update the text in a StaticTextClass, which doesn't have a linked variable), then you need to override DialogClass::ActivateEvent(). ActivateEvent() is called whenever a DialogClass object is displayed, i.e., has become active. There's also WindowClass::Setup(), which is called when a dialog, or a parent dialog, is first displayed because of Execute() or Wizard().

Meanwhile, CanClose() corresponds to the user clicking "Ok" or one of "Next" or "Previous". Returning false from CanClose() will cause the user to remain looking at the current dialog; the idea is that you could perform some validation in CanClose() and if it failed, call ErrorMessage() and return false.

With DialogClass/WindowClass virtual functions, it is generally necessary to call the parent function. If you don't, bad things will happen. The exception to this is CheckControls().

Below is a modified version of Jeff's test script that demonstrates how this works. Try modifying one of the ActivateEvent() functions to return false.


class MainClass {
  String test, test2;

  int  myint;

  void Main() {
    myint = 7;
    test = "text";
    test2 = "text2";
    SystemClass::ClearConsole();
    MasterDialogClass dlg(this);
    dlg.Wizard();
  }
}


class PageDialogClass1: DialogClass {
  StaticTextClass Message;

    PageDialogClass1(DialogClass parent, MainClass &m):
    DialogClass(parent, "First Page"),
    Message(this, m.test, START, START, DEFAULT, DEFAULT, READONLY)
  {
  }
}



class PageDialogClass2: DialogClass {
  StringEditClass Str_Text;
  IntEditClass    Int;

  PageDialogClass2(DialogClass parent, MainClass &m):
    DialogClass(parent, "Second Page"),
    Str_Text (this, "Text", START, START, 80, DEFAULT, 0, m.test2, 80, WindowClass::REQUIRED),
    Int(this, "Int", SAME, NEXT, 100, DEFAULT, 0, m.myint, 0, 30, 0)
  {
  }

  virtual void Setup() {
    Console.WriteLine("Page2 Setup");
    DialogClass::Setup();
  }

  virtual bool ActivateEvent() {
    Console.WriteLine("Page2 ActivateEvent");
    return DialogClass::ActivateEvent();
  }

  virtual bool CanClose() {
    Console.WriteLine("Page2 CanClose");
    return DialogClass::CanClose();
  }
}


class PageDialogClass3: DialogClass {
  MainClass       Main;
  StaticTextClass Message;
  IntEditClass    Int;


  PageDialogClass3(DialogClass parent, MainClass &m):
    DialogClass(parent, "Third Page"),
    Main = m,
    Message(this, m.test2, START, START, 80, 12, READONLY), //I want this to show the text entered on Page2   
    Int(this, "Int", SAME, NEXT, 100, DEFAULT, 0, m.myint, 0, 30, 0)
  {
  }

  virtual void Setup() {
    Console.WriteLine("Page3 Setup");
    DialogClass::Setup();
  }

  virtual bool ActivateEvent() {
    Console.WriteLine("Page3 ActivateEvent");
    Message.SetText(Main.test2);
    return DialogClass::ActivateEvent();
  }

  virtual bool CanClose() {
    Console.WriteLine("Page3 CanClose");
    return DialogClass::CanClose();
  }
}


class MasterDialogClass: DialogClass {

  PageDialogClass1          Page1;
  PageDialogClass2          Page2;
  PageDialogClass3          Page3;

  MasterDialogClass(MainClass m):
 
    DialogClass(null, ""),
    Page1(this, m),
    Page2(this, m),
    Page3(this, m)
  {}

  virtual void Setup() {
    Console.WriteLine("Master Setup");
    DialogClass::Setup();
  }

  virtual bool ActivateEvent() {
    Console.WriteLine("Master ActivateEvent");
    return DialogClass::ActivateEvent();
  }

  virtual bool CanClose() {
    Console.WriteLine("Master CanClose");
    return DialogClass::CanClose();
  }
}