如題所示,一般的桌面程序,用戶登錄很簡單,就是找到用戶名和密碼輸入框,輸入相應的用戶名和密碼,然後點擊“登錄”按鈕,完成登錄操作。這是人爲操作的步驟,如果這些動作通過程序來完成,比如調用系統輸入和點擊事件,同樣可以達到登錄的效果。
我們可以利用user32提供的SendMessageW函數,發送設置文本指令和鼠標點擊指令,讓程序完成上述人爲操作。
難點就是找到窗口,然後找到窗口裏面的組件,然後給對應的“用戶名”組件設置用戶名文本,給“密碼”組件設置密碼文本,點擊“登錄”提交按鈕。
按照慣例,我們需要安裝對應的依賴庫:
npm install ffi ref --save
安裝過程中需要源碼編譯,如果不能正常安裝,需要全局安裝winows-build-tools:
npm install -g --production windows-build-tools
按照我們分析的思路,我們給出如下代碼:
autologin.js
var ffi = require('ffi');
var ref = require("ref");
function strBuf(str) {
//return Buffer.from(`${text}\0`, "ucs2");
return Buffer.from(str+'\0','ucs2');
}
var user32 = new ffi.Library("user32", {
FindWindowW:["int32",["string","string"]],
GetWindow: ["int32",["int32","int32"]],
SendMessageW: ["int32",["int32","uint","uint64","int64"]]
});
function getControls(hwnd, indices) {
const GW_CHILD = 5;
const GW_HWNDNEXT = 2;
const controls = {};
const map = [];
for (let key in indices) {
map[indices[key]] = key;
}
let childhwnd = user32.GetWindow(hwnd, GW_CHILD);
let index = 0;
while (isValidHandle(childhwnd)) {
if (map[index]) {
controls[map[index]] = childhwnd;
}
childhwnd = user32.GetWindow(childhwnd, GW_HWNDNEXT);
index++;
}
return controls;
}
function isValidHandle(hwnd) {
return typeof hwnd === 'number' && hwnd > 0
|| typeof hwnd === 'bigint' && hwnd > 0
|| typeof hwnd === 'string' && hwnd.length > 0;
}
function click(hwnd){
const BM_CLICK = 0xF5;
user32.SendMessageW(hwnd, BM_CLICK, 0, 0);
}
function input(hwnd,str){
const WM_SETTEXT = 0x000C;
const addr = ref.address(strBuf(str));
console.log(addr);
user32.SendMessageW(hwnd,WM_SETTEXT,0,addr);
}
async function login(){
var hwnd = user32.FindWindowW(null,strBuf("loginapp"));
var indices = {accoutlabel:0,passwordlabel:1,account:2,password:3,submit:4,cancel:5};
var controls = getControls(hwnd,indices);
console.log(controls);
input(controls.account,"admin");
await delay(100);
input(controls.password,"123456");
await delay(100);
click(controls.submit);
}
login();
async function delay(milliseconds){
return new Promise(resolve=>setTimeout(resolve,milliseconds));
}
核心操作就是:
1、找到“loginapp”的這個桌面程序窗口句柄,根據句柄,我們可以找到界面的控件,分別是用戶名標籤,密碼標籤,用戶名輸入框,密碼輸入框,提交按鈕,取消按鈕。
2、找到了控件,接下來 就是輸入用戶名和密碼。
3、用戶名和密碼輸入無誤,點擊“登錄”提交按鈕。
現在給出,手動登錄的截圖:
這個登錄程序是我自己製作的,就是利用vs2019做的一個windows desktop application,界面很簡單,點擊登錄,判斷用戶名和密碼是否爲admin與123456,如果是提示登錄成功,否則,提示登錄失敗。
我們運行自動登錄程序,看看效果:
這裏需要注意的是,SendMessageW()函數的參數,按照官方文檔定義如下:
最後兩個參數一個是unit_ptr,一個是long_ptr,這裏我剛開始都設置默認的int或者int32都不對,計算ref.address(strBuf(str))的時候。總提示超出範圍:-2147483648 ~ 2147483647,後來改爲long,還是不對,最後根據ref文檔提示,改爲uint64和int64,問題順利解決。
數據類型設置錯誤,報錯信息如下:
(node:16748) UnhandledPromiseRejectionWarning: RangeError [ERR_OUT_OF_RANGE]: error setting argument 3 - The value of "value" is out of range. It must be >= -2147483648 and <= 2147483647. Received 3089491790416
今天的這個博客很有意義,幫助我們學習了user32系統函數調用,通過SendMessageW函數可以模擬設置文本,模擬鼠標點擊事件,簡直不要太刺激,其實按照這個思路,做一些簡單的自動登錄都是可以的,稍微簡單一點的驗證碼也許也是可以攻破的,但是複雜的驗證碼可能就夠嗆了,比如倒立的漢字,圖片等等就無法解決了。
這篇博客是參考了這篇文章而來,感覺很有價值。