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#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#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#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:
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!