linux逆向-手动加载执行elf文件
elf文件是Linux下和Unix下的标准二进制文件格式,ELF文件可以用于可执行文件格式,共享库和目标文件,以及coredump文件,甚至是内核引导镜像文件。所以在学习Linux文件中,ELF文件格式是非常重要的一部分。在本文中,主要学习如何在内存中手动加载执行ELF可执行文件,以及在运行时ELF文件中重要的程序段。
在程序运行时,加载器主要依靠段来解析ELF文件,将在执行时需要的的段加载到内存中。
在ELF文件中,使用如下的结构体来标识段的信息。
PT_LOAD
一个可执行文件至少要包含有一个PT_LOAD类型的段,这类程序头描述的是可以装载的段,也就是说,这种类型的段将被装载或映射到内存中。
例如,一个需要动态链接的ELF可执行文件通常包含以下两个段
PT_INTERP
PT_INTERP段只将位置和 大小信息存放在一个以null为终止符的字符串中,是对程序链接器位置的描述,例如/lib/ld-linux.so.2,一般是指动态链接器的位置,也即程序解释器的位置。
整理完了ELF文件的程序段的一些基础知识,下面整理一下Linux系统中,执行一个程序,需要那些元素。在这里,建议大家读这篇文章How programs get run: ELF binaries,在这里提取一下这篇文中,关于ELF加载执行的流程步骤。
How programs get run: ELF binaries
https://github.com/malisal/loaders
/
/
Program header
for
ELF32.
struct Elf32_Phdr {
Elf32_Word p_type;
/
/
segment 的类型
Elf32_Off p_offset;
/
/
segment 在文件中的偏移
Elf32_Addr p_vaddr;
/
/
segment 在内存中的虚拟地址
Elf32_Addr p_paddr;
/
/
segment 在采用物理地址系统中的物理地址
Elf32_Word p_filesz;
/
/
segment 在文件中的字节数
Elf32_Word p_memsz;
/
/
segment 加载到内存中的字节数
Elf32_Word p_flags;
/
/
Segment 属性信息
Elf32_Word p_align;
/
/
Segment 内存对齐值
};
/
/
Program header
for
ELF32.
struct Elf32_Phdr {
Elf32_Word p_type;
/
/
segment 的类型
Elf32_Off p_offset;
/
/
segment 在文件中的偏移
Elf32_Addr p_vaddr;
/
/
segment 在内存中的虚拟地址
Elf32_Addr p_paddr;
/
/
segment 在采用物理地址系统中的物理地址
Elf32_Word p_filesz;
/
/
segment 在文件中的字节数
Elf32_Word p_memsz;
/
/
segment 加载到内存中的字节数
Elf32_Word p_flags;
/
/
Segment 属性信息
Elf32_Word p_align;
/
/
Segment 内存对齐值
};
/
/
Default function called upon exit()
in
the ELF. Depends on the architecture,
/
/
as some archs don't call it at
all
.
static void _exit_func(
int
code)
{
exit(code);
}
static void _get_rand(char
*
buf,
int
size)
{
int
fd
=
open
(
"/dev/urandom"
, O_RDONLY,
0
);
read(fd, (unsigned char
*
) buf, size);
close(fd);
}
static char
*
_get_interp(char
*
buf)
{
int
x;
/
/
Check
for
the existence of a dynamic loader
Elf_Ehdr
*
hdr
=
(Elf_Ehdr
*
)buf;
Elf_Phdr
*
phdr
=
(Elf_Phdr
*
)(buf
+
hdr
-
>e_phoff);
for
(x
=
0
; x < hdr
-
>e_phnum; x
+
+
)
{
if
(phdr[x].p_type
=
=
PT_INTERP)
{
/
/
There
is
a dynamic loader present, so load it
return
buf
+
phdr[x].p_offset;
}
}
return
NULL;
}
static Elf_Shdr
*
_get_section(char
*
name, void
*
elf_start)
{
int
x;
Elf_Ehdr
*
ehdr
=
NULL;
Elf_Shdr
*
shdr;
ehdr
=
(Elf_Ehdr
*
) elf_start;
shdr
=
(Elf_Shdr
*
)(elf_start
+
ehdr
-
>e_shoff);
Elf_Shdr
*
sh_strtab
=
&shdr[ehdr
-
>e_shstrndx];
char
*
sh_strtab_p
=
elf_start
+
sh_strtab
-
>sh_offset;
for
(x
=
0
; x < ehdr
-
>e_shnum; x
+
+
)
{
/
/
printf(
"%p %s\n"
, shdr[x].sh_addr, sh_strtab_p
+
shdr[x].sh_name);
if
(!strcmp(name, sh_strtab_p
+
shdr[x].sh_name))
return
&shdr[x];
}
return
NULL;
}
void
*
elf_sym(void
*
elf_start, char
*
sym_name)
{
int
x, y;
Elf_Ehdr
*
hdr
=
(Elf_Ehdr
*
)elf_start;
Elf_Shdr
*
shdr
=
(Elf_Shdr
*
)(elf_start
+
hdr
-
>e_shoff);
for
(x
=
0
; x < hdr
-
>e_shnum; x
+
+
)
{
if
(shdr[x].sh_type
=
=
SHT_SYMTAB)
{
const char
*
strings
=
elf_start
+
shdr[shdr[x].sh_link].sh_offset;
Elf_Sym
*
syms
=
(Elf_Sym
*
)(elf_start
+
shdr[x].sh_offset);
for
(y
=
0
; y < shdr[x].sh_size
/
sizeof(Elf_Sym); y
+
+
)
{
/
/
printf(
"@name:%s\n"
, strings
+
syms[y].st_name);
if
(strcmp(sym_name, strings
+
syms[y].st_name)
=
=
0
)
return
(void
*
)syms[y].st_value;
}
}
}
return
NULL;
}
void elf_load(char
*
elf_start, void
*
stack,
int
stack_size, size_t
*
base_addr, size_t
*
entry)
{
Elf_Ehdr
*
hdr;
Elf_Phdr
*
phdr;
int
x;
int
elf_prot
=
0
;
int
stack_prot
=
0
;
size_t base;
hdr
=
(Elf_Ehdr
*
)elf_start;
phdr
=
(Elf_Phdr
*
)(elf_start
+
hdr
-
>e_phoff);
if
(hdr
-
>e_type
=
=
ET_DYN)
{
/
/
If this
is
a DYNAMIC ELF (can be loaded anywhere),
set
a random base address
base
=
(size_t)mmap(
0
,
100
*
PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON,
-
1
,
0
);
munmap((void
*
)base,
100
*
PAGE_SIZE);
}
else
base
=
0
;
if
(base_addr !
=
NULL)
*
base_addr
=
-
1
;
if
(entry !
=
NULL)
*
entry
=
base
+
hdr
-
>e_entry;
for
(x
=
0
; x < hdr
-
>e_phnum; x
+
+
)
{
/
/
Get flags
for
the stack
if
(stack !
=
NULL && phdr[x].p_type
=
=
PT_GNU_STACK)
{
if
(phdr[x].p_flags & PF_R)
stack_prot
=
PROT_READ;
if
(phdr[x].p_flags & PF_W)
stack_prot |
=
PROT_WRITE;
if
(phdr[x].p_flags & PF_X)
stack_prot |
=
PROT_EXEC;
/
/
Set
stack protection
mprotect((unsigned char
*
) stack, stack_size, stack_prot);
}
if
(phdr[x].p_type !
=
PT_LOAD)
continue
;
if
(!phdr[x].p_filesz)
continue
;
void
*
map_start
=
(void
*
) ROUND_DOWN(phdr[x].p_vaddr, PAGE_SIZE);
int
round_down_size
=
(void
*
) phdr[x].p_vaddr
-
map_start;
int
map_size
=
ROUND_UP(phdr[x].p_memsz
+
round_down_size, PAGE_SIZE);
mmap(base
+
map_start, map_size, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED,
-
1
,
0
);
memcpy((void
*
) base
+
phdr[x].p_vaddr, elf_start
+
phdr[x].p_offset, phdr[x].p_filesz);
/
/
Zero
-
out BSS,
if
it exists
if
(phdr[x].p_memsz > phdr[x].p_filesz)
memset((void
*
)(base
+
phdr[x].p_vaddr
+
phdr[x].p_filesz),
0
, phdr[x].p_memsz
-
phdr[x].p_filesz);
/
/
Set
proper protection on the area
if
(phdr[x].p_flags & PF_R)
elf_prot
=
PROT_READ;
if
(phdr[x].p_flags & PF_W)
elf_prot |
=
PROT_WRITE;
if
(phdr[x].p_flags & PF_X)
elf_prot |
=
PROT_EXEC;
mprotect((unsigned char
*
) (base
+
map_start), map_size, elf_prot);
/
/
Clear cache on this area
cacheflush(base
+
map_start, (size_t)(map_start
+
map_size),
0
);
/
/
Is this the lowest memory area we saw. That
is
,
is
this the ELF base address?
if
(base_addr !
=
NULL && (
*
base_addr
=
=
-
1
||
*
base_addr > (size_t)(base
+
map_start)))
*
base_addr
=
(size_t)(base
+
map_start);
}
}
void elf_run(void
*
buf, char
*
*
argv, char
*
*
env)
{
int
x;
int
str_len;
int
str_ptr
=
0
;
int
stack_ptr
=
1
;
int
cnt
=
0
;
int
argc
=
0
;
int
envc
=
0
;
Elf_Ehdr
*
hdr
=
(Elf_Ehdr
*
)buf;
size_t elf_base, elf_entry;
size_t interp_base
=
0
;
size_t interp_entry
=
0
;
char rand_bytes[
16
];
/
/
Fill
in
16
random bytes
for
the loader below
_get_rand(rand_bytes,
16
);
int
(
*
ptr)(
int
, char
*
*
, char
*
*
);
/
/
First, let's count arguments...
while
(argv[argc])
argc
+
+
;
/
/
...
and
envs
while
(env[envc])
envc
+
+
;
/
/
Allocate some stack space
void
*
stack
=
mmap(
0
, STACK_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON,
-
1
,
0
);
/
/
Map
the ELF
in
memory
elf_load(buf, stack, STACK_SIZE, &elf_base, &elf_entry);
/
/
Check
for
the existence of a dynamic loader
char
*
interp_name
=
_get_interp(buf);
/
/
加载动态链接解释器
if
(interp_name)
{
int
f
=
open
(interp_name, O_RDONLY,
0
);
/
/
Find out the size of the
file
int
size
=
lseek(f,
0
, SEEK_END);
lseek(f,
0
, SEEK_SET);
void
*
elf_ld
=
mmap(
0
, ROUND_UP(size, PAGE_SIZE), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON,
-
1
,
0
);
read(f, elf_ld, size);
elf_load(elf_ld, stack, STACK_SIZE, &interp_base, &interp_entry);
munmap(elf_ld, ROUND_UP(size, PAGE_SIZE));
}
/
/
Zero out the whole stack, Justin Case
memset(stack,
0
, STACK_STORAGE_SIZE);
unsigned
long
*
stack_storage
=
stack
+
STACK_SIZE
-
STACK_STORAGE_SIZE
-
STACK_STRING_SIZE;
char
*
string_storage
=
stack
+
STACK_SIZE
-
STACK_STRING_SIZE;
unsigned
long
*
s_argc
=
stack_storage;
unsigned
long
*
s_argv
=
&stack_storage[
1
];
/
/
Setup argc
*
s_argc
=
argc;
/
/
设置argv,以NULL结束
/
/
Setup argv
for
(x
=
0
; x < argc; x
+
+
)
{
str_len
=
strlen(argv[x])
+
1
;
/
/
Copy the string on to the stack inside the string storage area
memcpy(&string_storage[str_ptr], argv[x], str_len);
/
/
Make the startup struct point to the string
s_argv[x]
=
(unsigned
long
) &string_storage[str_ptr];
str_ptr
+
=
str_len;
stack_storage[stack_ptr
+
+
]
=
0
;
unsigned
long
*
s_env
=
&stack_storage[stack_ptr];
/
/
设置环境变量,以NULL结束
for
(x
=
0
; x < envc; x
+
+
)
{
str_len
=
strlen(env[x])
+
1
;
/
/
Copy the string on to the stack inside the string storage area
memcpy(&string_storage[str_ptr], env[x], str_len);
/
/
Make the startup struct point to the string
s_env[x]
=
(unsigned
long
) &string_storage[str_ptr];
str_ptr
+
=
str_len;
stack_ptr
+
+
;
}
/
/
End
-
of
-
env NULL
stack_storage[stack_ptr
+
+
]
=
0
;
/
/
Let's run the constructors
Elf_Shdr
*
init
=
_get_section(
".init"
, buf);
Elf_Shdr
*
init_array
=
_get_section(
".init_array"
, buf);
size_t base
=
0
;
if
(hdr
-
>e_type
=
=
ET_DYN)
{
/
/
It's a PIC
file
, so make sure we add the base when we call the constructors
base
=
elf_base;
}
if
(init)
{
ptr
=
(
int
(
*
)(
int
, char
*
*
, char
*
*
))base
+
init
-
>sh_addr;
ptr(argc, argv, env);
}
if
(init_array)
{
for
(x
=
0
; x < init_array
-
>sh_size
/
sizeof(void
*
); x
+
+
)
{
ptr
=
(
int
(
*
)(
int
, char
*
*
, char
*
*
))base
+
*
((
long
*
)(base
+
init_array
-
>sh_addr
+
(x
*
sizeof(void
*
))));
ptr(argc, argv, env);
}
}
struct ATENTRY
*
at
=
(struct ATENTRY
*
) &stack_storage[stack_ptr];
/
/
填充辅助向量
/
/
AT_PHDR
at[cnt].
id
=
AT_PHDR;
at[cnt
+
+
].value
=
(size_t)(elf_base
+
hdr
-
>e_phoff);
/
/
AT_PHENT
at[cnt].
id
=
AT_PHENT;
at[cnt
+
+
].value
=
sizeof(Elf_Phdr);
/
/
AT_PHNUM
at[cnt].
id
=
AT_PHNUM;
at[cnt
+
+
].value
=
hdr
-
>e_phnum;
/
/
AT_PGSIZE
at[cnt].
id
=
AT_PAGESZ;
at[cnt
+
+
].value
=
PAGE_SIZE;
/
/
AT_BASE (base address where the interpreter
is
loaded at)
at[cnt].
id
=
AT_BASE;
at[cnt
+
+
].value
=
interp_base;
/
/
AT_FLAGS
at[cnt].
id
=
AT_FLAGS;
at[cnt
+
+
].value
=
0
;
/
/
AT_ENTRY
at[cnt].
id
=
AT_ENTRY;
at[cnt
+
+
].value
=
elf_entry;
/
/
AT_UID
at[cnt].
id
=
AT_UID;
at[cnt
+
+
].value
=
getuid();
/
/
AT_EUID
at[cnt].
id
=
AT_EUID;
at[cnt
+
+
].value
=
geteuid();
/
/
AT_GID
at[cnt].
id
=
AT_GID;
at[cnt
+
+
].value
=
getgid();
/
/
AT_EGID
at[cnt].
id
=
AT_EGID;
at[cnt
+
+
].value
=
getegid();
/
/
AT_RANDOM (address of
16
random bytes)
at[cnt].
id
=
AT_RANDOM;
at[cnt
+
+
].value
=
(size_t)rand_bytes;
/
/
AT_NULL
at[cnt].
id
=
AT_NULL;
at[cnt
+
+
].value
=
0
;
/
/
当程序是动态连接的情况下,程序的入口地址需要指定为连接器的入口地址。
if
(interp_entry)
jump_start(stack_storage, (void
*
)_exit_func, (void
*
)interp_entry);
else
jump_start(stack_storage, (void
*
)_exit_func, (void
*
)elf_entry);
/
/
Shouldn't be reached, but just
in
case
exit(
1234
);
}
/
/
Default function called upon exit()
in
the ELF. Depends on the architecture,
/
/
as some archs don't call it at
all
.
static void _exit_func(
int
code)
{
exit(code);
}
static void _get_rand(char
*
buf,
int
size)
{
int
fd
=
open
(
"/dev/urandom"
, O_RDONLY,
0
);
read(fd, (unsigned char
*
) buf, size);
close(fd);
}
static char
*
_get_interp(char
*
buf)
{
int
x;
/
/
Check
for
the existence of a dynamic loader
Elf_Ehdr
*
hdr
=
(Elf_Ehdr
*
)buf;
Elf_Phdr
*
phdr
=
(Elf_Phdr
*
)(buf
+
hdr
-
>e_phoff);
for
(x
=
0
; x < hdr
-
>e_phnum; x
+
+
)
{
if
(phdr[x].p_type
=
=
PT_INTERP)
{
/
/
There
is
a dynamic loader present, so load it
return
buf
+
phdr[x].p_offset;
}
}
return
NULL;
}
static Elf_Shdr
*
_get_section(char
*
name, void
*
elf_start)
{
int
x;
Elf_Ehdr
*
ehdr
=
NULL;
Elf_Shdr
*
shdr;
ehdr
=
(Elf_Ehdr
*
) elf_start;
shdr
=
(Elf_Shdr
*
)(elf_start
+
ehdr
-
>e_shoff);
Elf_Shdr
*
sh_strtab
=
&shdr[ehdr
-
>e_shstrndx];
char
*
sh_strtab_p
=
elf_start
+
sh_strtab
-
>sh_offset;
for
(x
=
0
; x < ehdr
-
>e_shnum; x
+
+
)
{
/
/
printf(
"%p %s\n"
, shdr[x].sh_addr, sh_strtab_p
+
shdr[x].sh_name);
if
(!strcmp(name, sh_strtab_p
+
shdr[x].sh_name))
return
&shdr[x];
}
return
NULL;
}
void
*
elf_sym(void
*
elf_start, char
*
sym_name)
{
int
x, y;
Elf_Ehdr
*
hdr
=
(Elf_Ehdr
*
)elf_start;
Elf_Shdr
*
shdr
=
(Elf_Shdr
*
)(elf_start
+
hdr
-
>e_shoff);
for
(x
=
0
; x < hdr
-
>e_shnum; x
+
+
)
{
if
(shdr[x].sh_type
=
=
SHT_SYMTAB)
{
const char
*
strings
=
elf_start
+
shdr[shdr[x].sh_link].sh_offset;
Elf_Sym
*
syms
=
(Elf_Sym
*
)(elf_start
+
shdr[x].sh_offset);
for
(y
=
0
; y < shdr[x].sh_size
/
sizeof(Elf_Sym); y
+
+
)
{
/
/
printf(
"@name:%s\n"
, strings
+
syms[y].st_name);
if
(strcmp(sym_name, strings
+
syms[y].st_name)
=
=
0
)
return
(void
*
)syms[y].st_value;
}
}
}
return
NULL;
}
void elf_load(char
*
elf_start, void
*
stack,
int
stack_size, size_t
*
base_addr, size_t
*
entry)
{
Elf_Ehdr
*
hdr;
Elf_Phdr
*
phdr;
int
x;
int
elf_prot
=
0
;
int
stack_prot
=
0
;
size_t base;
hdr
=
(Elf_Ehdr
*
)elf_start;
phdr
=
(Elf_Phdr
*
)(elf_start
+
hdr
-
>e_phoff);
if
(hdr
-
>e_type
=
=
ET_DYN)
{
/
/
If this
is
a DYNAMIC ELF (can be loaded anywhere),
set
a random base address
base
=
(size_t)mmap(
0
,
100
*
PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON,
-
1
,
0
);
munmap((void
*
)base,
100
*
PAGE_SIZE);
}
else
base
=
0
;
if
(base_addr !
=
NULL)
*
base_addr
=
-
1
;
if
(entry !
=
NULL)
*
entry
=
base
+
hdr
-
>e_entry;
for
(x
=
0
; x < hdr
-
>e_phnum; x
+
+
)
{
/
/
Get flags
for
the stack
if
(stack !
=
NULL && phdr[x].p_type
=
=
PT_GNU_STACK)
{
if
(phdr[x].p_flags & PF_R)
stack_prot
=
PROT_READ;
if
(phdr[x].p_flags & PF_W)
stack_prot |
=
PROT_WRITE;
if
(phdr[x].p_flags & PF_X)
stack_prot |
=
PROT_EXEC;
/
/
Set
stack protection
mprotect((unsigned char
*
) stack, stack_size, stack_prot);
}
if
(phdr[x].p_type !
=
PT_LOAD)
continue
;
if
(!phdr[x].p_filesz)
continue
;
void
*
map_start
=
(void
*
) ROUND_DOWN(phdr[x].p_vaddr, PAGE_SIZE);
int
round_down_size
=
(void
*
) phdr[x].p_vaddr
-
map_start;
int
map_size
=
ROUND_UP(phdr[x].p_memsz
+
round_down_size, PAGE_SIZE);
mmap(base
+
map_start, map_size, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED,
-
1
,
0
);
memcpy((void
*
) base
+
phdr[x].p_vaddr, elf_start
+
phdr[x].p_offset, phdr[x].p_filesz);
/
/
Zero
-
out BSS,
if
it exists
if
(phdr[x].p_memsz > phdr[x].p_filesz)
memset((void
*
)(base
+
phdr[x].p_vaddr
+
phdr[x].p_filesz),
0
, phdr[x].p_memsz
-
phdr[x].p_filesz);
/
/
Set
proper protection on the area
if
(phdr[x].p_flags & PF_R)
elf_prot
=
PROT_READ;
if
(phdr[x].p_flags & PF_W)
elf_prot |
=
PROT_WRITE;
if
(phdr[x].p_flags & PF_X)
elf_prot |
=
PROT_EXEC;
mprotect((unsigned char
*
) (base
+
map_start), map_size, elf_prot);
/
/
Clear cache on this area
cacheflush(base
+
map_start, (size_t)(map_start
+
map_size),
0
);
/
/
Is this the lowest memory area we saw. That
is
,
is
this the ELF base address?
if
(base_addr !
=
NULL && (
*
base_addr
=
=
-
1
||
*
base_addr > (size_t)(base
+
map_start)))
*
base_addr
=
(size_t)(base
+
map_start);
}
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)