Linux 使用 hook 来劫持 Java 对主目录路径的获取

通常情况下,IDEA 和 Eclipse 都会在用户主目录下写各种配置文件,虽说可以使用配置文件更改,但是我经常性在 IDEA 的 Terminal 里写一些命令,而我又想让每个不同的编辑器的 Terminal 历史分开保存,所以我便想起来,既然这些都是保存在用户主目录下的,那么我只要让 IDEA 获取用户主目录时获取到我所规定的位置不就好了?

一、寻找切入点

于是,查阅Java代码,发现获取用户主目录的是 System.getProperty(“user.home”) 这条命令,好吧,既然 JVM 底层还是执行的 C 代码, 那我就 hook 掉底层函数即可。

首先需要查阅 Java JVM 实现代码,那么就下载源码吧,下载源码有两种方式(推荐第二种)

  1. http://download.java.net/openjdk/jdk8/
  2. apt-get source openjdk-8-jdk

因为Linux和Windows的获取用户目录的底层方法肯定不可能相同,那么,java的实现一定在C代码里,直接在C代码里搜索 user.home 试试

search user.home from java source

windows 开头的肯定不是,那里面应该是 Windows 的实现方式,那查看一下 Solaris 的代码吧?

在580行左右我找到了实现方式,看下面代码,Mac OS实现也是一样的,可以看到,是调用了 getpwuid 来获得用户信息的

search user_home from java c code

好吧,目标锁定,劫持 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"));
    }
}

编译并运行,发现显示的是正确的目录

Test Java Hook-1

现在加入劫持环境变量

Test Java Hook-2

成功劫持了

五、编写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_NAMEAPP_START_FILEHOOK_HOME 三部分,其中的

-Dawt.useSystemAAFontSettings=on -Dswing.aatext=true -Dsun.java2d.xrender=true

是为了让IDEA的字体在Linux下更加清晰,如不需要可以删除

六、最终效果

Hook Java Final

七、意外效果

由于这个劫持的存在,出现了一些让我意想不到的效果…随便列举几个吧

  • 由劫持主程序打开的所有程序均会被劫持(环境变量的向下传递)
  • 所以相关开发程序产生的文件,例如 Composer 等的文件均会保存到劫持的用户主目录下
  • 备份或者迁移代码/配置/连同其依赖的环境,仅需要一次打包(都在一起了),感觉相当于Windows下的绿化程序
  • 同一个IDEA可以安装不同插件/不同配置而不需要多个主程序,只需要复制多份启动程序。将劫持路径修改即可

 八、劫持例外

如果程序主动切换了用户空间上下文,则劫持不起作用

例如使用了susudo

您可能还喜欢...

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据