More File System Shenanigans

It’s really rather funny to have tools available that make otherwise challenging programming tasks really easy. I make tools to understand an area and to make it easier to prototype a solution. Just the other day, I was thinking, how can I detect when a virus is being hidden in a filestream attached to an NTFS file?

In order to understand what this attack might look like, you have to understand a bit about the NTFS file system. Basically, there is this concept of ‘streams’, which is an attachment mechanism within the NTFS file system. It stems from the earlier days when NTFS was able to read files from the Macintosh, including their “forks”. What it amounts to is you can attach anything you want to any file, whether it is an image, an executable, or what have you. You can use the same “CreateFile” api to get a handle on the attached file, as long as you know the name of the file. These stream attachments don’t normally show up in the file explorer, and simple usage of the command line “dir” command won’t show them either. If you use “dir /R “, you will get a list of the files, as well as their “alternate data streams”, which is what these attached stream things are called.

Here’s a task I wanted to perform. I want to get a list of all the streams that are attached to all of the files in my entire file system. So, first, I will attach a simple iterator to the FileSystemItem object that I’ve used previously:

ffi.cdef[[
typedef enum _STREAM_INFO_LEVELS {
    FindStreamInfoStandard,
    FindStreamInfoMaxInfoLevel
} STREAM_INFO_LEVELS;

typedef struct _WIN32_FIND_STREAM_DATA {
    LARGE_INTEGER StreamSize;
    WCHAR cStreamName[ MAX_PATH + 36 ];
} WIN32_FIND_STREAM_DATA, *PWIN32_FIND_STREAM_DATA;

HANDLE FindFirstStreamW(
    LPCWSTR lpFileName,
    STREAM_INFO_LEVELS InfoLevel,
    LPVOID lpFindStreamData,
    DWORD dwFlags);

BOOL FindNextStreamW(
    HANDLE hFindStream,
	LPVOID lpFindStreamData
);
]]

local k32Lib = ffi.load("Kernel32");


FileSystemItem.streams = function(self)
  local lpFileName = core_string.toUnicode(self:getFullPath());
  local InfoLevel = ffi.C.FindStreamInfoStandard;
  local lpFindStreamData = ffi.new("WIN32_FIND_STREAM_DATA");
  local dwFlags = 0;

  local rawHandle = k32Lib.FindFirstStreamW(lpFileName,
    InfoLevel,
    lpFindStreamData,
    dwFlags);
  
  local firstone = true;
  local fsHandle = FsFindFileHandle(rawHandle);

  local closure = function()
    if not fsHandle:isValid() then return nil; end

    if firstone then
      firstone = false;
      return core_string.toAnsi(lpFindStreamData.cStreamName);
    end
		 
    local status = k32Lib.FindNextStreamW(fsHandle.Handle, lpFindStreamData);
    if status == 0 then
      local err = errorhandling.GetLastError();
      return nil;
    end
    
    return core_string.toAnsi(lpFindStreamData.cStreamName);
  end

  return closure;
end

With this bit of code, I can do something like:

fsItem = FileSystemItem({Name="c:\\Temp\\filename.txt")
for _, streamName in ipairs(fsItem:streams()) do
  print(streamName);
end

That will get me the name of the streams that might be attached to one particular file system item, whether it be a directory or a file.

If I want to get the names of all the streams attached to all of the files in my entire file system, I would do the following:

local getFsStreams = function(fsItem)
  local res = {}

  for item in fsItem:itemsRecursive() do
    local entry = {Path=item:getFullPath()}
    local streams = {};
    for stream in item:streams() do
      table.insert(streams, {Name = stream});
    end
    if #streams > 0 then
      entry.Streams = streams;
    end

    table.insert(res, entry);
  end
  return res;
end

There aren’t actually that many unique names used as alternate streams, but if I wanted to get a list of them, I would do this:

local getUniqueStreamNames = function(fsItem)
  local items = getFsStreams(fsItem);

  local names = {}
  for _,item in ipairs(items) do  
    if item.Streams then
      for _,entry in ipairs(item.Streams) do
        if not names[entry.Name] then
          names[entry.Name] = 1;
        else
          names[entry.Name] = names[entry.Name] + 1;
        end
      end
    end
  end

  return names;
end

local test_findUniqueStreams = function(fsItem)
  local uniqueNames = getUniqueStreamNames(fsItem);

  local jsonstr = JSON.encode(uniqueNames, {indent=true});

  print(jsonstr);
end

This will return:

  ":Zone.Identifier:$DATA":3657,
  ":CA_INOCULATEIT:$DATA":1,
  ":OECustomProperty:$DATA":2,
  "::$DATA":302687,
  ":favicon:$DATA":2,
  ":encryptable:$DATA":2

That’s kind of handy and informative. I can now look at my file system and see what kinds of alternate data streams are being used on files. Having this in hand, if I want to get a list of files that have a paricular alternate stream attached to them, I can do this:

local test_findFilesWithStream = function(fsItem, streamType)
  local items = getFsStreams(fsItem);

  local res = {};
  for _, item in ipairs(items) do
    if item.Streams then
      for _,entry in ipairs(item.Streams) do
        if entry.Name == streamType then
          table.insert(res, item);
        end
      end
    end
  end

  local jsonstr = JSON.encode(res, {indent=true});
  print(jsonstr);
end

local rootName = arg[1] or "c:";
local streamType = arg[2] or ":Zone.Identifier:$DATA";
local fsItem = FileSystemItem({Name=rootName});
test_findFilesWithStream(fsItem, streamType);

That will basically list all files on my ‘c:’ drive which have an attached stream named “:Zone.Identifier:$DATA”. Of course, it’s instructive to Bing the names of the alternate data streams and see what they’re about. This is also a handy way of figuring out where those viruses are hiding attached to your files relatively unseen, ready to pounce.

Advertisements


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s