0x31 Binary Options


Esta es una tarea donde tengo que convertir hexadecimal a binario cuando se llama a un comando en el terminal. Mi profesor no está enseñando realmente C así Im muerto perdido. Tengo que incluir un procedimiento, void printBits (unsigned long i), que imprime los bits en i. Se invocará desde la línea de comandos utilizando el modificador - p seguido por un entero largo sin signo de 32 bits en forma hexadecimal. EX: lab3 - p 0x5 Salida: 0000 0000 0000 0000 0000 0000 0000 0101 Por favor, no me dé el código. Necesito entender esto. Aquí hay una pista, mira las siguientes funciones de la biblioteca C: putchar (), strcmp (), sscanf (). Éstos le permitirán solucionar todos los problemas que el quotteacherquot ha fijado para usted. Finalmente, para tratar con los argumentos de la línea de comandos, necesitará utilizar los parámetros quotargcquot y quotargvquot en su función main (), estos son ampliamente utilizados y pueden ser buscados en Google para fácilmente. Ndash Wossname Feb 19 at 8:24 4 Respuestas Hay un número de maneras de acercarse a imprimir la representación binaria para cualquier número. En primer lugar, puede simplemente mostrar el resultado de su cambio y la operación de índice directamente (a stdout, un archivo, etc) Este parece ser el enfoque que comenzó con, pero luego declaró un búfer de 32 bits. Aunque ciertamente puede hacer eso, no hay necesidad de almacenar en búfer los resultados si no va a devolver un puntero al búfer completo. (Que me lleva a mi tercer punto, a continuación) Simplemente la salida de bits sin almacenar / devolver un puntero a los bits en una cadena terminada en nul, tiene su lugar, pero es generalmente de uso limitado. Sin embargo, es un problema común que abarca los fundamentos de todos los enfoques. Crear una representación binaria unpadded se puede abordar de la siguiente manera: Los comentarios son bastante explicativos. El esquema consiste en cambiar cada bit comenzando con el bit más significativo (por ejemplo, el bit 31 (31-0) para un número de 32 bits. Compruebe si hay 1 bits después del cambio (si no, el desplazamiento supera el máximo Posición de bit significativo en el número y nada hay que imprimir) Una vez que hay un bit encontrado en rem, siempre habrá bits bits para imprimir a lo largo del resto de las iteraciones de bucle porque está cambiando por una cantidad decreciente. El bit más significativo (que imprime primero), se termina con los bits impresos en el orden correcto y sólo la impresión del número de bits que componen el número. Generalmente, cuando usted está simplemente la salida de la representación binaria directamente a la pantalla, Sólo desea dar salida a los bits hasta el bit más significativo (lo que impide la salida de un 1 con 63 0 s delante de él haciendo un lío fuera de las cosas.) A continuación, es la salida de una representación binaria acolchada a un cierto número de bits. Útil si sólo desea ver los bits inferiores 8, 16, 32. en cualquier número, pero desea una representación con un número fijo de bits cada vez. Aquí simplemente pasa el número de bits que desea ver. Su función hará un bucle sobre ese número de posiciones de bits en su número y emite los resultados: Notará que la principal diferencia aquí es que no tiene que preocuparse de comprobar si quedan bits para evitar la impresión de ceros iniciales no deseados, porque usted Están controlando el número de bits con el parámetro sz. (Su hasta usted qué usted hace si un tamaño 0 es pasado, yo apenas elige a la salida 0) ahora para el tercer punto mencionado arriba. Simplemente la salida de bits es engorrosa desde un punto de vista de formato de nuevo en el cuerpo principal de su código. Creo que es mucho más útil para almacenar los bits en una matriz de caracteres (nul-terminated por lo que se puede tratar como una cadena) y devolver un puntero a la matriz para que pueda pasar a printf. Etc. Ahora bien, debe pasar una matriz de tamaño adecuado como parámetro, declarar una matriz estática para que la matriz no se destruya en la función return o asignar dinámicamente el almacenamiento para la matriz dentro de la función. Todos tienen ventajas y desventajas que usted tiene que pesar dependiendo de las necesidades de su código. Por ejemplo: El código funciona igual que su homólogo acolchado no amortiguado arriba. Observe cómo p devuelve la posición inicial dentro del búfer en el que comienza sz número de bits. También tenga en cuenta que necesitará una constante para BITSPERLONG que denota el número de bits en un largo en su hardware. (Que se maneja normalmente de una manera similar a BUILD64) nota: apenas tenga en cuenta que una limitación a una declaración estática es que la función de conversión se puede utilizar solamente una vez en cualquier llamada de printf (o dentro de cualquier sola línea de código) puesto que hay Sólo una matriz de almacenamiento para la conversión binaria. (Siempre puede hacer cualquier número de llamadas y almacenar los resultados en diferentes ubicaciones justo antes de hacer la llamada printf) Una última variación en la impresión binaria es la impresión de la representación con separadores incluidos para facilitar la identificación y comparación entre cadenas binarias (Especialmente cuando se trata de secuencias más largas de 0 s y 1 seg: La función funciona esencialmente igual que el binpad anterior, pero con la adición de que el búfer estático es más grande para acomodar los separadores y una comprobación adicional en la posición del bit para determinar cuándo Separador se debe agregar al búfer: El resto de su problema es simplemente procesar los argumentos de la línea de comandos, y realizar la conversión de cadena de entrada a valor sin signo, junto con la validación comprueba que el número no exceda de 32 bits, etc Usted Puede procesar los argumentos con getops o para un pequeño número de opciones simples puede simplemente usar un bucle. En Linux, los únicos argumentos necesarios que su código debe responder son - h para ayuda y - v para la versión. Mientras que nadie hace esto para ejemplos cortos, etc. es por lo menos agradable tener esa información. Mire el siguiente ejemplo que pone todas las piezas juntas y hágamelo saber si tiene alguna pregunta: Primero convierta la cadena que obtiene de la línea de comandos a un entero, lo más sencillo es usar sscanf ahora tiene el valor decimal n. Con el fin de convertir a binario que necesita para pasar por cada bit en el entero sin signo. Haciendo bit a bit y con el valor decimal puede comprobar cada bit individual si está configurado e imprimir un 1 o 0 dependiendo de la introducción de codificación bitbasE91 basE91 es un método avanzado para codificar datos binarios como caracteres ASCII. Es similar a UUencode o base64, pero es más eficiente. La sobrecarga producida por basE91 depende de los datos de entrada. Se asciende como máximo a 23 (frente a 33 para base64) y puede variar hasta 14, lo que ocurre típicamente en bloques de 0 bytes. Esto hace que basE91 sea muy útil para transferir archivos más grandes a través de conexiones binarias no seguras como correo electrónico o líneas terminales. Alfabeto Como su nombre indica, basE91 necesita 91 caracteres para representar los datos binarios codificados en ASCII. De los 94 caracteres ASCII imprimibles (0x21-0x7E), los tres siguientes han sido omitidos para construir el alfabeto basE91: La tabla de traducción se compone de los caracteres restantes como se muestra a continuación. November 17, 2016 Ahora theyve ido un hecho. Una agencia no identificada ha difundido un poderoso virus informático en todas las computadoras del mundo y ha eliminado los binarios de cada copia de cada herramienta de desarrollo de software. Incluso las copias sin conexión son tan potentes. La mayor parte del código fuente sigue existiendo, incluso para los compiladores, y la mayoría de los sistemas informáticos seguirán funcionando sin interrupción, pero no se podrá desarrollar ningún nuevo software a menos que sea escrito byte por byte en código de máquina en bruto. Sólo los programadores reales pueden hacer cualquier cosa. Los desarrolladores de software más importantes del mundo se han puesto a trabajar arrancando un compilador C (y otros) completamente desde cero para que podamos volver a la normalidad. Sin un ensamblador, es un proceso lento y tedioso. Mientras tanto, en lugar de esperar a que el trabajo de arranque para completar, el resto de nosotros se han asignado programas individuales afectados por el virus. Por ejemplo, muchas utilidades básicas de unix han sido borradas, y el bootstrap se beneficiaría de tenerlas. Tener grupos diferentes abordar cada programa perdido permitirá el esfuerzo de arranque para avanzar un poco en paralelo. Al menos eso es lo que nos dicen los nerds del compilador. La verdadera razón es que theyre cansado de ser preguntado si theyre hecho todavía, y estas tareas nos mantendrá el resto de nosotros tranquilamente ocupado. Afortunadamente a ti ya mí se nos ha asignado la tarea más fácil de todos: Escribir el verdadero comando desde cero. Bueno, tienes que averiguarlo byte by byte. El objetivo es x86-64 Linux, lo que significa que necesitamos la siguiente documentación: Especificación de formato ejecutable y de enlace (ELF). Este es el formato binario utilizado por los modernos sistemas Unix, como Linux. Una forma más conveniente de acceder a este documento es man 5 elf. Manual de desarrolladores de software Intel 64 y IA-32 Architectures (volumen 2). Esto documenta completamente el conjunto de instrucciones y su codificación. Es toda la información necesaria para escribir código de máquina x86 a mano. Los manuales de AMD funcionarían también. Interfaz binaria de aplicación System V: Complemento de procesador de arquitectura AMD64. Sólo se necesitan algunas piezas de información de este documento, pero se necesitarían más para un programa más sustancial. Algunos números mágicos de los archivos de cabecera. Asamblea manual El programa que escribió es verdadero. Cuyo comportamiento se documenta como no hacer nada, con éxito. Todos los argumentos de la línea de comandos se ignoran y no se lee ninguna entrada. El programa sólo necesita realizar la llamada al sistema de salida, finalizando inmediatamente el proceso. Según el documento ABI (3) Apéndice A, los registros para argumentos de llamada de sistema son: rdi. Rsi Rdx. R10. R8. R9. El número de llamada del sistema entra en rax. La llamada al sistema de salida toma sólo un argumento, y ese argumento será 0 (éxito), por lo que rdi se debe establecer en cero. Su probable que su ya cero cuando el programa se inicia, pero el documento ABI dice que su contenido es indefinido (3.4), por lo que bien establecido explícitamente. Para Linux en x86-64, el número de llamada al sistema para salir es 60, (/usr/include/asm/unistd64.h), por lo que rax se establecerá en 60, seguido por syscall. No hay ensamblador disponible para convertir esto en código de máquina, por lo que tiene que ser montado a mano. Para eso necesitamos el manual Intel (2). La primera instrucción es xor. Así que busca ese mnemónico en el manual. Al igual que la mayoría de los mnemónicos x86, hay muchos opcodes diferentes y muchas maneras de codificar la misma operación. En este caso, hay 22 opciones. Los operandos son dos registros de 32 bits, por lo que hay dos opciones disponibles (opcodes 0x31 y 0x33): El r / m32 significa que el operando puede ser un registro o la dirección de una región de memoria de 32 bits. Con dos operandos de registro, ambas codificaciones son igualmente válidas, ambas tienen la misma longitud (2 bytes) y ninguna es canónica, por lo que la decisión es totalmente arbitraria. Vamos a elegir el primero, opcode 0x31, ya que aparece en primer lugar. El / r después del código de operación significa que el operando de registro único (r32 en ambos casos) se especificará en el byte ModR / M. Este es el byte que sigue inmediatamente al opcode y especifica uno de dos de los operandos. El byte ModR / M se divide en tres partes: mod (2 bits), reg (3 bits), r / m (3 bits). Esto se pone un poco complicado, pero si se mira a la Tabla 2-1 en el manual de Intel durante un tiempo suficiente, finalmente tiene sentido. En resumen, dos bits de alta (11) para mod indica estaban trabajando con un registro en lugar de una carga. Heres donde estaban en para ModR / M: El orden de los registros x86 no es intuitivo: ax. Cx Dx Bx Sp. Pb si. Di Con 0-indexing, que da di un valor de 7 (111 en binario). Con edi como ambos operandos, esto hace ModR / M: Or, en hexadecimal, FF. Y eso es todo por esta instrucción. Con el opcode (0x31) y el byte ModR / M (0xFF): La codificación para mov es un poco diferente. Busque y haga coincidir los operandos. Al igual que antes, hay dos opciones posibles: En la notación B8rd significa que el operando de registro de 32 bits (rd para palabra doble de registro) se agrega al opcode en lugar de tener un byte ModR / M. Su seguida por un valor inmediato de 32 bits (id para la doble palabra entera). Eso es un total de 5 bytes. El segundo medio 0 entra en el campo reg de ModR / M, y la instrucción completa es seguida por el 32-bit inmediato (id). Eso es un total de 6 bytes. Puesto que esto es más largo, utilice bien la primera codificación. Por lo tanto, es opcode 0xB8 0. ya que eax es el número de registro 0, seguido por 60 (0x3C) como un pequeño endian, valor de 4 bytes. Heres la codificación para la segunda instrucción: La instrucción final es un cakewalk. No hay operandos, viene en una sola forma de dos operandos. Así que la codificación de esta instrucción es: Poner todo junto el programa es de 9 bytes: Arent te alegra que normalmente no tienen que montar programas enteros a mano Construir el ELF En los viejos tiempos es posible que haya sido capaz de simplemente dejar caer estos bytes en Un archivo y ejecutarlo. Eso es cómo DOS COM programas funcionaron. Pero esto definitivamente no funcionará si lo probaste en Linux. Los binarios deben estar en el formato ejecutable y de enlace (ELF). Este formato le indica al cargador cómo inicializar el programa en la memoria y cómo iniciarlo. Afortunadamente para este programa bien sólo es necesario rellenar dos estructuras: la cabecera ELF y una cabecera del programa. El binario será el encabezado ELF, seguido inmediatamente por el encabezado del programa, seguido inmediatamente por el programa. Para llenar este binario, utilice cualquier método que el virus deje atrás para escribir bytes sin formato en un archivo. Por ahora, supongo que el comando echo sigue estando disponible, y usa hexadecimal xNN escapes para escribir bytes crudos. Si esto no está disponible, es posible que tenga que utilizar la aguja magnética y el método de mano constante, o las mariposas. La primera estructura en un archivo ELF debe ser el encabezado ELF de la especificación ELF (1): Ningún otro dato está en una ubicación fija porque este encabezado especifica dónde se puede encontrar. Si está escribiendo un programa C en el futuro, una vez que los compiladores hayan sido reiniciados, podrá acceder a esta estructura en elf. h. El encabezado ELF La macro EINIDENT es 16, por lo que eident es de 16 bytes. Los primeros 4 bytes son fijos: 0x7F, E, L, F. El quinto byte se denomina EICLASS. Un programa de 32 bits (ELFCLASS32 1) o un programa de 64 bits (ELFCLASS64 2). Este será un programa de 64 bits (2). El 6º byte indica el formato de número entero (EIDATA). El que queremos para x86-64 es ELFDATA2LSB (1), dos complementos, little-endian. El 7mo byte es la versión ELF (EIVERSION), siempre 1 a partir de esta escritura. El octavo byte es el ABI (ELFOSABI), que en este caso es ELFOSABISYSV (0). El 9no byte es la versión (EIABIVERSION), que es apenas 0 otra vez. El resto es relleno cero. Así que escribir el encabezado ELF: El siguiente campo es el etype. Este es un programa ejecutable, por lo que su ETEXEC (2). Otras opciones son archivos de objeto (ETREL 1), bibliotecas compartidas (ETDYN 3) y archivos de núcleo (ETCORE 4). El valor para emachine es EMX8664 (0x3E). Este valor no está en la especificación ELF, sino en el documento ABI (4.1.1). En BSD esto se llama EMAMD64. Para eversión su siempre 1, como en el encabezado. El campo de eentry será de 8 bytes porque se trata de un ELF de 64 bits. Esta es la dirección virtual del punto de entrada de los programas. Es donde el cargador pasará el control y por lo tanto su donde bien cargar el programa. La dirección de entrada típica está alrededor de 0x400000. Por una razón que mal explicaré en breve, nuestro punto de entrada será de 120 bytes (0x78) después de ese bonito número redondo, en 0x40000078. El campo ephoff contiene el desplazamiento de la tabla de cabecera del programa. El encabezado ELF es 64 bytes (0x40) y esta estructura seguirá inmediatamente. También tiene 8 bytes. El encabezado eshoff contiene el desplazamiento de la tabla de sección. En un programa ejecutable no necesitamos secciones, así que esto es cero. El campo eflags tiene banderas específicas del procesador, que en nuestro caso es sólo 0. El eehsize contiene el tamaño de la cabecera ELF, que, como he dicho, es de 64 bytes (0x40). El ephentsize es el tamaño de una cabecera de programa, que es 56 bytes (0x38). El campo ephnum indica cuántos encabezados de programa hay. Sólo necesitamos el: el segmento con los 9 bytes del programa, para ser cargado en la memoria. El eshentsize es el tamaño de un encabezado de sección. No estaban usando esto, pero bien hacer nuestra diligencia debida. Estos son 64 bytes (0x40). El campo eshnum es el número de secciones (0). El eshstrndx es el índice de la sección con la tabla de cadenas. No existe, por lo que es 0. El encabezado del programa Siguiente es nuestro encabezado del programa. El campo ptype indica el tipo de segmento. Este segmento mantendrá el programa y se cargará en la memoria, por lo que queremos PTLOAD (1). Otros tipos de segmentos establecen carga dinámica y tal. El campo pflags proporciona las protecciones de memoria. Queremos ejecutable (PFX 1) y legible (PFR 4). Estos son ORed juntos para hacer 5. El poffset es el archivo de desplazamiento para el contenido de este segmento. Este será el programa que reunimos. Seguirá inmediatamente este encabezado. El encabezado ELF era de 64 bytes, más un encabezado de programa de 56 bytes, que es 120 (0x78). El pvaddr es la dirección virtual donde se cargará este segmento. Este es el punto de entrada de antes. Una restricción es que este valor debe ser congruente con poffset modulo el tamaño de la página. Es por eso que el punto de entrada fue compensado por 120 bytes. El ppaddr no se utiliza para esta plataforma. El pfilesz es el tamaño del segmento en el archivo: 9 bytes. El pmemsz es el tamaño del segmento en la memoria, también 9 bytes. Puede sonar redundante, pero se les permite diferir, en cuyo caso su bien truncado o rellenado con ceros. El palint indica la alineación de los segmentos. No nos importa la alineación. Agregue el programa Finalmente, agregue el programa que montamos al principio.

Comments