Lo primero, el código del server:
Código
#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
#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
#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
#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
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!