Форум программистов, компьютерный форум, киберфорум
Наши страницы
politoto
Войти
Регистрация
Восстановить пароль
Рейтинг: 4.67. Голосов: 3.

Акционистское "программирование" на входном языке компоновщика GNU ld

Запись от politoto размещена 03.11.2019 в 17:17

Пример скрипта GNU ld для создания исполняемого файла ELF без компиляторов, ассемблеров, объектных и библиотечных файлов
art.ld
родной раскраски синтаксиса нет, поэтому беру то, что короче называется
Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PHDRS { 
..  PT_LOAD FILEHDR PHDRS FLAGS(_____); 
} 
OUTPUT("^_^")
        _._   = 0100000000; 
ENTRY( "-|-" ) ;
 
SECTIONS{ 
. = _._ + SIZEOF_HEADERS ;
   "^_^"
     : {
   . = . + 1234 ;  
    ...  = .  ;
   } : .. 
 
   "-|-" = . - 02331 ;
 
}
_____ = ~ - ( 0x80cd41 << 010 ) ;
OUTPUT_FORMAT("elf32-i386", 
              "elf32-i386",
          "elf32-i386")
OUTPUT_ARCH(i386)
Собираем
Bash
1
2
3
4
ld  -dT art.ld -s  /dev/stdin << [[^vv^]]
SECTIONS 
{      }
[[^vv^]]
Можно и так: ( в надежде, что паразитная пустая секция не помешает )
Bash
1
ld -dT art.ld -s -b binary /dev/null
( компоновщику для счастья нужен "входной файл" )

Тестируем, то, что получилось:
Bash
1
objdump --disassemble ^_^ ; strace ./^_^ ; nm ^_^
Код:
^_^:     file format elf32-i386

execve("./^_^", ["./^_^"], [/* 36 vars */]) = 0
_exit(0)                                = ?
nm: ^_^: no symbols
Код:
$wc ^_^ 
  0   3 220 ^_^
Вложения
Тип файла: zip art.ld.zip (396 байт, 664 просмотров)
Размещено в Бесполезное
Просмотров 6011 Комментарии 4
Всего комментариев 4
Комментарии
  1. Старый комментарий
    Аватар для Evg
    Код:
    $ readelf --headers a.out
    ...
      Entry point address:               0x100004d
    ...
    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      LOAD           0x000000 0x01000000 0x01000000 0x00054 0x00526 RWE 0x200000
    ...
    
    $ objdump -h a.out
    ...
    Sections:
    Idx Name          Size      VMA       LMA       File off  Algn
      0 ^_^           000004d2  01000054  01000054  00000054  2**0
                      ALLOC
    ...
    
    $ readelf --sections a.out
    Section Headers:
      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            00000000 000000 000000 00      0   0  0
      [ 1] ^_^               NOBITS          01000054 000054 0004d2 00  WA  0   0  1
      [ 2] .shstrtab         STRTAB          00000000 000054 00000f 00      0   0  1
    Получился несколько корявый файл. Секция "^_^" не имеет признака EXEC, но у единственный сегмента признак E выставлен.

    Код:
    $ hexdump -C a.out
    00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
    00000010  02 00 03 00 01 00 00 00  4d 00 00 01 34 00 00 00  |........M...4...|
    00000020  68 00 00 00 00 00 00 00  34 00 20 00 01 00 28 00  |h.......4. ...(.|
    00000030  03 00 02 00 01 00 00 00  00 00 00 00 00 00 00 01  |................|
    00000040  00 00 00 01 54 00 00 00  26 05 00 00 ff 40 cd 80  |....T...&...Ъ@м─|
    00000050  00 00 20 00 00 2e 73 68  73 74 72 74 61 62 00 5e  |.. ...shstrtab.^|
    00000060  5f 5e 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |_^..............|
    00000070  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    *
    00000090  0b 00 00 00 08 00 00 00  03 00 00 00 54 00 00 01  |............T...|
    000000a0  54 00 00 00 d2 04 00 00  00 00 00 00 00 00 00 00  |T...р...........|
    000000b0  01 00 00 00 00 00 00 00  01 00 00 00 03 00 00 00  |................|
    000000c0  00 00 00 00 00 00 00 00  54 00 00 00 0f 00 00 00  |........T.......|
    000000d0  00 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|
    Здесь тоже на первый взгляд выглядит таким образом, что таблица секций в файле построена неправильно - не похоже, что смещения в файле секций реально являются началами секций, тем более обе секции имеют одинаковое смещение. Либо я что-то путаю, предметно надо ковыряться в стандарте ELF'а

    Хз, что означает звёздочка на строке 0x80, вероятно, это повтор предыдущей строки, ибо

    Код:
    $ hexdump -C -s 0x80 a.out
    00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    Я так и не разобрался, как стандартным objdump сделать дизассемблирование для файла без символьной таблицы

    Из-под отладчика дизассемблер печатает что-то типа:

    Код:
    (gdb) b *0x100004d   <-- entry point
    Breakpoint 1 at 0x100004d
    
    (gdb) run
    Breakpoint 1, 0x0100004d in ?? ()
    
    (gdb) disassemble 0x100004d, 0x1000080
    Dump of assembler code from 0x100004d to 0x1000080:
    => 0x0100004d:  inc    %eax
       0x0100004e:  int    $0x80   <--- syscall
       0x01000050:  add    %al,(%eax)
       0x01000052:  and    %al,(%eax)
       0x01000054:  add    %al,(%eax)
       0x01000056:  add    %al,(%eax)
       0x01000058:  add    %al,(%eax)
       0x0100005a:  add    %al,(%eax)
       0x0100005c:  add    %al,(%eax)
    далее одно и то же до адреса 0x1001000, далее уже конец валидной страницы памяти
    Судя по описанию сегмента, там 0x526 байт, но он состоит из одинаковых опкодов. Нулевой опкод по идее должен быть nop'ом, но на Intel'е может быть и не так, т.е. ноль это "add %al,(%eax)", проверять лень

    В коде видим только один системный вызов, а по strace'у их два. Возможно, что второй - это уже в новом процессе. Т.е. сначала исполнили execve, а уже в новом процессе исполнили exit. Чтобы детально в этом проковыряться, надо точно знать программные соглашения по системным вызовам на i386. Вроде бы там номер системного вызова в регистре %eax. Т.е. чтобы исполнился системный вызов execve (номер 0xb), надо чтобы начальное значение регистра %eax равнялось 0xa. Но по факту из-под отладчика оно равняется нулю

    В итоге, я не совсем понимаю, как это работает
    Запись от Evg размещена 04.11.2019 в 17:31 Evg вне форума
  2. Старый комментарий
    Цитата:
    Сообщение от Evg Просмотреть комментарий
    В коде видим только один системный вызов, а по strace'у их два. Возможно, что второй - это уже в новом процессе. Т.е. сначала исполнили execve, а уже в новом процессе исполнили exit.
    execve() выполнился в новом процессе, созданном fork(), но ещё в старом адресном пространстве.
    А exit(0) выполнился в том же новом процессе, но уже в пространстве программы ^_^ которое linux и наполнил новым кодом, отрабатывая execve()
    Запись от politoto размещена 05.11.2019 в 08:49 politoto вне форума
  3. Старый комментарий
    Цитата:
    Сообщение от Evg Просмотреть комментарий
    Получился несколько корявый файл. Секция "^_^" не имеет признака EXEC, но у единственный сегмента признак E выставлен.
    ...
    Здесь тоже на первый взгляд выглядит таким образом, что таблица секций в файле построена неправильно - не похоже, что смещения в файле секций реально являются началами секций, тем более обе секции имеют одинаковое смещение. Либо я что-то путаю, предметно надо ковыряться в стандарте ELF'а
    Согласно спецификации, в исполняемом ELF обязательны только File header & Programs Headers.
    Секции нужны программам вроде посмертных и прижизненных отладчиков, чтобы разобраться во внутренней структуре загруженных сегментов.

    По моим наблюдениям, чтобы linux попытался выполнить exec(), достаточно одного загружаемого программного сегмента, read-write-execute не важны. Конечно, в результате linux просигналить новой программе сегфолтом, но exec() уже выполнится.

    Программа запускается, даже если забить заголовки секций мусором:
    Bash
    1
    2
    
    cp /usr/bin/cal c
    readelf -h c
    Цитата:
    Сообщение от readelf
    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: 0x80495a0
    Start of program headers: 52 (bytes into file)
    Start of section headers: 24820 (bytes into file)
    Flags: 0x0
    Size of this header: 52 (bytes)
    Size of program headers: 32 (bytes)
    Number of program headers: 9
    Size of section headers: 40 (bytes)
    Number of section headers: 32
    Section header string table index: 31
    Bash
    1
    2
    3
    4
    
    yes | dd conv=notrunc seek=24820 bs=1 count=$((40*32)) of=c
    readelf -l c
    objdump -x c
    od -a c --skip-bytes=24820 --read-bytes=$((40*32))
    Код:
    1280+0 records in
    1280+0 records out
    1280 bytes (1.3 kB) copied, 0.0128514 s, 99.6 kB/s
    readelf: Error: Unable to read in 0xa790a79 bytes of string table
    readelf: Error: no .dynamic section in the dynamic segment
    
    Elf file type is EXEC (Executable file)
    Entry point 0x80495a0
    There are 9 program headers, starting at offset 52
    
    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      PHDR           0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
      INTERP         0x000154 0x08048154 0x08048154 0x00013 0x00013 R   0x1
          [Requesting program interpreter: /lib/ld-linux.so.2]
      LOAD           0x000000 0x08048000 0x08048000 0x04bc6 0x04bc6 R E 0x1000
      LOAD           0x004ee4 0x0804dee4 0x0804dee4 0x00b60 0x00b8c RW  0x1000
      DYNAMIC        0x004ef0 0x0804def0 0x0804def0 0x00100 0x00100 RW  0x4
      NOTE           0x000168 0x08048168 0x08048168 0x00044 0x00044 R   0x4
      GNU_EH_FRAME   0x0042ac 0x0804c2ac 0x0804c2ac 0x000fc 0x000fc R   0x4
      GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
      GNU_RELRO      0x004ee4 0x0804dee4 0x0804dee4 0x0011c 0x0011c R   0x1
    objdump: c: File format not recognized
    0060364   y  nl   y  nl   y  nl   y  nl   y  nl   y  nl   y  nl   y  nl
    *
    0062764
    Bash
    1
    
    ./c 1 2020
    Код:
        January 2020      
    Su     5 12 19 26   
    Mo     6 13 20 27   
    Tu     7 14 21 28   
    We  1  8 15 22 29   
    Th  2  9 16 23 30   
    Fr  3 10 17 24 31   
    Sa  4 11 18 25
    Цитата:
    Судя по описанию сегмента, там 0x526 байт, но он состоит из одинаковых опкодов. Нулевой опкод по идее должен быть nop'ом, но на Intel'е может быть и не так, т.е. ноль это "add %al,(%eax)", проверять лень
    Угадали
    Код:
    $ echo 'add %al,(%eax)' | as -al
    GAS LISTING  			page 1
    
    
       1 0000 0000     	add %al,(%eax)
    Из файла в адресное пространство программы ^_^ попадают только заголовки файла и программных сегментов. Всё остальное - неинициализированные данные.
    Запись от politoto размещена 07.11.2019 в 20:54 politoto вне форума
  4. Старый комментарий
    Аватар для Evg
    Цитата:
    Сообщение от politoto Просмотреть комментарий
    execve() выполнился в новом процессе, созданном fork(), но ещё в старом адресном пространстве.
    А exit(0) выполнился в том же новом процессе
    Какой же я тупой
    Запись от Evg размещена 11.11.2019 в 20:17 Evg вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2019, vBulletin Solutions, Inc.