Last active
December 31, 2015 03:59
-
-
Save aogail/7930766 to your computer and use it in GitHub Desktop.
Attempt to read NTFS streams using BackupRead() and BackupSeek() via FFI.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
C:\Development\ruby-ads>ruby streams_ffi.rb | |
C:/Ruby193/lib/ruby/gems/1.9.1/gems/ffi-1.0.9-x86-mingw32/lib/ffi/platform.rb:27: Use RbConfig instead of obsolete and d | |
eprecated Config. | |
CreateFile handle=192 | |
Before BackupRead loop. Context=0, handle=192 | |
After BackupRead(), context=8537608 | |
#<Win::FFI::File::Win32StreamId dw_stream_id=0x1, dw_stream_attributes=0x0, size_low=16, size_high=0, dw_stream_name_siz | |
e=0, c_stream_name=''> | |
BackupSeek failed: BackupSeek(192, 16, 0, #<FFI::MemoryPointer address=0x2a7bbf0 size=4>, #<FFI::MemoryPointer address=0 | |
x2a7bc08 size=4>, #<FFI::MemoryPointer address=0x2a7bc20 size=4>). skipped_low=0, skipped_high=0, context=8537608, handl | |
e=192 | |
Closing BackupRead context | |
Closing file handle | |
streams_ffi.rb:195:in `alternate_streams': The operation completed successfully. (RuntimeError) | |
from streams_ffi.rb:222:in `<main>' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'pp' | |
require 'ffi' | |
module Win | |
module FFI | |
module File | |
extend ::FFI::Library | |
ffi_lib 'kernel32' | |
ffi_convention :stdcall | |
# CreateFile flags | |
OPEN_EXISTING = 3 | |
GENERIC_READ = 0x80000000 | |
FILE_SHARE_READ = 0x00000001 | |
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 | |
# Backup Stream IDs | |
BACKUP_DATA = 0x00000001 | |
BACKUP_EA_DATA = 0x00000002 | |
BACKUP_SECURITY_DATA = 0x00000003 | |
BACKUP_ALTERNATE_DATA = 0x00000004 | |
BACKUP_LINK = 0x00000005 | |
BACKUP_PROPERTY_DATA = 0x00000006 | |
BACKUP_OBJECT_ID = 0x00000007 | |
BACKUP_REPARSE_DATA = 0x00000008 | |
BACKUP_SPARSE_BLOCK = 0x00000009 | |
BACKUP_TXFS_DATA = 0x0000000A | |
# Stream attributes | |
STREAM_NORMAL_ATTRIBUTE = 0x00000000 | |
STREAM_MODIFIED_WHEN_READ = 0x00000001 | |
STREAM_CONTAINS_SECURITY = 0x00000002 | |
STREAM_CONTAINS_PROPERTIES = 0x00000004 | |
STREAM_SPARSE_ATTRIBUTE = 0x00000008 | |
# FormatMessage flags | |
FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100 | |
FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200 | |
FORMAT_MESSAGE_FROM_STRING = 0x00000400 | |
FORMAT_MESSAGE_FROM_HMODULE = 0x00000800 | |
FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 | |
FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000 | |
FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF | |
typedef :uint32, :dword | |
typedef :uintptr_t, :handle | |
=begin | |
typedef struct _WIN32_STREAM_ID { | |
DWORD dwStreamId; | |
DWORD dwStreamAttributes; | |
LARGE_INTEGER Size; | |
DWORD dwStreamNameSize; | |
WCHAR cStreamName[ANYSIZE_ARRAY]; | |
} WIN32_STREAM_ID, *LPWIN32_STREAM_ID; | |
=end | |
class Win32StreamId < ::FFI::Struct | |
layout :dw_stream_id, :uint32, | |
:dw_stream_attributes, :uint32, | |
# Size is actually a LARGE_INTEGER union, | |
# but we only need it for BackupSeek(), which | |
# takes two DWORDs, anyway. | |
:size_low, :ulong, | |
:size_high, :long, | |
:dw_stream_name_size, :uint32, | |
:c_stream_name, :pointer | |
def inspect | |
"#<#{self.class.inspect} dw_stream_id=0x#{self[:dw_stream_id].to_s(16)}, dw_stream_attributes=0x#{self[:dw_stream_attributes].to_s(16)}, size_low=#{self[:size_low]}, size_high=#{self[:size_high]}, dw_stream_name_size=#{self[:dw_stream_name_size]}, c_stream_name='#{name}'>" | |
end | |
def name | |
self[:c_stream_name].read_string(self[:dw_stream_name_size]) | |
end | |
def size | |
self[:size_low] + self[:size_high] << 32 | |
end | |
end | |
=begin | |
HANDLE WINAPI CreateFile( | |
_In_ LPCTSTR lpFileName, | |
_In_ DWORD dwDesiredAccess, | |
_In_ DWORD dwShareMode, | |
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, | |
_In_ DWORD dwCreationDisposition, | |
_In_ DWORD dwFlagsAndAttributes, | |
_In_opt_ HANDLE hTemplateFile | |
); | |
=end | |
attach_function :CreateFile, :CreateFileA, [:pointer, :dword, :dword, :pointer, :dword, :dword, :pointer], :handle | |
=begin | |
DWORD WINAPI GetLastError(void); | |
=end | |
attach_function :GetLastError, :GetLastError, [], :dword | |
=begin | |
DWORD WINAPI FormatMessage( | |
_In_ DWORD dwFlags, | |
_In_opt_ LPCVOID lpSource, | |
_In_ DWORD dwMessageId, | |
_In_ DWORD dwLanguageId, | |
_Out_ LPTSTR lpBuffer, | |
_In_ DWORD nSize, | |
_In_opt_ va_list *Arguments | |
); | |
=end | |
attach_function :FormatMessage, :FormatMessageA, [:dword, :pointer, :dword, :dword, :pointer, :dword, :varargs], :dword | |
=begin | |
BOOL WINAPI CloseHandle( | |
_In_ HANDLE hObject | |
); | |
=end | |
attach_function :CloseHandle, :CloseHandle, [:handle], :bool | |
=begin | |
BOOL BackupRead( | |
_In_ HANDLE hFile, | |
_Out_ LPBYTE lpBuffer, | |
_In_ DWORD nNumberOfBytesToRead, | |
_Out_ LPDWORD lpNumberOfBytesRead, | |
_In_ BOOL bAbort, | |
_In_ BOOL bProcessSecurity, | |
_Out_ LPVOID *lpContext | |
); | |
=end | |
attach_function :BackupRead, :BackupRead, [:handle, :pointer, :dword, :pointer, :bool, :bool, :pointer], :bool | |
=begin | |
BOOL BackupSeek( | |
_In_ HANDLE hFile, | |
_In_ DWORD dwLowBytesToSeek, | |
_In_ DWORD dwHighBytesToSeek, | |
_Out_ LPDWORD lpdwLowByteSeeked, | |
_Out_ LPDWORD lpdwHighByteSeeked, | |
_In_ LPVOID *lpContext | |
); | |
=end | |
attach_function :BackupSeek, :BackupSeek, [:handle, :dword, :dword, :pointer, :pointer, :pointer], :bool | |
end | |
end | |
end | |
class File | |
include Win::FFI::File | |
# Read stream information from this file and return an array of | |
# (name, size) pairs. | |
def alternate_streams() | |
handle = CreateFile( | |
self.path, | |
GENERIC_READ, | |
FILE_SHARE_READ, | |
nil, | |
OPEN_EXISTING, | |
FILE_FLAG_BACKUP_SEMANTICS, | |
nil | |
) | |
$stderr.puts "CreateFile handle=#{handle}" | |
if handle != 0 | |
streams = [] | |
begin | |
buf = FFI::MemoryPointer.new(1024) | |
required_size = FFI::MemoryPointer.new(:uint32) | |
skipped_low = FFI::MemoryPointer.new(:ulong) | |
skipped_high = FFI::MemoryPointer.new(:ulong) | |
context = FFI::MemoryPointer.new(:pointer) | |
$stderr.puts "Before BackupRead loop. Context=#{context.read_uint32}, handle=#{handle}" | |
while BackupRead(handle, buf, buf.size, required_size, false, false, context) | |
$stderr.puts "After BackupRead(), context=#{context.read_uint32}" | |
info = Win32StreamId.new buf | |
$stderr.puts info.inspect | |
case info[:dw_stream_id] | |
when BACKUP_ALTERNATE_DATA | |
streams.push [info.name, info.size] | |
when BACKUP_DATA | |
streams.push ['<unnamed>', info.size] | |
else | |
$stderr.puts "Unknown stream ID: #{info[:dw_stream_id]}" | |
end | |
# BackupSeek fails but GetLastERror indicates success. | |
unless BackupSeek(handle, info[:size_low], info[:size_high], skipped_low, skipped_high, context) | |
$stderr.puts "BackupSeek failed: BackupSeek(#{handle}, #{info[:size_low]}, #{info[:size_high]}, #{skipped_low}, #{skipped_high}, #{context}). skipped_low=#{skipped_low.read_long}, skipped_high=#{skipped_high.read_long}, context=#{context.read_uint32}, handle=#{handle}" | |
err = GetLastError() | |
err_buf = FFI::MemoryPointer.new :char, 1024 | |
num_chars = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, nil, err, 0, err_buf, err_buf.size, *[:dword, 0]) | |
raise num_chars == 0 ? nil : err_buf.get_bytes(0, num_chars).strip | |
end | |
break | |
end | |
return streams | |
ensure | |
$stderr.puts 'Closing BackupRead context' | |
BackupRead(handle, nil, 0, nil, true, false, context) | |
$stderr.puts 'Closing file handle' | |
CloseHandle(handle) | |
end | |
end | |
end | |
end | |
File.open('streams.txt', 'w+') do |f| | |
f.puts 'unnamed stream' | |
end | |
File.open('streams.txt:ads1', 'w+') do |f| | |
f.puts 'stream one' | |
end | |
File.open('streams.txt:ads2', 'w+') do |f| | |
f.puts 'stream two' | |
end | |
pp File.new('streams.txt').alternate_streams | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment