ELF(Executable and Linkable Format)是Linux系统下常见的可执行文件、共享库和目标文件的存储格式,其结构包含文件头、程序头表、节头表等多个部分,其中文件头记录了文件的基础属性,节头表则存储了各个节区的详细信息,是解析ELF文件的核心内容。
ELF文件基础结构
ELF文件开头是一段固定的文件头,长度在不同架构下有区别,32位ELF文件头长度为52字节,64位为64字节。文件头之后会跟随节头表,每个节头描述一个节区的基本信息,包括节名偏移、节类型、节在文件中的偏移、节大小等。
要解析这些信息,首先需要明确ELF文件的字节序,ELF文件头的前4个字节是魔数0x7F,ELF,第5个字节标识是32位(1)还是64位(2),第6个字节标识字节序,1为小端,2为大端。
定义ELF结构对应的C#类
首先需要定义和ELF文件结构对应的C#结构体,方便按字节读取后直接映射到内存中。
ELF文件头结构
先定义通用的ELF标识部分,再分别定义32位和64位的文件头:
using System;
using System.IO;
using System.Text;
// ELF文件标识部分,前16字节
public struct ElfIdent
{
public byte Magic0; // 固定为0x7F
public byte Magic1; // 固定为'E'
public byte Magic2; // 固定为'L'
public byte Magic3; // 固定为'F'
public byte Class; // 1为32位,2为64位
public byte Data; // 1为小端,2为大端
public byte Version; // ELF版本,固定为1
public byte OsAbi; // OS/ABI标识
public byte AbiVersion; // ABI版本
public byte[] Pad; // 剩余7字节填充
}
// 32位ELF文件头
public struct Elf32Header
{
public ElfIdent Ident;
public ushort Type; // 文件类型
public ushort Machine; // 架构类型
public uint Version; // ELF版本
public uint Entry; // 程序入口地址
public uint PhOff; // 程序头表偏移
public uint ShOff; // 节头表偏移
public uint Flags; // 处理器标志
public ushort EhSize; // ELF文件头大小
public ushort PhEntSize;// 程序头表项大小
public ushort PhNum; // 程序头表项数量
public ushort ShEntSize;// 节头表项大小
public ushort ShNum; // 节头表项数量
public ushort ShStrNdx; // 节名字符串表对应的节头索引
}
// 64位ELF文件头
public struct Elf64Header
{
public ElfIdent Ident;
public ushort Type;
public ushort Machine;
public uint Version;
public ulong Entry;
public ulong PhOff;
public ulong ShOff;
public uint Flags;
public ushort EhSize;
public ushort PhEntSize;
public ushort PhNum;
public ushort ShEntSize;
public ushort ShNum;
public ushort ShStrNdx;
}
节头结构定义
同样分别定义32位和64位的节头结构:
// 32位节头
public struct Elf32SectionHeader
{
public uint Name; // 节名在字符串表中的偏移
public uint Type; // 节类型
public uint Flags; // 节标志
public uint Addr; // 节加载到内存的地址
public uint Offset; // 节在文件中的偏移
public uint Size; // 节大小
public uint Link; // 链接信息
public uint Info; // 额外信息
public uint AddrAlign; // 地址对齐要求
public uint EntSize; // 表项大小(针对某些特殊节)
}
// 64位节头
public struct Elf64SectionHeader
{
public uint Name;
public uint Type;
public ulong Flags;
public ulong Addr;
public ulong Offset;
public ulong Size;
public uint Link;
public uint Info;
public ulong AddrAlign;
public ulong EntSize;
}
实现字节读取和解析逻辑
接下来需要实现字节序处理、文件头解析、节头解析和节名读取的逻辑。
字节序处理工具
根据ELF标识中的字节序字段,转换读取到的多字节数据:
public static class ByteOrderHelper
{
// 判断是否需要翻转字节序
public static bool NeedReverse(byte data)
{
return (data == 2 && BitConverter.IsLittleEndian) || (data == 1 && !BitConverter.IsLittleEndian);
}
// 翻转字节数组
public static void ReverseBytes(byte[] bytes)
{
Array.Reverse(bytes);
}
// 读取ushort并处理字节序
public static ushort ReadUInt16(BinaryReader reader, bool reverse)
{
byte[] bytes = reader.ReadBytes(2);
if (reverse) ReverseBytes(bytes);
return BitConverter.ToUInt16(bytes, 0);
}
// 读取uint并处理字节序
public static uint ReadUInt32(BinaryReader reader, bool reverse)
{
byte[] bytes = reader.ReadBytes(4);
if (reverse) ReverseBytes(bytes);
return BitConverter.ToUInt32(bytes, 0);
}
// 读取ulong并处理字节序
public static ulong ReadUInt64(BinaryReader reader, bool reverse)
{
byte[] bytes = reader.ReadBytes(8);
if (reverse) ReverseBytes(bytes);
return BitConverter.ToUInt64(bytes, 0);
}
}
解析ELF文件头
读取文件的前16字节获取标识信息,再根据位数解析完整文件头:
public class ElfParser
{
private string filePath;
private bool is64Bit;
private bool reverseByteOrder;
private object header; // 存储32位或64位文件头
private List<object> sectionHeaders = new List<object>();
private byte[] sectionNameStringTable;
public ElfParser(string path)
{
filePath = path;
}
// 解析文件头
public void ParseHeader()
{
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
using (BinaryReader reader = new BinaryReader(fs))
{
// 读取前16字节标识
ElfIdent ident = new ElfIdent();
ident.Magic0 = reader.ReadByte();
ident.Magic1 = reader.ReadByte();
ident.Magic2 = reader.ReadByte();
ident.Magic3 = reader.ReadByte();
// 验证魔数
if (ident.Magic0 != 0x7F || ident.Magic1 != 'E' || ident.Magic2 != 'L' || ident.Magic3 != 'F')
{
throw new Exception("不是有效的ELF文件");
}
ident.Class = reader.ReadByte();
ident.Data = reader.ReadByte();
ident.Version = reader.ReadByte();
ident.OsAbi = reader.ReadByte();
ident.AbiVersion = reader.ReadByte();
ident.Pad = reader.ReadBytes(7);
is64Bit = ident.Class == 2;
reverseByteOrder = ByteOrderHelper.NeedReverse(ident.Data);
// 根据位数解析剩余文件头
if (is64Bit)
{
Elf64Header h = new Elf64Header();
h.Ident = ident;
h.Type = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
h.Machine = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
h.Version = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
h.Entry = ByteOrderHelper.ReadUInt64(reader, reverseByteOrder);
h.PhOff = ByteOrderHelper.ReadUInt64(reader, reverseByteOrder);
h.ShOff = ByteOrderHelper.ReadUInt64(reader, reverseByteOrder);
h.Flags = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
h.EhSize = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
h.PhEntSize = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
h.PhNum = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
h.ShEntSize = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
h.ShNum = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
h.ShStrNdx = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
header = h;
}
else
{
Elf32Header h = new Elf32Header();
h.Ident = ident;
h.Type = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
h.Machine = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
h.Version = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
h.Entry = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
h.PhOff = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
h.ShOff = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
h.Flags = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
h.EhSize = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
h.PhEntSize = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
h.PhNum = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
h.ShEntSize = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
h.ShNum = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
h.ShStrNdx = ByteOrderHelper.ReadUInt16(reader, reverseByteOrder);
header = h;
}
}
}
}
解析节头表和节名
根据文件头中的节头表偏移和节头大小,遍历所有节头,再读取节名字符串表获取节名:
// 在ElfParser类中添加以下方法
// 解析所有节头
public void ParseSections()
{
if (header == null)
{
throw new Exception("请先解析文件头");
}
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
using (BinaryReader reader = new BinaryReader(fs))
{
if (is64Bit)
{
Elf64Header h = (Elf64Header)header;
// 定位到节头表起始位置
fs.Seek((long)h.ShOff, SeekOrigin.Begin);
for (int i = 0; i < h.ShNum; i++)
{
Elf64SectionHeader sh = new Elf64SectionHeader();
sh.Name = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
sh.Type = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
sh.Flags = ByteOrderHelper.ReadUInt64(reader, reverseByteOrder);
sh.Addr = ByteOrderHelper.ReadUInt64(reader, reverseByteOrder);
sh.Offset = ByteOrderHelper.ReadUInt64(reader, reverseByteOrder);
sh.Size = ByteOrderHelper.ReadUInt64(reader, reverseByteOrder);
sh.Link = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
sh.Info = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
sh.AddrAlign = ByteOrderHelper.ReadUInt64(reader, reverseByteOrder);
sh.EntSize = ByteOrderHelper.ReadUInt64(reader, reverseByteOrder);
sectionHeaders.Add(sh);
}
// 读取节名字符串表
ReadSectionNameStringTable64(h);
}
else
{
Elf32Header h = (Elf32Header)header;
fs.Seek((long)h.ShOff, SeekOrigin.Begin);
for (int i = 0; i < h.ShNum; i++)
{
Elf32SectionHeader sh = new Elf32SectionHeader();
sh.Name = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
sh.Type = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
sh.Flags = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
sh.Addr = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
sh.Offset = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
sh.Size = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
sh.Link = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
sh.Info = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
sh.AddrAlign = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
sh.EntSize = ByteOrderHelper.ReadUInt32(reader, reverseByteOrder);
sectionHeaders.Add(sh);
}
// 读取节名字符串表
ReadSectionNameStringTable32(h);
}
}
}
// 读取32位ELF的节名字符串表
private void ReadSectionNameStringTable32(Elf32Header h)
{
if (h.ShStrNdx == 0 || h.ShStrNdx >= h.ShNum)
{
sectionNameStringTable = new byte[0];
return;
}
Elf32SectionHeader strSh = (Elf32SectionHeader)sectionHeaders[h.ShStrNdx];
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
fs.Seek((long)strSh.Offset, SeekOrigin.Begin);
sectionNameStringTable = new byte[strSh.Size];
fs.Read(sectionNameStringTable, 0, (int)strSh.Size);
}
}
// 读取64位ELF的节名字符串表
private void ReadSectionNameStringTable64(Elf64Header h)
{
if (h.ShStrNdx == 0 || h.ShStrNdx >= h.ShNum)
{
sectionNameStringTable = new byte[0];
return;
}
Elf64SectionHeader strSh = (Elf64SectionHeader)sectionHeaders[h.ShStrNdx];
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
fs.Seek((long)strSh.Offset, SeekOrigin.Begin);
sectionNameStringTable = new byte[strSh.Size];
fs.Read(sectionNameStringTable, 0, (int)strSh.Size);
}
}
// 根据节头中的名称偏移获取节名
public string GetSectionName(uint nameOffset)
{
if (sectionNameStringTable == null || sectionNameStringTable.Length == 0)
{
return string.Empty;
}
int end = (int)nameOffset;
while (end < sectionNameStringTable.Length && sectionNameStringTable[end] != 0)
{
end++;
}
return Encoding.ASCII.GetString(sectionNameStringTable, (int)nameOffset, end - (int)nameOffset);
}
使用示例
调用上述解析类读取ELF文件的头部和节信息:
class Program
{
static void Main(string[] args)
{
string elfPath = "/home/test/a.out"; // Linux下的ELF文件路径,Windows下可替换为对应路径
try
{
ElfParser parser = new ElfParser(elfPath);
parser.ParseHeader();
parser.ParseSections();
// 输出文件头信息
if (parser.is64Bit)
{
Elf64Header h = (Elf64Header)parser.header;
Console.WriteLine("ELF文件类型:{0}", h.Type);
Console.WriteLine("目标架构:{0}", h.Machine);
Console.WriteLine("入口地址:0x{0:X}", h.Entry);
Console.WriteLine("节头数量:{0}", h.ShNum);
}
else
{
Elf32Header h = (Elf32Header)parser.header;
Console.WriteLine
C#ELF文件Linux可执行文件文件解析修改时间:2026-06-22 18:13:31