Skip to content

Commit 38005a6

Browse files
authored
fix: attach on glibc >= 2.35 (alibaba#21)
* fix: support attach on glibc >= 2.35 by modifying shellcode in the higher address space, which is safer, padded and aligned to 4KB size. Signed-off-by: bppps <bpppsaka@gmail.com>
1 parent 643a701 commit 38005a6

3 files changed

Lines changed: 66 additions & 18 deletions

File tree

csrc/inject/LibraryInjector.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,18 @@ LibraryInjector::initializeInjectionEnvironment(long &code_injection_address,
235235
// Copy original registers to working registers
236236
*working_registers = *original_registers;
237237

238-
// Find a good address to copy code to
238+
// Find a good address to copy code to.
239+
// The findFreeMemoryAddress function returns the END of the first executable
240+
// region minus a safe offset, placing shellcode in the alignment padding area
241+
// that is typically unused but still has execute permissions.
239242
code_injection_address =
240243
ProcessUtils::findFreeMemoryAddress(target_process_id_) + 8;
241244

245+
if (process_tracer_.isDebugMode()) {
246+
std::cout << "[DEBUG] PyFlightProfiler: Using injection address at 0x"
247+
<< std::hex << code_injection_address << std::dec << std::endl;
248+
}
249+
242250
// Set the target's rip to the injection address
243251
// Advance by 2 bytes because rip gets incremented by the size of the current
244252
// instruction

csrc/inject/ProcessTracer.cpp

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,28 @@ bool ProcessTracer::continueExecution() {
9696
int result = ptrace(PTRACE_CONT, process_id_, NULL, NULL);
9797
CHECK_PTRACE_RESULT(result, PTRACE_CONT);
9898

99+
// Wait for the target process to stop (e.g., hit INT3 breakpoint)
100+
int wait_status;
101+
pid_t waited_pid = waitpid(process_id_, &wait_status, 0);
102+
if (waited_pid != process_id_) {
103+
if (debug_mode_) {
104+
std::cerr << "[ERROR] PyFlightProfiler: waitpid(" << process_id_
105+
<< ") failed or returned unexpected pid " << waited_pid << ": "
106+
<< strerror(errno) << std::endl;
107+
}
108+
return false;
109+
}
110+
111+
// Check if the process stopped (not exited or terminated)
112+
if (!WIFSTOPPED(wait_status)) {
113+
if (debug_mode_) {
114+
std::cerr << "[ERROR] PyFlightProfiler: process " << process_id_
115+
<< " did not stop as expected, wait_status=" << wait_status
116+
<< std::endl;
117+
}
118+
return false;
119+
}
120+
99121
// Make sure the target process received SIGTRAP after stopping.
100122
return verifySignalStatus();
101123
}
@@ -201,16 +223,15 @@ bool ProcessTracer::writeMemory(unsigned long address, const void *buffer,
201223
* @return siginfo_t structure containing signal information
202224
*/
203225
siginfo_t ProcessTracer::getSignalInfo() {
204-
sleepMs(5);
205-
206226
siginfo_t signal_info;
207-
// When PTRACE_GETSIGINFO returns -1, tracee may not reach int3 point, so
208-
// spin on it waiting at most 500ms
209-
for (int i = 0; i < 100; i++) {
227+
// With waitpid() in continueExecution(), the process should already be
228+
// stopped. Retry a few times just in case, but much shorter timeout is
229+
// needed.
230+
for (int i = 0; i < 10; i++) {
210231
if (ptrace(PTRACE_GETSIGINFO, process_id_, NULL, &signal_info) != -1) {
211232
return signal_info;
212233
}
213-
sleepMs(5);
234+
sleepMs(1);
214235
}
215236

216237
// this is mostly due to gil lock not released, so injected code cannot

csrc/inject/ProcessUtils.cpp

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@
1515
/**
1616
* @brief Find a free memory address in the target process
1717
*
18-
* Parses /proc/[pid]/maps to find a memory region with execute permissions.
18+
* Parses /proc/[pid]/maps to find the END of the first executable memory
19+
* region. We use the end of the region (with a small offset back) because:
20+
* 1. The end of code segments typically has alignment padding (unused space)
21+
* 2. This avoids overwriting active code at the beginning of the segment
22+
* 3. The padding area is still executable (same permissions as the code
23+
* segment)
1924
*
2025
* @param process_id PID of the target process
21-
* @return Address of free memory, or 0 on failure
26+
* @return Address of free memory (end of executable region minus offset), or 0
27+
* on failure
2228
*/
2329
long ProcessUtils::findFreeMemoryAddress(pid_t process_id) {
2430
std::string filename = "/proc/" + std::to_string(process_id) + "/maps";
@@ -30,27 +36,40 @@ long ProcessUtils::findFreeMemoryAddress(pid_t process_id) {
3036
}
3137

3238
std::string line;
33-
long address = 0;
39+
long end_address = 0;
3440

3541
while (std::getline(maps_file, line)) {
3642
std::istringstream iss(line);
3743
std::string range, permissions, offset, device, inode, path;
3844

3945
if (iss >> range >> permissions >> offset >> device >> inode) {
40-
// Extract address from range (format: address1-address2)
41-
size_t dash_pos = range.find('-');
42-
if (dash_pos != std::string::npos) {
43-
std::string address_str = range.substr(0, dash_pos);
44-
address = std::stol(address_str, nullptr, 16);
45-
}
46-
46+
// Check if this is an executable region
4747
if (permissions.find('x') != std::string::npos) {
48+
// Extract end address from range (format: start_address-end_address)
49+
size_t dash_pos = range.find('-');
50+
if (dash_pos != std::string::npos) {
51+
std::string end_address_str = range.substr(dash_pos + 1);
52+
end_address = std::stol(end_address_str, nullptr, 16);
53+
}
4854
break;
4955
}
5056
}
5157
}
5258

53-
return address;
59+
// Calculate the minimum safe offset for shellcode injection.
60+
// The shellcode (inject_shared_library function) is approximately:
61+
// - ~80 bytes of assembly instructions (stack ops, calls, int3 breakpoints)
62+
// - +2 bytes NOP prefix (for syscall restart handling)
63+
// - +16 bytes alignment padding (x86_64 ABI requires 16-byte stack alignment)
64+
// - +16 bytes safety margin
65+
// Total: ~114 bytes, rounded up to 128 bytes (0x80) for 16-byte alignment
66+
const long SHELLCODE_MIN_SIZE = 128;
67+
68+
// Use the end of the executable region minus the minimum required offset.
69+
// This minimizes the risk of overwriting active code while ensuring enough
70+
// space for the shellcode in the alignment padding area.
71+
return (end_address > SHELLCODE_MIN_SIZE) ? (end_address - SHELLCODE_MIN_SIZE)
72+
: 0;
5473
}
5574

5675
/**

0 commit comments

Comments
 (0)