Temat jest w pewnym stopniu związany z wyzwaniem, o którym pisałem wczoraj. Dostępny jest zrzut pamięci, a zadanie polega na odzyskaniu określonego pliku. Czy jest to możliwe? Tak, o ile plik ten znajduje się w pamięci. Pewna szansa na to istnieje, a to z uwagi na coś, co nazywa się file system cache. Poniżej prosty przykład jak można w pamięci znaleźć zawartość pliku.
Odczytywanie plików z filecache
W przykładzie tym skorzystam z live debugging, który jest dostępny w WinDbg i Windows XP (w Windows Vista domyślnie nie jest on dostępny/włączony). Dzięki temu, że przykłady będą robione "na żywca", łatwe będzie pokazanie zmian, które powoduje np. zapis do pliku.
By rozpocząć debuggowanie w trybie live należy uruchomić windbg z prawami administratora, a następnie wybrać z menu File opcje Kernel Debug oraz Local (ostatnia zakładka). Po naciśnięciu OK proces debuggowania się rozpocznie. Konieczne może być jeszcze ustawienie właściwych symboli, o czym można przeczytać w artykule Use the Microsoft Symbol Server to obtain debug symbol files. Dla porządku przypomnę jeszcze, że w temacie debuggowania jestem słaby, więc proszę o wyrozumiałość :) Bardziej chcę pokazać, że się coś takiego da zrobić, niż wytłumaczyć dokładnie jak, dlaczego i kiedy to działa.
Pliki testowe, a właściwie plik testowy, będę generował przy pomocy IPython, czyli "interaktywnej nakładki na Pythona".
Na początek utworzę plik i coś do niego zapiszę:
In [59]: f = open("output.dat","wb") In [60]: f.write("test") In [61]: f.flush()
Teraz należy znaleźć ten plik w pamięci. Można to zrobić na dwa sposoby:
- przeglądając zawartość filecache (!filecache),
- enumerując handle danego procesu (w tym wypadku interpretera Pythona),
Wersja z !filecache wygląda mniej więcej tak:
lkd> !filecache ***** Dump file cache****** Reading and sorting VACBs ... Removed 442 nonactive VACBs, processing 1596 active VACBs ... File Cache Information Current size 161764 kb Peak size 288236 kb 1596 Control Areas Skipping view @ c1240000 - no VACB, but PTE is valid! Loading file cache database (100% of 130560 PTEs) SkippedPageTableReads = 0 File cache has 25073 valid pages Usage Summary (in Kb): Control Valid Standby/Dirty Shared Locked FsContext Name 888f6a20 12 0 0 0 e2e47d90 local.sqlite 8a48b818 8000 1252 0 0 8a3f6510 $Mft 8a3d8dc0 4 0 0 0 e1c4ad20 $Directory 885e2410 24992 52228 0 0 e1de50d0 global-messages-db.sqlite 886b1260 460 0 0 0 8a2971d0 $Mft 8a2c6b38 1532 368 0 0 e295ed90 places.sqlite 8a439b90 31084 4 0 0 e1bc0a50 SOFTWARE (...)
Problem tylko w tym, że pliku (nazwa: output.dat) tu nie znalazłem, więc z tego powodu trzeba wykorzystać drugą metodę:
lkd> !process 0 0 (...) PROCESS 884f5a60 SessionId: 0 Cid: 083c Peb: 7ffde000 ParentCid: 0b88 DirBase: 0a940620 ObjectTable: e21e5738 HandleCount: 93. Image: python.exe lkd> .process /p 884f5a60 Implicit process is now 884f5a60 lkd> !handle processor number 0, process 884f5a60 PROCESS 884f5a60 SessionId: 0 Cid: 083c Peb: 7ffde000 ParentCid: 0b88 DirBase: 0a940620 ObjectTable: e21e5738 HandleCount: 93. Image: python.exe (...) 000c: Object: 8a0dc028 GrantedAccess: 00120196 (Inherit) Entry: e2304018 Object: 8a0dc028 Type: (8a518ad0) File ObjectHeader: 8a0dc010 (old version) HandleCount: 1 PointerCount: 1 Directory Object: 00000000 Name: \VirtualPC\output.dat {HarddiskVolume2} (...)
W ten sposób uzyskaliśmy adres interesującego nas obiektu FILE_OBJECT. Można go obejrzeć w ten sposób:
lkd> dt nt!_FILE_OBJECT 8a0dc028 +0x000 Type : 5 +0x002 Size : 112 +0x004 DeviceObject : 0x8a4f7030 _DEVICE_OBJECT +0x008 Vpb : 0x8a4add08 _VPB +0x00c FsContext : 0xe4b5bd90 +0x010 FsContext2 : 0xe4b5bee8 +0x014 SectionObjectPointer : 0x8a465d94 _SECTION_OBJECT_POINTERS +0x018 PrivateCacheMap : 0x8a1e01c0 +0x01c FinalStatus : 0 +0x020 RelatedFileObject : 0x89fe3b90 _FILE_OBJECT +0x024 LockOperation : 0 '' +0x025 DeletePending : 0 '' +0x026 ReadAccess : 0 '' +0x027 WriteAccess : 0x1 '' +0x028 DeleteAccess : 0 '' +0x029 SharedRead : 0x1 '' +0x02a SharedWrite : 0x1 '' +0x02b SharedDelete : 0 '' +0x02c Flags : 0x43042 +0x030 FileName : _UNICODE_STRING "\VirtualPC\output.dat" +0x038 CurrentByteOffset : _LARGE_INTEGER 0x4 +0x040 Waiters : 0 +0x044 Busy : 0 +0x048 LastLock : (null) +0x04c Lock : _KEVENT +0x05c Event : _KEVENT +0x06c CompletionContext : (null)
W tym przypadku jednak wygodniejsze będzie wykorzystanie !fileobj:
lkd> !fileobj 8a0dc028 \VirtualPC\output.dat Related File Object: 0x89fe3b90 Device Object: 0x8a4f7030 \Driver\Ftdisk Vpb: 0x8a4add08 Event signalled Access: Write SharedRead SharedWrite Flags: 0x43042 Synchronous IO Cache Supported Modified Size Changed Handle Created FsContext: 0xe4b5bd90 FsContext2: 0xe4b5bee8 Private Cache Map: 0x8a1e01c0 CurrentByteOffset: 4 Cache Data: Section Object Pointers: 8a465d94 Shared Cache Map: 8a278e10 File Offset: 4 in VACB number 0 Vacb: 8a4e1f88 Your data is at: ceb00004
Tu poza kilkoma innymi ciekawymi informacjami, otrzymujemy również adres obiektu SHARED_CACHE_MAP, który również można obejrzeć dokładniej:
lkdGgt; dt nt!_SHARED_CACHE_MAP 8a278e10 +0x000 NodeTypeCode : 767 +0x002 NodeByteSize : 304 +0x004 OpenCount : 2 +0x008 FileSize : _LARGE_INTEGER 0x4 +0x010 BcbList : _LIST_ENTRY [ 0x8a278e20 - 0x8a278e20 ] +0x018 SectionSize : _LARGE_INTEGER 0x100000 +0x020 ValidDataLength : _LARGE_INTEGER 0x4 +0x028 ValidDataGoal : _LARGE_INTEGER 0x4 +0x030 InitialVacbs : [4] 0x8a4e1f88 _VACB +0x040 Vacbs : 0x8a278e40 -> 0x8a4e1f88 _VACB +0x044 FileObject : 0x8a481ba0 _FILE_OBJECT +0x048 ActiveVacb : (null) +0x04c NeedToZero : (null) +0x050 ActivePage : 0 +0x054 NeedToZeroPage : 0 +0x058 ActiveVacbSpinLock : 0 +0x05c VacbActiveCount : 0 +0x060 DirtyPages : 0 +0x064 SharedCacheMapLinks : _LIST_ENTRY [ 0x8a3efe9c - 0x8a4879d4 ] +0x06c Flags : 0x400 +0x070 Status : 0 +0x074 Mbcb : 0x8857a8c8 _MBCB +0x078 Section : 0xe47d0588 +0x07c CreateEvent : (null) +0x080 WaitOnActiveCount : (null) +0x084 PagesToWrite : 0 +0x088 BeyondLastFlush : 0 +0x090 Callbacks : 0xb9e2422c _CACHE_MANAGER_CALLBACKS +0x094 LazyWriteContext : 0xe4b5bd90 +0x098 PrivateList : _LIST_ENTRY [ 0x8a278f34 - 0x8a1e020c ] +0x0a0 LogHandle : (null) +0x0a4 FlushToLsnRoutine : (null) +0x0a8 DirtyPageThreshold : 0 +0x0ac LazyWritePassCount : 2 +0x0b0 UninitializeEvent : (null) +0x0b4 NeedToZeroVacb : (null) +0x0b8 BcbSpinLock : 0 +0x0bc Reserved : (null) +0x0c0 Event : _KEVENT +0x0d0 VacbPushLock : _EX_PUSH_LOCK +0x0d8 PrivateCacheMap : _PRIVATE_CACHE_MAP
Dzięki temu otrzymujemy między innymi informację o rozmiarze pliku.
Kolejnym krokiem będzie uzyskanie "mapy" tego, jak plik rozmieszczony jest w pamięci. Można to zrobić korzystając z !openmaps, jako parametr polecenia należy podać użyty wcześniej adres mapy:
lkd> !openmaps 8a278e10 1 SharedCacheMap 8a278e10 Section Size 100000 Levels 1 VacbActiveCount 0 0 ActiveCount 0 @ Vacb 8a4e1f88 Total VACBs 1 Active 0 Unable to read bcb at 0xffffffff8a278e10
Z tych informacji najbardziej ważne są adresy VACB. Wskazują one na obszary pamięci, do których właściwy plik jest zapisany. Obszary te mają stałą wielkość (0x40000), ich ilość zależy od wielkości pliku, przy czym trzeba pamiętać, że nie zawsze w cache znajduje się cały plik, wiec niektóre obszary pliku mogą być "niemapowane".
Mając adres VACB należy ustalić adres danych, w tym celu należy obejrzeć obiekt VACB:
kd> dt nt!_VACB 8a4e1f88 +0x000 BaseAddress : 0xceb00000 +0x004 SharedCacheMap : 0x8a278e10 _SHARED_CACHE_MAP +0x008 Overlay : __unnamed +0x010 LruList : _LIST_ENTRY [ 0x8a4e3648 - 0x8a4ea560 ]
Poszukiwana informacja znajduje się w polu BaseAddress. Zaglądając pod ten adres można znaleźć co następuje:
lkd> db 0xceb00000 ceb00000 74 65 73 74 00 00 00 00-00 00 00 00 00 00 00 00 test............ ceb00010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ ceb00020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ ceb00030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ ceb00040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ ceb00050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ ceb00060 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ ceb00070 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
Dopisanie do tego pliku fragmentu tekstu znajdzie odzwierciedlenie w pamięci:
In [62]: f.write("abcdef") In [63]: f.flush()
lkd> db 0xceb00000 ceb00000 74 65 73 74 61 62 63 64-65 66 00 00 00 00 00 00 testabcdef...... ceb00010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
Jeśli do pliku zostanie zapisana większa porcja danych, ilość VACB ulegnie zwiększeniu:
In [64]: buff = "A"*0x40000 In [65]: f.write(buff) In [66]: f.write(buff) In [67]: f.flush() In [68]: f.write("EOF") In [70]: f.flush()
lkd> !openmaps 8a278e10 1 SharedCacheMap 8a278e10 Section Size 100000 Levels 1 VacbActiveCount 0 0 ActiveCount 0 @ Vacb 8a4e1f88 40000 ActiveCount 0 @ Vacb 8a4eab68 80000 ActiveCount 0 @ Vacb 8a4e9608 Total VACBs 3 Active 0 Unable to read bcb at 0xffffffff8a278e10
Dla przykładu odczytanie ostatniego fragment pliku:
lkd> dt nt!_VACB 8a4e9608 +0x000 BaseAddress : 0xd9980000 +0x004 SharedCacheMap : 0x8a278e10 _SHARED_CACHE_MAP +0x008 Overlay : __unnamed +0x010 LruList : _LIST_ENTRY [ 0x8a4e1ab8 - 0x8a4e6948 ] lkd> db 0xd9980000 d9980000 41 41 41 41 41 41 41 41-41 41 45 4f 46 00 00 00 AAAAAAAAAAEOF... d9980010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ d9980020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
Teraz nieco inny eksperyment. Plik ten zostanie zamknięty, następnie otwarty w trybie do odczytu i zostanie odczytany jego fragment.
In [72]: f = open("output.dat") In [73]: f.seek(0x40000+256) In [74]: buff = f.read(64)
Tym razem plik znajdzie się przez !filecache (przedtem zapewne też był, tylko nie miał nazwy):
lkd> !filecache Reading and sorting VACBs ... Removed 431 nonactive VACBs, processing 1607 active VACBs ... File Cache Information Current size 162204 kb Peak size 288236 kb 1607 Control Areas Skipping view @ c1240000 - no VACB, but PTE is valid! Skipping view @ dfd00000 - no VACB, but PTE is a prototype! Loading file cache database (100% of 130560 PTEs) SkippedPageTableReads = 0 File cache has 25519 valid pages Usage Summary (in Kb): Control Valid Standby/Dirty Shared Locked FsContext Name 885e2410 26336 50968 0 0 e1de50d0 global-messages-db.sqlite 888f6a20 12 0 0 0 e2e47d90 local.sqlite 8a48b818 8056 1196 0 0 8a3f6510 $Mft 8a3d8dc0 4 0 0 0 e1c4ad20 $Directory 886b1260 460 0 0 0 8a2971d0 $Mft 8a2c6b38 1532 224 0 0 e295ed90 places.sqlite 8a439b90 31084 4 0 0 e1bc0a50 SOFTWARE (...) 8a42bf88 4 0 0 0 e314d0d0 cookies.sqlite-journal 886474c0 8 508 0 0 e4b5bd90 output.dat 8a015148 8 0 0 0 e28100d0 $Directory 89fff840 4 0 0 0 8a2710a0 $Mft (...)
W porównaniu do poprzedniej procedury różnica jest tylko jedna, mamy adres Control Area, a nie pliku, ale jest on łatwy do uzyskania:
lkd> !ca 886474c0 ControlArea @ 886474c0 Segment e451bc88 Flink 00000000 Blink 00000000 Section Ref 1 Pfn Ref 81 Mapped Views 1 User Ref 0 WaitForDel 0 Flush Count 0 File Object 8a270428 ModWriteCount 0 System Views 1 Flags (9008080) File WasPurged HadUserReference Accessed File: \VirtualPC\output.dat Segment @ e451bc88 Type nt!_MAPPED_FILE_SEGMENT not found.
Dalej już tradycyjnie:
lkd> !fileobj 8a270428 \VirtualPC\output.dat Related File Object: 0x89fe3b90 Device Object: 0x8a4f7030 \Driver\Ftdisk Vpb: 0x8a4add08 Event signalled Access: Read SharedRead SharedWrite Flags: 0xc0042 Synchronous IO Cache Supported Handle Created Fast IO Read FsContext: 0xe4b5bd90 FsContext2: 0xe4b5bee8 Private Cache Map: 0x8895f0e0 CurrentByteOffset: 41100 Cache Data: Section Object Pointers: 8a465d94 Shared Cache Map: 8895f008 File Offset: 41100 in VACB number 1 Vacb: 8a4eab68 Your data is at: dbfc1100 lkd> !openmaps 8895f008 1 SharedCacheMap 8895f008 Section Size c0000 Levels 1 VacbActiveCount 1 40000 ActiveCount 1 @ Vacb 8a4eab68 Total VACBs 1 Active 1 Unable to read bcb at 0xffffffff8895f008
Warto zauważyć, że na liście pojawia się tylko jeden VACB. Z lewej strony znajduje się offset (0x40000). Wynika to ze sposobu działania cache. Dla przypomnienia - po otwarciu pliku odczytałem z niego fragment danych na offsecie 0x40100. System załadował cały segment wielkości 0x40000, w którym odczytane przeze mnie dane się znajdują. Czyli w pamięci znajduje się fragment od 0x40000 do 0x80000, pozostałych fragmentów w tej chwili nie ma (w rzeczywistości nawet nie ma pełnego obszaru 0x40000 do 0x80000).
Dla sprawdzenia:
lkd> dt nt!_VACB 8a4eab68 +0x000 BaseAddress : 0xdbfc0000 +0x004 SharedCacheMap : 0x8895f008 _SHARED_CACHE_MAP +0x008 Overlay : __unnamed +0x010 LruList : _LIST_ENTRY [ 0x8a4e58b0 - 0x8a4e5be0 ] lkd> db 0xdbfc0000 dbfc0000 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA dbfc0010 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA dbfc0020 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA dbfc0030 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA dbfc0040 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA dbfc0050 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA dbfc0060 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA dbfc0070 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
Dysponując zrzutem pamięci możliwe jest odzyskanie części zawartości plików, które były w użyciu w trakcie wykonania tego zrzutu. Sposób postępowania jest następujący:
- uzyskanie adresu obiektu FILE_OBJECT (np. używając !filecache, !handle),
- uzyskanie adresu SHARED_CACHE_MAP (np. przez !fileobj),
- uzyskanie adresu/adresów VACB (np. przez !openmaps),
- odczytanie fragmentów pamięci, na które wskazują VACB,
Dlaczego tak? Nie podejmuje się tłumaczenia :) Jeśli ktoś jest zainteresowany, warto przeczytać odpowiednie fragmenty z Windows Internals.
Trzeba pamiętać, że odzyskanie kompletnych plików nie zawsze będzie możliwe, bo:
- nie cały plik został załadowany do cache,
- pamięć używana przez (fragment) cache została "zrzucona do swap",
Jeśli komuś znudzi się "świąteczna atmosfera" albo świąteczne obżarstwo, może zająć się kilkoma wyzwaniami. O większości z nich już wspominałem, ale postanowiłem wspomnieć jeszcze raz.Testowanie kontroli dostępu do funkcji Pierwszy temat związany jest z
Przesłany: Apr 02, 13:09
Opublikowane zostało rozwiązanie wyzwania Forensic Challenge 2010 - Banking Troubles. Choć nie uczestniczę w tego typu wyzwaniach (nie wysyłam odpowiedzi), to zadaniom się przyglądam i część z nich, te ciekawsze, rozwiązuję. Czytam również zgłaszane odpow
Przesłany: May 13, 15:54
Jeśli po mojej prezentacji na dzisiejszym spotkaniu OWASP kogoś zainteresował temat ogólnie pojętego forensic, być może zainteresują go również starsze wpisy dotyczące tego tematu. Nazbierało się tego trochę, więc małe przypomnienie części z poruszanych t
Przesłany: Jun 10, 20:54