Wednesday, July 20, 2011

Writing to executable (and currently running) file

If you are trying to write to the currently executing file, like this:
open(argv[0], O_RDWR);
You will get the ETXTBSY error. It seems that the only way to do is to unmap all process memory (both text and data):
void do_munmap_and_write(int m0, int l0, int m1,
int l1, int f, int b, int s)
{
        int h;
        /* munmap */
        asm volatile ("int $0x80":: "a"(91), "b"(m0), "c"(l0));
        asm volatile ("int $0x80":: "a"(91), "b"(m1), "c"(l1));
        /* open */
        asm volatile ("int $0x80": "=a"(h): "a"(5), "b"(f), "c"(2));
        /* lseek */
        asm volatile ("int $0x80":: "a"(19), "b"(h), "c"(0), "d"(2));
        /* write */
        asm volatile ("int $0x80":: "a"(4), "b"(h), "c"(b), "d"(s));
        /* exit */
        asm volatile ("int $0x80":: "a"(1));
}

void *get_base(uint32_t addr) {
        Elf32_Ehdr *ehdr;
        addr &= ~4095;
        do {
                while (*(uint32_t*)addr != 0x464c457fUL)
                        addr -= 4096;
                ehdr = (Elf32_Ehdr*)addr;
        } while (
                ehdr->e_ehsize != sizeof(Elf32_Ehdr) ||
                ehdr->e_ident[EI_CLASS] != ELFCLASS32 ||
                ehdr->e_ident[EI_DATA] != ELFDATA2LSB ||
                ehdr->e_machine != EM_386 ||
                ehdr->e_version != EV_CURRENT
        );
        return (void*)addr;
}

int main(int argc, char **argv, char **envp)
{
        uint8_t *p = get_base(main);
        Elf32_Ehdr *ehdr = (Elf32_Ehdr*)p;
        Elf32_Phdr *phdr = (Elf32_Phdr*)(p + ehdr->e_phoff);
        int i;
        uint32_t m0, l0, m1, l1;
        for (i = 0; i < ehdr->e_phnum; i++) {
                if (phdr[i].p_type == PT_LOAD) {
                        if (phdr[i].p_offset == 0) {
                                m0 = phdr[i].p_vaddr & 0xffff000;
                                l0 = phdr[i].p_memsz;
                        } else {
                                m1 = phdr[i].p_vaddr & 0xffff000;
                                l1 = phdr[i].p_memsz;
                        }
                }
        }
        char *selfexe;
        for (i = 0; envp[i + 1]; i++) ;
        for (selfexe = envp[i]; *selfexe++; ) ;
        uint8_t *op = mmap(0, 4096, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
        memcpy(op, do_munmap_and_write, 128);
        mprotect(op, 4096, PROT_READ | PROT_EXEC);
        ((void(*)())op)(m0, l0, m1, l1, selfexe, op, 4096);
}
Surely, the code (which will update argv[0]) and data (that should be written) must be copied somewhere before unmaping (you can saw the branch where you're siting, but carefuly) Here is the strace:
mmap2(....., MAP_PRIVATE|MAP_ANONYMOUS, 0, 0) = 0xb7fa3000
mprotect(0xb7fa3000, 4096, PROT_READ|PROT_EXEC) = 0
munmap(0x8048000, 2004)                 = 0
munmap(0x8049000, 272)                  = 0
open("./a.out", O_RDWR)                 = 4
lseek(4, 0, SEEK_END)                   = 9892
write(4, "...\24\315"..., 4096) = 4096
_exit(4)                                = ?
Have a nice day :-)

No comments:

Post a Comment