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 */
2329long 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