nodejs利用ffi庫調用windows系統user32函數模擬用戶登錄操作

    如題所示,一般的桌面程序,用戶登錄很簡單,就是找到用戶名和密碼輸入框,輸入相應的用戶名和密碼,然後點擊“登錄”按鈕,完成登錄操作。這是人爲操作的步驟,如果這些動作通過程序來完成,比如調用系統輸入和點擊事件,同樣可以達到登錄的效果。

    我們可以利用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函數可以模擬設置文本,模擬鼠標點擊事件,簡直不要太刺激,其實按照這個思路,做一些簡單的自動登錄都是可以的,稍微簡單一點的驗證碼也許也是可以攻破的,但是複雜的驗證碼可能就夠嗆了,比如倒立的漢字,圖片等等就無法解決了。

    這篇博客是參考了這篇文章而來,感覺很有價值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章