Etiquetas

C (31) Cpp (28) Linux (14) asm (8) Telegram (5) bot (5) libreria (5) Algoritmo (3) Errores comunes (3) python (3) Opengl (2) kali (2) Android (1) Snippet (1) nano (1) recursividad (1)

martes, 11 de marzo de 2025

Intro al Reversing

Ingeniería Inversa Definición por pancake aka trufae
 La ingeniería inversa sirve para obtener información de un producto, cómo ha sido construido o cómo funciona por dentro. 
 El objetivo de la ingeniería inversa es obtener información o un diseño a partir de un producto accesible al público, con el fin de determinar de qué está hecho, qué lo hace funcionar y cómo fue fabricado. 
Hoy en día los productos más comúnmente sometidos a ingeniería inversa son los programas. Sirve para: 
 Clasificar Malware 
Liberar drivers/hardware 
Analizar vulnerabilidades 
Hacker crackmes/ctf (Capture the Flag) 
Contabilidad con software Espionaje industrial 
 ¿Qué tipos de ejecutables existen? 
¿Todos se depuran de la misma forma? 
¿Por qué los ejecutables tienen un encabezado? 
¿Qué información hay en ese encabezado (header en inglés)? 
¿Cómo se analiza un virus y qué modifica? 
¿Cómo se modifica un programa? 
¿Cómo puedo depurar mi programa para buscar errores? 
Una vez que he aprendido a depurar, ¿cómo puedo mejorar la protección de mi software? Del Código Máquina al Lenguaje Ensamblador Cuando nos encontramos frente a una variante de algún código malicioso en un sistema y nos disponemos a analizarlo tenemos el archivo binario y debemos utilizar un desensamblador para generar el código en assembler con el objetivo de analizarlo. 
Ensamblador (assembler) es en realidad una clase de lenguaje de programación. Cada variante de ensamblador corresponde a una familia particular de microprocesadores tales como x86, x64, SPARC, PowerPC, MIPS o ARM. Dentro de todas estas familias la más habitual dentro las arquitecturas de procesadores es la x86, aunque con el pasar de los años vemos más y más procesadores x64. 

 Data: La sección de datos de un programa hace referencia a una región específica de memoria. Contiene lo que se conoce como las variables estáticas que no cambian con la ejecución del programa. También en esta sección se encuentran las variables globales, que están disponibles desde cualquier parte del programa. 

Code: En esta región de memoria se almacena el código que se ejecuta del programa donde se alojan todas las instrucciones que se van a ejecutar. 

Heap: El heap es una región de memoria que se utiliza para alocar nuevos valores durante la ejecución del programa como así también para eliminarlos una vez que se dejaron de utilizar. El heap es una memoría dinámica y su contenido varía a medida que se ejecuta el programa 

Stack (Pila): La pila se utiliza para alojar las variables locales, parámetros y valores de retorno de una función como así también contiene las direcciones de retorno entre una llamada a una función y otra, siendo muy útil para controlar el flujo de ejecución del programa. Cualquier software se ejecuta de forma secuencial, es decir una instrucción detrás de otra, la siguiente instrucción a ejecutar se almacena en un registro llamado IP, mediante programación se puede desviar esa ejecución hacia funciones que realicen tareas concretas, el programa en un principio ejecutará el flujo normal hasta que llega a una llamada a una función, en ese momento guarda en RAM el registro IP, ejecuta la función y retorna al flujo principal del programa porque fué capaz de leer el valor almacenado en RAM del registro IP.
Registros
Un registro es, de forma simplificada, un pequeño almacén en la CPU y es, evidentemente, la forma más rápida que tiene la CPU de acceder a datos.
En la arquitectura x86 de Intel existen ocho registros de propósito general: EAX, EDX, ECX, ESI, EDI, EBP, ESP y EBX (cambiar E por R en arquitecturas de 64 bits).
Son utilizados para facilitar la tarea en el procesado de las instrucciones, cómo para almacenar datos que se utilizaran posteriormente por las mismas.
El registro EAX (RAX)
El registro EDX (RDX)
El registro ECX (RCX)
Los registros ESI (XSI) y EDI (RDI)
Los registros ESP (RSP) y EBP (RBP)
El registro EBX (RBX)
Registro especial EIP (RIP)
Estos son alguno de los registros básicos que existen:
-EAX (Accumulator register): Utilizado tanto para realizar cálculos, cómo para el almacenamiento de valores de retorno en "calls".
-EDX (Data register): Extensión de EAX, utilizada para el almacenamiento de datos en cálculos más complejos.
-ECX (Count register): Utilizado en funciones que necesiten de contadores, como por ejemplo bucles.
-EBX (Base register): Se suele utilizar para apuntar a datos situados en la memoria.
-ESI (Source index): Utilizado para la lectura de datos.
-EDI (Destination index): Utilizado para la escritura de datos.
-ESP (Stack pointer): Apunta a la cima de la pila “stack”.
-EBP (Base pointer: Apunta a la base de la pila “stack”.

Instrucciones -Son acciones predefinidas en el lenguaje ensamblador. Algunas de las más habituales de ver son:
-PUSH: Guarda el valor en la pila.
-POP: Recupera valor de la pila.
-MOV (dst, src): Copia el operador “src” en el operador “dst”.
-LEA (reg, src): Copia una dirección de memoria en el registro destino (ej: EAX).
-ADD (o1, o2): Suma los dos operadores y los almacena en el operador uno.
-SUB (o1, o2): Resta el valor del segundo operador sobre el primero y lo almacena en el primer operador.
-INC: Incrementa en 1 el valor del operador indicado.
-DEC: Decrementa en 1 el valor del operador indicado.
-AND: El resultado es 1 si los dos operadores son iguales, y 0 en cualquier otro caso.
-OR: El resultado es 1 si uno o los dos operadores es 1, y 0 en cualquier otro caso.
-CMP: Compara dos operadores.
-JMP Salta a la dirección indicada.
-CALL: Llama/Salta a la dirección/función indicada.
-NOP: Not Operation.

Estructura de la pila (Stack)
La pila es una estructura FILO (First In, Last Out) donde los argumentos son apilados en la cima de la misma cuando se invoca una función y retirados cuando la función finaliza. El registro ESP se usa para seguir la pista a la parte más alta del marco de la pila y el registro EBP para seguirle la pista a la parte baja (aunque ya vimos que ciertos compiladores pueden decidir no usar ebp para eso). 
 La PILA o Stack es un conjunto de direcciones de memoria encargadas de almacena información de llamadas a funciones, variables locales, direcciones de retorno a funciones anteriores, entre otras tareas. La PILA es dinámica, por lo que va cambiando su tamaño dependiendo de la función a la cual se encuentre asociada, dispone de una estructura “First In, Last Out”, por lo que lo último que entra será lo primero en salir, delimitada siempre por su cima (ESP) y por su base (EBP).
Puntos de Interrupción (Breakpoints)
La habilidad de parar un proceso que está siendo depurado se consigue mediante el fijado de puntos de interrupción o breakpoints. Al parar el proceso podemos inspeccionar el valor de las variables, los argumentos de la pila, y las direcciones de memoria sin que el proceso modifique estos valores hasta que no se lo indicas de esa forma al depurador. Los puntos de interrupción son la herramienta principal proporcionada por los depuradores. Los puntos de interrupción le permiten interrumpir la ejecución del programa en un lugar específico. Hay dos tipos de puntos de interrupción:
Puntos de interrupción de software
Puntos de interrupción de hardware
Es muy difícil realizar ingeniería inversa de software sin puntos de interrupción. Las tácticas populares de ingeniería anti-reversa se basan en la detección de puntos de interrupción, proporcionando una serie de métodos anti-depuración correspondientes.
Cómo omitir una verificación de punto de interrupción de software
No existe un enfoque universal para evitar una verificación de punto de interrupción de software. Para evitar esta protección, debes encontrar el código que calcula la suma de verificación y sustituir el valor devuelto con una constante, así como los valores de todas las variables que almacenan sumas de verificación de funciones.
Puntos de interrupción de hardware
DR0-DR3 – breakpoint registers -“Debug Address Registers” or “Address-Breakpoint Registers”
DR4 - DR5 – reserved - “Reserved Debug Registers”
DR6 – debug status - "Debug Status Register”
DR7 – debug control -“Debug Control Register”

SEH (Structured Exception Handling)
El manejo estructurado de excepciones es un mecanismo proporcionado por el sistema operativo a una aplicación que le permite recibir notificaciones sobre situaciones excepcionales como la división por cero, la referencia a un puntero inexistente o la ejecución de una instrucción restringida. Este mecanismo le permite manejar excepciones dentro de una aplicación, sin la participación del sistema operativo. Si no se maneja una excepción, dará como resultado la finalización anormal del programa. Los desarrolladores suelen ubicar punteros a SEH en la pila, que se denominan marcos SEH. La dirección de trama SEH actual se encuentra en el desplazamiento 0 en relación con el selector FS (o el selector GS para los sistemas x64).
VEH (Vectored Exception Handler)
VEH se introdujo en Windows XP y es una variación de SEH. VEH y SEH no dependen el uno del otro y funcionan simultáneamente. Cuando se agrega un nuevo "manejador" VEH, la cadena SEH no se ve afectada ya que la lista de manejadores VEH se almacena en la variable no exportada ntdll! LdrpVectorHandlerList. Los mecanismos VEH y SEH son bastante similares, la única diferencia es que las funciones documentadas se utilizan para configurar y eliminar un controlador VEH.
Exploiting sobre Linux-x86
La porción de RAM utilizada por un programa cuando se llama a una función es comunmente llamada pila o stack, debemos tener en cuenta que la pila crece de arriba a abajo, como podemos ver hay una parte de la ram que almacena la copia del registro IP(EIP) en el momento de realizar la llamada a la función, en el programa no se tiene en cuenta que el dato introducido sea de una longitud concreta, se realiza la copia a ciegas a la variable nombre, aprovechando este descuido podemos hacer crecer la variable nombre hasta llegar a ocupar la dirección de retorno EIP haciendo así que el software termine retornando a otra posición de memoria y no la que se guardó al realizar la llamada a la función func.
¿Qué es una Shellcode?
Una shellcode no es mas que el conjunto de opcodes (instrucciones en hexadecimal) que ejecutará el procesador para realizar un acción en concreto, las shellcodes suelen estar escritas en ensamblador ya que nos permite un control total sobre el proceso de ejecución además de un tamaño inferior de la shellcode.

Definición Buffer
Un espacio de memoria de cierto tamaño que se reserva para guardar datos, y manejar los mismos.
Un ejemplo básico es una lata de 20 litros que tengo vacía para guardar allí un contenido, el mismo podrá ser menor o igual a 20 litros, el cual es el tamaño máximo que puedo guardar en este buffer de 20 litros, si quisiera guardar más en un solo depósito debería buscar la forma de tener un buffer más grande, sino, al tratar de guardar por ejemplo 40 litros en una lata de 20 litros se desbordaría. Un buffer overflow ocurre cuando un programa informático excede el uso de cantidad de memoria reservado para ello , escribiendo en el bloque de memoria contiguo.
En verdad, un buffer overflow se produce en una aplicación informática cuando no cuenta con los chequeos de seguridad necesarios en su código de programación, como por ejemplo medir la cantidad de datos que se copiara a un buffer y que no exceda el tamaño del mismo.
Los tipos más comunes de buffer overflows son los stack buffer overflows y los heap buffer overflows. Bueno aqui vemos la definición de buffer overflow, y en nuestro ejemplo anterior si trato de guardar 40 litros en una lata de 20 litros se desbordara como vimos, ese desborde que se produce es el buffer overflow, o sea el desbordamiento de mi depósito al sobrepasar la máxima capacidad del mismo.
Stack y heap overflow
Ahora una idea acerca de la diferencia entre stack y heap: STACK : El stack se utiliza para guardar las variables locales de una función que sólo necesitan durar tanto como la ejecución de la función. En la mayoría de los lenguajes de programación es fundamental que sepamos en tiempo de compilación qué tan grande es una variable si queremos almacenarla en el stack. HEAP: El heap se utiliza para reservar memoria dinámica, cuya vida útil no se sabe muy bien por adelantado, pero se espera que duren un tiempo. Si no sabemos su tamaño o el mismo se decide en tiempo de ejecución se deberá calcular y reservar en el heap.
Stack Buffer Overflow
In software, a stack buffer overflow occurs when a program writes to a memory address on the program's call stack outside of the intended data structure; usually a fixed length buffer.
Es decir, la vulnerabilidad Stack Buffer Overflow ocurre cuando una aplicación no controla correctamente el número de bytes que son almacenados en una dirección de memoria previamente reservada, de forma que la cantidad de bytes que se van a almacenar son superiores a los reservados. Nuestro principal objetivo es llegar a sobrescribir la dirección de retorno (almacenada en la PILA) con un valor que apunte a nuestra shellcode.
-Registro EIP (Extended Instruction Pointer): Este registro apunta a la siguiente dirección de memoria que el procesador va a ejecutar.
-Instrucción RETN: Es la instrucción encargada de recoger el valor de ESP y almacenarlo en el registro EIP, de este modo el valor de ESP será la próxima dirección de memoria que el procesador va a ejecutar.
-Dirección de retorno: Es el valor exacto que nos indica la dirección de memoria donde habíamos dejado la aplicación ante de entrar en una subfunción, para así cuando esta termine volver a la posición exacta donde nos quedamos. Este valor se encontrará almacenado en la siguiente dirección de memoria del EBP de la subfunción.

Anti-sandboxing y Anti-debugging
Debuggers como OllyDbg, WindDbg, Radare o IDA ya sabréis que se requieren conocimientos sobre lenguaje ensamblador y estructuras internas de ficheros.
Una técnica anti-debugging muy fácil de implementar, pero también muy fácil de evadir, es la función “IsDebuggerPresent”.
PEB (Process Environment Block)
Los códigos de espagueti y basura hacen que las herramientas de análisis comunes sean ineficaces
El primer problema de ofuscación que requiere una solución es la eliminación de las instrucciones basura y el "código de espagueti", que es una técnica que pretende confundir los programas de desamblado El código de espagueti hace que el programa fluya de forma difícil de leer al agregar saltos continuos de código, de ahí el nombre.
Este problema no es nuevo, y en situaciones comunes se conocen complementos de reversing que pueden ayudar en esta tarea. Sin embargo, a veces no podemos encontrar un buen complemento de desensamblador interactivo (IDA) que pueda normalizar el flujo de código.

VM detection
hardware IDs are VmBus in case of HyperV VEN_15AD in case of VMware Debugger detection IsDebuggerPresent ThreadHideFromDebugger ProcessDebugPort ProcessDebugObjectHandle ProcessDebugFlags CheckRemoteDebuggerPresent NtQueryInformationProcess ProcessBasicInformation Fuente:elhacker.net

miércoles, 5 de junio de 2024

No encontrar las librerías .h en linux

Las librerías que usa gcc y g++ (y creo que clang) están en /usr/include/ ...a veces el comando locate no encuentra las librerías.

viernes, 31 de mayo de 2024

Los 11 fallos más comunes en Lenguaje C

(1)Mezclar enteros signed y unsigned.
(2)Sobrepasando límites de un arreglo.
(3)Perdiendo la condición base de una funcion recursiva.
(4)Usando constantes de caracteres en vez de literales de cadena y viceversa.
(5)Literales de tipo float son por defecto de tipo double.
(6)Olvidando liberar la memoria.
(7)Añadiendo un semicolon a #define.
(8)No ser cuidadoso con el semicolon.
(9)Erroneamente escribir = o ==.
(10)Copiando demasiado.
(11)Las macros son solo reeemplazo de cadenas.
1.- Mezclar enteros signed y unsigned en operaciones aritmeticas
#include stdio.h
int main(void){
unsigned int a = 1000;signed int b = -1;
if (a > b) puts("a is more than b");
else puts("a is less or equal than b");
return 0;}
Como 1000 es mayor que -1 uno esperaría que a es mayor que b. Antes de hacer la comparación, b es convertida a unsigned int. Cuando es convertido a unsigned int toma el valor máximo de unsigned int... el cual es mayor que 1000. Por esto se puede apreciar que a > b es una falso.
2.- Sobrepasando los límites de un arreglo.

Los arreglos siempre empiezan en 0 y terminan en la longitud del arreglo - 1.
#include stdio.h
int main(){
int x = 0;int myArray[5] = {1,2,3,4,5};
for(x=1; x<=5; x++){printf("%d\t",myArray[x]);}
printf("\n");return 0;} //Output: 2 3 4 5 GarbageValue
La forma correcta:
#include stdio.h
int main(){
int x = 0;int myArray[5] = {1,2,3,4,5};
for(x=0; x<5; x++){printf("%d\t",myArray[x]);}
printf("\n");return 0;} //Output: 1 2 3 4 5
Entonces, hay que conocer el límite de nuestros arreglos porque osino podemos corromper el buffer o provocar un fallo de segmentacion por acceder a un área de memoria distinta.
3.- Perdiendo la condición base en una función recursiva.
Calcular la factorización de un número es un ejemplo clásico de recursividad:
#include stdio.h
int factorial(int n){return n * factorial(n - 1);}

int main(){
printf("Factorial %d = %d\n", 3, factorial(3));return 0;}
//Typical output: Segmentation fault
El problema con esta función es que va a estar en un ciclo infinito, lo que causará fallo de segmentación. Necesita una condición base para detener la recursividad. La forma correcta:
#include stdio.h
int factorial(int n){
// Base Condition, very crucial in designing the recursive functions.
if (n == 1){return 1;}else{return n * factorial(n - 1);}}

int main(){
printf("Factorial %d = %d\n", 3, factorial(3));return 0;}
//Esta función va a terminar tan pronto alcance 1.
//Output : Factorial 3 = 6
Reglas a seguir:
1 Iniciar el algoritmo.
Las funciones recursivas necesitan con frecuencia un valor inicial con el que empezar. Esto es acompañado sea por un parametro de la función o una función puerta que no es recursiva pero pone los valores iniciales para la recursión.

2 Revisar para ver si los valores actuales que se están procesando coinciden con el caso base.Si es asi, entonces procesa y devuelve un valor


3 Redefine la respuesta en terminos de un problema pequeño o simple subproblema o subproblemas.

4 Ejecuta el algoritmo en un subproblema.
5 Combina los resultados en la formulación de la respuesta.
6 Retorna los resultados.

4.- Usando constantes de caracteres en vez de literales de cadena y viceversa.

En lenguaje C, las cadenas de caracteres y literales de cadena son cosas distintas. 'a' ..esto es una cadena de caracter. Una cadena de caracter es de tipo entero que tiene asignado un número para ese caracter. "asdf"... es un literal de caracteres. Un literal de caracteres un arreglo inmodificable cuyos elementos son de tipo char. "asdf" tiene 5 caracteres, porque el caracter final es un \0. Este caracter final es conocido como carácter nulo. {a,s,d,f,\0}.
//ejemplo 1: una cadena de caracteres es usada donde debería ir una literal de cadena. Esto da un comportamiento indefinido.
#include stdio.h
int main(void){
const char *hello = 'hello, world'; /* bad */puts(hello);return 0;}
//ejemplo 2:
un literal de cadena se usa donde se debería usar una cadena de caracteres. El resultado es una cosa sin sentido.
#include stdio.h
int main(void) { char c = "a"; /* bad */ printf("%c\n", c); return 0;}
En ambos casos el compilador se va a quejar de la mezcla. Si no pasa esto, necesita usar más advertencias para la compilación, o derechamente usar un mejor compilador.

5.- Literales de tipo float son por defecto de tipo double.

Hay que tener cuidado al inicializar una variable float a valores literales o compararlas con estos. Esto es debido a que literales float de valor 0.1 por lo regular son de tipo double. Este tipo de cosas nos puede conducir a sorpresas:

#include stdio.h

int main(){
float n = 0.1; if (n > 0.1) printf("Wierd\n");return 0;}
// Prints "Wierd" when n is float
n fue inicializada y redondeada por precisión, resultando en 0.10000000149011612. Entonces, n es vuelto a convertir en double para ser comparado con el valor literal 0.1 (lo cual es igual a 0.10000000000000001) lo que da una discordancia. Mezclar variables float con literales dobles puede resultar en pobre desempeño en plataformas donde no hay soporte de hardware para doble precisión.

6.- Olvidando liberar la memoria.

Uno siempre debe recordar liberar la memoria alojada, sea una función hecha por tí o por una función de librería llamada por tu función.
#include stdlib.h #include stdio.h

int main(void){
char *line = NULL;size_t size = 0;
/* memory implicitly allocated in getline */
getline(&line, &size, stdin);
/* uncomment the line below to correct the code */
/* free(line); */return 0;}
Es un error inocente en este ejemplo específico,porque cuando un proceso termina,la mayoría de los sistemas operativos libera la memoria alojada en vez de tí.
7.- Añadiendo un semicolon a #define

Muchas veces me pasó a mí!!! Es fácil confundirse con el preprocesador de C, y tratarlo como parte del lenguaje. Pero es un error, porque el preprocesador es sólo un mecanismo de reemplazo de texto.
// WRONG
#define MAX 100; int arr[MAX]={0};
//lo cual se traduce como
int arr[100]={0}; //Que se traduce como error de sintaxis.

8.- Ser cuidadoso con el semicolon
//Esto: if (x > a); a = x;
//Significa esto:
if (x > a) {} a = x;
Aveces, perder un semicolon puede provocar problemas inesperados:
if (i < 0) return day = date[0]; hour = date[1]; minute = date[2];
/*El semicolon antes de return está perdido, por lo que day =date[0]; va a ser regresado. El compilador lee hasta que encuentra ; como el fin de línea.*/

9.- Erroneamente escribir = en vez de ==.
El = es para asignar.
El == es para COMPARAR.
A veces hacemos:
/* assign y to x */ if (x = y) {/* logic */}
//cuando lo que uno queria era:
/* compare if x is equal to y */ if (x == y) {/* logic */}

//lo cual es equivalente a:
/* compare if x is equal to y */ if (x == y) != 0{/* logic */}

10.- Copiando demasiado.
char buf[8]={0}; /* tiny buffer, easy to overflow */
printf("What is your name?\n");
scanf("%s", buf); /* WRONG */
scanf("%7s", buf); /* RIGHT */
Si uno pone más caracteres que los requeridos por scanf, van a empezar a sobreescribirse zonas de memorias aledañas al buffer. Esto puede derivar en comportamiento indefinido. Los hackers maliciosos con frecuencia usan esto para sobreeescribir la dirección de return, y cambiar la dirección por la del código malicioso creado por este.
11.- Las Macros son solo reemplazo de cadenas
#include stdio.h
#define SQUARE(x) x*x
//este es el error.
int main(void){
printf("%d\n", SQUARE(1+2));return 0;}
Esperarías que este código devolviese 9, pero devolverá 5 porque la macro será expandida a 1+2*1+2. Para evadir este problema debe encerrar entre () las fichas para evadir este problema.
#include stdio.h
#define SQUARE(x) ((x)*(x))
int main(void){
printf("%d\n", SQUARE(1+2));return 0;}

TextView and EditText en Android

crear un archivo .xml con esto: LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> /LinearLayout> ........Encerrar LinearLayout entre <> (no me deja poner el código xml) y escribir esto en un archivo .xml: LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> /LinearLayout> TextView and EditText in Android

Diferencias entre const int y int const

Una respuesta simple: leer hacia atrás. Los punteros como sabréis son variables especiales que apuntan a otras variables, y las constantes son variables que no cambian durante la ejecución de un programa. int * ptr = ptr es un puntero hacia int. int const * ptr = ptr es un puntero hacia constante int. int * const ptr = ptr es un puntero constante hacia int. const int * const ptr = ptr es una constante puntero a una constante int. const int * ptr es igual a int const * ptr const int * const ptr es igual a int const * const ptr véase que lo que cambia es lo que esta antes de *ptr o *const ptr. int ** ptr = is a pointer to pointer(p2p) to int. Un puntero que apunta hacia otro puntero. int ** const ptr = ptr es un puntero constante de un puntero hacia int. int * const * ptr = ptr es un puntero a una constante puntero hacia int. int const **ptr = ptr es un puntero de un puntero hacia una constante int. int * const * const ptr = ptr es un puntero constante de otro puntero constante hacia int. ¿Como saber si const se refiere al puntero o lo apuntado? Si está del lado derecho del asterico, entonces se refiere a lo apuntado. Si está del lado izquierdo del asterico, del puntero. Fuente: What is the difference between const int and int const.

miércoles, 22 de mayo de 2024

Colores c.nanorc

## Syntax highlighting for C and C++ files. syntax c "\.([ch](pp|xx)?|C|cc|c\+\+|cu|H|hh|ii?)$" header "-\*-.*\" # Labels. color brightmagenta "^[[:blank:]]*[A-Z_a-z][0-9A-Z_a-z]*:[[:blank:]]*$" color normal ":[[:blank:]]*$" # Types and related keywords. color green "\<(auto|bool|char|const|double|enum|extern|float|inline|int|long|restrict|short|signed|sizeof|static|struct|typedef|union|unsigned|void)\>" color green "\<([[:lower:]][[:lower:]_]*|(u_?)?int(8|16|32|64))_t\>" color green "\<(_(Alignas|Alignof|Atomic|Bool|Complex|Generic|Imaginary|Noreturn|Static_assert|Thread_local))\>" color green "\<(class|explicit|friend|mutable|namespace|override|private|protected|public|register|template|this|typename|using|virtual|volatile)\>" # Flow control. color brightyellow "\<(if|else|for|while|do|switch|case|default)\>" color brightyellow "\<(try|throw|catch|operator|new|delete)\>" color magenta "\<(break|continue|goto|return)\>" # Single-quoted stuff (characters, backslash escapes, hex and octal byte codes). color brightmagenta "'([^'\]|\\(["'\abfnrtv]|x[[:xdigit:]]{1,2}|[0-3]?[0-7]{1,2}))'" # GCC builtins. color cyan "__attribute__[[:blank:]]*\(\([^)]*\)\)|__(aligned|asm|builtin|hidden|inline|packed|restrict|section|typeof|weak)__" # Strings and names of included files. color brightyellow ""([^"]|\\")*"|#[[:blank:]]*include[[:blank:]]*<[^>]+>" # Preprocessor directives. color brightcyan start="^[[:blank:]]*#[[:blank:]]*(if(n?def)?|elif|warning|error|pragma)\>" end="(\`|[^\])$" color brightcyan "^[[:blank:]]*#[[:blank:]]*((define|else|endif|include(_next)?|line|undef)\>|$)" # Comments. color brightblue "//.*" color brightblue start="/\*" end="\*/" # Reminders. color brightwhite,yellow "\<(FIXME|TODO|XXX)\>" # Trailing whitespace. color ,green "[[:space:]]+$"

viernes, 12 de abril de 2024

ELF Basics Internal: Elf Basics.

/*Este material no lo hice yo. Source: oxhat.blogspot.com*/ In this post I will share details on ELF binary basics.

So let is begin with a very simple hello world program in C

#include stdio.h

int main(){printf("\nHello World\n");return 0;}

As I am on a 64 bit Linux system I will compile the binary for both 32bit and 64bit mode.

We will compile this code with gcc by issuing the command
for 64 bit -> gcc hello.c -o hello64
for 32 bit -> gcc hello.c -m32 -o hello32

, ( in case we get error we can install gcc multilib by issuing command sudo apt-get install gcc-multilib ) If we issue file command on the binary we created we would see the following output

pentest@ubuntu:~/Desktop$ file hello64 hello64: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=797fa6ea8a92b773eb5106c822a76788441ceac1, not stripped
pentest@ubuntu:~/Desktop$ file hello32 hello32: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=ba188ad09ee9ff9ac774833b8a7c87d8afbc443a, not stripped

So let us try to understand what all these mean (we will analyze the result of 64 bit binary)
hello64: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=797fa6ea8a92b773eb5106c822a76788441ceac1, not stripped

hello64: This is the filename of the binary on which we are executing the file command
ELF - Executable and Linking Format or Executable and Linkable Format - This mean that the binary type is associated with mainly UNIX type operating system , like Linux, Solaris but also supports certain non UNIX operating system.
64bit - This gives us information tells about the architecture of the binary that it is 64 bit. So if it is a 32 bit binary it will be shown as 32 If we are in a 64bit machine and if we want to create a 32 bit binary we can pass the -m32 option
LSB - Least Significant Byte - It means the binary is in little endian format. In Intel architecture you will often find this as LSB. However in architectures like PowerPC , SPARC and so on it is possible to get this in big endian format i.e MSB ( Most Significant Byte )
Shared object - This result can either be Shared Object or Relocatable or Executable.
Let us see how these are different from each other and how we can generate them using gcc. The two terms which we are going to use here are PIC ( Position Independent Code ) and PIE ( Position Independent Executable) .
When we are planning to create a library that can be called by many process, we need to make it a PIC so that they can be loaded in the memory at any virtual address and just because they are position independent it can be accessed with relative offsets without worrying about the clashes of fixed locations in memory. We can create a PIE when Shared Object - By default the gcc compiler compiles the source code with -fPIC which makes address of the sections in the program relative to each other.
Executable - This mean this is not a PIE application. This loads with absolute address and thus we can find no reference of .plt.got sections here as the program is loaded in memory with fixed address .We can disable PIE with -no-pie option in gcc and thus we will get a executable object file.
Relocatable - This means this is just an object code without any linking of libraries or files that are necessary for the execution.
There are some steps involved when we make a program that can be executable ( Please Note: The term executable here means here is to make it run or execute and should not be confused with the above executable object type ).
To make an executable from source program the following process is involved.
Preprocessing -> Compilation -> Object File Creation -> Linking.

Normally in gcc we do in one step like gcc hello.c -o hello.out
but however we can do in 2 steps like gcc -c hello.c ; this will create an object file called hello.o This is how the disassembly of main looks like in object code.
0000000000000000 <main>:
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 48 8d 3d 00 00 00 00 lea rdi,[rip+0x0] # b <main+0xb>
b: e8 00 00 00 00 call 10 <main+0x10>
10: 90 nop
11: 5d pop rbp
12: c3 ret

This program cannot run or do anything because the object code doesn't have the necessary linked objects or libraries required for execution. We can generate an executable binary from object code using the command
gcc hello.o -o hello-executable Now if we run objdump on the binary we can see lots of sections getting created with the location to the linkers.
This is how the disassembly of main looks like after linking
000000000000063a <main>:
63a: 55 push rbp
63b: 48 89 e5 mov rbp,rsp
63e: 48 8d 3d 8f 00 00 00 lea rdi,[rip+0x8f] # 6d4
<_IO_stdin_used+0x4>
645: e8 c6 fe ff ff call 510
<puts@plt>
64a: 90 nop
64b: 5d pop rbp
64c: c3 ret
64d: 0f 1f 00 nop DWORD PTR [rax]

Nota: Lo marcado con fondo rojo va al final de la línea anterior.
version 1 (SYSV) - This means that it uses version 1 and the target operating system for the binary is SYSTEM V. There can be other possible values for this for example FreeBSD, HP-UX , etc,. I didn't get enough resource from where I can find more details on the version 1 result and how it can affect something.

Dynamically linked, interpreter /lib/ld-linux.so.2, - It means that the binary uses some dynamically linked libraries. There is 2 possible values possible for this.Dynamically linked and Statically Linked.

Dynamically Linked - It means the linker actually uses a reference to load dynamically linked libraries in memory during execution of the program from the location /lib/ld-linux.so.2
We can verify it by running the ldd on the binary
pentest@ubuntu:~/Desktop$ ldd hello64
linux-vdso.so.1 (0x00007fff96bc2000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f425eb2a000)
/lib64/ld-linux-x86-64.so.2 (0x00007f425f11d000)

Statically Linked - It means that the binary has been packed along with the libraries. So there is no dynamically linked libraries inside it. So if a binary is compiled with -shared option in gcc it will create a statically linked binary. So if we run ldd command on the binary it will tell that there is not a dynamic executable.
pentest@ubuntu:~/Desktop$ ldd helloStatic
not a dynamic executable
There is a huge difference in sizes of the binary when compiled with -shared option ( i.e statically )
-rwxrwxr-x 1 pentest pentest 8296 Feb 6 08:55 hello64
-rwxrwxr-x 1 pentest pentest 844704 Feb 7 09:28 helloStatic
At this point you might feel confused between the relocation of the binary that we discussed before and the linking which we are discussing now. Well when we talk about shared object or executable or relocatable object type, then we are actually dealing how the program will be loaded in memory but when we talk about linking, then it is all about how the external libraries will be linked to binaries - either dynamically via some shared resources or statically by packing it with the actual binary. So we can make this statement , an executable object type may have dynamic linked libraries. Than means even if we disable PIE we can still get an executable with dynamically linked libraries.
pentest@ubuntu:~/Desktop$ file helloNOPIE
helloNOPIE: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=2d986bca273f541af7a48ffb51f4d5fd22177c22, not stripped

pentest@ubuntu:~/Desktop$ ldd helloNOPIE
linux-vdso.so.1 (0x00007ffffe990000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcb5e8c0000) /lib64/ld-linux-x86-64.so.2 (0x00007fcb5ecb1000)
for GNU/Linux 3.2.0 - The minimum kernel version required to execute the binary BuildID[sha1]=ba188ad09ee9ff9ac774833b8a7c87d8afbc443a - This ID is assigned to the binary during the build phase. Possibly during the linking phase as this is not visible in object code after compilation.
not stripped - This means that the certain but not all debugging information are available. It can also have a value stripped in case we remove the debug symbols. A stripped binary is smaller in size than an not stripped binary. When we strip a binary we remove some extra sections from a binary that is not relevant or required for execution but was added for making debugging easy. We can verify using gdb and we can keep debug symbols in a binary by compiling with -g option
pentest@ubuntu:~/Desktop$ gcc hello.c -g -o helloDebugSymbols pentest@ubuntu:~/Desktop$ gdb -q ./helloDebugSymbols
Reading symbols from ./helloDebugSymbols...done.
(gdb) info functions
All defined functions: File hello.c:
void main();
Non-debugging symbols:
0x00000000000004e8 _init
0x0000000000000510 puts@plt
0x0000000000000520 __cxa_finalize@plt
0x0000000000000530 _start
0x0000000000000560 deregister_tm_clones
0x00000000000005a0 register_tm_clones
0x00000000000005f0 __do_global_dtors_aux
0x0000000000000630 frame_dummy
0x0000000000000650 __libc_csu_init
0x00000000000006c0 __libc_csu_fini
0x00000000000006c4 _fini
Now we will try the same with Stripped Binary and we can see that as there there is no debug symbols there is no reference to the function void main() as per source code. However there are still certain debug information available. For example I can find the address of main function.
pentest@ubuntu:~/Desktop$ gcc hello.c -o helloNoDebugSymbols pentest@ubuntu:~/Desktop$ gdb -q ./helloNoDebugSymbols
Reading symbols from ./helloNoDebugSymbols...(no debugging symbols found)...done.
(gdb) info functions
All defined functions:
Non-debugging symbols:
0x00000000000004e8 _init
0x0000000000000510 puts@plt
0x0000000000000520 __cxa_finalize@plt
0x0000000000000530 _start
0x0000000000000560 deregister_tm_clones
0x00000000000005a0 register_tm_clones
0x00000000000005f0 __do_global_dtors_aux
0x0000000000000630 frame_dummy
0x000000000000063a main
0x0000000000000650 __libc_csu_init
0x00000000000006c0 __libc_csu_fini
0x00000000000006c4 _fini
We can strip it down further using strip function
pentest@ubuntu:~/Desktop$ strip -s helloNoDebugSymbols -o helloNoDebugSymbolsStripped
pentest@ubuntu:~/Desktop$ gdb -q ./helloNoDebugSymbolsStripped
Reading symbols from ./helloNoDebugSymbolsStripped...(no debugging symbols found)...done.
(gdb) info functions
All defined functions:
Non-debugging symbols:
0x0000000000000510 puts@plt
0x0000000000000520 __cxa_finalize@plt
So that's all for this blog post. In my further posts I will talk in more details about each of the part of elf binary in more details