README.md
Rendering markdown...
# Attempt to download a full libc file from libc.rip given a partial libc file containing the build id. Then try to print libc version.
# Example: python try_download_libc.py page1_img5.bmp.extracted
import struct
import sys
import pwnlib.libcdb
from argparse import ArgumentParser
import re
# Constant for the standard GNU Build ID Note Type
NT_GNU_BUILD_ID = 3
def extract_build_id(file_path: str) -> str | None:
"""
Manually searches the raw bytes of a file for the GNU Build ID note structure,
checking the Note Type (n_type) to ensure it's a Build ID (type 3).
"""
try:
# 1. Read the entire file content
with open(file_path, 'rb') as f:
data = f.read()
# 2. Define the magic marker for the GNU note name ('GNU\x00')
GNU_NAME = b'GNU\x00'
# 3. Iterate and search for ALL occurrences of the marker
search_start_index = 0
while True:
try:
# Find the next occurrence of the 'GNU\x00' marker
name_offset = data.index(GNU_NAME, search_start_index)
except ValueError:
break
# 4. The Note Header (12 bytes) precedes the name.
header_offset = name_offset - 12
if header_offset < 0:
# Cannot be a full note header if it's too close to the file start
search_start_index = name_offset + 1
continue
# 5. Unpack the Note Header (n_namesz, n_descsz, n_type)
# Assuming 32-bit little-endian integers (<III)
try:
n_namesz, n_descsz, n_type = struct.unpack(
'<III', data[header_offset:header_offset + 12]
)
except struct.error:
# Not enough data for a header
search_start_index = name_offset + 1
continue
if n_namesz == 4 and n_type == NT_GNU_BUILD_ID:
# 7. Extract the Build ID data (immediately following the name)
build_id_data = data[name_offset + n_namesz : name_offset + n_namesz + n_descsz]
return build_id_data.hex()
# If the note isn't a Build ID, continue searching after the current position
search_start_index = name_offset + 1
# If the loop finishes without finding the Build ID
print("Error: No note matching the standard GNU Build ID (type 3) structure was found.")
return None
except Exception as e:
print(f"An error occurred during manual extraction: {e}", file=sys.stderr)
return None
def print_libc_version(libc_path: str):
"""
Reads the raw file content and uses a regex anchor on the "GNU C Library" string to extract the full version
"""
try:
# 1. Read the file content
with open(libc_path, 'rb') as f:
data = f.read()
version_pattern = re.compile(b'(GNU C Library.*?)\x00', re.DOTALL)
match = version_pattern.search(data)
if match:
full_version_string = match.group(1)[:100].decode('ascii', errors='ignore')
print(f"Extracted libc version: {full_version_string}")
else:
print("Error: The complete 'GNU C Library' string followed by a version was not found.")
except Exception as e:
print(f"Error occurred getting libc version: {e}", file=sys.stderr)
parser = ArgumentParser(description="Download full libc file from libc.rip given partial libc file containing build id")
parser.add_argument('file', help='input file path containing partial libc file with build id')
parser.add_argument('-o', '--output', help='output file path to save downloaded libc', default='libc')
args = parser.parse_args()
build_id = extract_build_id(args.file)
if build_id:
print(f"Build ID: {build_id}")
else:
print("Failed to extract Build ID.", file=sys.stderr)
sys.exit(1)
data = pwnlib.libcdb.provider_libc_rip(build_id, 'build_id')
if not data:
print("Failed to download libc from libc.rip", file=sys.stderr)
sys.exit(1)
with open(args.output, 'wb') as f:
f.write(data)
print(f"Downloaded libc file saved to: {args.output}")
print_libc_version(args.output)