; Reto: La Boveda en Ensamblador
; Saul Castro Treviño A00844534

global _start

; Numeros de las syscalls de Linux
SYS_READ    equ 0
SYS_WRITE   equ 1
SYS_EXIT    equ 60

; Limites del programa
MAX_MSG     equ 255             ; el mensaje puede tener hasta 255 caracteres
MAX_KEY     equ 15              ; una clave mas larga que esto ya es invalida

section .data
titulo      db "=== BOVEDA ===", 10
titulo_len  equ $ - titulo
p_clave     db "Clave (1-25): "
p_clave_len equ $ - p_clave
p_msg       db "Mensaje: "
p_msg_len   equ $ - p_msg
txt_error   db "Error: clave invalida", 10
error_len   equ $ - txt_error
n1_a        db "[N1] Letras: "
n1_a_len    equ $ - n1_a
n1_b        db " | Digitos: "
n1_b_len    equ $ - n1_b
n1_c        db " | Otros: "
n1_c_len    equ $ - n1_c
n2_txt      db "[N2] Cifrado: "
n2_len      equ $ - n2_txt
n3_txt      db "[N3] Checksum: 0x"
n3_len      equ $ - n3_txt
n4_a        db "[N4] Descifrado: "
n4_a_len    equ $ - n4_a
n4_ok       db "[N4] Verificacion: OK", 10
n4_ok_len   equ $ - n4_ok
n4_fallo    db "[N4] Verificacion: FALLO", 10
n4_fallo_len equ $ - n4_fallo
salto       db 10

section .bss
keybuf      resb 16             ; aqui se guarda lo que el usuario escribe como clave
msgbuf      resb 256            ; mensaje original (no se modifica nunca)
cipherbuf   resb 256            ; mensaje cifrado
plainbuf    resb 256            ; mensaje descifrado
hexbuf      resb 2              ; los dos digitos hexadecimales del checksum
charbuf     resb 1              ; un solo caracter, para leer y para imprimir numeros
clave       resq 1              ; la clave ya convertida a numero
msglen      resq 1              ; cuantos caracteres tiene el mensaje
letras      resq 1              ; contador de letras
digitos     resq 1              ; contador de digitos
otros       resq 1              ; contador de otros caracteres
desp_letra  resq 1              ; cuanto se desplazan las letras
desp_digito resq 1              ; cuanto se desplazan los digitos

section .text

; Programa principal: ejecuta los cuatro niveles en orden

_start:
    call nivel1
    call nivel2
    call nivel3
    call nivel4

    mov rax, SYS_EXIT
    mov rdi, 0                  ; codigo 0 = todo salio bien si sale 1 salio mal
    syscall


; NIVEL 1: pedir la clave y el mensaje, validar la clave y contar caracteres
nivel1:
    push rax
    push rcx
    push rdx
    push rsi
    push rdi

    mov rsi, titulo
    mov rdx, titulo_len
    call imprimir
    mov rsi, p_clave
    mov rdx, p_clave_len
    call imprimir

    mov rdi, keybuf
    mov rsi, MAX_KEY
    call leer_linea             ; rax = cuantos caracteres escribio el usuario
    cmp rax, MAX_KEY
    jg clave_mala               ; si escribio mucho, no puede ser una clave valida

    mov rcx, rax                ; rcx = largo de la clave
    mov rsi, keybuf
    call convertir_clave        ; rax = clave, o -1 si no es valida
    cmp rax, -1
    je clave_mala
    mov [clave], rax

    mov rsi, p_msg
    mov rdx, p_msg_len
    call imprimir

    mov rdi, msgbuf
    mov rsi, MAX_MSG
    call leer_linea
    cmp rax, MAX_MSG
    jle largo_ok
    mov rax, MAX_MSG            ; si escribio mas de 255, solo se usan los primeros 255
largo_ok:
    mov [msglen], rax

    mov rsi, msgbuf
    mov rcx, [msglen]
    call contar                 ; deja los resultados en letras, digitos y otros

    ; Se imprime un salto de linea antes de [N1] porque
    ; no hay eco del Enter y la linea quedaria pegada al aviso.
    mov rsi, salto
    mov rdx, 1
    call imprimir
    mov rsi, n1_a
    mov rdx, n1_a_len
    call imprimir
    mov rax, [letras]
    call imprimir_numero
    mov rsi, n1_b
    mov rdx, n1_b_len
    call imprimir
    mov rax, [digitos]
    call imprimir_numero
    mov rsi, n1_c
    mov rdx, n1_c_len
    call imprimir
    mov rax, [otros]
    call imprimir_numero
    mov rsi, salto
    mov rdx, 1
    call imprimir

    pop rdi
    pop rsi
    pop rdx
    pop rcx
    pop rax
    ret

clave_mala:
    mov rsi, salto              ; el error debe quedar en su propia linea
    mov rdx, 1
    call imprimir
    mov rsi, txt_error
    mov rdx, error_len
    call imprimir
    mov rax, SYS_EXIT
    mov rdi, 1                  ; codigo 1 = clave invalida
    syscall


; NIVEL 2: cifrar el mensaje con la clave

nivel2:
    push rax
    push rbx
    push rcx
    push rdx
    push rsi
    push rdi

    mov rax, [clave]
    mov [desp_letra], rax       ; las letras se mueven k posiciones

    mov rdx, 0
    mov rbx, 10
    div rbx                     ; el resto de dividir k entre 10 queda en rdx
    mov [desp_digito], rdx      ; los digitos se mueven k mod 10 posiciones

    mov rsi, msgbuf
    mov rdi, cipherbuf
    mov rcx, [msglen]
    call desplazar

    mov rsi, n2_txt
    mov rdx, n2_len
    call imprimir
    mov rsi, cipherbuf
    mov rdx, [msglen]
    call imprimir
    mov rsi, salto
    mov rdx, 1
    call imprimir

    pop rdi
    pop rsi
    pop rdx
    pop rcx
    pop rbx
    pop rax
    ret

; NIVEL 3: suma de control del texto cifrado

nivel3:
    push rax
    push rcx
    push rdx
    push rsi

    mov rsi, cipherbuf
    mov rcx, [msglen]
    call checksum               ; el resultado queda en al

    call a_hexadecimal          ; escribe los dos digitos en hexbuf

    mov rsi, n3_txt
    mov rdx, n3_len
    call imprimir
    mov rsi, hexbuf
    mov rdx, 2
    call imprimir
    mov rsi, salto
    mov rdx, 1
    call imprimir

    pop rsi
    pop rdx
    pop rcx
    pop rax
    ret

; NIVEL 4: descifrar y comprobar que se recupero el mensaje original

nivel4:
    push rax
    push rbx
    push rcx
    push rdx
    push rsi
    push rdi

    ; Retroceder k letras es lo mismo que avanzar 26 - k
    mov rax, 26
    sub rax, [clave]
    mov [desp_letra], rax

    ; Para los digitos es avanzar 10 - (k mod 10)
    mov rax, [clave]
    mov rdx, 0
    mov rbx, 10
    div rbx                     ; rdx = k mod 10
    mov rax, 10
    sub rax, rdx
    cmp rax, 10
    jl desp_ok
    mov rax, 0                  ; si k mod 10 es 0 no hay que mover nada
desp_ok:
    mov [desp_digito], rax
                                ;llevo 5 horas haciendo esto ayuda
    mov rsi, cipherbuf
    mov rdi, plainbuf
    mov rcx, [msglen]
    call desplazar

    mov rsi, n4_a
    mov rdx, n4_a_len
    call imprimir
    mov rsi, plainbuf
    mov rdx, [msglen]
    call imprimir
    mov rsi, salto
    mov rdx, 1
    call imprimir

    mov rsi, msgbuf
    mov rdi, plainbuf
    mov rcx, [msglen]
    call comparar               ; rax = 1 si son iguales, 0 si no
    cmp rax, 1
    jne hay_fallo
    mov rsi, n4_ok
    mov rdx, n4_ok_len
    call imprimir
    jmp fin_nivel4
hay_fallo:
    mov rsi, n4_fallo
    mov rdx, n4_fallo_len
    call imprimir
fin_nivel4:
    pop rdi
    pop rsi
    pop rdx
    pop rcx
    pop rbx
    pop rax
    ret


; SUBRUTINAS AUXILIARES



; imprimir: escribe en pantalla rdx caracteres que empiezan en [rsi]

imprimir:
    push rax
    push rdi
    push rcx
    push r11                    ; syscall modifica rcx y r11, hay que guardarlos
    mov rax, SYS_WRITE
    mov rdi, 1
    syscall
    pop r11
    pop rcx
    pop rdi
    pop rax
    ret

 
; leer_linea: lee una linea del teclado en [rdi], guardando maximo rsi
; caracteres. Devuelve en rax cuantos caracteres tenia la linea.
; Se lee de un caracter en un caracter para no pasarnos al leer la
; siguiente linea.

leer_linea:
    push rbx
    push rcx
    push rdx
    push rsi
    push rdi
    push r11
    mov rbx, rdi                ; rbx = inicio del buffer
    mov rdx, rsi                ; rdx = maximo que se puede guardar
    mov rcx, 0                  ; rcx = caracteres leidos hasta ahora
leer_otro:
    push rcx                    ; syscall modifica rcx, se guarda el contador
    push rdx
    mov rax, SYS_READ
    mov rdi, 0
    mov rsi, charbuf
    mov rdx, 1
    syscall
    pop rdx
    pop rcx
    cmp rax, 1
    jne fin_lectura             ; no llego nada: se acabo la entrada
    mov al, [charbuf]
    cmp al, 10
    je fin_lectura              ; llego el Enter
    cmp al, 13
    je leer_otro                ; se ignora el retorno de carro de Windows
    cmp rcx, rdx
    jge no_guardar              ; ya no cabe: se cuenta pero no se guarda
    mov [rbx + rcx], al
no_guardar:
    inc rcx
    jmp leer_otro
fin_lectura:
    mov rax, rcx
    pop r11
    pop rdi
    pop rsi
    pop rdx
    pop rcx
    pop rbx
    ret


; convertir_clave: pasa el texto de la clave a numero.
; Entrada: rsi = texto, rcx = largo.  Salida: rax = clave, o -1 si es invalida

convertir_clave:
    push rbx
    push rcx
    push rdx
    push rsi
    cmp rcx, 0
    je clave_invalida           ; no escribio nada
    mov rax, 0                  ; aqui se va armando el numero
siguiente_digito:
    mov bl, [rsi]
    cmp bl, '0'
    jl clave_invalida           ; es menor que '0', no es digito
    cmp bl, '9'
    jg clave_invalida           ; es mayor que '9', no es digito
    sub bl, '0'                 ; de caracter ASCII a su valor (0-9)
    movzx rbx, bl
    mov rdx, 10
    mul rdx                     ; numero = numero * 10
    add rax, rbx                ; y se le suma el digito nuevo
    inc rsi
    dec rcx
    jnz siguiente_digito
    cmp rax, 1
    jl clave_invalida           ; debe ser al menos 1
    cmp rax, 25
    jg clave_invalida           ; y como maximo 25
    jmp fin_convertir
clave_invalida:
    mov rax, -1
fin_convertir:
    pop rsi
    pop rdx
    pop rcx
    pop rbx
    ret


; contar: cuenta letras, digitos y otros en los rcx caracteres de [rsi]

contar:
    push rax
    push rcx
    push rsi
    mov qword [letras], 0
    mov qword [digitos], 0
    mov qword [otros], 0
ciclo_contar:
    cmp rcx, 0
    je fin_contar
    mov al, [rsi]
    cmp al, 'A'
    jl revisar_digito
    cmp al, 'Z'
    jle es_letra                ; esta entre 'A' y 'Z'
    cmp al, 'a'
    jl es_otro                  ; entre 'Z' y 'a' no hay letras ni digitos
    cmp al, 'z'
    jle es_letra                ; esta entre 'a' y 'z'
    jmp es_otro
revisar_digito:
    cmp al, '0'
    jl es_otro
    cmp al, '9'
    jg es_otro
    inc qword [digitos]
    jmp siguiente_contar
es_letra:
    inc qword [letras]
    jmp siguiente_contar
es_otro:
    inc qword [otros]
siguiente_contar:
    inc rsi
    dec rcx
    jmp ciclo_contar
fin_contar:
    pop rsi
    pop rcx
    pop rax
    ret


; desplazar: copia rcx caracteres de [rsi] a [rdi] moviendo las letras
; desp_letra posiciones y los digitos desp_digito posiciones.
; Cuando se pasa de 'Z' (o 'z', o '9') se da la vuelta restando el tamano
; del grupo (26 letras o 10 digitos).

desplazar:
    push rax
    push rcx
    push rsi
    push rdi
ciclo_desplazar:
    cmp rcx, 0
    je fin_desplazar
    mov al, [rsi]

    cmp al, 'A'
    jl no_mayuscula
    cmp al, 'Z'
    jg no_mayuscula
    sub al, 'A'                 ; posicion dentro del abecedario (0-25)
    add al, [desp_letra]
    cmp al, 26
    jl mayuscula_lista
    sub al, 26                  ; se paso de la Z, se regresa a la A
mayuscula_lista:
    add al, 'A'
    jmp guardar_caracter

no_mayuscula:
    cmp al, 'a'
    jl no_minuscula
    cmp al, 'z'
    jg no_minuscula
    sub al, 'a'
    add al, [desp_letra]
    cmp al, 26
    jl minuscula_lista
    sub al, 26
minuscula_lista:
    add al, 'a'
    jmp guardar_caracter

no_minuscula:
    cmp al, '0'
    jl guardar_caracter         ; no es digito, se copia igual
    cmp al, '9'
    jg guardar_caracter
    sub al, '0'
    add al, [desp_digito]
    cmp al, 10
    jl digito_listo
    sub al, 10                  ; se paso del 9, se regresa al 0
digito_listo:
    add al, '0'

guardar_caracter:
    mov [rdi], al
    inc rsi
    inc rdi
    dec rcx
    jmp ciclo_desplazar
fin_desplazar:
    pop rdi
    pop rsi
    pop rcx
    pop rax
    ret


; checksum: suma de control de los rcx caracteres de [rsi].
; Para cada caracter: c = rotar_izquierda(c, 1) XOR caracter.  Resultado en al.

checksum:
    push rcx
    push rsi
    mov rax, 0                  ; el acumulador empieza en 0
ciclo_checksum:
    cmp rcx, 0
    je fin_checksum
    rol al, 1                   ; rotar 8 bits: el bit que sale por la izquierda entra por la derecha
    xor al, [rsi]
    inc rsi
    dec rcx
    jmp ciclo_checksum
fin_checksum:
    pop rsi
    pop rcx
    ret


; a_hexadecimal: convierte el numero en al a dos caracteres hexadecimales
; (mayusculas) y los guarda en hexbuf.

a_hexadecimal:
    push rax
    push rbx
    mov bl, al                  ; copia del numero para sacar el digito bajo

    shr al, 4                   ; con el desplazamiento queda solo el digito alto
    cmp al, 10
    jl alto_numero
    add al, 'A' - 10            ; 10 a 15 se escriben A a F
    jmp alto_listo
alto_numero:
    add al, '0'
alto_listo:
    mov [hexbuf], al

    mov al, bl
    and al, 0Fh                 ; la mascara deja solo los 4 bits de abajo
    cmp al, 10
    jl bajo_numero
    add al, 'A' - 10
    jmp bajo_listo
bajo_numero:
    add al, '0'
bajo_listo:
    mov [hexbuf + 1], al

    pop rbx
    pop rax
    ret


; comparar: compara rcx caracteres de [rsi] con los de [rdi].
; Devuelve rax = 1 si son iguales, rax = 0 si hay alguna diferencia.

comparar:
    push rbx
    push rcx
    push rsi
    push rdi
    mov rax, 1                  ; se supone que son iguales hasta encontrar diferencia
ciclo_comparar:
    cmp rcx, 0
    je fin_comparar
    mov bl, [rsi]
    cmp bl, [rdi]
    jne son_distintos
    inc rsi
    inc rdi
    dec rcx
    jmp ciclo_comparar
son_distintos:
    mov rax, 0
fin_comparar:
    pop rdi
    pop rsi
    pop rcx
    pop rbx
    ret


; imprimir_numero: imprime el numero que esta en rax en decimal.
; Se divide entre 10 varias veces; los restos salen de derecha a izquierda,
; asi que se guardan en la pila y al sacarlos quedan en el orden correcto.

imprimir_numero:
    push rax
    push rbx
    push rcx
    push rdx
    push rsi
    mov rcx, 0                  ; cuantos digitos llevamos
    mov rbx, 10
dividir:
    mov rdx, 0
    div rbx                     ; rax = cociente, rdx = resto (un digito)
    add rdx, '0'
    push rdx
    inc rcx
    cmp rax, 0
    jne dividir                 ; seguir hasta que el cociente sea 0
sacar_digito:
    pop rdx
    mov [charbuf], dl
    mov rsi, charbuf
    mov rdx, 1
    call imprimir
    dec rcx
    jnz sacar_digito
    pop rsi
    pop rdx
    pop rcx
    pop rbx
    pop rax
    ret

ウェブサイトに埋め込む

このプロジェクトをあなたのウェブサイトに埋め込むには、以下のコードをコピーしてウェブサイトのHTMLに貼り付けてください。