📜 ⬆️ ⬇️

Teach telnet.exe to play the correct MUDs

image

Once, having decided to play something unusual, I turned my eyes on MUDs - text-based computer multiplayer games with chat. You can play them either with the help of specialized clients written for specific servers, or via telnet .

Choosing one of the currently existing servers (https://www.bat.org/), I armed myself with the default telnet client for Windows and ... I felt frustrated. No, it's not about the game at all, but about how telnet.exe interacts with this game. Sad to know, but none of the characters I entered (character name, various actions etc) were displayed on the console screen. Yes, the teams were sent by pressing the Enter key, but the absence of even a minimal interactive made such a game almost impossible (it was especially inconvenient to delete the characters entered earlier, because you had to count in your mind how many characters you had already deleted and on which you are currently).
')
Without thinking twice, I decided to try to connect to the same server using putty and ... Wow ! I see the characters I entered!

Why does echo not work in telnet.exe? Is there any way to fix this? Let's figure it out.

How was the process, and what came of it, read under the cut. Before reading this article, I also strongly recommend that you familiarize yourself with the previous ones , since they have already explained many of the points outlined here.

The first step is to get the test subject. Install the telnet client (Win-R -> appwiz.cpl -> Turn Windows features on or off -> tick the box next to “Telnet Client” and click on the “OK” button) and copy the executable file telnet.exe from “% WINDIR% \ System32 "to any other directory.

The next step is to arm yourself with the necessary tools. Download PE Tools and OllyDbg , which I have already mentioned many times in previous articles , and unpack them in any convenient directory.

Next, you need to understand whether ASLR technology is enabled for the binary that we are going to explore. Start PE Tools, press Alt-1, select telnet.exe and click on the “Optional Header” button:

image

Yes, ASLR is enabled. Let's turn it off - we replace 0x8140 with 0x8100 (why this has already been explained earlier - see, for example, here ) and click on the “Ok” button.

So, what thoughts? The first thing that came to my mind is that the application can explicitly disable echo using the SetConsoleMode WinAPI function. We launch our binary in OllyDbg, open the window with the list of intermodule calls and see that the calls of this function are really present in the application:

image

We set breakpoints on them, press F9 and stop at one of the breakpoints:

image

Let's look at the arguments in the stack window:

image

We read documentation:

ENABLE_ECHO_INPUT
0x0004

The characters are read by the ReadFile or ReadConsole. This mode can be used only if the ENABLE_LINE_INPUT mode is also enabled

Exactly what is needed! However, there is an easier way to do this - simply do not call this function:

ENABLE_WINDOW_INPUT are enabled by default when a console is created.

Let's restart debugging, close its call.

image

and check if echo works now. No, the result is the same as before - the entered characters are not displayed on the console screen.

Okay, let's wait for the moment when the game asks for a name.

image

, and press F12 (Pause) in OllyDbg.

I propose to look around to understand where we are at the moment. First, open the Call Stack by pressing Alt-K:

image

So, we hang somewhere in the depths of user32.dll. We jump to the closest “user” code (that is, the code that belongs to the telnet module) located at 0x0100D0D0 , from where we ended up in user32.dll:

image

An experienced Windows developer should already understand what address of the function is most likely in the EDI register at the time of execution of the selected instruction - GetMessage . But let's see for yourself. Set the bryak to this address, restart debugging and press F9 before getting to the required place:

image

As you can see, this is really GetMessage . The problem in our case is that this function will not return control to the code that called it before pressing the Enter key, which means that it has nothing to do with echo.

Then let's take a look at what the other streams are doing at this moment (if they, of course, exist at all). Run the program again for further execution using F9, press F12 and open the “Threads” window (View -> Threads):

image

Open each of them in the CPU window (right-click on the corresponding line in the Threads window -> Open in CPU), except for the one highlighted in red (this is the current thread we just looked at), and look at their Call Stacks. Your attention should have attracted the flow with the following call stack:

image

ReadConsoleInput is a more interesting function in our case. We put the bryak on her call, restart debugging and ... We stop on it every time we transfer the focus to the telnet window:

image

Notice that there is a switch nearby, in which, most likely, a jump is made to the handler of the corresponding event. By running in the debugger, you can see that in case of a change of focus, control is transferred to the default case:

image

Judging by the code analysis carried out by OllyDbg, there are not so many options here - besides the default case, we also have cases 10 and 1, the first of which, after executing several instructions, jumps to the newly considered default case . Let's try to remove the breakpoint from the call to the ReadConsoleInput function and put the breakpoint on case 1:

image

Restart debugging, wait for a message asking to enter a name, press '1' and dwell on this very case block:

image

What can we do now? And now you can verify the operation of telnet.exe in case of a connection to bat.org and, say, smtp.gmail.com, where, as I remember, echo worked correctly. Open the window “Run trace” (View -> Run trace), right-click on it, select the menu item called “Log to file”, select any file name and press Ctrl-F11 (Trace into). After performing the trace, close the file (right-click on the window “Run trace” -> Close log file) and do the same in the case of smtp.gmail.com:25 (if you explicitly specify the port, telnet needs to separate the IP address from it with a space character, i.e. the command should look like this - “telnet.exe smtp.gmail.com 25”).

A noticeable difference in behavior begins with the address 0x0100A2F9 :

In the case of bat.org
 Address Thread Command;  Registers and comments
 0100AB9F 00002EA0 JNZ telnet.0100AED2
 0100ABA5 00002EA0 TEST BYTE PTR SS: [EBP-24], 3
 0100ABA9 00002EA0 JE telnet.0100AED2
 [...]
 0100A2F7 00002EA0 TEST EAX, EAX
 0100A2F9 00002EA0 JNZ SHORT telnet.0100A304
 0100A2FB 00002EA0 TEST BYTE PTR DS: [1010740], 10
 [...]

In the case of smtp.gmail.com
 Address Thread Command;  Registers and comments
 0100AB9F 00002EA0 JNZ telnet.0100AED2
 0100ABA5 00002EA0 TEST BYTE PTR SS: [EBP-24], 3
 0100ABA9 00002EA0 JE telnet.0100AED2
 [...]
 0100A2F7 000031D4 TEST EAX, EAX
 0100A2F9 000031D4 JNZ SHORT telnet.0100A304
 0100A304 000031D4 PUSH EDI;  Arg4 = 01024CA0
 [...]


In the case when telnet.exe communicates with bat.org, the jump to the address 0x0100A304 is not performed. Let's make the instruction at address 0x0100A2F9 an unconditional jump. Restart debugging, go to the “telnet” module, press Ctrl-G, enter the address 0x0100A2F9 in the window that appears and press Enter. Now press the space bar and replace the JNZ instruction with JMP :

image

Press F9, enter '1' in the telnet window to the request to choose one of the options or enter a name and ... See the character entered by us:

image

If you run in the debugger, you can see that now we are in the code branch, where calls of such WinAPI functions, such as SetConsoleCursorPosition and WriteConsoleOutputCharacter, are made :

image

Why, then, did we not get here before? Let's see what made the decision to make the jump:

image

It depended on the result of the operation TEST EAX, EAX , and the value of EAX in the register fell from the address 0x01010754 , as seen in the previous screenshot. Well, let's try to understand why it was zero in the case of bat.org.

In order to find out, I propose to put a hardware breakpoint on the record at 0x01010754 . To jump on it, right-click on the instruction located at 0x0100A2BD -> Follow in Dump -> Memory address:

image

Right-click on the first byte at the specified address -> Breakpoint -> Hardware, on write -> Dword. Restart debugging and find the last access to the address 0x01010754 , when it gets zero. Such an appeal is here:

image

If we look at the Call Stack and skip to the procedure from which we were called here, we will see the call to the recv function and then parse the incoming data:

image

Note the constant 0xFF. According to the telnet specification , then this byte is followed by the commands used in this protocol:

    The following are the defined TELNET commands.  Note that these codes
    immediately
    preceded by an IAC.

       NAME CODE MEANING

       SE 240 End of subnegotiation parameters.
       NOP 241 No operation.
       Data Mark 242 The Synch.
                                  This should always be accompanied
                                  by a TCP Urgent notification.
       Break 243 NVT character BRK.
       Interrupt Process 244 The function IP.
       Abort output 245 The function AO.
       Are You There 246 The function AYT.
       Erase character 247 The function EC.
       Erase Line 248 The function EL.
       Go ahead 249 The GA signal.
       SB 250 Indicates that what follows is
                                  subnegotiation of the indicated
                                  option.
       WILL (option code) 251 Indicates the desire to begin
                                  performing, or confirmation that
                                  you are now performing, the
                                  indicated option.
       WON'T (option code) 252 Indicates the refusal to perform,
                                  or continue performing, the
                                  indicated option.
       DO (option code) 253 Indicates the request that the
                                  other party perform, or
                                  confirmation that you are expecting
                                  to the party
                                  indicated option.
       DON'T (option code) 254 Indicates the demand that the
                                  other party stop performing,
                                  or confirmation that you are no
                                  longer expecting the other party
                                  to perform, the indicated option.
       IAC 255 Data Byte 255.


Looking at the stack, you can see that we are confronted with the byte sequence 0xFF 0xF9, which stands for a command called “Go ahead”. About her on the Microsoft website reported the following:

The original Telnet implementation defaulted to half duplex operation. It can be used as a guideline. "This is what the radio has adopted."

For some reason, in the implementation of the telnet client from Microsoft, this command affects echo without returning the contents at 0x01010754 to a nonzero value.

You can verify this by writing a small server in Python:

import socket, threading s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('', 1900)) s.listen(1) class daemon(threading.Thread): def __init__(self, (socket, address)): threading.Thread.__init__(self) self.socket = socket self.address = address def run(self): self.socket.send('Greetings!') while True: data = self.socket.recv(1024) if data[0] == '1': data = 'Response' elif data[0] == '2': data = bytearray() data.append(0xFF) data.append(0xF9) self.socket.send(data); self.socket.close() while True: daemon(s.accept()).start() 

If you start this server and connect to it using the “telnet.exe 127.0.0.1 1900” command, then we will be able to observe that echo will work exactly until we receive an answer to our command '2':

Greetings! 1Response1Response1Response1Response2ResponseResponseResponseResponseResponseResponse

But that is not all! In fact, other teams also have similar behavior. For example, the byte sequence 0xFF 0xF1, denoting “No operation,” absolutely also “disables” echo in a telnet client.

Bug Feature? Who knows. The main thing is that now we have taught our telnet.exe to the correct game of MUDs!

Afterword


Of course, the solution is not perfect yet. For example, when you press the Backspace key, the character in front of the cursor is not deleted (however, the “internal” view of the command entered by the user changes, as expected). Yes, this is just a cosmetic moment and you can put up with it, but we started this article with cosmetic inconveniences, right?

Thank you for your attention, and again I hope that the article was useful to someone.

Source: https://habr.com/ru/post/262009/


All Articles