nodejs利用ffi库调用windows系统user32函数模拟用户登录操作

2020/11/14 11:56
阅读数 115

    如题所示,一般的桌面程序,用户登录很简单,就是找到用户名和密码输入框,输入相应的用户名和密码,然后点击“登录”按钮,完成登录操作。这是人为操作的步骤,如果这些动作通过程序来完成,比如调用系统输入和点击事件,同样可以达到登录的效果。

    我们可以利用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函数可以模拟设置文本,模拟鼠标点击事件,简直不要太刺激,其实按照这个思路,做一些简单的自动登录都是可以的,稍微简单一点的验证码也许也是可以攻破的,但是复杂的验证码可能就够呛了,比如倒立的汉字,图片等等就无法解决了。

    这篇博客是参考了这篇文章而来,感觉很有价值。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部