6

我正在尝试使用 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.
4

2 回答 2

6

首先,在测试期间用无害的东西替换包含(顽皮的)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

可执行文件将加载到名为 LOADADDR. 的值LOADADDR取决于系统——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 程序头表中的唯一条目涵盖了要加载的区域,从 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.
于 2012-01-17T05:56:03.850 回答
3

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

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标题本身,即为Offset0(您希望将大小设置为0x55( == 0x21 + 0x34))。或者,您可以安排Offset入住0x000034并拥有一个VirtAddr0x08040834

于 2012-01-16T17:54:03.527 回答