[C++ & Qt][Linux] Crear un builder

Iniciado por 0xDani, 17 Julio 2013, 16:27 PM

0 Miembros y 1 Visitante están viendo este tema.

0xDani

Bueno, tal y como me han pedido, aquí pongo el código de un server builder en Qt para Linux.

Lo primero, el código del server:

#include <iostream>
#include <iomanip>

static char ip[30] __asm__("__IP__") = "__IP__ here";
static char port[30] __asm__ ("__PORT__") = "__PORT__ here";

int main(int argc, char **argv)
{

std::cout << "ip address: " << std::hex << (void*) ip << std::endl;
std::cout << "port address: " << std::hex << (void*) port << std::endl;

std::cout << "IP: " << ip << std::endl;
std::cout << "Port: " << port << std::endl;


/* ... */

return 0;
}


Analicemos el código: tiene dos variables globales, para asegurarnos de que esas variables van a ser incluidas en la sección .data del ejecutable como símbolos. También nos aseguramos de que estos símbolos tengan el nombre que queramos ("__IP__" y "__PORT__") con la directiva de ensamblador.

Las cadenas que les asigno son para que se puedan ver en un editor hexadecimal.

Bien, ahora el builder, que se va encargar de que cuando ejecutemos el server, en esas dos variables estén la IP y el puerto.

mainwindow.hpp
Código (cpp) [Seleccionar]
#ifndef MAINWINDOW_HPP
#define MAINWINDOW_HPP

#include <QMainWindow>
#include <QWidget>
#include <QVBoxLayout>
#include <QLineEdit>
#include <QPushButton>

#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <elf.h>
#include <stdint.h>
#include <cstring>

#include <iostream>
#include <iomanip>

class MainWindow : public QMainWindow
{
   Q_OBJECT
   
public:
   MainWindow(QWidget *parent = 0);
   ~MainWindow();

public slots:
   void onButton();

private:
   QWidget     *centralWidget; // Central widget
   QVBoxLayout *layout;        // Window's layout
   QLineEdit   *ipEdit;        // Edit for the IP
   QLineEdit   *portEdit;      // Edit for the port
   QPushButton *button;        // Button to build the server
};

#endif // MAINWINDOW_HPP


mainwindow.cpp
Código (cpp) [Seleccionar]
#include "mainwindow.hpp"

MainWindow::MainWindow(QWidget *parent)
   : QMainWindow(parent)
{
   /* Constructor to build the GUI */

   centralWidget = new QWidget();
   layout = new QVBoxLayout();

   ipEdit = new QLineEdit();
   portEdit = new QLineEdit();

   button = new QPushButton("Build!");

   layout->addWidget(ipEdit);
   layout->addWidget(portEdit);
   layout->addWidget(button);

   centralWidget->setLayout(layout);
   setCentralWidget(centralWidget);

   connect(button, SIGNAL(clicked()), this, SLOT(onButton()));
}

MainWindow::~MainWindow()
{
   /* Delete reserved objects */

   delete ipEdit;
   delete portEdit;
   delete button;
   delete layout;
   delete centralWidget;
}

void MainWindow::onButton()
{
   int fdIn, fdOut; // Input and output files

   fdIn = open("server", O_RDONLY); // Open input file

   struct stat st;
   fstat(fdIn, &st); // Get input file size

   /* Map input file into memory */
   void *map = mmap(0, st.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fdIn, 0);

   Elf32_Ehdr *hdr = (Elf32_Ehdr*) map; /* Get the ELF header,
                                           that's at the start of the file*/

   /* Get pointer to the ELF sections */
   Elf32_Shdr *sections = (Elf32_Shdr*) ((char*) map + hdr->e_shoff);

   /* Get pointer to the .shstrtab section content */
   char *shStrTabData = (char*) map+sections[hdr->e_shstrndx].sh_offset;

   Elf32_Shdr tabs[2]; // First symtab, second strtab


   /* Find and save .symtab and .strtab sections */
   const char *sectionNames[] = {".symtab", ".strtab"};
   for(int i=0; i<2; i++)
   {
       for(int j=0; j<hdr->e_shnum; j++)
       {
           if(!strcmp(shStrTabData+sections[j].sh_name, sectionNames[i]))
           {
               tabs[i] = sections[j];
               break;
           }
       }
   }

   //std::cout << ".symtab at offset " << std::hex << tabs[0].sh_offset << std::endl;
   //std::cout << ".strtab at offset " << std::hex << tabs[1].sh_offset << std::endl;

   /* Get pointer to the .strtab section content */
   char *strTabData = (char*) map + tabs[1].sh_offset;

   /* Get pointer to the symbols in the .symtab section */
   Elf32_Sym *syms = (Elf32_Sym*) ((char*) map + tabs[0].sh_offset);

   Elf32_Sym toModify[2]; // First __IP__, second __PORT__

   const char *symbolNames[] = {"__IP__", "__PORT__"};


   // Find and save the symbols we want to modify
   for(int i=0; i<2; i++)
   {
       for(unsigned int j=0; j<(tabs[0].sh_size/sizeof(Elf32_Sym)); j++)
       {
           if(!strcmp(strTabData+syms[j].st_name, symbolNames[i]))
           {
               toModify[i] = syms[j];
           }
       }
   }

   //std::cout << "__IP__ value: " << std::hex << toModify[0].st_value << std::endl;
   //std::cout << "__PORT__ value: " << std::hex << toModify[1].st_value << std::endl;

   Elf32_Off ipOff, portOff;


   /* Find symbols offsets: get symbol offset into the section that
      contains the symbol: symbol_address - section_base_address; and then
      add the offset of the section into the file                       */
   ipOff = toModify[0].st_value - sections[toModify[0].st_shndx].sh_addr +
           sections[toModify[0].st_shndx].sh_offset;

   portOff = toModify[1].st_value - sections[toModify[1].st_shndx].sh_addr +
           sections[toModify[1].st_shndx].sh_offset;

   //std::cout << "__IP__ section: " << shStrTabData+sections[toModify[0].st_shndx].sh_name << std::endl;
   //std::cout << "__PORT__ section: " << shStrTabData+sections[toModify[1].st_shndx].sh_name << std::endl;


   //std::cout << "__IP__ offset: " << std::hex << ipOff << std::endl;
   //std::cout << "__PORT__ offset: " << std::hex << portOff << std::endl;

   // Put the text in the edit in the file at the offsets
   strcpy((char*) map+ipOff, ipEdit->text().toStdString().c_str());
   strcpy((char*) map+portOff, portEdit->text().toStdString().c_str());


   /* Open output file and write the memory map to it */
   fdOut = open("server-built", O_TRUNC|O_CREAT|O_WRONLY, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IROTH);

   write(fdOut, map, st.st_size);

   ::close(fdOut);


   munmap(map, st.st_size);

   ::close(fdIn);
}


main.cpp
Código (cpp) [Seleccionar]
#include "mainwindow.hpp"
#include <QApplication>

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   MainWindow w;
   w.show();
   
   return a.exec();
}


Lo primero, este último código funciona sólo para ejecutables de 32 bits, pero se puede portar fácilmente.

¿Cómo funciona?

Pues los ejecutables ELF suelen tener una tabla de strings con los nombres de todas las secciones (la sección .shstrtab). Con esa tabla, encontramos las secciones .symtab (la tabla de símbolos) y la .strtab (la tabla de strings de símbolos).

Comparando los nombres de cada símbolo con los que hay en la tabla, encotramos los dos que queremos modificar. No tenemos el offset de estos símbolos directamente, así que tenemos que hallarlo. Tenemos la dirección del símbolo una vez cargado en memoria, la sección en la que se encuentra este símbolo (y por tanto la dirección de esta sección en memoria y el offset de esta sección en el archivo); así que hacemos la siguiente cuenta:

       Dirección del símbolo - Dirección base de la sección + Offset de la sección en el archivo

Eso corresponde a esta parte del código:

Código (cpp) [Seleccionar]
ipOff = toModify[0].st_value - sections[toModify[0].st_shndx].sh_addr +
          sections[toModify[0].st_shndx].sh_offset;

  portOff = toModify[1].st_value - sections[toModify[1].st_shndx].sh_addr +
          sections[toModify[1].st_shndx].sh_offset;


Luego escribimos el texto de los edits en el mapa de memoria en el offset de cada símbolo, de forma que cuando se carguen en memoria contengan esos datos, y nuestro server pueda conectar a la IP y por el puerto indicado.

Bueno, hace falta conocimiento del formato ELF para entender esto totalmente, así que más información aquí y también se pueden preguntar dudas.

Saludos!
I keep searching for something that I never seem to find, but maybe I won't, because I left it all behind!

I code for $$$
Hago trabajos en C/C++
Contactar por PM

OmarHack

Muy bueno el aporte.
Tengo una duda, ¿los datos de ips se actualizan automáticamente? Es decir:
Si el objetivo ejecuta el server y tiene una ip dinámica el programa actualiza la ip del objetivo automáticamente? ¿O tendría que volver a ejecutar el server para que se tome su nueva ip?
I like to test things.

0xDani

Cita de: OmarHack en 17 Julio 2013, 17:06 PM
Muy bueno el aporte.
Tengo una duda, ¿los datos de ips se actualizan automáticamente? Es decir:
Si el objetivo ejecuta el server y tiene una ip dinámica el programa actualiza la ip del objetivo automáticamente? ¿O tendría que volver a ejecutar el server para que se tome su nueva ip?

Bueno, eso es flipar mucho y no depende del builder. La verdad no creo que haya ningún server que haga eso. Lo que se suele hacer es conseguirse un dominio de no-ip.

Yo nunca he encontrado como hacer esto en Linux y lo tuve que implementar mirando la man page, a ver si ahora alguien se anima a hacer alguna criatura para Linux.
I keep searching for something that I never seem to find, but maybe I won't, because I left it all behind!

I code for $$$
Hago trabajos en C/C++
Contactar por PM

OmarHack

I like to test things.