Page 1 of 2

Linux device driver

Posted: Mon Apr 11, 2011 4:22 am
by robert
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: Select all

;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: Select all

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: Select all

mov edx, esp
and edx, 0ffffe000h
cmp [edx+18h], eax
What is that about ?
And what do the two SBB instructions do ?

Best,
Robert

Posted: Mon Apr 11, 2011 9:13 am
by Maximus
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.

Posted: Mon Apr 11, 2011 1:17 pm
by robert
Maximus wrote: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 ?

Posted: Mon Apr 11, 2011 1:49 pm
by Maximus
...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.

Posted: Mon Apr 11, 2011 5:07 pm
by robert
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: Select all

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 ?

Posted: Mon Apr 11, 2011 6:32 pm
by reverser
The magic stuff is getting the current task structure. See explanation here
http://www.gossamer-threads.com/lists/l ... nel/117765

Posted: Tue Apr 12, 2011 1:23 am
by robert
reverser wrote:The magic stuff is getting the current task structure. See explanation here
http://www.gossamer-threads.com/lists/l ... nel/117765

That seems correct.
Thanks for the help guys.

Posted: Tue Apr 12, 2011 2:58 pm
by robert
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: Select all

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: Select all

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

Posted: Tue Apr 12, 2011 7:22 pm
by reverser
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.

Posted: Wed Apr 13, 2011 3:49 am
by robert
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: Select all

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: Select all

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.

Posted: Wed Apr 13, 2011 4:11 am
by reverser
The calling convention is unlikely to change, but task_struct could be different.

Posted: Wed Apr 13, 2011 8:02 am
by robert
reverser wrote: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: Select all

$ 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.

Posted: Wed Apr 13, 2011 8:53 am
by Maximus
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?

Posted: Thu Apr 14, 2011 7:53 am
by reverser
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: Select all

   /* 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: Select all

/*
 * 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; })

Posted: Thu Apr 14, 2011 9:47 am
by robert
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.