Search code examples
assemblyprocesskernelinternals

Relocate Thread Control Block


I am currently in a situation where I need to relocate the TCB (Thread Control Block). From what I understood, the following scheme is in place in processes execution when calling the VDSO (according to this article)

  • All is triggered by the instruction call *%gs:0x10
  • %gs is a segment register that uses the Global Descriptor Table. The GDT is an associative table stored in the kernel stack associated with the process, and keeps correspondance between segment register values and addresses in the Address Space of the process. It is initialization randomly and chosen by the loader (part of glibc library)
  • Once the correspondance is established, the processor reads the address at *(GDT[%gs]+0x10) and makes the call that goes in the VDSO.
  • The syscall is executed and returning from the kernel to the user process uses a return address store in the kernel's AS.

My problem is that I need to override some of my process memory with other things, and most of the time, it erases the section at the address (GDT[%gs]+0x10).

What I would like to do is to relocate the contents of the TCB (from GDT[%gs] to GDT[%gs]+0x..) in a free location in my process' AS, but that would mean altering the GDT contents, what I think I cannot do from the user mode. In other words, I'd like to change the association of %GS in my GDT.

Thanks in advance for any answer, /iansus


Solution

  • Ok, I found the solution :

    int relocateTCB()
    {
    
    #ifndef TCB_LDT_INDEX
    #define TCB_LDT_INDEX 6
    #endif
    
    
        struct user_desc* u_info = (struct user_desc*) malloc(sizeof(struct user_desc));
        int r,j;
        int mstart, map;
        int PS = sysconf(_SC_PAGESIZE);
    
        if(u_info == NULL)
        {
            errno = EFAULT;
            return -1;
        }
    
        u_info->entry_number = TCB_LDT_INDEX;
        r = syscall(SYS_get_thread_area, u_info);
    
        if(r==-1) return -1;
    
        mstart = (u_info->base_addr) & ~(PS-1);
        map = (int) mmap(NULL, PS, PROT_EXEC|PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, (off_t)0);
    
        if(!map)
        {
            errno = EFAULT;
            return -1;
        }
    
        for(j=mstart; j<mstart+PS; j+=sizeof(int))
        {
            * ((int*) (map+j-mstart)) = * ((int*) j);
        }
    
        u_info->base_addr = map + u_info->base_addr - mstart;
        r = syscall(SYS_set_thread_area, u_info);
    
        if(r==-1)
        {
            errno = EINVAL;
            return -1;
        }
    
        for(j=mstart; j<mstart+PS; j+=sizeof(int))
            *((int*) j) = 0;
    
        // Keep *%gs = %gs
        *((int*) u_info->base_addr) = u_info->base_addr;
    
        return 0;
    }