U-Boot Shell password protected

Home » Hawktesters Cybersecurity Research » U-Boot Shell password protected

In scenarios where you are looking to modify environment variables such as bootargs from U-Boot, it is critical to check if the device implements integrity verification mechanisms or signatures on those variables. This is especially critical on devices protected by Secure Boot or other forms of cryptographic validation.

Having access to the U-Boot shell is often a key gateway to reverse engineer, recover the system or modify the boot flow. But what happens when this access is blocked by password, automatic timeout or restricted commands?

This is a short list of the most common mechanisms:

  • 🔐 Password at the U-Boot prompt (CONFIG_AUTOBOOT_STOP_STR).
  • ⏱️ Extremely short or null timeout
  • 🔒 Lockout by disabled commands (by compilation)
  • 🧪 Environment validation (env) with signature or checksum
  • 📦 Protection by fixed layout in SPI/NAND/EMMC (change invalidates the load)

Let’s look at an example of a device that was recently analyzed and identified such protection behavior Password at the U-Boot prompt.

In this example it is assumed that you already have a firmware dump and the goal is to analyze the u-boot to extract the password protection.

The memory dump looks like this from binwalk.

26624         0x6800          uImage header, header size: 64 bytes, header CRC: 0x23E856C, created: 2022-03-17 04:15:42, image size: 155294 bytes, Data Address: 0x80100000, Entry Point: 0x0, data CRC: 0x3D78B201, OS: Firmware, CPU: MIPS, image type: Firmware Image, image name: "u-boot-lzo.img"

I use the following script to obtain the part we are interested in, U-Boot

#!/bin/bash

# ─────────────────────────────────────────────────────────────
# Script: extract_uboot.sh
# Extrae el U-Boot desde un firmware dump que contiene uImage LZO
# Autor: ChatGPT
# Uso: ./extract_uboot.sh flashdump.bin
# ─────────────────────────────────────────────────────────────

set -e

if [ $# -ne 1 ]; then
  echo "Uso: $0 <firmware.bin>"
  exit 1
fi

INPUT="$1"
UIMAGE_OFFSET=26624         # Offset del encabezado uImage (0x6800)
UIMAGE_HEADER=64            # Tamaño de cabecera uImage
LZO_OFFSET=$((UIMAGE_OFFSET + UIMAGE_HEADER))
LZO_SIZE=524288             # 512 KB (más que suficiente para U-Boot + padding)

echo "[+] Extrayendo bloque LZO desde offset $LZO_OFFSET..."
dd if="$INPUT" of=u-boot.lzo bs=1 skip=$LZO_OFFSET count=$LZO_SIZE status=none
echo "[+] Bloque LZO extraído como: u-boot.lzo"

echo "[+] Descomprimiendo LZO..."
lzop -d u-boot.lzo -o u-boot.bin || {
  echo "[-] Error al descomprimir u-boot.lzo"
  exit 2
}

echo "[+] U-Boot descomprimido correctamente: u-boot.bin"

# Validación rápida
echo "[+] Cadena U-Boot encontrada:"
strings u-boot.bin | grep -i 'U-Boot' || echo "(sin coincidencias)"

exit 0

This will help us to extract the part of the firmware we are interested in, and will allow us to perform reverse engineering using a disassembler like ghidra for example.

By executing the script we get the U-Boot part of the firmware.

Once U-Boot has been extracted, we search for strings belonging to U-Boot and verify that it has been successfully extracted.

strings u-boot.bin | grep -Ei 'U-Boot|bootcmd|bootdelay|bootargs|console'

A key string that has the timeout to enter the password before starting the firmware is as follows.

strings u-boot.bin | grep -Ei 'Autobooting'                              

Autobooting in %d seconds

So if we position ourselves in the desensitizer and look for that string we can see what is behind this, note that at the prompt you usually do not see a message asking you to enter a password to enter the U-Boot shell, this is something silent that is evaluated under the hood.

If we go to the reference of this string, we can see that lines below, there is the password validation, which in this case for the camera device tplink tapo c200 is `slp`.

Let’s see the most relevant code fragments, this is my code unwrapped in ghidra at the general level of the function we are interested in.


void FUN_80110558(undefined4 param_1,uint *param_2,uint param_3,char ********param_4)

{
  undefined *puVar1;
  char pReadPasswordPermit;
  bool bVar2;
  byte *pbVar3;
  uint *puVar4;
  char *pcVar5;
  uint uVar6;
  uint uVar8;
  char *pcVar9;
  int iVar10;
  char *pcVar11;
  uint uVar12;
  undefined3 extraout_var;
  uint *puVar13;
  undefined4 uVar14;
  undefined4 uVar15;
  uint *puVar16;
  char ********ppppppppcVar17;
  uint uVar18;
  undefined *puVar19;
  char *pcVar20;
  uint local_e0;
  uint local_dc [4];
  undefined4 local_cc;
  char *p2ReadPasswordPermit;
  int local_bc;
  uint uStack_b0;
  uint auStack_ac [18];
  char acStack_61 [33];
  char *******apppppppcStack_40 [2];
  uint *local_38;
  char ********local_34;
  uint *local_30;
  char ********local_2c;
  char *pcVar7;
  
  pbVar3 = (byte *)FUN_80109434(PTR_DAT_8013022c + -0x3d78,param_2,param_3,param_4);
  if (pbVar3 == (byte *)0x0) {
    puVar4 = (uint *)0x1;
  }
  else {
    param_2 = (uint *)0x0;
    param_3 = 10;
    puVar4 = (uint *)FUN_8011d924(pbVar3,(undefined4 *)0x0,10);
  }
  pcVar5 = (char *)FUN_80109434(PTR_DAT_8013022c + -0x7568,param_2,param_3,param_4);
  if ((puVar4 != (uint *)0xffffffff) && (pcVar5 != (char *)0x0)) {
    uVar6 = FUN_80100614();
    uVar14 = 0;
    param_3 = 0x30;
    FUN_8011cd7c(&local_e0,0,0x30);
    local_e0 = FUN_80109434(PTR_DAT_8013022c + -0x3d6c,uVar14,param_3,param_4);
    local_dc[1] = 1;
    local_dc[2] = FUN_80109434(PTR_DAT_8013022c + -0x3d5c,uVar14,param_3,param_4);
    local_cc = 1;
    pcVar7 = (char *)FUN_80109434(PTR_DAT_8013022c + -0x3d4c,uVar14,param_3,param_4);
    p2ReadPasswordPermit = pcVar7;
    local_bc = FUN_80109434(PTR_DAT_8013022c + -0x3d40,uVar14,param_3,param_4);
    if (puVar4 != (uint *)0x0) {
      param_2 = puVar4;
      AutobootingFunction(PTR_DAT_8013022c + -0x3d30,(uint)puVar4,param_3,param_4);
      if (pcVar7 == (char *)0x0) {
        p2ReadPasswordPermit = PTR_DAT_8013022c + -0x3d14;
      }
      puVar13 = &local_e0;
      uVar18 = 0;
      do {
        if ((char *)*puVar13 == (char *)0x0) {
          uVar8 = 0;
        }
        else {
          uVar8 = FUN_8011cc78((char *)*puVar13);
        }
        if (0x20 < uVar8) {
          uVar8 = 0x20;
        }
        puVar13[1] = uVar8;
        puVar13 = puVar13 + 3;
        if (uVar18 < uVar8) {
          uVar18 = uVar8;
        }
      } while (puVar13 != &uStack_b0);
      pcVar9 = (char *)FUN_80100518(0);
      pcVar7 = acStack_61 + 1;
      uVar8 = 0;
      pcVar20 = pcVar7 + uVar18;
      do {
        iVar10 = FUN_8010b590(pcVar7,(uint)param_2,param_3,param_4);
        if (iVar10 != 0) {
          pcVar11 = acStack_61 + 2;
          if (uVar8 < uVar18) {
            uVar14 = ReadInputUsers(pcVar7,(uint)param_2,param_3,param_4);
            acStack_61[uVar8 + 1] = (char)uVar14;
            uVar8 = uVar8 + 1;
          }
          else {
            for (; pcVar11 != pcVar20; pcVar11 = pcVar11 + 1) {
              pcVar11[-1] = *pcVar11;
            }
            uVar14 = ReadInputUsers(pcVar7,(uint)param_2,param_3,param_4);
            acStack_61[uVar18] = (char)uVar14;
          }
        }
        puVar13 = local_dc;
        puVar16 = auStack_ac;
        param_4 = (char ********)0x0;
        ppppppppcVar17 = (char ********)0x1;
        do {
          param_3 = *puVar13;
          if ((param_3 != 0) && (param_3 <= uVar8)) {
            param_2 = (uint *)puVar13[-1];
            local_38 = puVar13;
            local_34 = param_4;
            local_30 = puVar16;
            local_2c = ppppppppcVar17;
            iVar10 = strncmp(acStack_61 + (uVar8 - param_3) + 1,(char *)param_2,param_3);
            puVar13 = local_38;
            param_4 = local_34;
            puVar16 = local_30;
            ppppppppcVar17 = local_2c;
            if (iVar10 == 0) {
              param_4 = local_2c;
            }
          }
          puVar13 = puVar13 + 3;
        } while (puVar13 != puVar16);
        if (param_4 != (char ********)0x0) goto LAB_80110950;
        pcVar7 = pcVar9;
        uVar12 = FUN_80100518((int)pcVar9);
      } while ((((int)puVar4 >> 0x1f) * uVar6 + (int)((ulonglong)uVar6 * ZEXT48(puVar4) >> 0x20) !=
                0) || (uVar12 <= (uint)((ulonglong)uVar6 * ZEXT48(puVar4))));
    }
    uVar14 = FUN_8010b7b4(1);
    FUN_801103e0(pcVar5,0xffffffff);
    FUN_8011cd7c(&local_e0,0,0x80);
    FUN_8011e180((char *)&local_e0,PTR_DAT_8013022c + -0x3d10,0x80200000,0x60000);
    FUN_801103e0((char *)&local_e0,0xffffffff);
    param_4 = apppppppcStack_40;
    FUN_80120968((char *)0x80200000,0x20000,(byte *)0x80600000,(int *)param_4);
    FUN_8011cd7c(&local_e0,0,0x80);
    uVar15 = 0x80600000;
    pcVar5 = PTR_DAT_8013022c + -0x3cec;
    FUN_8011e180((char *)&local_e0,pcVar5,0x80600000,param_4);
    AutobootingFunction((char *)&local_e0,(uint)pcVar5,uVar15,param_4);
    param_2 = (uint *)0xffffffff;
    param_3 = 0;
    FUN_801103e0((char *)&local_e0,0xffffffff);
    FUN_8010b7b4(uVar14);
  }
LAB_80110950:
  puVar1 = PTR_DAT_8013022c;
  iVar10 = 1;
  ppppppppcVar17 = (char ********)(PTR_DAT_8013022c + -0x4840);
  pcVar5 = PTR_DAT_8013022c + 0x18ac;
  puVar19 = PTR_DAT_8013022c + -0x3ce4;
  do {
    param_2 = (uint *)FUN_8010fed8(ppppppppcVar17,param_2,param_3,param_4);
    if ((int)param_2 < 1) {
      if (param_2 != (uint *)0xffffffff) {
        param_2 = (uint *)(uint)(param_2 == (uint *)0x0);
        goto LAB_801109c0;
      }
      FUN_8010b61c(puVar19);
    }
    else {
      FUN_8011cb80(pcVar5,DAT_801303bc);
      param_2 = (uint *)0x0;
LAB_801109c0:
      bVar2 = FUN_801103a4(pcVar5,param_2);
      iVar10 = CONCAT31(extraout_var,bVar2);
    }
    if (iVar10 < 1) {
      puVar1[0x18ac] = 0;
    }
  } while( true );
}

The disassembled code in function FUN_80110558 shows that the Tapo C200’s U-Boot implements a password validation routine during early boot.

  1. The allowed password is loaded from a fixed memory offset:
p2ReadPasswordPermit = PTR_DAT_8013022c + -0x3d14;  // → "slp"

User input is read character-by-character into buffer acStack_61 using repeated calls to ReadInputUsers().

The allowed password length is calculated dynamically and capped at 0x20 (32 bytes).

A loop compares the user input against the valid password "slp" using strncmp():

strncmp(acStack_61 + (uVar8 - len) + 1, "slp", len)
  1. If there’s an exact match, boot proceeds. Otherwise, it loops again or blocks after timeout.

🔍 The validation is plain text, no hashing or encryption, making the password easy to extract from the binary or SPI flash.

So once you boot the device and type the password you will have a beautiful u-boot shell.

Samir Sánchez Garnica

Hi, Samir Sanchez Garnica is a seasoned Purple Team professional with over 12 years of expertise in ethical hacking, specializing in security testing across web environments, cloud platforms (Azure, AWS, Google Cloud), and on-premise infrastructures—with a primary focus on the banking sector. His extensive experience encompasses mobile application security, reverse engineering, network team exercises, and social engineering initiatives. A passionate programmer, Samir continually enhances his work through the automation of pentesting processes, leveraging his proficiency in SHELLSCRIPT, Python3, PHP, C, JavaScript, PowerShell, Objective-C, Node.js, Dart, and Assembly Language. Samir’s current endeavors are centered on reverse engineering, where he excels as both a reverser and shellcode writer across Windows, macOS, and GNU/Linux environments, spanning user land and kernel land. His latest research efforts delve into debugging within iOS mobile environments, IoT technologies, and the intricacies of reversing on MIPS and ARM architectures, with a specialized focus on radio frequency-based hardware exploitation.

Post navigation

Leave a Comment

Leave a Reply

Your email address will not be published. Required fields are marked *