Thursday, December 6, 2018

Practical Reverse Engineering - Chapter 1 pg 35 - Exercise #4

Question: Implement the following functions in x86 assembly: strlen, strchr, memcpy, memset, strcmp, strset.

strlen
push ebp
mov ebp, esp
push edi; Save nonvolatile register.
push esi; Save nonvolatile register.
mov edi, [ebp+0x8]; Set ESI to the first argument passed in 
                  ; aka the pointer to the string.
mov esi, edi; Save original starting point of the string into ESI.
xor al, al; Set AL or the byte being searched for, 
          ; to 0, or a NULL byte.
repne scasb; Keep incrementing EDI and comparing it to see 
           ; if it is a NULL byte. Stop loop when a NULL 
           ; byte is hit.
sub edi, esi; EDI will contain the address of a NULL byte. Subtract
            ; this address by ESI or the address of the start of the
            ; string to find the number of bytes read aka the string 
            ; length and save this number in EDI.
mov eax, edi; Return EDI or the string length.
pop esi; Restore original ESI value.
pop edi; Restore original EDI value.
mov esp, ebp; Restore ESP value
pop ebp; Restore frame pointer value
ret; Return

strchr
push ebp
mov ebp, esp; Save ESP value for later restoration. Part of function prologue.
push ebx ; EBX is not a volatile register, so save it onto 
         ; the stack for later restoration. EAX, ECX, and 
         ; EDX are volatile (https://en.wikibooks.org/wiki/X86_Assembly/High-Level_Languages#STDCALL)
mov edx, [ebp+0xC] ; Set EDX to the value of the second 
                   ; argument aka int character
mov ebx, [ebp+0x8] ; Set EBX to the value of the first
                   ; argument aka char * str
check_null:
    mov ecx, byte ptr [ebx]; Perform a memory read to get the value
                           ; of the current byte in the string.
                           ; Doing this at the top of the loop saves
                           ; us having to do extra memory reads.
    cmp ecx, 0 ; Check that we haven't reached the end of the 
               ; string aka the NULL byte.
    mov  eax, 0 ; Prepare to return 0, aka a 
                ; NULL pointer, as the result.
    jz finish ; Jump to finish if the current byte in the string 
              ; is NULL, aka we hit the end of the string.
check_equal:
    cmp ecx, edx ; Check if current character in string
                 ; is the one being searched for.
    mov eax, ebx ; Set EAX to the location where the character was
                 ; found to set return value correctly.
    jz finish ; Jump to finish if the current byte in the
              ; string is the one being searched for.
loop_jump:
    inc ebx ; Increment EBX to point to the 
            ; next character in the string
    jmp check_null ; Jump back to the top of the loop to
                   ; continue searching through the string.
finish:
    pop ebx ; Restore EBX back to its original value.
    mov esp, ebp ; Restore ESP back to its original value.
    pop ebp ; Set EBP or the frame pointer, back to its previous value.
    ret ; Return

memcpy
push ebp
mov ebp, esp ; Function prologue
push esi ; Save original ESI value, nonvolatile register.
push edi ; Save original EDI value, nonvolatile register.
mov ecx, [ebp+0x10] ; Set ECX to size or the number of bytes to copy over.
mov esi, [ebp+0xC] ; Set ESI to the value of the source argument.
mov edi, [ebp+0x8] ; Set EDI to the value of the destination argument.
cld ; Thanks to zerosum0x0 for pointing out in his articles that 
    ; this needs to be done. Otherwise we can't be sure that EDI 
    ; and ESI will be incremented when doing movsb operation.
rep movsb ; Move ECX number of bytes from ESI, or source to 
          ; EDI, or destination, incrementing EDI and ESI
          ; with each byte read.
mov eax, [ebp+0x8] ; Set EAX, or the return value, to destination.
pop edi ; Restore original EDI value.
pop esi ; Restore original ESI value.
mov esp, ebp
pop ebp ; Function epilogue
ret

memset
push ebp
mov ebp, esp ; Function prologue
push edi ; Save value of EDI register, nonvolatile register.
mov ecx, [ebp+0x10] ; Set ECX to the argument num 
                    ; or number of bytes to set.
mov eax, [ebp+0xC] ; Set EAX to the argument value or 
                   ; the value to set the bytes to.
mov edi, [ebp+0x8] ; Set EDI to the argument ptr or the 
                   ; address of the block of memory to fill.
cld ; Set the direction flag to 0 to ensure EDI is incremented
    ; every time the stosb instruction is executed.
rep stosb ; Fill ECX bytes of memory with the value in EAX,
          ; starting at the address in EDI. EDI will be 
          ; incremented each time the stosb instruction is executed.
mov eax, [ebp+0x8] ; Set the return value to the start of the
                   ; block of memory that was filled out.
pop edi ; Set EDI back to its original value.
mov esp ebp
pop ebp ; Function epilogue
ret;

strset
push ebp ; Save previous frame pointer onto the stack.
mov ebp, esp ; Set EBP to point to the current frame pointer.
mov eax, [ebp+0xC] ; Set EAX to the argument int c
mov ecx, [ebp+0x8]; Set ECX to the argument char * str
loop_start:
   cmp byte ptr [ecx], 0 ; Check if the current byte of the 
                         ; string being processed is NULL.
   jz finish ; Jump to the finish if it is.
   mov byte ptr [ecx], al ; Set the current byte in the string
                          ; to the value of the argument c.
   inc ecx ; Set ECX to point to the next byte in the string.
   jmp loop_start ; Jump back to the start of the loop.
finish:
  mov eax, [ebp+8] ; Set the return value to the address of 
                   ; the first byte of the string that was set.
  mov esp, ebp ; Restore previous ESP value
  pop ebp ; Restore previous stack frame value.
  ret

strcmp
push ebp
mov ebp, esp ; Function prologue code
push edi ; Save EDI value, nonvolatile register.
push esi ; Save ESI value, nonvolatile register.
mov edx, [ebp+0xC] ; Set EDX to str2
mov ecx, [ebp+0x8] ; Set ECX to str1
dec edx ; Subtract EDX by 1 since the later loop_start code will be 
        ; incrementing it by 1 for each iteration of the loop.
dec ecx ; Subtract ECX by 1 since the later loop_start code will be 
        ; incrementing it by 1 for each iteration of the loop.
loop_start:
    inc edx ; Increment EDX to point to the next byte in the str2 string.
    inc ecx ; Increment ECX to point to the next byte in the str1 string.
    mov edi, byte ptr [edx] ; Set EDI to current byte in str2 being processed.
    mov esi, byte ptr [ecx] ; Set ECX to current byte in str1 being processed.
    cmp esi, edi ; Compare current byte in str1 to current byte in str2.
    jl lower_str1 ; If the value of the current byte being processed 
                  ; in str1 is lower than the value of the current 
                  ; byte being processed in str2, then jump to lower_str1.
    jg greater_str1 ; If the value of the current byte being processed 
                  ; in str1 is greater than the value of the current 
                  ; byte being processed in str2, then jump to greater_str1.
    jmp compare_null ; If the current byte being processed in str1 is 
                     ; equal to the current byte being processed in str2,
                     ; then jump to compare_null to see if we the byte 
                     ; that was compared is a NULL byte aka we have 
                     ; reached the end of both strings.
lower_str1:
    mov eax, -1 ; If the first character that does not match has a lower
                ; value in str1 than str2, return a value < 0. The value
                ; -1 will work for our purposes.
    jmp finish ; Jump to cleanup code.
greater_str1:
    mov eax, 1 ; If the first character that does not match has a 
               ; greater value in str1 than str2, return a value
               ; > 0. The value 1 will work for our purposes.
    jmp finish ; Jump to cleanup code.
compare_null:
    test edi, edi ; Check that the bytes being currently being processed 
                  ; in str1 and str2, which have been determined to be 
                  ; the same as one another, are not NULL bytes.
    jnz loop_start ; If the bytes that were matched are not NULL 
                   ; bytes, continue the processing loop.
    mov eax, 0 ; Otherwise if they are both NULL bytes, set the
               ; return value to 0 to indicate the two strings 
               ; are the same as one another.
finish:
    pop esi ; Restore the original value of ESI.
    pop edi ; Restore the original value of EDI.
    mov esp, ebp
    pop ebp ; Function epilogue code
    ret