bilbo
November 9th, 2004, 03:23
Quote:
[Originally Posted by Kayaker]Perhaps it is time to look towards the Softice PHYS command for answers |
Well, the algorithm used by Softice is rather intuitive. It scans all the PTEs related to the current 4GB virtual space, starting from the current Page Directory pointer (CR3 register). For every PTE, it compares the physical address associated to it with the physical address we want to map. As soon as we find a match between the two physical addresses, we have found a new virtual address translating the physical one.
In the case that all PTEs are 4K sized, the number of scanned PTE will be 1024 (PDE entries) * 1024 (PTEs per PDE): about one million!
Let's see the code in detail (DS 3.1):
Code:
;
; phys2virt(EAX=address, ESI=callback)
; callback is called with: EDX=passed context, EAX=translated virtual,
; and can be called more than once
;
byte_1806B db 0 ; global parameter: stop_at_first_translation
sub_1806C proc near
var_4 = dword ptr -4
pusha
mov ebp, esp
sub esp, 4
and eax, 0FFFh ; offset in page
mov [ebp+var_4], eax ; initialize translated virtual
; save_bios_pte_contents()
; see below
call sub_181FD
mov eax, [ebp+1Ch] ; saved EAX after PUSHA: physical address
and eax, 0FFFFF000h ; start of page
mov edx, eax ; physical address to search
; EDI <- virtual address of Page Directory
mov eax, cr3 ; physical address of Page Directory
mov edi, eax
add edi, dword_E15FF ; mapped_zero_phys (see below)
mov dword_115A06, 0 ; cached_selector_start
mov dword_115A02, 0FFFFFFFFh ; cached_selector_end
mov ecx, 400h ; number of PDE's in Page Directory
; outer loop: a loop per PDE
loc_180AD:
; EDI = physptr_to_PDE + mapped_zero_phys
; peekphyslong(EDI=phys+mapped_zero_phys)
call sub_18286
jb loc_18168 ; break if ko
or eax, eax
jz loc_18157 ; null PDE: continue
test eax, 1
jz loc_18157 ; not present PDE: continue
test eax, 80h ; page directory size
jz short loc_18103
; page directory size is 4M: mask page base address
; (physical address of the first byte of a 4K page)
and eax, 0FFC00000h
cmp edx, eax ; CMP passed physical
jb short loc_18157 ; continue if passed physical is lower
add eax, 400000h ; end of 4M page
cmp edx, eax ; CMP passed physical
jnb short loc_18157 ; continue if passed physical is bigger
; found one virtual
sub eax, 400000h ; go back to start of 4M page
; EAX = bias_in_page = passed physical - physical start of page
sub eax, edx
neg eax
; EAX = current translated virtual + bias_in_page
add eax, [ebp+var_4]
push edx
mov edx, [ebp+14h] ; restore saved EDX
call [ebp+4h] ; saved ESI after PUSHA: callback
pop edx
; see if we must continue or break
cmp ds:byte_1806B, 1 ; global parameter: stop_at_first_translation
jz short loc_18168 ; break
jmp short loc_18157 ; continue
; page directory size is 4K
loc_18103:
; mask PTE lower bits
and eax, 0FFFFF000h
add eax, dword_E15FF ; mapped_zero_phys
push ecx
push edi
mov edi, eax
mov ecx, 400h ; number of PTE's
xor esi, esi ; init bias to add to translated virtual (4K per PTE)
; inner loop
loc_18119:
; peekphyslong(EDI=phys+mapped_zero_phys)
call sub_18286
jb short loc_1814A ; continue inner loop if ko
test eax, 1 ; present bit
jz short loc_1814A ; continue inner loop if PTE not present
and eax, 0FFFFF000h ; physical address of first byte in 4K page
cmp eax, edx ; CMP passed physical
jnz short loc_1814A ; continue inner loop
; matching found
mov eax, [ebp+var_4] ; update translated virtual
; add bias to base translated virtual
or eax, esi
push edx
mov edx, [ebp+14h] ; restore saved EDX
call [ebp+4h] ; saved ESI: callback
pop edx
cmp ds:byte_1806B, 0 ; global parameter: stop_at_first_translation
jz short loc_1814A ; continue inner loop
; done with inner loop
pop edi
pop ecx
jmp short loc_18168 ; break from outer loop
; continue inner loop
loc_1814A:
add esi, 1000h ; 4K more to base translated virtual
add edi, 4 ; update physical PTE pointer
loop loc_18119 ; decrement ECX (number of PTEs)
; done with inner loop
pop edi
pop ecx
loc_18157: ; continue to next PDE (outer loop)
add [ebp+var_4], 400000h ; update translated virtual (+= 4M)
add edi, 4 ; update physptr to PDE
dec ecx ; update remaining PDEs
jnz loc_180AD ; continue
; done with outer loop
loc_18168:
; restore_bios_pte_contents()
call sub_18217
mov esp, ebp
popa ; matches initial PUSHA
retn
sub_1806C endp ; sp = 20h
Some explanations are required regarding mapped_zero_phys and save_bios_pte_contents(), before analyzing the tricky subroutine peekphyslong(), which will map a physical address (the current PTE pointer) to a virtual one, in order to read it.
At init time a MmMapIoSpace(0, 0, 0x100000, 0) is executed (the first two zeros are the physical address to map), and the return value, a virtual address, is saved in mapped_zero_phys: this is the start address of a virtual range mapping the physical range 0-100000. Furthermore, a particular PTE is located which initially maps the BIOS space (F0000-100000): its virtual address is mapped_zero_phys+0xF0000; we'll call it mapped_bios. Its virtual PTE pointer is calculated:
C0000000 (start of consecutive PTEs) + (mapped_bios/4K) * 4 bytesperPTE; we'll call it pBIOS_PTE.
Now we can understand how peekphyslong() works. We have a physical address; we build a new PTE with it; we replace this PTE into pBIOS_PTE; we can now read the 4K page at virtual address mapped_bios. Before and after this operation we issue save_bios_pte_contents() and restore_bios_pte_contents().
Let's see the code:
Code:
;
; peekphyslong(EDI=phys+mapped_zero_phys)
;
sub_18286 proc near
push ebx
push edi
; subtract back mapped_zero_phys
sub edi, dword_E15FF
mov ebx, offset sub_6D3E3 ; callback: fastpeeklong(EDI=address)
; map_phys_to_virt_and_do_callback(
; EDI=physaddr, EBX=callback)
call sub_18231
pop edi
pop ebx
retn
sub_18286 endp
;
; map_phys_to_virt_and_do_callback(EDI=physaddr, EBX=callback)
; callback is called with mappedvirt in EDI
;
sub_18231 proc near
push ecx
push edx
push edi
mov ecx, edi ; physaddr
mov edi, dword_D9EB1 ; pBIOS_PTE (it holds F017B: phys F0000)
or edi, edi
jnz short loc_18248
; pBIOS_PTE null: simply add mapped_zero_phys and call callback
; never follow this way!
add ecx, dword_E15FF ; mapped_zero_phys
jmp short loc_1827E ; call callback(EDI<-ECX mappedvirt)
loc_18248:
; pBIOS_PTE not null:
; build 4K PTE
mov edx, ecx
and edx, 0FFFFF000h ; physaddr, start of page
or dl, 7 ; set low pte bits: userpriv|readwrite|present
cmp dword_E4CE4, edx ; saved_bios_pte
jz short loc_18272 ; jump if we have already set it
; set built PTE in pBIOS_PTE
push eax
; pokelong_(EDI=addr, EAX=value) must return !CY
mov eax, edx
call sub_6D5B4
pop eax
jb short loc_18282 ; return if ko
mov dword_E4CE4, edx ; update saved_bios_pte
; flush cache
mov edx, cr3
mov cr3, edx
; passed physaddr: add page offset
loc_18272:
and ecx, 0FFFh
add ecx, dword_D9EB5 ; add mapped_bios to convert to virtual
; call callback(EDI<-ECX)
loc_1827E:
mov edi, ecx
call ebx
; return
loc_18282:
pop edi
pop edx
pop ecx
retn
sub_18231 endp
I hope someone could follow this (rather obfuscated) explanation. Sorry, mates, but I couldn't find easier words.
Best regards, bilbo