修改http請求文件為本地文件的一種方法:hook InternetReadFile 和 HttpOpenRequest


  今天沒事的時候學了一下easyhook來hook本進程API,確實很簡單就能hook。然后想到這個問題:替換webbrowser請求的文件為本地文件。有什么用就不說了,都懂。因為沒有用API寫過http方面的東西,所以先hook了幾個函數,其中InternetReadFile是webbrowser用來獲取文件的,而文件句柄可以來源於internetopenurl和 HttpOpenRequest等API,挨個下一下鈎子就知道用的是 HttpOpenRequest。當然,獲取方法是多種多樣的,也可以用x64dbg等調試工具。確定下來hook這兩個可以達成目標就可以了:

1、從HttpOpenRequest知道要下載的是哪個文件,過濾需要替換的那個。

2、HttpOpenRequest的返回值就是打開文件句柄了,在InternetReadFile中識別這個句柄就可以。

3、在給InternetReadFile的自定義例程中,對需要替換的文件進行替換。

  接下來,讓我們考慮一下替換的流程:webbrowser調用InternetReadFile的時候,先到達我們的自定義例程,然后由我們調用API函數InternetReadFile,於是我們可以在自定義函數中把相應文件句柄的數據請求吃掉——把我們的數據寫入緩沖區,而后直接返回不掉用API。對於我們不關心的文件句柄調用API。

  查看InternetReadFile的API聲明:

    <DllImport("wininet.dll", SetLastError:=True)>
    Public Shared Function InternetReadFile(ByVal hFile As IntPtr, ByVal lpBuffer As IntPtr, ByVal dwNumberOfBytesToRead As Integer, ByRef lpdwNumberOfBytesRead As Integer) As Boolean
    End Function

可以知道,第一個參數是HttpOpenRequest返回的文件句柄,lpbuffer是接收數據的緩沖區,dwnumberofbytestoread是期望讀取的字節數,即緩沖區大小,lpdwnumberofbytesread是實際寫入緩沖區的字節數。返回值表示函數調用是否成功。所以,當lpdwnumberofbytesread為0且函數返回值為true時,達到文件結尾——函數調用成功卻沒有數據寫入,說明數據寫完了。於是,在我們的自定義例程中也需要遵守該約定——當寫入數據時返回實際寫入的數據大小,寫完數據后再次被調用時返回0並進行清理。首先,來看一下比較簡單的一個hook:

Imports System.Runtime.InteropServices
Imports System.Text

Public Class HookHttpOpenRequest

    <DllImport("wininet.dll")>
    Public Shared Function HttpOpenRequestW(hConnect As IntPtr, szVerb As IntPtr, szURI As IntPtr, szHttpVersion As IntPtr, szReferer As IntPtr, accetpType As IntPtr, dwflags As Integer, dwcontext As IntPtr) As IntPtr
    End Function

    Private Delegate Function HttpOpenRequestDelegate(hConnect As IntPtr, szVerb As IntPtr, szURI As IntPtr, szHttpVersion As IntPtr, szReferer As IntPtr, accetpType As IntPtr, dwflags As Integer, dwcontext As IntPtr) As IntPtr
    Private Shared hook As EasyHook.LocalHook = Nothing

    Friend Shared Sub Install()
        Using hook
            If EasyHook.NativeAPI.GetModuleHandle("wininet.dll") = IntPtr.Zero Then
                EasyHook.NativeAPI.LoadLibrary("wininet.dll")
            End If
            hook = EasyHook.LocalHook.Create(EasyHook.LocalHook.GetProcAddress("wininet.dll", "HttpOpenRequestW"), New HttpOpenRequestDelegate(AddressOf sendProc), Nothing)
            hook.ThreadACL.SetInclusiveACL(New Integer() {0})
        End Using
    End Sub

    Friend Shared Sub UnInstall()
        Using hook
            If hook IsNot Nothing Then
                hook.ThreadACL.SetExclusiveACL(New Integer() {0})
            End If
        End Using
    End Sub

    Private Shared Function sendProc(hConnect As IntPtr, szVerb As IntPtr, szURI As IntPtr, szHttpVersion As IntPtr, szReferer As IntPtr, accetpType As IntPtr, dwflags As Integer, dwcontext As IntPtr) As IntPtr
        Dim uri As String = Marshal.PtrToStringUni(szURI)
        Dim result As IntPtr = HttpOpenRequestW(hConnect, szVerb, szURI, szHttpVersion, szReferer, accetpType, dwflags, dwcontext)
        If uri.Contains("/56896-20170216102630488-270057596.jpg") Then '根據名稱區分要替換的圖片. 
HookInternetReadFile.CheatFileHandle = result

End If

Return result

End Function

End Class

easyhook用起來確實比較簡單,首先是注入過程,因為webbrowser對wininte.dll的加載是請求第一個頁面時,所以可能導致這個DLL不在進程空間,那么先加載它。之后的hook非常易懂(函數名我沒有修改,復制粘貼的之前寫的sendhook),唯一需要注意的是實際hook的過程調用的是ThreadACL.SetInclusiveACL,unhook類似。在自定義函數中,首先調用API,得到句柄,然后根據要替換的名稱來確定是否啟動給InternetReadFile的自定義例程。而后,看一下對數據的處理過程:

Imports System.IO
Imports System.Runtime.InteropServices

Public Class HookInternetReadFile
    <DllImport("wininet.dll", SetLastError:=True)>
    Public Shared Function InternetReadFile(ByVal hFile As IntPtr, ByVal lpBuffer As IntPtr, ByVal dwNumberOfBytesToRead As Integer, ByRef lpdwNumberOfBytesRead As Integer) As Boolean
    End Function

    Private Delegate Function InternetReadFileDelegate(ByVal hFile As IntPtr, ByVal lpBuffer As IntPtr, ByVal dwNumberOfBytesToRead As Integer, ByRef lpdwNumberOfBytesRead As Integer) As Boolean
    Private Shared hook As EasyHook.LocalHook = Nothing

    Friend Shared CheatFileHandle As IntPtr = IntPtr.Zero   '要替換的文件的句柄,來源於HttpOpenRequest的返回值。
    Friend Shared CheatFile() As Byte = File.ReadAllBytes(My.Application.Info.DirectoryPath & "\abc.jpg")    '用於替換的文件
    Private Shared curcnt As Integer = 0

    Friend Shared Sub Install()
        Using hook
            If EasyHook.NativeAPI.GetModuleHandle("wininet.dll") = IntPtr.Zero Then
                EasyHook.NativeAPI.LoadLibrary("wininet.dll")
            End If
            hook = EasyHook.LocalHook.Create(EasyHook.LocalHook.GetProcAddress("wininet.dll", "InternetReadFile"), New InternetReadFileDelegate(AddressOf sendProc), Nothing)
            hook.ThreadACL.SetInclusiveACL(New Integer() {0})
        End Using
    End Sub

    Friend Shared Sub UnInstall()
        Using hook
            If hook IsNot Nothing Then
                hook.ThreadACL.SetExclusiveACL(New Integer() {0})
            End If
        End Using
    End Sub

    Private Shared Function sendProc(ByVal hFile As IntPtr, ByVal lpBuffer As IntPtr, ByVal dwNumberOfBytesToRead As Integer, ByRef lpdwNumberOfBytesRead As Integer) As Boolean
        If hFile = CheatFileHandle Then
            If curcnt = CheatFile.Length Then                  
                CheatFileHandle = IntPtr.Zero
                curcnt = 0
                lpdwNumberOfBytesRead = 0                      
            Else                                                
                If curcnt + dwNumberOfBytesToRead <= CheatFile.Length Then             
                    lpdwNumberOfBytesRead = dwNumberOfBytesToRead                       
                    Marshal.Copy(CheatFile, curcnt, lpBuffer, lpdwNumberOfBytesRead)    
                    curcnt += dwNumberOfBytesToRead                                     
                Else                                                                    
                    lpdwNumberOfBytesRead = CheatFile.Length - curcnt                   
                    Marshal.Copy(CheatFile, curcnt, lpBuffer, lpdwNumberOfBytesRead)    
                    curcnt = CheatFile.Length                                          
                End If
            End If
            Return True
        Else
            Return InternetReadFile(hFile, lpBuffer, dwNumberOfBytesToRead, lpdwNumberOfBytesRead)
        End If
    End Function

End Class

因為是一個基本結構范例,所以偷懶直接用了參數,這里應該有錯誤處理過程才行。hook的過程和前面一致,只是在自定義函數中處理把自定義數據寫入緩沖區然后返回了。具體的API過程沒有跟蹤,所以不知道不調用InternetReadFile這個API會不會有內存泄漏之類的什么問題,如果需要調用也非常簡單:在恰當的時機循環讀取一次就可以了。最后是窗體代碼:

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        wb.Navigate("http://images2015.cnblogs.com/blog/56896/201702/56896-20170216102630488-270057596.jpg")
    End Sub

    Private Sub butGotoUrl_Click(sender As Object, e As EventArgs) Handles butGotoUrl.Click
        wb.Refresh()
    End Sub

    Private Sub chkCheat_CheckedChanged(sender As Object, e As EventArgs) Handles chkCheat.CheckedChanged
        If chkCheat.Checked Then
            HookHttpOpenRequest.Install()
            HookInternetReadFile.Install()
        Else
            HookHttpOpenRequest.UnInstall()
            HookInternetReadFile.UnInstall()
        End If
    End Sub

End Class

窗體上一個webbrowser重命名為wb,一個checkbox重命名為chkcheat,一個button重命名為butgotourl,另外,在程序所在目錄放一個abc.jpg。對,這個范例就是這么簡陋,好在現在就可以測試了。如果圖片換名字了,那需要修改url地址的同時修改

If uri.Contains("/56896-20170216102630488-270057596.jpg") Then '根據名稱區分要替換的圖片

才可以正確運行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM