Page 1 of 2 12 LastLast
Results 1 to 15 of 16

Thread: Linux device driver

  1. #1

    Linux device driver

    Hi all

    I am reversing an SRAM driver, mostly for learning how to reverse, but also so that I can emulate the hardware later on.
    It is going very well, but there is a piece of code, that I do not really understand. It is a character device driver and the function I am having problems with is the 'write' function in a 'struct file_operations'. The following is all the code up to the point I am having problems with:

    Code:
    ;Prototype:
    ;   size_t sram_write(struct file *file,        <-- Passed in EAX
    ;                     const char __user * data, <-- Passed in EDX
    ;                     size_t len,               <-- Passed in ECX
    ;                     loff_t * offset)          <-- Passed as first stack variable
    
    ;Set up stack frame
    push   ebp
    mov    ebp, esp
    sub    esp, 14h
    
    ;Now EBX, ESI and EDI are stored.
    ;They are restored before returning from this function
    mov    [ebp-0ch], ebx    ;Save EBX for later restoration
    mov    [ebp-08h], esi    ;Save ESI for later restoration
    mov    [ebp-04h], edi    ;Save EDI for later restoration
    
    ;Now make sure that write does not extend available size
    mov    eax, ds:size      ;'ds:size' contains total size of memory
    mov    edi, [ebp+8]      ;Put 'offset' into EDI
    mov    [ebp-10h], edx    ;Store 'data' pointer
    mov    esi, [edi]        ;Read low 32 bits offset into ESI (high 32 bits are ignored)
    sub    eax, esi          ;EAX = ds:size - *offset = Space left in memory relative to offset
    cmp    eax, ecx          ;Compare space left to 'len'
    mov    ds:aux_pos, eax   ;Store space left
    jb     short too_short   ;If len > space left go to too_short
    
    magic_happens:
    mov    edx, esp          ;Put stack pointer into EDX
    mov    ebx, ecx          ;Put 'len' into EBX
    mov    eax, [ebp-10h]    ;Put 'data' pointer into EAX
    and    edx, 0ffffe000h   ;???? WHY ?????
    add    eax, ebx          ;EAX = data + len = end of data
    sbb    ecx, ecx          ;Subtract with borrow...WHY ????
    cmp    [edx+18h], eax    ;Compare obscured address with end of data...WHY ??
    sbb    ecx, 0            ;???
    test   ecx, ecx          ;Set flags for ECX...but why ??
    jnz    short err1
    
    ;Here starts the actual writing...not so interesting to my problem
    ;....
    xor    ebx, ebx
    jmp    end
    
    err1:
    mov    [esp], offset str2;Format string: "<4>Cannot read from write-call-buffer\n"
    xor    ebx, ebx
    call printk
    jmp    short end
    
    too_short:
    mov    [esp], offset str1;Format string on stack: "<4>Limiting write to available memory\n"
    call    printk
    mov    ecx, ds:aux_pos   ;Put available memory size into ECX
    jmp    short magic_happens
    
    end:
    mov    eax, ebx        ;Set return value
    mov    esi, [ebp-08h]  ;Restore ESI
    mov    ebx, [ebp-0ch]  ;Restore EBX
    mov    edi, [ebp-04h]  ;Restore EDI
    
    ;Tear down stack frame
    mov    esp, ebp
    pop    ebp
    ret
    The problem I am having is with the 'magic_happens' block. My guess is that these two instructions:
    Code:
    mov edx, esp
    cmp [edx+18h], eax
    ...would compare the return address to whatever is in eax, as there are 14h local bytes and EBP on the stack, buth then there is the AND instruction in between:
    Code:
    mov edx, esp
    and edx, 0ffffe000h
    cmp [edx+18h], eax
    What is that about ?
    And what do the two SBB instructions do ?

    Best,
    Robert

  2. #2
    mah... the AND is a way to access the bottom of the stack (8kb of stack) - a sort of structure 'dynamically allocated' at end of available stack - think of it of a sort of " access a prior malloc_at_bottomof_stack(struct x)" where data had been saved.

    CMP == sub without altering dest, so it is like:

    "SUB" [ dword MyStruct+field18 ] , EAX

    which will set CF iff eax>>field18
    SBB reg, reg will make reg = 0 and will add -CF, so if prior has CF set, will make reg == -1.

    Now, similar effect happens if you do sbb reg,0 , as -CF will be added to reg.
    Last edited by Maximus; April 11th, 2011 at 09:20. Reason: made clear struct was already there...
    I want to know God's thoughts ...the rest are details.
    (A. Einstein)
    --------
    ..."a shellcode is a command you do at the linux shell"...

  3. #3
    Quote Originally Posted by Maximus View Post
    mah... the AND is a way to access the bottom of the stack (8kb of stack) - a sort of structure 'dynamically allocated' at end of available stack - think of it of a sort of " access a prior malloc_at_bottomof_stack(struct x)" where data had been saved.
    Alright, makes sense. But I have almost reversed the entire driver and I have not found any allocation like that. The 'read' call does more or less the same thou. I at least have the module 'init' and file 'open' funktions fully reversed and they did no such thing.

    Do you know if the kernel allocates something for you on the bottom of the stack ?
    Or if you can specify magic compiler attributes in the code to have this done at link time ?

  4. #4
    ...check better.

    Such code indicates a structure 'allocated/written' at bottom of your driver's stack - very likely some static data for the module. So I'd re-check hunting for code that you might have left somewhere that zero/write over such memory locations.

    edit--- ok, i examined better the code you pasted:

    you say EAX contains the file structure - look, that value is OVERWRITTEN! so your driver is not using such parameter, but likely a 'static' one it allocates at end of stack (hence its access to it, i guess). Check how you did define that file structure and see if its usage of the field at +18 makes sense - if it does so, that i'm 99% correct in my assumption.
    Last edited by Maximus; April 11th, 2011 at 13:59.
    I want to know God's thoughts ...the rest are details.
    (A. Einstein)
    --------
    ..."a shellcode is a command you do at the linux shell"...

  5. #5
    The list of imported functions does not contain memory allocation functions at all, and the 'open' and 'release' functions for the file simply return 0:
    Code:
    sram_open proc near
    push  ebp
    mov   ebp, esp
    xor   eax, eax
    pop   ebp
    retn
    The 'sbb ecx, 0' will subtract zero or one from ecx depending on whether or not the carry flag was set during 'cmp [edx+18h], eax'. Am I right ?

  6. #6
    The magic stuff is getting the current task structure. See explanation here
    http://www.gossamer-threads.com/lists/linux/kernel/117765

  7. #7
    Quote Originally Posted by reverser View Post
    The magic stuff is getting the current task structure. See explanation here
    http://www.gossamer-threads.com/lists/linux/kernel/117765
    That seems correct.
    Thanks for the help guys.

  8. #8
    Hmm...it seems that the idea that the reference is to a member in the task structure raises more questions.

    When entering this function ECX contains the number of bytes to be written and EDX contains the pointer in userspace memory to write from. Would you agree, that these instructions puts the address AFTER the memory area in EAX ?:

    Code:
    mov    [ebp-10h], edx    ;Store 'data' pointer
    ...
    mov    ebx, ecx          ;Put 'len' into EBX
    mov    eax, [ebp-10h]    ;Put 'data' pointer into EAX
    ..
    add    eax, ebx          ;EAX = data + len = end of data
    sbb    ecx, ecx
    ...and that ECX is now -1 if 'data + len' cannot fit in 32 bits and zero otherwise ?

    The current task structure lives at ESP&0xffffe000 as reverser pointed out and the member at offset 18h within the task structure is 'prio' (I checked this with a simple printk("Prio: %ld\n", offsetof(struct task_struct, prio)); ).

    But why would you compare the current tasks priority to a memory address ?

    Code:
    mov    edx, esp
    ...
    and    edx, 0ffffe00h
    ...
    cmp    [edx+18h], eax
    Full code in my original post.

  9. #9
    Are you sure your kernel headers match what the driver thinks? I also think it's very strange that address is compared with priority.

    Anyway, I can at least explain the fiddling with the flags.

    add eax, ebx ;eax = data + len = end of data

    -> If there was overflow during addition, carry flag will be set.

    sbb ecx, ecx ;ecx = ecx - ecx - carry

    -> ecx will become 0 if there was no carry, and -1 if there was

    cmp [edx+18h], eax ;Compare a field in task_struct with end_of_data

    -> Carry flag will be set if the left-hand side is less than right-hand one

    sbb ecx, 0 ; ecx = ecx - carry

    -> If carry was set, ecx will be decreased by one

    test ecx, ecx ; compare ecx with 0
    jnz short err1 ; jump if it was not 0

    In short, ecx is used to store the results of two checks: overflow check and comparison check. If any of them is triggered, ecx will not be zero, and err1 handler will be used.

  10. #10
    Nice to know that I at least figured out the SBB's (with a little help)

    Inserting a module into a different kernel than what it was built for should not work so I implemented a module with the following function:
    Code:
    ssize_t my_writeop(struct file * file, const char __user * data, size_t len, loff_t * offset) {
        printk(KERN_INFO "File: %p\n", file);
        printk(KERN_INFO "Data: %p\n", data);
        printk(KERN_INFO "Len: %d\n", len);
        printk(KERN_INFO "Offset: %p\n", offset);
        return 0;
    }
    It disassembles to the following:
    Code:
    public my_writeop
    my_writeop proc near
    
    var_10= dword ptr -10h
    var_C= dword ptr -0Ch
    var_8= dword ptr -8
    var_4= dword ptr -4
    arg_0= dword ptr  8
    
    push    ebp
    mov     ebp, esp
    sub     esp, 10h
    mov     [ebp+var_8], ebx
    mov     [ebp+var_4], esi
    call    mcount
    mov     ebx, edx
    mov     esi, ecx
    mov     [esp+10h+var_C], eax
    mov     [esp+10h+var_10], offset a6FileP ; "<6>File: %p\n"
    call    printk
    mov     [esp+10h+var_C], ebx
    mov     [esp+10h+var_10], offset a6DataP ; "<6>Data: %p\n"
    call    printk
    mov     [esp+10h+var_C], esi
    mov     [esp+10h+var_10], offset a6LenD ; "<6>Len: %d\n"
    call    printk
    mov     eax, [ebp+arg_0]
    mov     [esp+10h+var_10], offset a6OffsetP ; "<6>Offset: %p\n"
    mov     [esp+10h+var_C], eax
    call    printk
    mov     ebx, [ebp+var_8]
    xor     eax, eax
    mov     esi, [ebp+var_4]
    mov     esp, ebp
    pop     ebp
    retn
    my_writeop endp
    Both the original module and my own inserts fine into the same kernel, so they should agree on calling convention.

  11. #11
    The calling convention is unlikely to change, but task_struct could be different.

  12. #12
    Quote Originally Posted by reverser View Post
    The calling convention is unlikely to change, but task_struct could be different.
    Thats true, but I compiled against the same kernel headers as the vendor:
    Code:
    $ modinfo orig/qmem.ko | grep vermagic
    vermagic:       2.6.35-22-generic SMP mod_unload modversions 686
    $ modinfo mymod.ko | grep vermagic
    vermagic:       2.6.35-22-generic SMP mod_unload modversions 686
    $ insmod mymod.ko
    $ tail -n 10 /var/log/kern.log | grep prio
    Apr 13 14:50:39 slotmachine kernel: [ 4410.196350] prio: 0x18
    Apr 13 14:50:39 slotmachine kernel: [ 4410.196352] static_prio: 0x1c
    Apr 13 14:50:39 slotmachine kernel: [ 4410.196354] normal_prio: 0x20
    Apr 13 14:50:39 slotmachine kernel: [ 4410.196356] rt_priority: 0x24
    $ tail -n 16 qxtmem.c 
    #define II(x,y) printk(KERN_INFO #x ": 0x%x\n", offsetof(y, x))
    #define I(x) II(x, struct task_struct)
    
    int mymod_init(void) {
        I(prio);
        I(static_prio);
        I(normal_prio);
        I(rt_priority);
        return 0;
    }
    
    void mymod_cleanup(void) {
    }
    
    module_init(mymod_init);
    module_exit(mymod_cleanup);
    $
    Hmm...I guess I will have to figure out how to attach a debugger to the kernel.

  13. #13
    man, you didnt read: IF the code you pasted is complete and correct about EAX struct file assumption, the data at bottom isnt a kernel structure, but rather a statically allocated 'struct file' that is being used in place of the dynamically one passed in eax. Have you checked it?
    I want to know God's thoughts ...the rest are details.
    (A. Einstein)
    --------
    ..."a shellcode is a command you do at the linux shell"...

  14. #14
    Oh, I think I've figured out what's up. Looks like things has changed somewhat since 2.2 It seems now the structure on bottom of the stack is not task_struct but thread_info (which has a pointer to task_struct):
    Code:
       /* how to get the thread information struct from C */
       static inline struct thread_info *current_thread_info(void)
       {
               struct thread_info *ti;
               __asm__("andl %%esp,%0; ":"=r" (ti) : "0" (~(THREAD_SIZE - 1)));
               return ti;
       }
    
       struct thread_info {
               struct task_struct      *task;          /* main task structure */
               struct exec_domain      *exec_domain;   /* execution domain */
               unsigned long           flags;          /* low level flags */
               unsigned long           status;         /* thread-synchronous flags */
               __u32                   cpu;            /* current CPU */
               int                     preempt_count;  /* 0 => preemptable, <0 => BUG */
       
       
               mm_segment_t            addr_limit;     /* thread address space:
                                                          0-0xBFFFFFFF for user-thread
                                                          0-0xFFFFFFFF for kernel-thread
                                                       */
               void                    *sysenter_return;
               struct restart_block    restart_block;
       
               unsigned long           previous_esp;   /* ESP of the previous stack in case
                                                          of nested (IRQ) stacks
                                                       */
               __u8                    supervisor_stack[0];
       };
    0x18 seems to be addr_limit.
    And I actually found a macro that matches the code perfectly:
    Code:
    /*
     * Test whether a block of memory is a valid user space address.
     * Returns 0 if the range is valid, nonzero otherwise.
     *
     * This is equivalent to the following test:
     * (u33)addr + (u33)size >= (u33)current->addr_limit.seg
     *
     * This needs 33-bit arithmetic. We have a carry...
     */
    #define __range_ok(addr,size) ({ \
            unsigned long flag,sum; \
            asm("addl %3,%1 ; sbbl %0,%0; cmpl %1,%4; sbbl $0,%0" \
                    :"=&r" (flag), "=r" (sum) \
                    :"1" (addr),"g" ((int)(size)),"g" (current->addr_limit.seg)); \
            flag; })
    Last edited by reverser; April 14th, 2011 at 07:58.

  15. #15
    NICE!!!

    I had searched around the kernel source for the get_current() makro and found that it referenced the thread info structure but not yet figured out that it was the thread info, that was on the bottom of the stack. That makes much more sense now.

    Thanks to both of you.

Similar Threads

  1. Reversing a Win driver and writing one for Linux
    By Darkelf in forum Advanced Reversing and Programming
    Replies: 7
    Last Post: April 24th, 2012, 23:33
  2. Replies: 4
    Last Post: January 16th, 2010, 16:49
  3. Replies: 3
    Last Post: August 12th, 2008, 14:59
  4. using HID device in Driver and strange device corruption
    By Hero in forum Advanced Reversing and Programming
    Replies: 2
    Last Post: February 17th, 2008, 00:30
  5. How to unpack a .sys file?(device driver)
    By cloud_y in forum Malware Analysis and Unpacking Forum
    Replies: 3
    Last Post: February 19th, 2004, 14:34

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •