Skip to content

Instantly share code, notes, and snippets.

@SeeFlowerX
Last active July 6, 2023 13:44
Show Gist options
  • Save SeeFlowerX/148336c839b183e70e287c66dac482d4 to your computer and use it in GitHub Desktop.
Save SeeFlowerX/148336c839b183e70e287c66dac482d4 to your computer and use it in GitHub Desktop.
import logging
from pathlib import Path
from datetime import datetime
from bcc import BPF
GLOBAL_LOGGERS = {}
logger = None # type: logging.Logger
def setup_logger(log_tag: str, log_path: Path, first_call: bool = False) -> logging.Logger:
'''
输出的信息太多 Terminal可能不全 记录到日志文件
'''
logger = GLOBAL_LOGGERS.get(log_tag)
if logger:
return logger
logger = logging.getLogger(log_tag)
GLOBAL_LOGGERS[log_tag] = logger
# 避免重新载入脚本时重复输出
if first_call and logger.hasHandlers():
logger.handlers.clear()
# 设置所有 handler 的日志等级
logger.setLevel(logging.DEBUG)
# 添加终端 handler 只打印原始信息
formatter = logging.Formatter('%(message)s')
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setFormatter(formatter)
logger.addHandler(ch)
# 添加文件 handler 记录详细时间和内容
formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(levelname)s: %(message)s', datefmt='%H:%M:%S')
fh = logging.FileHandler(log_path.as_posix(), encoding='utf-8', delay=True)
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
logger.addHandler(fh)
return logger
BPF_CODE_include = """
#include <linux/ptrace.h>
"""
BPF_CODE_open = """
struct ArtMethod {
u32 declaring_class_;
u32 access_flags_;
//u32 dex_code_item_offset_;
u32 dex_method_index_;
u16 method_index_;
};
struct MirrorClass {
u32 class_loader_;
u32 pad0;
u32 component_type_;
u32 pad1;
u32 dex_cache_;
u32 pad2;
u64 ext_data_;
};
struct DexCache {
u32 class_loader_;
u32 pad0;
u32 location_;
u32 pad1;
void* dex_file_;
u64 preresolved_strings_;
};
struct DexFile {
void* vptr;
uint8_t* begin_;
size_t size_;
uint8_t* data_begin_;
size_t data_size_;
char location_[24];
uint32_t location_checksum_;
void* header_;
void* string_ids_;
void* type_ids_;
void* field_ids_;
void* method_ids_;
void* proto_ids_;
};
struct MethodId {
uint16_t class_idx_;
uint16_t proto_idx_;
uint32_t name_idx_;
};
struct StringId {
uint32_t string_data_off_;
};
struct TypeId {
uint32_t descriptor_idx_;
};
struct FieldId {
uint16_t class_idx_;
uint16_t type_idx_;
uint32_t name_idx_;
};
struct ProtoId {
uint32_t shorty_idx_;
uint16_t return_type_idx_;
uint16_t pad_;
uint32_t parameters_off_;
};
struct TypeItem {
uint16_t type_idx_;
};
struct DataEvent {
u32 pid;
u32 tid;
u64 art_method;
u32 dex_method_index;
u16 method_index;
u32 method_id_size2;
u32 method_id_size3;
char class_name[128];
char ret_sig[128];
char method_name[128];
u32 param_size;
char param_sig1[128];
char param_sig2[128];
char param_sig3[128];
char param_sig4[128];
char param_sig5[128];
char param_sig6[128];
};
struct JniDataEvent {
u64 env;
u64 obj;
u64 art_method;
u32 dex_method_index;
u16 method_index;
u32 method_id_size2;
u32 method_id_size3;
u32 param_size;
};
BPF_PERCPU_ARRAY(jni_perf_data, struct DataEvent, 1);
BPF_PERF_OUTPUT(perf_jni);
int probe_hook_jni_invoke_enter(struct pt_regs *ctx) {
u64 pid_tgid = bpf_get_current_pid_tgid();
u32 pid = pid_tgid >> 32;
u32 tid = pid_tgid & 0xffffffff;
u32 uid = bpf_get_current_uid_gid();
PID_FILTER
TID_FILTER
UID_FILTER
struct JniDataEvent jni_data_event_t;
//__builtin_memset(&jni_data_event_t, 0, sizeof(jni_data_event_t));
jni_data_event_t.art_method = PT_REGS_PARM3(ctx);
struct ArtMethod art_method;
bpf_probe_read_user(&art_method, sizeof(art_method), (void *)jni_data_event_t.art_method);
jni_data_event_t.dex_method_index = art_method.dex_method_index_;
jni_data_event_t.method_index = art_method.method_index_;
struct MirrorClass mirror_class;
bpf_probe_read_user(&mirror_class, sizeof(mirror_class), art_method.declaring_class_);
struct DexCache dex_cache;
bpf_probe_read_user(&dex_cache, sizeof(dex_cache), mirror_class.dex_cache_);
struct DexFile dex_file;
bpf_probe_read_user(&dex_file, sizeof(dex_file), dex_cache.dex_file_);
struct TypeId type_id;
struct StringId string_id;
struct MethodId method_id;
bpf_probe_read_user(&method_id, sizeof(method_id), dex_file.method_ids_ + (sizeof(method_id) * art_method.dex_method_index_));
uint32_t parameters_offset;
bpf_probe_read_user(&parameters_offset, sizeof(parameters_offset), dex_file.proto_ids_ + (12 * method_id.proto_idx_ + 8));
jni_data_event_t.method_id_size2 = parameters_offset;
//bpf_trace_printk("[jni_invoke] parameters_offset_0 before:%d after:%d\\n", jni_data_event_t.method_id_size2, parameters_offset);
void* param_sig_ptr[6];
jni_data_event_t.param_size = 0;
if (parameters_offset > 0) {
jni_data_event_t.method_id_size3 = parameters_offset;
//bpf_trace_printk("[jni_invoke] parameters_offset before:%d after:%d\\n", jni_data_event_t.method_id_size2, jni_data_event_t.method_id_size3);
uint32_t param_size;
bpf_probe_read_user(&param_size, sizeof(param_size), dex_file.data_begin_ + parameters_offset);
if (param_size > 0 && param_size <= 6) {
struct TypeItem type_item;
struct TypeId param_type_id;
jni_data_event_t.param_size = param_size;
//uint16_t param_type_idx;
//bpf_probe_read_user(&param_type_idx, sizeof(param_type_idx), dex_file.data_begin_ + parameters_offset + 4);
#pragma unroll
for (uint32_t i = 0; i < 6; i++) {
if (i == param_size) break;
bpf_probe_read_user(&type_item, sizeof(type_item), dex_file.data_begin_ + parameters_offset + 4 + sizeof(type_item) * i);
bpf_probe_read_user(&param_type_id, sizeof(param_type_id), dex_file.type_ids_ + (sizeof(param_type_id) * type_item.type_idx_));
bpf_probe_read_user(&string_id, sizeof(string_id), dex_file.string_ids_ + (sizeof(string_id) * param_type_id.descriptor_idx_));
param_sig_ptr[i] = dex_file.data_begin_ + string_id.string_data_off_;
}
}
}
struct ProtoId proto_id;
bpf_probe_read_user(&proto_id, sizeof(proto_id), dex_file.proto_ids_ + (sizeof(proto_id) * method_id.proto_idx_));
bpf_probe_read_user(&type_id, sizeof(type_id), dex_file.type_ids_ + (sizeof(type_id) * proto_id.return_type_idx_));
bpf_probe_read_user(&string_id, sizeof(string_id), dex_file.string_ids_ + (sizeof(string_id) * type_id.descriptor_idx_));
void *ret_sig_ptr = dex_file.data_begin_ + string_id.string_data_off_;
bpf_probe_read_user(&type_id, sizeof(type_id), dex_file.type_ids_ + (sizeof(type_id) * method_id.class_idx_));
bpf_probe_read_user(&string_id, sizeof(string_id), dex_file.string_ids_ + (sizeof(string_id) * type_id.descriptor_idx_));
void *class_name_ptr = dex_file.data_begin_ + string_id.string_data_off_;
bpf_probe_read_user(&string_id, sizeof(string_id), dex_file.string_ids_ + (sizeof(string_id) * method_id.name_idx_));
void *method_name_ptr = dex_file.data_begin_ + string_id.string_data_off_;
u32 zero = 0;
struct DataEvent* data = jni_perf_data.lookup(&zero);
if (data == NULL) return 0;
data->pid = pid;
data->tid = tid;
data->art_method = jni_data_event_t.art_method;
data->dex_method_index = jni_data_event_t.dex_method_index;
data->method_index = jni_data_event_t.method_index;
data->method_id_size2 = jni_data_event_t.method_id_size2;
data->method_id_size3 = jni_data_event_t.method_id_size3;
bpf_probe_read_user(data->ret_sig, sizeof(data->ret_sig), ret_sig_ptr);
bpf_probe_read_user(data->class_name, sizeof(data->class_name), class_name_ptr);
bpf_probe_read_user(data->method_name, sizeof(data->method_name), method_name_ptr);
data->param_size = jni_data_event_t.param_size;
//if (jni_data_event_t.param_size > 0) {
// bpf_probe_read_user(data->param_sig1, sizeof(data->param_sig1), (void*)param_sig_ptr[0]);
//}
//if (jni_data_event_t.param_size > 1) {
// bpf_probe_read_user(data->param_sig2, sizeof(data->param_sig2), (void*)param_sig_ptr[1]);
//}
//if (jni_data_event_t.param_size > 2) {
// bpf_probe_read_user(data->param_sig3, sizeof(data->param_sig3), (void*)param_sig_ptr[2]);
//}
//if (jni_data_event_t.param_size > 3) {
// bpf_probe_read_user(data->param_sig4, sizeof(data->param_sig4), (void*)param_sig_ptr[3]);
//}
//if (jni_data_event_t.param_size > 4) {
// bpf_probe_read_user(data->param_sig5, sizeof(data->param_sig5), (void*)param_sig_ptr[4]);
//}
//if (jni_data_event_t.param_size > 5) {
// bpf_probe_read_user(data->param_sig6, sizeof(data->param_sig6), (void*)param_sig_ptr[5]);
//}
perf_jni.perf_submit(ctx, data, sizeof(struct DataEvent));
return 0;
}
"""
def get_str(data: bytes, decode: bool = True):
if len(data) == 0:
return
mask = 0x80000000
if data[0] & mask != 0:
if data[1] & mask != 0:
if data[2] & mask != 0:
offset = 4
if data[3] < 0:
offset = 5
else:
offset = 3
else:
offset = 2
else:
offset = 1
if decode:
text = data[offset:].decode('utf-8')
return text.replace("/", ".").lstrip("L").rstrip(";")
else:
return data[offset:]
class BPFHooker:
def __init__(self, library: str, uid: int, pid: int = -1, tid: int = -1) -> None:
self.uid = uid
self.pid = pid
self.tid = tid
self.library = library
self.bpf_module = None # type: BPF
def hook(self):
text = BPF_CODE_include
text += BPF_CODE_open
if self.pid > 0:
text = text.replace('PID_FILTER', f'if (pid != {self.pid}) {{ return 0; }}')
else:
text = text.replace('PID_FILTER', '')
if self.tid > 0:
text = text.replace('TID_FILTER', f'if (tid != {self.tid}) {{ return 0; }}')
else:
text = text.replace('TID_FILTER', '')
if self.uid > 0:
text = text.replace('UID_FILTER', f'if (uid != {self.uid}) {{ return 0; }}')
else:
text = text.replace('UID_FILTER', '')
self.bpf_module = BPF(text=text)
sym_InvokeVirtualOrInterfaceWithVarArgs_ArtMethod = '_ZN3art35InvokeVirtualOrInterfaceWithVarArgsIPNS_9ArtMethodEEENS_6JValueERKNS_33ScopedObjectAccessAlreadyRunnableEP8_jobjectT_St9__va_list'
self.bpf_module.attach_uprobe(name=self.library, sym=sym_InvokeVirtualOrInterfaceWithVarArgs_ArtMethod, fn_name='probe_hook_jni_invoke_enter', pid=self.pid)
logger.info('attach end')
def print_event_perf_jni(self, ctx, data, size):
event = self.bpf_module['perf_jni'].event(data)
logger.info(
f'[env->CallObjectMethod] tid={event.tid} '
f'method={event.art_method:#12x} '
f'{get_str(event.ret_sig)} {get_str(event.class_name)}->{get_str(event.method_name)}(...) '
f'{event.param_size}'
)
def show(self):
self.bpf_module["perf_jni"].open_perf_buffer(self.print_event_perf_jni)
while 1:
try:
self.bpf_module.perf_buffer_poll()
except KeyboardInterrupt:
exit()
def main():
global logger
log_tag = 'jnitrace'
log_time = datetime.now().strftime('%Y%m%d_%H%M%S')
log_path = Path(__file__).parent / f'logs/{log_tag}_{log_time}.log'
logger = setup_logger(log_tag, log_path, first_call=True)
# uid = 10240
uid = 10235
uid = 10236
library = "/apex/com.android.art/lib64/libart.so"
bpf_hooker = BPFHooker(library, uid)
bpf_hooker.hook()
bpf_hooker.show()
# /sys/kernel/debug 是需要挂载 debugfs 才有
# echo 1 > /sys/kernel/tracing/tracing_on
# cat /sys/kernel/tracing/trace_pipe
# echo 0 > /sys/kernel/tracing/tracing_on
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment