package aotinject /* #cgo CFLAGS: -I ../inject/include #cgo amd64 LDFLAGS: ${SRCDIR}/../inject/lib/libhotpatch_amd64.a #cgo arm64 LDFLAGS: ${SRCDIR}/../inject/lib/libhotpatch_arm64.a #include "hotpatch.h" #include */ import "C" import ( "bufio" "debug/dwarf" "debug/elf" "flag" "fmt" "io" "log" "os" "strconv" "strings" "syscall" "unsafe" ) func InjectLib(pid int, libPath string) int { dll := C.CString(libPath) // 替换为实际的DLL路径 fmt.Printf("dll: %s\n", libPath) rootfs := C.CString("") defer C.free(unsafe.Pointer(dll)) // 确保在使用完字符串后释放内存 result := C.cw_inject_library(C.int(pid), C.int(0), dll, rootfs) fmt.Printf("Result JVM: %d\n", result) return int(result) } // readMemory 用于读取指定地址的内存数据 func readMemory(pid int, address uint64, size uint64) ([]byte, error) { memFile := fmt.Sprintf("/proc/%d/mem", pid) file, err := os.Open(memFile) if err != nil { return nil, err } defer file.Close() data := make([]byte, size) _, err = file.ReadAt(data, int64(address)) if err != nil { return nil, err } return data, nil } func getFunctionOffsetDBG(libPath, functionName string) (elf.Symbol, error) { dwarfFile, err := elf.Open(libPath) if err != nil { return elf.Symbol{}, fmt.Errorf("failed to open libPath %s: %v", libPath, err) } dwarfData, err := dwarfFile.DWARF() if err != nil { return elf.Symbol{}, fmt.Errorf("failed to read DWARF data: %v", err) } type uprobesDef struct { Name string Offset uint64 EntAddress uint64 RetAddress uint64 } listEntry := make(map[dwarf.Offset]uprobesDef) SpecListEntry := []dwarf.Entry{} entryReader := dwarfData.Reader() for { entry, err := entryReader.Next() if err == io.EOF { // We've reached the end of DWARF entries break } if err != nil { log.Println("Error reading entry: %v", err) } if entry == nil { log.Println("Warning: a nil entry was returned with no error") break } // fmt.Printf("-----entry address: %v, %v, %v, %v, %v\n", entry, entry.Val(dwarf.AttrName), entry.Tag, entry.Val(dwarf.AttrLowpc), entry.Val(dwarf.AttrHighpc)) if entry.Tag == dwarf.TagSubprogram { // fmt.Printf("entry address: %x, %d\n", entry.Offset, entry.Children) funName, ok := entry.Val(dwarf.AttrName).(string) if !ok { continue } if functionName == funName { entAddress, ok := entry.Val(dwarf.AttrLowpc).(uint64) if !ok { entAddress = 0 } retAddress, ok := entry.Val(dwarf.AttrHighpc).(uint64) if !ok { retAddress = 0 } fmt.Printf("Function %s address: %x, %x, %x\n", funName, entAddress, entry.Offset, retAddress) uprobes := uprobesDef{ Name: funName, Offset: uint64(entry.Offset), EntAddress: entAddress, RetAddress: retAddress, } listEntry[entry.Offset] = uprobes } specAddr, _ := entry.Val(dwarf.AttrSpecification).(dwarf.Offset) lowpc := entry.Val(dwarf.AttrLowpc) if lowpc != nil && specAddr > 0 && lowpc.(uint64) > 0 { SpecListEntry = append(SpecListEntry, *entry) } } } for _, v := range SpecListEntry { specAddr, _ := v.Val(dwarf.AttrSpecification).(dwarf.Offset) // fmt.Printf("SpecListEntrySpecListEntrySpecListEntry Attach Function: %x\n", specAddr) _, ok := listEntry[specAddr] if ok { vv := listEntry[specAddr] entAddr := v.Val(dwarf.AttrLowpc) if entAddr != nil { vv.EntAddress = entAddr.(uint64) } retAddr := v.Val(dwarf.AttrHighpc) if retAddr != nil { switch retAddr.(type) { case uint64: vv.RetAddress = uint64(retAddr.(uint64)) case int64: vv.RetAddress = uint64(retAddr.(int64)) default: fmt.Println("Unknown type") } } listEntry[specAddr] = vv } } for _, v := range listEntry { sSize := v.RetAddress if v.RetAddress > v.EntAddress { sSize = v.RetAddress - v.EntAddress } symbol := elf.Symbol{ Name: v.Name, Value: v.EntAddress, Size: 180, } fmt.Printf("Need Attach Function %s address: %x, %x, %d\n", v.Name, v.EntAddress, v.RetAddress, sSize) return symbol, nil } return elf.Symbol{}, fmt.Errorf("function %s not found", functionName) } func getFunctionOffset(libPath, functionName string) (elf.Symbol, error) { elfFile, err := elf.Open(libPath) if err != nil { return elf.Symbol{}, fmt.Errorf("failed to open ELF file: %v", err) } defer elfFile.Close() // 优先从 Symbols 中查找函数的地址,如果找不到则从 DynamicSymbols 中查找 symbols, err := elfFile.Symbols() if err != nil { fmt.Printf("Can Not find Symbols: %s, %s, %v, can find from DynamicSymbols\n", libPath, functionName, err) symbols, err = elfFile.DynamicSymbols() if err != nil { return elf.Symbol{}, fmt.Errorf("failed to read dynamic symbols: %v", err) } } for _, sym := range symbols { if sym.Name == functionName { return sym, nil } } return elf.Symbol{}, fmt.Errorf("function %s not found from Symbols", functionName) } // getProcessMapsInfo reads the first line of /proc//maps file and returns the memory map as a MemoryMap struct // If a library name is provided, it returns the start address of that library func getProcessMapsInfo(pid int, libraryName ...string) (*ProcessMapsInfo, error) { file, err := os.Open(fmt.Sprintf("/proc/%d/maps", pid)) if err != nil { return nil, err } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() fields := strings.Fields(line) addresses := strings.Split(fields[0], "-") if len(addresses) != 2 { return nil, fmt.Errorf("unexpected format in /proc/%d/maps", pid) } start, err := strconv.ParseUint(addresses[0], 16, 64) if err != nil { return nil, err } end, err := strconv.ParseUint(addresses[1], 16, 64) if err != nil { return nil, err } if len(libraryName) == 0 || (len(fields) > 5 && strings.Contains(fields[5], libraryName[0])) { return &ProcessMapsInfo{ Start: start, End: end, Path: fields[5], }, nil } } if err := scanner.Err(); err != nil { return nil, err } return nil, fmt.Errorf("specified library or process start address not found in /proc/%d/maps", pid) } func GetProcessFunctionInfo(pid int, libPath string, functionName string, libraryName ...string) (*ProcessFunctionInfo, error) { processMapsInfo, _ := getProcessMapsInfo(pid, libraryName...) baseAddress := uint64(processMapsInfo.Start) // functionName 是以 @plt 结尾的函数名,则调用 getFunctionOffsetPLT 获取函数的地址,并且去掉 @plt 后缀 if strings.HasSuffix(functionName, "@plt") { functionName = functionName[:len(functionName)-4] sendFunctionNew, _ := getFunctionOffsetPLT(pid, libPath, functionName) return &ProcessFunctionInfo{ Name: functionName, Offset: sendFunctionNew.Value, Start: baseAddress + sendFunctionNew.Value, Size: sendFunctionNew.Size, }, nil } // 获取函数的偏移量,有限直接从 ELF 文件中获取,如果失败则从 DWARF 文件中获取 functionSym, err := getFunctionOffset(libPath, functionName) if err != nil { fmt.Printf("Error getting function offset: %v, now to find dbg %s\n", err, libPath+".dbg") functionSym, err = getFunctionOffsetDBG(libPath+".dbg", functionName) if err != nil { fmt.Printf("Error getting function offset from DWARF file: %v\n", err) return nil, err } } fmt.Printf("get ProcessFunctionInfo: 0x%x, 0x%x\n", baseAddress, functionSym.Value) funRelAddr := functionSym.Value if functionSym.Value < baseAddress { funRelAddr += baseAddress } return &ProcessFunctionInfo{ Name: functionSym.Name, Offset: functionSym.Value, Start: funRelAddr, Size: functionSym.Size, }, nil } func AotInject(aotInjector AOTInjector) (int, error) { isInject := false // lib 注入 cwLibraryInfo, err := getProcessMapsInfo(aotInjector.PID, aotInjector.CWLibName) if err != nil { fmt.Printf("Not Find Lib %s: Now to InjectLib: %v\n", aotInjector.CWLibName, err) InjectLib(aotInjector.PID, aotInjector.CWLibPath) cwLibraryInfo, err = getProcessMapsInfo(aotInjector.PID, aotInjector.CWLibName) if err != nil { fmt.Printf("InjectLib %s Err: %v\n", aotInjector.CWLibName, err) return 0, fmt.Errorf("InjectLib %s Err: %v", aotInjector.CWLibName, err) } } else { isInject = true } fmt.Printf("find %s Lib Start: %x, End: %x, Path: %s\n", aotInjector.CWLibName, cwLibraryInfo.Start, cwLibraryInfo.End, cwLibraryInfo.Path) // 查找 cwFunctionName 函数的偏移量 cwSym, _ := GetProcessFunctionInfo(aotInjector.PID, cwLibraryInfo.Path, aotInjector.CWFunctionName, aotInjector.CWLibName) // 打印 cwFunctionName 函数的偏移量 fmt.Printf("%s: addr %x, size %d\n", aotInjector.CWFunctionName, cwSym.Start, cwSym.Size) if isInject == true { fmt.Printf("cwLibrary is already Injected, to find hookOffset\n") hookOffset, err := findNopFunctionHookOffset(aotInjector.PID, cwSym) if err != nil { fmt.Printf("findNopFunctionHookOffset Err: %v Need Inject\n", err) } else { fmt.Printf("findNopFunctionHookOffset: %d[%d]\n", hookOffset, hookOffset+nopEntryOffset) return hookOffset + nopEntryOffset, nil } } // 获取socketWrite0 和 NET_Send 函数的偏移量 jvmInjectorFunction, err := GetProcessFunctionInfo(aotInjector.PID, aotInjector.InjectLibPath, aotInjector.InjectFunctionName) if err != nil { fmt.Printf("Error getting function %s offset: %v\n", aotInjector.InjectFunctionName, err) return 0, fmt.Errorf("error getting function %s offset: %v", aotInjector.InjectFunctionName, err) } sendFunction, err := GetProcessFunctionInfo(aotInjector.PID, aotInjector.SendFunctionLibPath, aotInjector.SendFunctionName) if err != nil { fmt.Printf("Error getting function %s offset: %v\n", aotInjector.SendFunctionName, err) return 0, fmt.Errorf("error getting function %s offset: %v", aotInjector.SendFunctionName, err) } fmt.Printf("find %s, addr: %x, size:%d\n", aotInjector.InjectFunctionName, jvmInjectorFunction.Start, jvmInjectorFunction.Size) fmt.Printf("find %s, addr: %x, size:%d\n", aotInjector.SendFunctionName, sendFunction.Start, sendFunction.Size) // 读取 socketWrite0 函数的内存数据并解析 NET_Send 在 socketWrite0 中的调用位置 code, err := readMemory(aotInjector.PID, jvmInjectorFunction.Start, jvmInjectorFunction.Size) if err != nil { fmt.Printf("Error reading jvmInjectorFunction memory: %v\n", err) return 0, fmt.Errorf("error reading jvmInjectorFunction memory: %v", err) } // 打印 socketWrite0 函数的内存数据 fmt.Printf("socketWrite0 code: %v\n", code) SendFunctionAbsOffset, SendFunctionRelOffset, err := findSendFunctionNameAddr(jvmInjectorFunction.Start, code, aotInjector.SendFunctionName, sendFunction.Start) if err != nil { fmt.Printf("findSendFunctionNameAddr Err: %v\n", err) return 0, fmt.Errorf("findSendFunctionNameAddr Err: %v", err) } fmt.Printf("find %s : SendFunctionAbsOffset:%x, SendFunctionRelOffset: %d\n", aotInjector.SendFunctionName, SendFunctionAbsOffset, SendFunctionRelOffset) // 尝试获取 NET_Send 附近的数据凑够 17 个字节,用于长跳转到 libmylib 中 instMemStruct, err := findMemForLongJump(jvmInjectorFunction.Start, code, aotInjector.SendFunctionName, SendFunctionAbsOffset) if err != nil { fmt.Printf("findMemForLongJump Err: %v\n", err) return 0, fmt.Errorf("findMemForLongJump Err: %v", err) } // 打印 instMemStruct 结构体的内容 fmt.Printf("instMemStruct: %v\n", instMemStruct) // 生成长跳指令,跳转到 libmylib 中的 cwFunctionName 函数的+12地址 jumpCode := generateLongJumpCode(cwSym.Start+nopEntryOffset, len(instMemStruct.CodeArray), true) // 使用 parseAndPrintInstructions打印 jumpCode 的内容 fmt.Printf("jumpCode: %v\n", jumpCode) parseAndPrintInstructions(jumpCode) // 生成新的指令,用于替换原来的指令,一个参数是原来的指令 instCodeArray,另一个参数是跳转回去的地址,第三个参数是原始 Send 函数的地址,第四个参数是在哪个指令前插入自定义指令 newCode, hookOffset := generateNewCode(instMemStruct.InstCodeArray, instMemStruct.InstStartAddr+jumpBackAddrOffset, sendFunction.Start, instMemStruct.InsertIndex) if hookOffset == 0 { fmt.Printf("generateNewCode Err: %v\n", instMemStruct) return 0, fmt.Errorf("generateNewCode Err: %v", instMemStruct) } // 打印 hookIndex fmt.Printf("ebpf should hookIndex: %d[%d]\n", hookOffset, nopEntryOffset+hookOffset) // 使用 parseAndPrintInstructions 打印 newCode 的内容 fmt.Printf("newCode: %v\n", newCode) parseAndPrintInstructions(newCode) // 使用 ptrace attach 目标进程 fmt.Printf("Attach Process: %d\n", aotInjector.PID) if err := syscall.PtraceAttach(aotInjector.PID); err != nil { fmt.Printf("PtraceAttach Err: %v\n", err) return 0, fmt.Errorf("ptrace attach: %v", err) } // 等待目标进程停止 if _, err := syscall.Wait4(aotInjector.PID, nil, 0, nil); err != nil { fmt.Printf("wait4: %v", err) if err = syscall.PtraceDetach(aotInjector.PID); err != nil { fmt.Printf("ptrace DETACH: %v", err) } return 0, fmt.Errorf("wait4: %v", err) } fmt.Printf("Ptrace 将newCode使用ptrace写入到目标进程的内存中: %x\n", cwSym.Start+nopEntryOffset) // 将newCode使用ptrace写入到目标进程的的空白内存中,地址为 cwSym.Start+12 _, err = syscall.PtracePokeData(aotInjector.PID, uintptr(cwSym.Start+nopEntryOffset), newCode) if err != nil { fmt.Printf("PtracePokeData Err: %v\n", err) } fmt.Printf("Ptrace 将jumpCode使用ptrace写入到目标进程的内存中: %x\n", instMemStruct.InstStartAddr) // 将jumpCode使用ptrace写入到目标进程的内存中 _, err = syscall.PtracePokeData(aotInjector.PID, uintptr(instMemStruct.InstStartAddr), jumpCode) if err != nil { fmt.Printf("PtracePokeData Err: %v\n", err) } fmt.Printf("Ptrace DETACH: %d\n", aotInjector.PID) // 恢复执行 if err = syscall.PtraceDetach(aotInjector.PID); err != nil { fmt.Printf("ptrace DETACH: %v", err) } return hookOffset + nopEntryOffset, nil } type AOTInjector struct { PID int SendFunctionLibPath string SendFunctionName string InjectLibPath string InjectFunctionName string CWLibName string CWFunctionName string CWLibPath string } func main() { flag.StringVar(&PID, "p", "", "PID") flag.Parse() pidStr := PID // 替换为目标进程的 PID pid, err := strconv.Atoi(pidStr) if err != nil { log.Fatalf("Invalid PID: %v", err) } // NETCoreAOTInject // netCoreAotInjector := AOTInjector{ // PID: pid, // SendFunctionLibPath: "/data/NET8/CoreAoT/bin/Debug/net8.0/linux-x64/publish/CoreAoT", // SendFunctionName: "send@plt", // InjectLibPath: "/data/NET8/CoreAoT/bin/Debug/net8.0/linux-x64/publish/CoreAoT", // InjectFunctionName: "SystemNative_Send", // CWLibName: "libmylib.so", // CWFunctionName: "asmnop", // CWLibPath: "/data/NET8/CoreAoT/bin/Debug/net8.0/linux-x64/publish/CoreAoT", // } // JavaAOTInject javaAotInjector := AOTInjector{ PID: pid, SendFunctionLibPath: "/data/roger/ebpfdemo/graalve_demo/SimpleHttpServer", SendFunctionName: "NET_Send", InjectLibPath: "/data/roger/ebpfdemo/graalve_demo/SimpleHttpServer", InjectFunctionName: "Java_java_net_SocketOutputStream_socketWrite0", CWLibName: "libmylib.so", CWFunctionName: "asmnop", CWLibPath: "/data/roger/ebpfdemo/mylib/libmylib.so", } hookOffset, err := AotInject(javaAotInjector) if err != nil { fmt.Printf("AotInject Err: %v\n", err) } fmt.Printf("AotInject result hookOffset: %d\n", hookOffset) }