AsmLinux0x00: О том как узнать где лежат argv и envp.

Discussion in 'Реверсинг' started by randomword0x3f52, 7 Jun 2018.

  1. randomword0x3f52

    Joined:
    15 Oct 2016
    Messages:
    30
    Likes Received:
    25
    Reputations:
    9
    О том где лежат аргументы можно узнать двумя путями 1 - прочитать и поверить, 2 - посмотреть и проверить.(Вы думаете вызов wait существует в той форме в которой описан?)

    Что бы узнать где лежат argv и envp нужно сделать execute program с ними, а для этого сделать syscall execve. В man 2 syscall даны способы для различных архитектур. В man 2 execve сказано что argv и envp передаются в main.

    Пользуясь примерами из man 2 execve, cоздадим две программы - первую для запуска второй, и третью что бы узнать как передаются аргументы в функцию.

    ./execve.c
    Code:
    #include <unistd.h>
    #include <sys/user.h>
    int main()
    {
    	char *filename = "./main";
    	char *argv[] = {"argument", NULL};
    	char *envp[] = {"evironment", NULL};
    	execve(filename, argv, envp);
    	return(-1);
    }
    gcc -o ./execve ./execve.c

    ./main.c
    Code:
    int main(int argc, char *argv, char *envp)
    {
    	return(0);
    }
    gcc -o main main.c

    ./funcargs.c
    Code:
    void test(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7)
    {
    }
    
    int main()
    {
    	test(0, 1, 2, 3, 4, 5, 6, 7);
    	return(0);
    }
    gcc -o funcargs funcargs.c

    Перед отладкой добавим в ~/.gdbinit включатель дезасэмблирования в синтаксе intel
    Code:
    set disassembly-flavor intel
    Отладка.
    gdb ./execve
    break main
    run


    Добавим остановку после системного вызова.
    catch syscall
    continue
    disas

    Запоминаем адрес syscall (0x00007ffff7af2645)

    Прерываем исполнение, удаляем catch, запускаемся.
    signal 9
    info break
    delete 2
    run


    Останавливаемся на main, ставим бряк на адрес syscall.
    break *0x00007ffff7af2645
    continue
    info reg

    В rax номер execve 0x3b

    Рассмотрим аргументы из rsi и rdx.
    x/xg 0x7fffffffdff0
    x/xg 0x7fffffffdfe0

    Мы передавали массив указателей значит там лежат указатели на строки.
    x/s 0x000055555555479b
    x/s 0x00005555555547a4

    А если увеличить адреса на 8 то там будут лежать нули.
    x/xg 0x7fffffffdff8
    x/xg 0x7fffffffdfe8


    Необходимые данные создания запускателя получены.
    Изготовим execve на ассемблере.

    ./execve.s
    Code:
    .global _start
    .data
    	filename:	.asciz	"./main"
    	argp:		.fill	2, 8, 0
    	arg0:		.asciz	"argument"
    	envp:		.fill	2, 8, 0
    	env0:		.asciz	"environment=value"
    .text
    .intel_syntax	noprefix
    	_start:		mov	rax,0x3b
    			lea	rdi,filename
    			lea	rsi,argp
    			lea	rbx,arg0
    			mov	[rsi],rbx
    			lea	rdx,envp
    			lea	rbx,env0
    			mov	[rdx],rbx
    			syscall
    	_error:		mov	rax,0x3c
    			mov	rdi,-1
    			syscall
    
    Закодировали, склеили, исполнили, отобразили значение выхода.
    as -o execve.o execve.s && ld -o execve execve.o && ./execve ; echo $?

    gdb execve
    b _start
    r
    disas


    Брякнем syscall
    b *_start+53
    c


    Дошагаем до начала новой программы.
    si
    si
    si
    break main
    continue
    continue


    Пришло время посмотреть как передаются параметры функциям. Открываем новый терминал.
    gdb funcargs
    disas main

    Порядок передачи такой rdi, rsi, rdx, rcx, r8, r9 остальные через стэк.

    Закрываем новый терминал, возвращаемся к обратно к argv и envp.
    i r
    rdx 0x7fffffffee78 140737488350840
    rsi 0x7fffffffee68 140737488350824
    rsp 0x7fffffffed80 0x7fffffffed80

    Массивы указателей лежат в стэке.

    Проверим лежат ли они там на старте программы.
    signal 9
    quit
    gdb execve
    b _start
    r
    disas
    b *_start+53
    c
    si
    si
    si
    i r
    rsp 0x7fffffffee60
    x/xg 0x7fffffffee68
    x/s 0x00007fffffffefd6
    0x7fffffffefd6: "argument"

    Вот и нашли где лежат argv и envp, а rsp указывает на argc.

    Теперь смастерим пускатель который стартует прогу из argv1 и остальные argv передаёт этой проге.

    ./execve.s
    Code:
    .global _start
    .data
    	filename:	.quad	0x00
    	argp:		.quad	0x00
    	envp:		.fill	1, 8, 0
    .text
    .intel_syntax	noprefix
    	_start:		mov	rbx,[rsp+0x10]
    			mov	filename,rbx
    			mov	rbx,rsp
    			add	rbx,0x10
    			mov	argp,rbx
    			mov	rax,0x3b
    			mov	rdi,filename
    			mov	rsi,argp
    			lea	rdx,envp
    			syscall
    	_error:		mov	rax,0x3c
    			mov	rdi,-1
    			syscall
    
    as -o execve.o execve.s && ld -o execve execve.o
    ./execve /bin/echo "XA!"


    Этот код пригодится потом для эксперементов с ptrace.

    Теперь немного о номерах системных вызовов и их ошибках.
    apt-get download linux-headers-4.9.0-6-amd64
    dpkg -x ./linux-headers-4.9.0-6-amd64_4.9.88-1+deb9u1_amd64.deb ./
    less ./usr/src/linux-headers-4.9.0-6-amd64/arch/x86/include/generated/asm/syscalls_64.h
    less /usr/include/asm-generic/errno-base.h
    less /usr/include/asm-generic/errno.h

    Перед тем как смотреть ошибку после системного вызова сделайте neg rax.
     
  2. sn0w

    sn0w Статус пользователя:

    Joined:
    26 Jul 2005
    Messages:
    1,023
    Likes Received:
    1,284
    Reputations:
    327
    в RTL они лежат, и линкер вставляет код, который вызывает твою main/WinMain/DllMain.
    с ртл много приколов, можешь например попробовать такое(в случае с визуал сятиной):
    (с линупсом я не ебал мозги - ибо бессмысленно)

    // 1.cpp
    void DynamicInitializer()
    {
    __debugbreak();
    }

    #pragma data_seg(".CRT$XCU") // dynamic initializers list
    void *pCallbackCTOR = DynamicInitializer; // add pointer for fake initializer
    #pragma data_seg()

    поясню за код: в секции объектного кода с тегом .CRT$XCU находится таблица поинтеров на динамические инициализаторы, это в случае MSVC, и до вызова твоей main инициализируется сама библиотека, которая много всякой шляпы по работе с памятью да и с исключениями устанавливает, и в том числе и конструкторы статических объектов вызывает. указанным выше макаром мы вносим еще один указатель в эту секцию, поэтому в результате DynamicInitializer() сработает до вызова main()
     
    randomword0x3f52 likes this.
  3. DartPhoenix

    DartPhoenix Elder - Старейшина

    Joined:
    15 Sep 2013
    Messages:
    1,088
    Likes Received:
    8,367
    Reputations:
    25
    Linux же. Какой ушь там DllMain :)
    На точке входа esp вроде-как указывает на argc и argv[0] где-то там же рядом.
    Надо бы затестить но лениво.
     
    sn0w likes this.
  4. sn0w

    sn0w Статус пользователя:

    Joined:
    26 Jul 2005
    Messages:
    1,023
    Likes Received:
    1,284
    Reputations:
    327
    да я в курсе, это просто пример. а вот разработчикам нативного кода стоило бы разобраться кто как и что вызывает, немного нырнуть в объектный код - ведь православную иду слили в паблик и не раз, и читать всё это она прекрасно умеет.
    и если вкратце - то линкер ставит точку входа в рантайм библю, тотже объектный модуль, который по отработке ссылается на символ main WinMain DllMain итд.
    и всё, нет тут никакой магии, просто проще посмотрить исходный код библиотеки врем ени исполнения.
    на вход линкеру идут объектные модули, которые закомпилил компилер, и он увязывает все связи, когда например у тебя есть extern int g_NumInstance и обращение к ней в твоём коде - компилер скомпилет это, не проблема, а вот линкер на своём шаге будет уже искать в каком другом модуле её определение, вставлять адреса обращений итд
     
    DartPhoenix likes this.