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