任务目标
增加一种class文件格式变换方法,用工具对class文件进行变换,class parser提示并逆变换加载
修改一个class文件,将magic改掉,class parser能提示并正常加载
目标
test.java
public class test
{
public static void main(String[] args)
{
System.out.println("Test");
}
}
在vim中用%!xxd 打开16进制文件
00000000: cafe babe 0000 0035 001d 0a00 0600 0f09 .......5........
可以发现目前的magic值是0XCAFEBABE
我们编写的magic_change工具源码
#include<iostream>
#include<fstream>
#include<string.h>
using namespace std;
bool name_is_class(char* name)
{
int i;
int len = strlen(name);
for (i = 0; i < len; i++)
{
if (name[i] == '.')
break;
}
if (i == len)
return false;
string str_name = name;
string suffixname = str_name.substr(i + 1);
if (suffixname == "class")
return true;
return false;
}
int main(int argc, char** argv)
{
if (argv[1] == NULL)
{
cout << "give a proper class";
return 0;
}
char* classname = argv[1];
string filename;
if (!name_is_class(classname))
{
cout << classname << " is not a class file ";
return 0;
}
char* buffer = (char*)malloc(sizeof(char)*4);
fstream classfile(classname, ios::in | ios::out | ios::binary);
if (!classfile)
{
cout << filename << " can't open";
return 0;
}
char c;
classfile.seekg(3,ios::beg);
c = 0xBA;
classfile.write((char*)&c, sizeof(char));
classfile.close();
return 0;
}
Tips:vim中全部删除:按esc后,dG
经过magic_change后,
00000000: cafe baba 0000 0035 001d 0a00 0600 0f09 .......5........
MAGIC值被改变了。
那么要做到class分析的时候读取再将MAGIC值改回,就要对源码进行读写修改了。
修改源码
首先找到hotspot目录下的classfile文件夹,
理解classflie中的数据流处理方式;
那么很自然的grep MAGIC定位到classFileParser.cpp文件
Tips:
grep -rn "string_for_search"可以用来找在当前目录下的包含目标字符串的值
find . -name "name_for_search"找寻包含目标名的文件
发现在classFileParser.cpp文件中
#define JAVA_CLASSFILE_MAGIC 0xCAFEBABE
在该文件中搜索,发现有
stream->guarantee_more(8, CHECK); // magic, major, minor
// Magic value
const u4 magic = stream->get_u4_fast();
guarantee_property(magic == JAVA_CLASSFILE_MAGIC,
"Incompatible magic value %u in class file %s",
magic, CHECK);
这个函数整体的前一部分是
void ClassFileParser::parse_stream(const ClassFileStream* const stream,
TRAPS) {
assert(stream != NULL, "invariant");
assert(_class_name != NULL, "invariant");
// BEGIN STREAM PARSING
stream->guarantee_more(8, CHECK); // magic, major, minor
// Magic value
const u4 magic = stream->get_u4_fast();
guarantee_property(magic == JAVA_CLASSFILE_MAGIC,
"Incompatible magic value %u in class file %s",
magic, CHECK);
// Version numbers
_minor_version = stream->get_u2_fast();
_major_version = stream->get_u2_fast();
分析:
// BEGIN STREAM PARSING
stream->guarantee_more(8, CHECK); // magic, major, minor
这个是确保有8个字节以上的内容,
函数原型是
void guarantee_more(int size, TRAPS) const {
size_t remaining = (size_t)(_buffer_end - _current);
unsigned int usize = (unsigned int)size;
check_truncated_file(usize > remaining, CHECK);
}
Tips:在Visual Studio中看源码的时候可利用ctrl+shift+8返回,在Ubuntu中主要利用了grep -rn "需要查找的信息名"
然后就是读取magic值
const u4 magic = stream->get_u4_fast();
函数原型:
u4 get_u4_fast() const {
u4 res = Bytes::get_Java_u4((address)_current);
_current += 4;
return res;
}
读到这里分析一下应该是从目前的指针处往后读一定的字节,然后指针后移
那么可能这个put_Java_u4函数就是我可以用的,想找定义,
这里看文件夹名都是在cpu文件下,是不同类型对应的bytes处理方式,大概都大同小异,打开bytes_x86.hpp
// Efficient reading and writing of unaligned unsigned data in platform-specific byte ordering
// (no special code is needed since x86 CPUs can access unaligned data)
static inline u2 get_native_u2(address p) { return *(u2*)p; }
static inline u4 get_native_u4(address p) { return *(u4*)p; }
static inline u8 get_native_u8(address p) { return *(u8*)p; }
static inline void put_native_u2(address p, u2 x) { *(u2*)p = x; }
static inline void put_native_u4(address p, u4 x) { *(u4*)p = x; }
static inline void put_native_u8(address p, u8 x) { *(u8*)p = x; }
// Efficient reading and writing of unaligned unsigned data in Java
// byte ordering (i.e. big-endian ordering). Byte-order reversal is
// needed since x86 CPUs use little-endian format.
static inline u2 get_Java_u2(address p) { return swap_u2(get_native_u2(p)); }
static inline u4 get_Java_u4(address p) { return swap_u4(get_native_u4(p)); }
static inline u8 get_Java_u8(address p) { return swap_u8(get_native_u8(p)); }
static inline void put_Java_u2(address p, u2 x) { put_native_u2(p, swap_u2(x)); }
static inline void put_Java_u4(address p, u4 x) { put_native_u4(p, swap_u4(x)); }
static inline void put_Java_u8(address p, u8 x) { put_native_u8(p, swap_u8(x)); }
// Efficient swapping of byte ordering
static inline u2 swap_u2(u2 x); // compiler-dependent implementation
static inline u4 swap_u4(u4 x); // compiler-dependent implementation
static inline u8 swap_u8(u8 x);
};
例如
static inline void put_Java_u4(address p, u4 x) { put_native_u4(p, swap_u4(x)); }
static inline void put_native_u4(address p, u4 x) { *(u4*)p = x; }
将地址p的值写入u4类型的x;
在这里比较疑惑的是swap函数的作用,根据注释以及java的特性理解了一下,应该是大小端的区别,进行适当的调整写入。
那么我们回到classFileSteam.hpp文件,仿照get函数写一个change函数
void change_u4_fast() const {
_current -= 4;
Bytes::put_Java_u4((address)_current, (u4)0xCAFEBABE);
_current += 4;
}
再在classFileParser中修改为
// Magic value
u4 magic_check = stream->get_u4_fast();
if (magic_check == 0xCAFEBABA)
{
stream->change_u4_fast();
magic_check = 0xCAFEBABE;
}
const u4 magic =(const u4) magic_check;
guarantee_property(magic == JAVA_CLASSFILE_MAGIC,
"Incompatible magic value %u in class file %s",
magic, CHECK);
重新编译后,通过了。