【问题标题】:ELF generation using libelf hints使用 libelf 提示生成 ELF
【发布时间】:2012-02-11 09:27:29
【问题描述】:

我正在尝试使用 libelf 生成一个简单的静态 ELF,但我似乎遇到了麻烦。

我不希望生成一个目标文件然后用 LD 链接它,而是我希望自己生成它。

这个程序的主要目的是生成一个带有一个 LOAD 段的静态 ELF 并执行代码。

主要问题不在于 shellcode 本身,而可能在于我尝试以错误方式生成的某些标头。当我尝试运行生成的 ELF 时,它会被杀死,就好像内核无法找到它刚刚加载的段一样。

如果你们能提示我,我会很高兴。

create_elf.3.c

#include <err.h>
#include <fcntl.h>
#include <libelf.h>
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <unistd.h>

unsigned char code[] =
"\x0b\x58\x99\x52\x66\x68\x2d\x70"
"\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61"
"\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52"
"\x51\x53\x89\xe1\xcd\x80";

int main(int argc, char *argv[])
{
  int           fd;
  Elf           *e;
  Elf_Scn       *scn;
  Elf_Data      *data;
  Elf32_Ehdr    *ehdr;
  Elf32_Phdr    *phdr;
  Elf32_Shdr    *shdr;
  if (argc != 2)
    errx(EX_USAGE,"input... ./%s filename\n",argv[0]);
  if (elf_version(EV_CURRENT) == EV_NONE)
    errx(EX_SOFTWARE,"elf_version is ev_none, wtf? %s\n",elf_errmsg(-1));
  if ((fd = open(argv[1], O_WRONLY | O_CREAT, 0777)) < 0)
    errx(EX_OSERR, "open %s\n",elf_errmsg(-1));
  if ((e = elf_begin(fd, ELF_C_WRITE, NULL)) == NULL)
    errx(EX_SOFTWARE,"elf_begin %s\n",elf_errmsg(-1));
  if ((ehdr = elf32_newehdr(e)) == NULL)
    errx(EX_SOFTWARE,"elf32_newehdr %s\n",elf_errmsg(-1));
  /*
     without these definitions objdump/readelf/strace/elf loader
     will fail to load the binary correctly
     be sure to pick them carefully and correctly, preferred exactly like the
     ones like the system you are running on (so if you are running x86,
     pick the same values you seen on a regular readelf -a /bin/ls
     */
  ehdr->e_ident[EI_DATA] = ELFDATA2LSB;
  ehdr->e_ident[EI_CLASS] = ELFCLASS32;
  ehdr->e_machine = EM_386;
  ehdr->e_type = ET_EXEC;
  ehdr->e_entry = 0x8040800;
  if ((phdr = elf32_newphdr(e,1)) == NULL)
    errx(EX_SOFTWARE,"elf32_newphdr %s\n",elf_errmsg(-1));
  if ((scn = elf_newscn(e)) == NULL)
    errx(EX_SOFTWARE,"elf32_newscn %s\n",elf_errmsg(-1));
  if ((data = elf_newdata(scn)) == NULL)
    errx(EX_SOFTWARE,"elf32_newdata %s\n",elf_errmsg(-1));
  data->d_align = 4;
  data->d_off = 0LL;
  data->d_buf = code;
  data->d_type = ELF_T_WORD; // code :x
  data->d_size = sizeof(code);
  data->d_version = EV_CURRENT;
  if ((shdr = elf32_getshdr(scn)) == NULL)
    errx(EX_SOFTWARE,"elf32_getshdr %s\n",elf_errmsg(-1));
  shdr->sh_name = 0;
  shdr->sh_type = SHT_PROGBITS;
  shdr->sh_flags = SHF_EXECINSTR | SHF_ALLOC;
  shdr->sh_entsize = 0; // only used if we hold a table
  if (elf_update(e, ELF_C_NULL) < 0)
    errx(EX_SOFTWARE,"elf_update_1 %s\n",elf_errmsg(-1));
  phdr->p_type = PT_LOAD;
  phdr->p_offset = ehdr->e_phoff;
  phdr->p_filesz = elf32_fsize(ELF_T_PHDR, 1, EV_CURRENT);
  phdr->p_vaddr = 0x8040800;
  phdr->p_paddr = 0x8040800;
  phdr->p_align = 4;
  phdr->p_filesz = sizeof(code);
  phdr->p_memsz = sizeof(code);
  phdr->p_flags = PF_X | PF_R;
  elf_flagphdr(e, ELF_C_SET, ELF_F_DIRTY);
  if (elf_update(e, ELF_C_WRITE) < 0 )
    errx(EX_SOFTWARE,"elf32_update_2 %s\n",elf_errmsg(-1));
  elf_end(e);
  close(fd);
  return 1;
}

如果有人能提示我这里出了什么问题,我会很高兴

谢谢

编辑

很抱歉没有提供更多细节,

ELF 生成似乎工作正常,我没有收到任何语法错误等, 但是,每当我尝试运行我生成的 ELF 时,例如 ./create_elf.3 foo14 (而 foo14 是生成的 ELF)

它被杀死了,好像 execve/kernel 不想正确加载它 我尝试使用 IDA 加载它,但 IDA 显示反汇编代码足够好

这是 readelf 的输出

readelf -a foo14
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8040800
  Start of program headers:          52 (bytes into file)
  Start of section headers:          116 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           40 (bytes)
  Number of section headers:         2
  Section header string table index: 0
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0] <no-name>         NULL            00000000 000000 000000 00      0   0  0
  [ 1] <no-name>         PROGBITS        00000000 000054 000020 00  AX  0   0  4
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
There are no section groups in this file.
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000034 0x08040800 0x08040800 0x00021 0x00021 R E 0x4
There is no dynamic section in this file.
There are no relocations in this file.
There are no unwind sections in this file.
No version information found in this file.

【问题讨论】:

  • 也许你能告诉我们怎么了?
  • 错误是什么?怎么了?你能指望什么?关于错误生成的 ELF 文件,objdump 告诉您什么? code 应该做什么(我们大多数人无法在他们的头脑中拆卸)?
  • 为什么你的mainreturn 1; 结尾?通常应该是return 0;return EXIT_SUCCESS;
  • 我添加了更多细节,代码基本上只是我在exploit-db上找到的一些随机shellcode。该程序的主要目的是生成带有一个 LOAD 段的静态 ELF 并执行代码。每当我执行它时,我都会被“杀死”,就好像内核/执行程序无法正确加载它一样。如果有人能提示我这里的步骤有什么问题,我会很高兴
  • @dotdot 请先重新格式化您的代码并输出以删除所有多余的空行,这样我们就可以在没有(太多)滚动的情况下实际看到代码。

标签: c linux elf abi


【解决方案1】:

首先,在测试期间替换代码是个好主意 包含(顽皮的)shell 代码的片段,其中包含一些无害的东西,比如:

unsigned char code[] = {
    0xBB, 0x2A, 0x00, 0x00, 0x00, /* movl $42, %ebx */
    0xB8, 0x01, 0x00, 0x00, 0x00, /* movl $1, %eax */
    0xCD, 0x80            /* int $0x80 */
};

在 i386 GNU/Linux 系统上,这个修改后的代码片段会导致进程 立即退出,退出代码为 42。

以下 ASCII 艺术说明了 ELF 可执行文件的布局 正在建设中:

+----------------------------------+  <- LOADADDR (0x08048000)
|  The ELF Exec Header.            |  
+----------------------------------+
|  The ELF PHDR Table.             |
+----------------------------------+ <- ehdr->e_entry points here.
|  The ".text" section.            |
+----------------------------------+ <- The end of loadable region
|  The section name string table   |    for this object.
|  (optional).                     |
+----------------------------------+
|  Section headers:                |
|  - Header for section ".text".   |
|  - Section name string table     |
|    header.                       |
+----------------------------------+

部分名称字符串表是可选的。它有助于整理 readelf 的输出。

#define LOADADDR    0x08048000

可执行文件将被加载到指定的虚拟地址 LOADADDRLOADADDR 的值取决于系统——值为 0x08048000 似乎在我的系统上运行良好。

可执行代码片段被放置在 PHDR 表之后。这 ELF Executable Header 的e_entry 字段保存虚拟地址 控制权将转移到哪个位置。字段值 因此应该是:

size_t ehdrsz, phdrsz;

ehdrsz = elf32_fsize(ELF_T_EHDR, 1, EV_CURRENT);
phdrsz = elf32_fsize(ELF_T_PHDR, 1, EV_CURRENT);

/* ... */

ehdr->e_entry = LOADADDR + ehdrsz + phdrsz;

代码段将使用ELF_T_BYTE 的数据类型和节类型 SHT_PROGBITS,对齐为 1。

if ((scn = elf_newscn(e)) == NULL)
    errx(EX_SOFTWARE,"elf32_newscn %s\n", elf_errmsg(-1));

if ((data = elf_newdata(scn)) == NULL)
    errx(EX_SOFTWARE,"elf32_newdata %s\n", elf_errmsg(-1));

data->d_align = 1;
data->d_off = 0LL;
data->d_buf = code;
data->d_type = ELF_T_BYTE;
data->d_size = sizeof(code);
data->d_version = EV_CURRENT;

节头表条目的sh_addr 字段包含 节数据开始的虚拟地址。

if ((shdr = elf32_getshdr(scn)) == NULL)
   errx(EX_SOFTWARE,"elf32_getshdr %s\n", elf_errmsg(-1));

shdr->sh_name = 1;      /* Offset of ".text", see below. */
shdr->sh_type = SHT_PROGBITS;
shdr->sh_flags = SHF_EXECINSTR | SHF_ALLOC;
shdr->sh_addr = LOADADDR + ehdrsz + phdrsz;

ELF Program Header 表中的唯一条目涵盖了要执行的区域 已加载,从 ELF 标头开始并包括可执行文件 代码。

if ((phdr = elf32_newphdr(e,1)) == NULL)
   errx(EX_SOFTWARE,"elf32_newphdr %s\n", elf_errmsg(-1));

phdr->p_type = PT_LOAD;
phdr->p_offset = 0;
phdr->p_filesz = ehdrsz + phdrsz + sizeof(code);
phdr->p_memsz = phdr->p_filesz;
phdr->p_vaddr = LOADADDR;
phdr->p_paddr = phdr->p_vaddr;
phdr->p_align = 4;
phdr->p_flags = PF_X | PF_R;

部分名称字符串表是可选的,并且可以提供更好的输出 来自 readelf。一张手卷弦桌就足够了:

unsigned char strtab[] = {
    0, '.', 't', 'e', 'x', 't', 0,
    '.', 's', 'h', 's', 't', 'r', 't', 'a', 'b', 0
};

将字符串表添加到可执行文件的代码是:

/*
 * Allocate a string table for section names.
 */
if ((scn = elf_newscn(e)) == NULL)
   errx(EX_SOFTWARE,"elf32_newscn %s\n", elf_errmsg(-1));

if ((data = elf_newdata(scn)) == NULL)
   errx(EX_SOFTWARE,"elf32_newdata %s\n", elf_errmsg(-1));

data->d_align = 1;
data->d_off = 0LL;
data->d_buf = strtab;
data->d_type = ELF_T_BYTE;
data->d_size = sizeof(strtab);
data->d_version = EV_CURRENT;

if ((shdr = elf32_getshdr(scn)) == NULL)
   errx(EX_SOFTWARE,"elf32_getshdr %s\n", elf_errmsg(-1));   

shdr->sh_name = 7;      /* Offset of ".shstrtab". */
shdr->sh_type = SHT_STRTAB;
shdr->sh_flags = SHF_STRINGS;

通过这些更改,您的程序创建的 ELF 二进制文件应该是 可运行。

% cc a.c -lelf
% ./a.out foo
% ./foo; echo $?
42

生成的可执行文件的结构如下:

% readelf -a foo
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8048054
  Start of program headers:          52 (bytes into file)
  Start of section headers:          116 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           40 (bytes)
  Number of section headers:         3
  Section header string table index: 2
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        08048054 000054 00000c 00  AX  0   0  1
  [ 2] .shstrtab         STRTAB          00000000 000060 000011 00   S  0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
There are no section groups in this file.
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08048000 0x08048000 0x00060 0x00060 R E 0x4
 Section to Segment mapping:
  Segment Sections...
   00     .text 
There is no dynamic section in this file.
There are no relocations in this file.
There are no unwind sections in this file.
No version information found in this file.

【讨论】:

  • 次要问题:没有_exit 系统调用。如果您想强调它是系统调用,而不是您正在谈论的 libc 函数 exit(3),则它是 exitexit(2)。另外,您没有解释为什么您的 strtab 以 0 开头。
  • “原始”系统调用似乎被 POSIX 标准拼写为 _exit()。 ELF 字符串表通常以 NUL 字节开头,因此可以使用为零的sh_name 字段来指示长度为零的字符串——请参阅libelf by Example 教程了解更多信息。
  • POSIX 标准中的_exit不是系统调用。它是一个函数:pubs.opengroup.org/onlinepubs/000095399/functions/exit.html 它将在 Linux 上调用的系统调用是 exitexit_group
  • 传统上,C 库存根的名称用于表示相应的系统调用;例如,请参阅this manual page。也就是说,在最近的 GNU/Linux 中,_exit() 的实现似乎同时调用了 __NR_exit_group AND __NR_exit,因此 1-1 对应关系不再成立。我想我需要调整我的答案:)。
  • 嗨。感谢您提供的所有信息,它确实有效:)我真的很感谢您(:
【解决方案2】:

内核拒绝运行你的程序的原因很简单:

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000034 0x08040800 0x08040800 0x00021 0x00021 R E 0x4

这是无效的可执行文件,因为内核无法将您的.text 与偏移量0x34 映射到虚拟地址0x08040800。文件偏移量和VirtAddr 必须对齐。

通常第一个LOAD 段只包含ELF 标头本身,即Offset 为0(您可能希望将大小设置为0x55 (== 0x21 + 0x34))。或者,您可以安排Offset 留在0x000034 并拥有VirtAddr0x08040834

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-08
    • 2012-05-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多