Log in

View Full Version : Linux device driver


robert
April 11th, 2011, 04:22
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

Maximus
April 11th, 2011, 09:13
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.

robert
April 11th, 2011, 13:17
Quote:
[Originally Posted by Maximus;90017]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 ?

Maximus
April 11th, 2011, 13:49
...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.

robert
April 11th, 2011, 17:07
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 ?

reverser
April 11th, 2011, 18:32
The magic stuff is getting the current task structure. See explanation here
http://www.gossamer-threads.com/lists/linux/kernel/117765

robert
April 12th, 2011, 01:23
Quote:
[Originally Posted by reverser;90023]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.

robert
April 12th, 2011, 14:58
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.

reverser
April 12th, 2011, 19:22
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.

robert
April 13th, 2011, 03:49
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.

reverser
April 13th, 2011, 04:11
The calling convention is unlikely to change, but task_struct could be different.

robert
April 13th, 2011, 08:02
Quote:
[Originally Posted by reverser;90037]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.

Maximus
April 13th, 2011, 08:53
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?

reverser
April 14th, 2011, 07:53
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; })

robert
April 14th, 2011, 09:47
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.

Maximus
April 14th, 2011, 14:00
nice, indeed