【问题标题】:Beginner Tutorial: Assembler, Segmentation fault初学者教程:汇编器,分段错误
【发布时间】:2018-01-12 13:29:46
【问题描述】:

我是@coding 初学者,这是我关于堆栈溢出的第一个问题,尽管您的一些很棒的答案已经给我带来了一些进展..

在尝试此assembler-tutorial 时,我在运行程序后遇到了分段错误。我试图注释掉每一行,并注意到当我在第 30 行“调用 printString”时程序崩溃了。

当我尝试使用 gdb 进行调试时(哎呀,我真的不知道我在那里做什么..)我在 lenString 调用中的函数“iterateChar”中出现错误。 (在我的函数文件中 - baseOperators.asm - 第 50 行)

我怀疑,我不知何故弄乱了 eax 寄存器中的信息,但我不知道为什么,发生了什么以及如何解决这个问题。我的代码看起来与 asmtutor.com 上教程 16 中的代码非常相似——无论出于何种原因,我都编写了该代码,并且成功了。请帮忙。

(我正在使用“$ nasm -f elf assemblerTutorial.asm”+“$ ld -m elf_i386 assemblerTutorial.o -o assemblerTutorial)进行编译

;------------------------------------------
; my Assembler learning Environment

;%include "calculate.asm"
%include "baseOperators.asm"
%include "print.asm"

SECTION .text

global _start

_start:

    pop ecx         
    mov edx,    0

argumentsLoop:
    cmp ecx,    0h      
    jz  argumentsEnd        
    pop eax         

    call    atoi            

    add edx,    eax     
    dec     ecx         
    jmp argumentsLoop       

argumentsEnd:
    mov eax,    edx
    call    printString
    call    breakLine
    call    quit

我的 baseOperators.asm:

;------------------------------------------
; int atoi(Integer number)
; Ascii to integer function (atoi)
atoi:
    push    ebx         ; preserve ebx on the stack to be restored after function runs
    push    ecx         ; preserve ecx on the stack to be restored after function runs
    push    edx         ; preserve edx on the stack to be restored after function runs
    push    esi         ; preserve esi on the stack to be restored after function runs
    mov esi,    eax     ; move pointer in eax into esi (our number to convert)
    mov eax,    0       ; initialise eax with decimal value 0
    mov ecx,    0       ; initialise ecx with decimal value 0

.conversionLoop:
    xor ebx,    ebx     ; resets both lower and uppper bytes of ebx to be 0
    mov bl, [esi+ecx]   ; move a single byte into ebx register's lower half
    cmp bl, 48      ; compare ebx register's lower half value against ascii value 48 (char value 0)
    jl  .conversionEnd      ; jump if less than to label finished
    cmp bl, 57      ; compare ebx register's lower half value against ascii value 57 (char value 9)
    jg  .conversionEnd      ; jump if greater than to label finished
    cmp bl, 10      ; compare ebx register's lower half value against ascii value 10 (linefeed character)
    je  .conversionEnd      ; jump if equal to label finished
    cmp bl, 0       ; compare ebx register's lower half value against decimal value 0 (end of string)
    jz  .conversionEnd      ; jump if zero to label finished
    sub bl, 48      ; convert ebx register's lower half to decimal representation of ascii value
    add eax,    ebx     ; add ebx to our interger value in eax
    mov ebx,    10      ; move decimal value 10 into ebx
    mul ebx         ; multiply eax by ebx to get place value
    inc ecx         ; increment ecx (our counter register)
    jmp .conversionLoop     ; continue multiply loop

.conversionEnd:
    mov ebx, 10         ; move decimal value 10 into ebx
    div ebx         ; divide eax by value in ebx (in this case 10)

    pop esi         ; restore esi from the value we pushed onto the stack at the start
    pop edx         ; restore edx from the value we pushed onto the stack at the start
    pop ecx         ; restore ecx from the value we pushed onto the stack at the start
    pop ebx         ; restore ebx from the value we pushed onto the stack at the start
    ret


;------------------------------------------
; int lenString(String message)
; String length calculation function
lenString:
    push    ebx
    mov     ebx, eax

iterateChar:
    cmp     byte [eax], 0
    jz      finalize
    inc     eax
    jmp     iterateChar

finalize:
    sub     eax, ebx
    pop     ebx
    ret


;------------------------------------------
; void breakLine()
; Break a line - linefeed
breakLine:
        push    eax     ; push eax on the stack
    mov eax, 0x0a   ; move linefeed into eax - 0x0a = 0Ah

    push    eax     ; linefeed on stack to get adress
    mov     eax, esp    ; move adress of current pointer into eax
    call    printString
    pop eax
    pop     eax
    ret         ; return


;------------------------------------------
; void exit()
; Exit program and restore resources
quit:
    mov eax, 1      ; invoke SYS_EXIT (kernel opcode 1)
    mov ebx, 0      ; return 0 status on exit - 'No Errors' 
    int 0x80        ; 0x80=80h
    ret

以及 print.asm 中的打印函数:

;------------------------------------------
; void printInteger (Integer number)
; Integer printing function (itoa)
printInteger:
    push    eax             ; preserve eax on the stack to be restored after function runs
    push    ecx             ; preserve ecx on the stack to be restored after function runs
    push    edx             ; preserve edx on the stack to be restored after function runs
    push    esi             ; preserve esi on the stack to be restored after function runs
    mov     ecx, 0          ; counter of how many bytes we need to print in the end

divideLoop:
    inc     ecx             ; count each byte to print - number of characters
    mov     edx, 0          ; empty edx
    mov     esi, 10         ; mov 10 into esi
    idiv    esi             ; divide eax by esi
    add     edx, 48         ; convert edx to it's ascii representation - edx holds the remainder after a divide instruction
    push    edx             ; push edx (string representation of an intger) onto the stack
    cmp     eax, 0          ; can the integer be divided anymore?
    jnz     divideLoop      ; jump if not zero to the label divideLoop

printLoop:
    dec     ecx             ; count down each byte that we put on the stack
    mov     eax, esp        ; mov the stack pointer into eax for printing
    call    printString     ; call our string print function
    pop     eax             ; remove last character from the stack to move esp forward
    cmp     ecx, 0          ; have we printed all bytes we pushed onto the stack?
    jnz     printLoop       ; jump is not zero to the label printLoop

    pop     esi             ; restore esi from the value we pushed onto the stack at the start
    pop     edx             ; restore edx from the value we pushed onto the stack at the start
    pop     ecx             ; restore ecx from the value we pushed onto the stack at the start
    pop     eax             ; restore eax from the value we pushed onto the stack at the start
    ret

;------------------------------------------
; void printString(String message)
; String printing function
printString:
    push    edx
    push    ecx
    push    ebx
    push    eax

    call    lenString

    mov edx, eax    ; nbytes - number of bytes to write (len), one for each letter plus the zero terminating byte  
    pop eax

    mov ecx, eax    ; buffer - move the memory address of our message string into ecx                         
    mov     ebx, 1      ; fd - filedescriptor, write to the STDOUT file                                    
    mov     eax, 4      ; invoke SYS_WRITE (with fd, buf, nbytes / kernel opcode 4)

    int 0x80        ; prozessor interupt 0x80 jump to system call, stack clean, 0x80=80h

    pop ebx
    pop ecx
    pop edx
    ret

感谢任何提示,

亲切的问候

【问题讨论】:

  • idiv 是有符号除法:您应该准备使用cdqeax 符号扩展为edx:eax,而不是通过将edx 归零来进行零扩展。 (在div 之前这样做)。
  • 一些gdb小技巧,见底部x86 tag wiki

标签: assembly


【解决方案1】:
  1. 如果程序以正常、传统和合法的方式启动¹,堆栈上已经总是有一个参数:程序本身的路径。因此,第一个 POP (pop ecx) 至少获得 1。如果再添加两个参数,则值为 3。将 ECX 寄存器减 1 或将其与 1 进行比较:

    ...
    argumentsLoop:
        cmp ecx,    1h
        jz  argumentsEnd         ; See footnote ¹
    ...
    
  2. 第一个命令行参数的地址在堆栈的第三个位置。必须弹出程序路径的地址:

    ...
    _start:
    
        pop ecx                 ; Get the arguments count
        mov edx,    0
        pop eax                 ; Pop away the program path
    ...
    

    HereGunner写的一篇优秀的文章。

  3. 函数atoi 将ASCII 字符串转换为整数。函数printString 打印——顾名思义——只打印字符串,不打印整数。请改用printInteger

    ...
    argumentsEnd:
        mov eax,    edx
        call    printInteger
    ...
    

¹ 可以不带任何参数 (argc = 0) 或使用约定未涵盖的 argv[0] 启动程序(请参阅execve(2))。我写了一个例子来演示它:

get_argv.asm:

SECTION  .data
    LineFeed    dw  10
    nullstr     db '(null)',0
    argcstr     db 'argc = '
    argcstr1    db '---------------',0
    argvstr     db 'argv['
    argvstr1    db '---------------',0
    argvstr2    db '] = ',0

SECTION .text
global  _start

_start:
    push    ebp
    mov     ebp, esp

    mov eax, [ebp + 4]          ; argc
    mov edi, argcstr1
    call EAX_to_DEC             ; Convert EAX to a string pointed by EDI

    mov esi, argcstr
    call PrintString
    mov esi, LineFeed
    call PrintString

    xor ecx, ecx

    .J1:
    mov eax, ecx
    mov edi, argvstr1
    call EAX_to_DEC             ; Convert EAX to a string pointed by EDI

    mov esi, argvstr
    call PrintString
    mov esi, argvstr2
    call PrintString
    mov esi, [ebp+8+4*ecx]      ; argv[ECX]
    call PrintString
    test esi, esi
    jz .J2
    mov esi, LineFeed
    call PrintString
    add ecx, 1
    jmp .J1
    .J2:

    .exit:
    mov esi, LineFeed
    call PrintString

    mov     esp, ebp
    pop     ebp

    mov     eax, 1              ; SYS_EXIT
    xor     ebx, ebx            ; Exit code = 0 = no error
    int     0x80                ; Call Linux kernel

PrintString:                    ; ARG: ESI Pointer to ASCIZ string
    pusha

    test esi, esi
    jne .J0
    mov esi, nullstr

    .J0:

    mov eax, 4                  ; SYS_WRITE
    mov ebx, 1                  ; STDOUT
    mov ecx, esi

    xor edx, edx                ; Count of bytes to send
    .J1:
    cmp byte [esi], 0           ; Look for the terminating null
    je .J2
    add edx, 1
    add esi, 1
    jmp .J1

    .J2:
    int 0x80                    ; Call Linux kernel

    popa
    ret

EAX_to_DEC:                     ; ARG: EAX integer, EDI pointer to string buffer
    push ebx
    push ecx
    push edx

    mov ebx, 10                 ; Divisor = 10
    xor ecx, ecx                ; ECX=0 (digit counter)
    .J1:                        ; First Loop: store the remainders
    xor edx, edx                ; Don't forget it!
    div ebx                     ; EDX:EAX / EBX = EAX remainder EDX
    push dx                     ; Push the digit in DL (LIFO)
    add cl, 1                   ; = inc cl (digit counter)
    or eax, eax                 ; AX == 0?
    jnz .J1                     ; No: once more
    mov ebx, ecx                ; Store count of digits
    .J2:                        ; Second loop: load the remainders in reversed order
    pop ax                      ; get back pushed digits
    or al, 00110000b            ; to ASCII
    mov [edi], al               ; Store AL to [EDI] (EDI is a pointer to a buffer)
    add edi, 1                  ; = inc edi
    loop .J2                    ; until there are no digits left
    mov byte [edi], 0           ; ASCIIZ terminator (0)
    mov eax, ebx                ; Restore Count of digits

    pop edx
    pop ecx
    pop ebx
    ret                         ; RET: EAX length of string (w/o last null)

start_get_argv.c:

#include <stdio.h>
#include <unistd.h>

int main ( int argc, char *argv[] )
{
    char* asmprog = "./get_argv";

    puts ("execute me\n");
    printf ("argc = %d\n",argc);
    for (int i=0; i <= argc; ++i)
    {
        printf ("argv[%d]=%s\n",i,argv[i]);
    }

    printf ("\nexecve %s\n\n",asmprog);
    fflush (0);
    execve (asmprog, NULL, NULL);

    return 0;
}

在同一目录中构建两个文件并运行 ./start_get_argv。被调用的 ./get_argv 将报告 argc = 0 和 argv[0] = (null)。空指针表示“数组结束”。处理这种情况很简单:如果 argc 小于或等于 1,则退出:

...
argumentsLoop:
    cmp ecx,    1h
    jbe  argumentsEnd
...

【讨论】:

  • 不太总是:非标准调用者可能将空的argv[] 数组传递给execve(2),它是一个单独的execve arg,而不是路径可执行文件。 (shellcode 通常会这样做)。 IIRC,在这种情况下,内核不会创建包含可执行路径的 argv[] 。如果您正在编写一个不应信任其父级的可执行文件,请不要依赖它。 (而且可能一开始就不要用 asm 写它......)。不过,我认为没有粗体的“总是”就足够接近了。 :P
  • @PeterCordes:我已经相应地编辑了答案,希望它不会太混乱。我认为 shellcode 不应该成为这些事情的衡量标准。
  • 我并不是说实际的 shellcode 是相关的,这只是一个实际调用 execve 的例子。我认为我真正的反对意见是避免引入对 Unix 工作原理的误解(即设置 argv[0] 的是父进程,而不是内核)。并不是说任何人都可能在用 asm 编写的 set-uid 二进制文件中引入安全漏洞。
【解决方案2】:

您充满专业知识的回答帮助我再次学到了很多东西。谢谢你。 rkhb 的回答使我的代码工作,实现了这些更改:

_start:
    pop ecx                 ; Get the arguments count
    mov edx,    0
    pop eax                 ; Pop away the program path

argumentsLoop:
    cmp ecx,    1h
    jz  argumentsEnd         

...

argumentsEnd:
    mov eax,    edx
    call    printInteger

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-15
    • 2011-01-29
    • 1970-01-01
    • 2016-03-08
    • 1970-01-01
    • 2017-04-25
    相关资源
    最近更新 更多