Linux 使用 hook 来劫持 Java 对主目录路径的获取
通常情况下,IDEA 和 Eclipse 都会在用户主目录下写各种配置文件,虽说可以使用配置文件更改,但是我经常性在 IDEA 的 Terminal 里写一些命令,而我又想让每个不同的编辑器的 Terminal 历史分开保存,所以我便想起来,既然这些都是保存在用户主目录下的,那么我只要让 IDEA 获取用户主目录时获取到我所规定的位置不就好了?
一、寻找切入点
于是,查阅Java代码,发现获取用户主目录的是 System.getProperty(“user.home”) 这条命令,好吧,既然 JVM 底层还是执行的 C 代码, 那我就 hook 掉底层函数即可。
首先需要查阅 Java JVM 实现代码,那么就下载源码吧,下载源码有两种方式(推荐第二种)
- http://download.java.net/openjdk/jdk8/
- apt-get source openjdk-8-jdk
因为Linux和Windows的获取用户目录的底层方法肯定不可能相同,那么,java的实现一定在C代码里,直接在C代码里搜索 user.home 试试
windows 开头的肯定不是,那里面应该是 Windows 的实现方式,那查看一下 Solaris 的代码吧?
在580行左右我找到了实现方式,看下面代码,Mac OS实现也是一样的,可以看到,是调用了 getpwuid 来获得用户信息的
好吧,目标锁定,劫持 getpwuid 函数返回的 pw_name 和 pw_dir 即可
二、编写 Hook 代码
下面贴实现好的 C 代码
/*
* hook.c
*
* Created on: Jul 25, 2015
* Author: dlll
*
* sudo apt-get install lib32gcc-4.9-dev libc6-dev-i386
* gcc -D_GNU_SOURCE -fPIC -shared -o ASHook.so hook.c -ldl -Os
* gcc -D_GNU_SOURCE -fPIC -shared -o ASHook32.so hook.c -ldl -Os -m32
* sudo cp ASHook.so /usr/lib/x86_64-linux-gnu/ASHook.so
* sudo cp ASHook32.so /usr/lib/i386-linux-gnu/ASHook.so
*/
#include <dlfcn.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
typedef struct passwd *(*GETPWUID)(uid_t);
struct passwd *getpwuid(uid_t uid) {
static GETPWUID old_getpwuid = NULL;
static char *HackHome = NULL;
static char *HackUser = NULL;
static bool show = true;
if (!old_getpwuid) {
old_getpwuid = (GETPWUID) dlsym(RTLD_NEXT, "getpwuid");
HackHome = getenv("HOOK_HOME");
HackUser = getenv("HOOK_USER");
}
struct passwd *pwent = old_getpwuid(uid);
if (HackHome) memcpy(pwent->pw_dir, HackHome, strlen(HackHome) + 1);
if (HackUser) memcpy(pwent->pw_name, HackUser, strlen(HackUser) + 1);
if (show){
printf("### HackHome: %s\n### HackUser: %s\n",HackHome, HackUser);
show = false;
}
return pwent;
}
因为要在多个地方使用,劫持的地址不能写死,于是就从环境变量里面拿到了
三、编译 Hook 代码
编译需要 i386 和 amd64 环境(理论其他架构也没问题,只不过编译代码要改就是了),所以安装
(Ubuntu 16.04) apt install libgcc-5-dev:i386 libc6-dev-i386
其他系统请自行测试理论只需要 libgcc-(*)-dev:i386,libc6-dev-i386
四、测试
首先编写一个Java文件
class test{
public static void main(String[] args){
System.out.println(System.getProperty("user.home"));
}
}
编译并运行,发现显示的是正确的目录
现在加入劫持环境变量
成功劫持了
五、编写IDEA或Eclipse劫持专用启动器
代码如下:
/*
* main.cpp
*
* Created on: Mar 11, 2015
* Author: lutty
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <sstream>
#include <string>
#include <fstream>
int readFile(const char *pathname, unsigned char **data) {
std::ifstream file(pathname, std::ios::binary | std::ios::ate);
if (!file) return -1;
std::fstream::pos_type pos = file.tellg();
*data = new unsigned char[pos];
file.seekg(std::ios::beg);
file.read((char*) (*data), pos);
file.close();
return pos;
}
bool fileExists(const char *pathname) {
return access(pathname, F_OK) == 0;
}
std::string getBundlePath() {
pid_t procpid = getpid();
std::stringstream command;
command << "readlink /proc/" << procpid << "/exe";
FILE * f;
if (!(f = popen(command.str().c_str(), "r"))) return ""; // Can not open file
char buf[4096];
if (fgets(buf, 4096, f) != NULL) {
pclose(f);
return buf;
}
pclose(f);
return "";
}
extern char **environ;
#define APP_NAME "PyCharm"
#define APP_START_FILE "./pycharm.sh"
#define HOOK true
#ifdef HOOK
# define HOOK_HOME (preHome+"/workspace/Python").c_str()
# define HOOK_USER APP_NAME
# define HOOK_FILE "ASHook.so"
#endif
int main(int argc, char **argv) {
std::cout << "my pid: " << getpid() << std::endl;
std::string path = getBundlePath();
int pos = path.rfind('/');
std::string subpath = path.substr(0, pos + 1);
if (subpath == "") {
std::cerr << "getBundlePath error!" << std::endl;
return 1;
}
if (chdir(subpath.c_str()) != 0) {
perror("chdir");
return 2;
}
if (!fileExists(APP_START_FILE)) {
std::cerr << "the " << APP_NAME << " is not exists" << std::endl;
return 3;
}
setenv("_JAVA_OPTIONS", "-Dawt.useSystemAAFontSettings=on -Dswing.aatext=true -Dsun.java2d.xrender=true", true);
#ifdef HOOK
std::string preHome = getenv("HOME");
setenv("HOME", HOOK_HOME, true);
setenv("HOOK_HOME", HOOK_HOME, true);
setenv("USER", HOOK_USER, true);
setenv("HOOK_USER", HOOK_USER, true);
setenv("LD_PRELOAD", HOOK_FILE, true);
#endif
execve(APP_START_FILE, argv, environ);
return 0;
}
需要修改其中的 APP_NAME,APP_START_FILE,HOOK_HOME 三部分,其中的
-Dawt.useSystemAAFontSettings=on -Dswing.aatext=true -Dsun.java2d.xrender=true
是为了让IDEA的字体在Linux下更加清晰,如不需要可以删除
六、最终效果
七、意外效果
由于这个劫持的存在,出现了一些让我意想不到的效果…随便列举几个吧
- 由劫持主程序打开的所有程序均会被劫持(环境变量的向下传递)
- 所以相关开发程序产生的文件,例如 Composer 等的文件均会保存到劫持的用户主目录下
- 备份或者迁移代码/配置/连同其依赖的环境,仅需要一次打包(都在一起了),感觉相当于Windows下的绿化程序
- 同一个IDEA可以安装不同插件/不同配置而不需要多个主程序,只需要复制多份启动程序。将劫持路径修改即可
八、劫持例外
如果程序主动切换了用户空间上下文,则劫持不起作用
例如使用了su、sudo等





近期评论