Playing with the terminal (introduction to BeOS reversing) 03/2001 by Zadig. Tools needed: Debugger: db. Disassembler: dasm, disFunk, or RevEng. Hex Editor: bvi, or HexJuggler. BeIDE and/or nasm to ease the job. The most important: A mouse with a wheel! Introduction: If before BeOS you were working on an unix OS you problably use a lot the shell because most of the time it is more efficient than the tracker. So you think that the terminal is much better than playing with 10 windows but there is something anoying you: You can't use your mouse wheel to move the scrollbar which makes you lose precious seconds when writting your revolutionnary soft! These speed junkies will be happy today because we will learn how to add this feature to the BeOS terminal. I- The aim 1- The wheel and BeOS 2- Things to do II- Analysis of the terminal 1- The scrollbar 2- BeOS API functions 3- The BView position III- Reverse 1- Mapping 2- Handling the wheel message 3- Saving the BView position IV- Conclusion I- The aim What we want to do is very simple: We just want to use the wheel to scroll on the terminal. Each time we move the wheel we will make the shell's text scroll 3 lines up or down. However before disassembling the terminal and patching everything we must understand how is working the mouse wheel. 1- The wheel and BeOS As all BeOS events, the wheel changing event is received as a message in the application. A quick look into the BeBook gives us this: B_MOUSE_WHEEL_CHANGED Source: The system. Target: The BWindow of the view the mouse is pointing to. Sent when the user moves the mouse wheel (on mice that have them). Field Type code Description "when" B_INT64_TYPE Event time, in microseconds since 01/01/70 "be:wheel_delta_x" B_FLOAT_TYPE How much the Y value of the wheel has changed. "be:wheel_delta_y" B_FLOAT_TYPE How much the Y value of the wheel has changed. The standard mouse driver that comes with BeOS only supports a Y-oriented wheel. When we receive this message we must find in which direction the wheel was moved. This information is in the be:wheel_delta_y field. To get its value we must call the FindFloat method: status_t BMessage::FindFloat(const char *name, float *aFloat) const name is the name of the field (be:wheel_delta_y) and aFloat is a pointer to the float that will receive the movement value. When trying this on a test-soft I obtained 1 when the wheel moved down and -1 when moving up. To be sure that this will work with all mice we will only use the sign of the value. 2- Things to do To complete the job we will proceed in several steps: First we must find the MessageReceived function which receives the B_MOUSE_WHEEL_CHANGED message. The text we type in the terminal is in a BView. So we must find how to move the scrollbar and the BView. We will have to call this scrolling function when receiving the wheel message but without disturbing the terminal. We need to know at any time what is the position of the scrollbar: When we receive the wheel message we must start scrolling from this point. That's enougth for theory, let's start practice! II- Analysis of the terminal 1- The scrollbar The first thing to do is understand how the BView/Scrollbar are moved. We will start searching in the dead listing. Reveng's listing gives us some strange classes (A::A, W::V::V ???) and one very interesting: BTermView::BTermView. It contains several "subclasses" (I've never seen this notation before. If someone knows what it is then please tell it to me!) each of them having a message handler: BTermView::Blinker::MessageReceived(BMessage *) BTermView::Boss::MessageReceived(BMessage *) BTermView::Control::MessageReceived(BMessage *) BTermView::MessageReceived(BMessage *) BTermView::Mouser::MessageReceived(BMessage *) BTermView::SB::MessageReceived(BMessage *) Time has come to start debugging. So fire up db and you'll get this message: pid xxx 'Terminal' will break into the debugger when it exits the kernel This means that we will break into the terminal when leaving it... not really what we want. It is due to the fact that the terminal is already launched. To avoid this we will force BeOS to launch a new session of the terminal by copying the terminal in a new directory and launching db from the copy. Now let's put a breakpoint into BTermView::MessageReceived and play with the scrollbar and the wheel. We don't get anything when moving the scrollbar but db pops up when using the wheel. We found where we will get the wheel messages (easy isn't it?). Then we can try the same with the other functions that have interesting names (Mouser, Control) but we don't get anything. We will find all the scrollbar messages in BTermView::SB::MessageReceived (Yes you guessed, SB means ScrollBar). In this function there is one very interesting place: 0002d674: 52 push %edx 0002d675: 8d45f0 lea 0xfffffff0(%ebp),%eax 0002d678: 50 push %eax Reference to function "BView::Bounds(void) const" /* Get BView position */ 0002d679: e822b4feff call 18aa0 0002d67e: d945f4 flds 0xfffffff4(%ebp) /* Then calculate new */ 0002d681: d97dec fnstcw 0xffffffec(%ebp) /* position with the */ 0002d684: 8b4dec mov 0xffffffec(%ebp),%ecx /* scrollbar movement */ 0002d687: b50c mov $0xc,%ch 0002d689: 894de4 mov %ecx,0xffffffe4(%ebp) 0002d68c: d96de4 fldcw 0xffffffe4(%ebp) 0002d68f: df7de4 fistpll 0xffffffe4(%ebp) 0002d692: 8b45e4 mov 0xffffffe4(%ebp),%eax 0002d695: 8b55e8 mov 0xffffffe8(%ebp),%edx 0002d698: d96dec fldcw 0xffffffec(%ebp) 0002d69b: 50 push %eax /* New position */ 0002d69c: 8b4634 mov 0x34(%esi),%eax 0002d69f: 50 push %eax /* BTermView object handle */ Reference to function "BTermView::scroll(unsigned int)" 0002d6a0: e8b7e5ffff call 2bc5c 0002d6a5: eb40 jmp END Note: The BeOS API is C++, which means object oriented. However asm is not object oriented and only deals with functions. This is why C++ classes/methods names are mangled (to avoid that several functions have the same name), and why there is ALWAYS one more push than the method has parameters (one parameter is hidden). The last push (which means the first parameter) is the object's handle. Everything we need is just above (almost). When launching the terminal put a breakpoint at 0x8002d69b and play with the scrollbar. You will see that the new position is a multiple of 0xF (which represents one line of text) and that the higher position is 0x0. This means that when moving the wheel down we will have to add 0x2d (3 lines * 0xF), and when moving up we will substract 0x2d to the current position. To effectively move the terminal's view we will call BTermView::scroll. 2- BeOS API functions Let's continue with something trickier. In the first part we saw that we must call FindFloat to get the wheel's displacement value. So what is it's calling address? We just have to grep into the disassembly and we find this: 00020bc0: 6a00 push $0x0 /* long (2nd param) */ 00020bc2: 89d8 mov %ebx,%eax 00020bc4: 0525fbfeff add $0xfffefb25,%eax 00020bc9: 50 push %eax /* char * (1st param) */ 00020bca: 57 push %edi /* BMessage object handle */ Reference to function "BMessage::FindFloat(char const *, long) const" 00020bcb: e8207affff call 185f0 00020bd0: 83ec04 sub $0x4,%esp 00020bd3: d91c24 fstps (%esp,1) At first it seems to be perfect but it isn't at all: The method we found in the BeBook was "BMessage::FindFloat(const char *name,float *aFloat)" which is not the one that we have here (the last parameter is a long). I looked for documentation on it but I didn't find anything. This is probably an old and no more used method. Moreover there is something strange in it because each time it is called the last param is set to 0. So where does this f**ing function puts its results? Since we want a float it probably uses the processor's floating registers no? The floating registers can be viewed in db with "fregs" and guess what... our wheel value is here. There are 8 floating point registers on intel's processor: st(0)-st(7). They are not really registers because they are in a stack in memory. When you push a new value in the stack, it is put in st(0). We need to get the sign of this value (to know if we moved up or down). Unfortunately there is no asm instruction doing this (if it exists I didn't find it:-), so we will have to do it by hand. To do this we must first push this value somewhere in memory. This is done with the instruction "fst" (float store), you just have to tell it where to put it in memory and what size is this memory. The "fstp" instruction does the same thing but after storing the value it pops it from the floating stack. At address 0x20bd3 the "s" of "fstps" means that the memory is "simple" size (4 bytes). That's exactly what we will use except that we will put our value in memory and not in the stack. 3- The BView position We need one last element before patching. Each time we enter a command in the shell, the BView is moving down. When we move the wheel we must start scrolling from the current position. This means that we must save the current position of the BView each time it is moved. The problem is that the BView can be moved from a lot of places in the code. In part II.1 we saw that when moving the scrollbar, the function BTermView::scroll was called to move the view. Since this is the only function that moves the view we will save the new position in it. Now I think that we have all elements we need to start programming the wheel function. III- Reverse 1- Mapping First of all we need some free space in the file to add our code. That is a critical point because if we don't find some place, we won't be able to do anything. This would not be a problem under windows because if you don't find free space in a PE file (windows executable file format) you can easily add a code section. Unfortunately there is no way (today) to add such a section on an ELF executable (ie on a BeOS executable): ELF does only support 1 range of addr which contains code. Since our file contains other sections after the progbits, the new code section should be inserted in the file which would change a lot of offsets. If you want more details you should read Siul+Hacky's tutorials and the ELF specs. So we must find some free space in an executable section. Readelf gives us this (readelf -S): [Nr] Name Type Addr Off Size ES Flg Lk Inf Al ... [13] .init PROGBITS 00017768 017768 000035 00 AX 0 0 4 [14] .plt PROGBITS 000177a0 0177a0 002200 04 AX 0 0 4 [15] .text PROGBITS 000199a0 0199a0 01ddbc 00 AX 0 0 16 [16] .fini PROGBITS 0003775c 03775c 000030 00 AX 0 0 4 [17] .rodata PROGBITS 000377a0 0377a0 001623 00 A 0 0 32 [18] .data PROGBITS 00039de0 038de0 0070e8 00 WA 0 0 32 [19] .gcc_except_table PROGBITS 00040ec8 03fec8 0019e8 00 WA 0 0 4 [20] .eh_frame PROGBITS 000428b0 0418b0 00546c 00 WA 0 0 4 [21] .ctors PROGBITS 00047d1c 046d1c 000010 00 WA 0 0 4 [22] .dtors PROGBITS 00047d2c 046d2c 000010 00 WA 0 0 4 [23] .got PROGBITS 00047d3c 046d3c 000b30 04 WA 0 0 4 ... Searching in this range of addresses I found a lot of unsused bits starting at offset 0x39160 (in section ".data"). This will be perfect for us but we will have to be carefull: In this section the offset and virtual address are not the same. As a consequence the instruction which is at offset 38de0 will be mapped at address 39de0 (We must add 0x1000 to the offset to get the memory address). Moreover the base address of the terminal is 0x80000000. This means that our addresses will be mapped at 0x8003a160. This will be important when coding the jumps and calls. The variables will be at the begining of this space and after them will be the new code. We need the string "be:wheel_delta_y" to extract the float value from the wheel message, some place to hold this value, some place to store the bview's current position and handle. Here is the mapping I used: Addr Content 0x39160 "be:wheel_delta_y" 0x39174 Wheel's moving value 0x3917c BView's current position 0x39180 BView's handle 0x39190 Code start 2- Handling the wheel message Hooking the message In part II.1 we saw that the wheel message was received in the function BTermView::MessageReceived. We will have to make its code jump to our wheel handling function. Here is the start of it: 000282c6: 81fe454d494d cmp $0x4d494d45,%esi ; Message 'MIME' (B_MIME_DATA) 000282cc: 7432 je 28300 000282ce: 7711 ja 282e1 000282d0: 81fe41544144 cmp $0x44415441,%esi ; Message 'DATA' (B_SIMPLE_DATA) 000282d6: 0f8484000000 je 28360 ... What we will do here is replace the first cmp with a jump. The removed cmp will be added at the end of our code just before a jump 0x282cc, which won't change anything for the app: 000282c6: e9c51e0100 jmp 0x3a190 /* Wheel handling */ 000282cb: 90 nop 000282cc: 7432 je 28300 000282ce: 7711 ja 282e1 000282d0: 81fe41544144 cmp $0x44415441,%esi ; Message 'DATA' (B_SIMPLE_DATA) 000282d6: 0f8484000000 je 28360 ... Moving the BView This is the most important part of the code. We must first check if we deal with a wheel message or not. A look into Be's include file gives us that B_MOUSE_WHEEL_CHANGED is '_MWC' ie 0x5F4D5743. Then we will have to get the wheel's moving direction and scroll up or down (cf. part II). There is only one thing we are missing: The BTermview handle (we need it to call BTermView::scroll). We can find it easily with db by looking in other functions but this value will change each time the app is launched. So we have 2 solutions: Either we read this value from another function and store it in memory, either we find a place in memory where we will always find it. The second solution is easier since we don't have to patch some code. Happily I found it in the stack: The handle is at ebp-0x78. One last thing: At the begining of the scroll function the handle of the BMessage is stored in eax. Now we can write our function. Here is the code with comments: 0x3a190: 81fe43574D5F cmp $0x5F4D5743,%esi /* _MWC */ 0x3a196: 7555 jne NEXT_MESSAGE 0x3a198: 60 pusha /* backup all registers */ 6a00 push 00 b960a10380 mov $0x8003a160,%ecx /* "be:wheel_delta_y" */ 51 push %ecx /* push field */ 50 push %eax /* push message handle */ 0x3a1a2: e849e4fdff call 185f0 /* BMessage::FindFloat */ 0x3a1a7: ba74a10380 mov $0x8003a174,%edx /* wheel displacement addr */ d91a fstps (%edx) /* store the wheel displacement */ 83c40c add $0xc,%esp /* restore stack */ ba74a10380 mov 0x8003a174,%edx /* wheel displacement addr */ 8b12 mov (%edx),%edx /* put wheel displacement in edx */ b97c910380 mov 0x8003a17c, %ecx /* BView position addr */ 8b09 mov (%ecx), %ecx /* put BView position in ecx */ 81e200000080 and $0x80000000, %edx /* get wheel displacement sign */ 7410 jz DOWN /* if + scroll down */ UP: 83f92d cmp $0x2d, %ecx /* if current position is to near from top */ 7c08 jl SCROLL_TOP /* scroll to top of view */ 83e92d sub $0x2d, %ecx /* else sub 3 lines */ e907000000 jmp SCROLL_VIEW SCROLL_TOP: 31c9 xor %ecx;%ecx /* goto line 0 */ eb03 jmp SCROLL_VIEW DOWN: 83c12d add $0x2d, %ecx /* add 3 lines */ SCROLL_VIEW: 51 push %ecx 89e8 mov %ebp,%eax 83e878 sub $0x78,%eax /* gets BTermview handle */ 8b00 mov (%eax),%eax 50 push %eax 0x3a1e4: e8731affff call 2bc5c /* BTermView::scroll */ 0x3a1e9: 83c408 add $0x8,%esp /* restore stack */ HOOK_END: 61 popa /* restore registers */ NEXT_MESSAGE: 81fe646e6966 cmp $0x66696e64,%esi 0x3a1f3 e9d4e0feff jmp 0x282cc 0x3a1f8 Note: Unless you are masochistic, you won't want to find all the hex values by hand. To do this I used a BeIDE asm project template that was published on bebits severals weeks ago. This app is a simple window with a button (we don't care of it) and an asm function which is called when you press the button. I simply replaced the code with mine and recompiled the project (the asm function is compiled with nasm). After that you just have to copy/paste the hex values of the binary and modify the calls and long jumps. You can also compile it only with nasm but since I never used it before I prefered doing it this way. 3- Saving the BView position Finally we must save the View position each time it moves. This will be easier. Hooking the function We must hook the BTermView::scroll function to save the view"s position in memory. The same method than the message hook will be used: BTermView::scroll(unsigned int): 0002bc6b: 81c3d2c00100 add $0x1c0d2,%ebx 0002bc71: 8b7d08 mov 0x8(%ebp),%edi ; param 0 (Object handle) 0002bc74: 6a48 push $0x48 We will replace the add with a jump. The code that will save the position will start at offset 0x39200: 0002bc6b: e990e50000 jmp 0x3a200 0002bc70: 90 nop 0002bc71: 8b7d08 mov 0x8(%ebp),%edi ; param 0 (Object handle) 0002bc74: 6a48 push $0x48 Saving the position All we have to do here is store the first param at 0x8003a17c and go back to the scrolling function: 0x3a200: 81c3d2c00100 add $0x1c0d2,%ebx 0x3a206: 8b7d0c mov 0xc(%ebp),%edi 0x3a209: be7c910380 mov $0x8003a17c,%esi 0x3a20e 893e mov %edi,(%esi) 0x3a210 e95c1affff jmp 0x2bc71 0x3a215 IV- Conclusion That's all folks! Now you can play with your wheel in the terminal. Hope my explainations were clear enough. Before leaving you, here is the summary of things that were interesting in this reverse (in my opinion:-) and that may be usefull in other targets: How to use C++ methods in asm (Object handle is the first param). How to deal with messages fields. How to use the floating registers. Of course this was a very basic reverse. Since I'm sure you understood everything, you can improve this feature or add new ones (for example you may use the settings file to set how many lines the view will scroll at each wheel message). See you later, Zadig.