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等
近期评论