Didier Stevens

Saturday 6 December 2025

Quickpost: USB-C Rechargeable Batteries

Filed under: Hardware,Quickpost — Didier Stevens @ 10:52

I discovered USB-C rechargeable batteries, and bought a set of AA and AAA batteries.

They have a USB-C connector for recharging, so you don’t need a separate charger like you do for NiMH batteries.

This post is not a full blog post, but more a collection of lab notes.

These USB-C batteries deliver 1,5 Volt (unlike NiMH batteries that deliver 1,3 Volt). And during discharge tests, I noticed that the voltage almost doesn’t change. So not only must they have battery charger electronics inside, but also converter electronics that deliver a constant voltage. Probably something like a switching-mode power supply circuit, because when I look at the ripple of the voltage with an oscilloscope, I see a pattern that makes me think of a switching-mode power supply:

That’s for a AA battery that delivers power to an electronic load that draws 0,100 A current:

The ripple could also come from the electronic load itself, or some electronic noise source in my lab. So to rule that out, I discharged an alkaline battery and got this:

This is a different pattern and it repeats with a different frequency, to the ripple we saw in the first scope picture must come from the battery.

I also did measurements with a spectrum analyzer:

Here you can see a peak (and its harmonics) around 1,20 MHz.

That too comes from the battery, as these peaks do not appear with an alkaline battery:

In the picture of the electronic load screen, one can see 1493 mWh: that’s for the discharge of an AA battery at 0,100 A until the voltage reaches 0,5 V. 1493 is far less than the 3400 mWh printed in a large font on each battery.

I did a series of tests with my AA (0,100 A discharge current) and AAA (0,025 A discharge current) batteries, and on average I get:

TypeMeasured output (mWh)Advertized outputMeasured input (mWh)RTE
AA15273400211472%
AAA478120075463%

Unfortunately, these batteries deliver far less electrical energy than advertized.

For comparison, I also discharged an fresh alkaline AAA battery and got 1380 mWh out of it.

I created a discharge graph for a USB-C rechargeable AA battery:

During more than 9 hours, the voltage stays around 1,45 V (for a 0,100 A discharge current). Then it abruptly drops to 1,05 V, and then 0 V.

Charging the AA batteries requires 2114 mWh on average, the AAA batteries require 754 mWh. This is also far less than the advertized capacity. This allowed me to calculate the Round Trip Efficiency (RTE) in the table above.

Despite the discrepancy in capacity, these batteries have advantages too:

  • the nominal voltage is 1,5 Volt
  • the voltage curve remains (mostly) flat while discharging
  • their chemistry doesn’t result in battery leaks that corrode your electronics
  • you don’t need a battery charger

Disadvantages:

  • far less capacity than advertized
  • very abrupt voltage drop when fully discharged
  • they can’t negotiate power with a USB charger (you can’t charge them with a USB-C to USB-C cable, you must use a USB-A to USB-C cable like the one included)
  • some electronic noise because of the switching power supply

Quickpost info

Friday 5 December 2025

Quickpost: USB Electric Razor

Filed under: Hardware,Quickpost — Didier Stevens @ 20:26

The USB Power Delivery protocol (USB PD) defines power negotiation.

For example, USB trigger boards negotiate power requirements with a USB power source (like a charger or a powerbank), and can ask for a higher voltage than the standard 5V of a USB source that does not support USB PD.

This also explains why there are (cheap) devices with a USB-C port, that can only be powered with a USB-A to USB-C cable, and not with a USB-C to USB-C cable. These devices are not capable to negotiate their power requirements, they expect 5.0 Volts on the VBUS pins. These devices do not support USB PD.

USB PD made it possible to invent all kinds of gadgets, adapters, …

One adapter I want to talk about in this blog post, allows me to charge my Philips electric razor with a USB-C powerbank.

My Philips electric razor has its own proprietary charger and connector, operating at 15 Volts. When I travel, I need to remember to bring along the charger (it does not fit in the razor’s case).

But now I have this adapter:

On one end, it has a USB-C receptacle:

And on the other end, it has the Philips proprietary razor plug.

The adapter negotiates 15 volts with the USB power source:

I didn’t know such adapters existed, but while experimenting with USB PD, I realized that it makes such adapters possible, and so I started to search and found one on AliExpress.

This allows me to travel without an extra charger, just with a very small adapter (and a USB power source).


Quickpost info

Monday 1 December 2025

Overview of Content Published in November

Filed under: Announcement — Didier Stevens @ 0:00

Here is an overview of content I published in November:

Blog posts:

SANS ISC Diary entries:

Sunday 30 November 2025

Copy/Paste Delays In Excel Because Of Default Printer

Filed under: Networking — Didier Stevens @ 0:00

I experienced delays in Excel whenever I would copy/paste some cells, like this:

A delay of 1 to several seconds was clearly noticeable and inconvenient.

I started to review what had recently changed on my Windows computer. Turns out this was caused by a printer setting: I had recently set a default printer (a network printer), and whenever that printer was not online, I would experience the delay.

I removed that setting and no longer have the copy/paste delay.

Saturday 29 November 2025

Quickpost: CR1225 vs CR1220

Filed under: Hardware,Quickpost — Didier Stevens @ 0:00

I had to replace a button cell, a CR1225, but I only had a CR1220.

So I just used that CR1220 in stead. This works, because a CR1220 and CR1225 differ in mechanical properties (dimension), but not in electrical properties (voltage).

Both cells have a nominal voltage of 3 Volts.

CR1220 means the following:

  • C: Lithium battery
  • R: Round
  • 12: 12mm diameter
  • 20: 2,0mm thickness

The difference between a CR1220 and CR1225 is the thickness: a CR1220 is 0,5mm slimmer than a CR1225. So a CR1220 fits in a CR1225 holder without problem.


Quickpost info

Friday 21 November 2025

Quickpost: Power Requirements Of A Keylogger

Filed under: Hardware,Quickpost — Didier Stevens @ 0:00

I did some tests with a Keelog keylogger, the AirDrive Forensic Keylogger: I wanted to find out how much power that keylogger requires.

This is my test setup:

  1. This is the USB keyboard
  2. The USB cable of the keyboard is plugged into the USB breakout board
  3. This is the USB breakout board, allowing me to measure the voltage and current of the USB power lines
  4. This is specialized multimeter that can measure power (by measuring voltage and current simultaneously)
  5. The USB cable of the USB breakout board is plugged into a USB extension cable that is plugged into a computer

In this standby state, with all its LEDs turned of, the keyboard consumes 11 mW.

That’s not much power. Compare this with the Numlock LED turned on, and we have 4 times as much: 47 mW:

And here I have the keylogger plugged in (between the keyboard and the USB breakout board):

Now the total power measured is 383 mW: that’s for the keyboard (with LEDs turned off) and the keylogger.

That’s a huge difference with 11 mW for a keyboard without keylogger.

If this keylogger would be hidden into the keyboard, it would be easily detected using this measurement method, because this particular keyboard requires 30+ times less power than the keylogger itself.


Quickpost info

Saturday 15 November 2025

Update: numbers-to-hex.py Version 0.0.4

Filed under: My Software,Update — Didier Stevens @ 10:13

This update add option -e to handle binary numeric expressions like 79+1.

numbers-to-hex_V0_0_4.zip (http)
MD5: 8CD22E998E84F80D1FD92504B3D3A559
SHA256: 6963ED3F013D9C6E70ACA95DA00399B0F95DD279597EABE5BA1EC51E0B28DD4D

Monday 3 November 2025

Update: cs-parse-traffic.py Version 0.0.6

Filed under: My Software,Update — Didier Stevens @ 11:28

This is a bugfix version.

cs-parse-traffic_V0_0_6.zip (http)
MD5: AED53E99D7BFF14EC45F573663A91780
SHA256: C73614FD69660C4D0E851414D86091E9E90DE9A92D58F9E6AC71D76B4A6EC638

Saturday 1 November 2025

Overview of Content Published in October

Filed under: Announcement — Didier Stevens @ 7:38
Here is an overview of content I published in October:

Blog posts: SANS ISC Diary entries:

Monday 27 October 2025

Bytes over DNS Tools

Filed under: Hacking,Networking — Didier Stevens @ 9:07

Here are the tools I used to conduct my “Bytes over DNS” tests.

On the server side, I start my dnsresolver.py program with the following custom script:

LOGFILENAME = 'bod-dnsresolver-test.log'

def BoDTest(request, reply, dCommand):
    if request.q.qtype == dnslib.QTYPE.A:
        if len(request.q.qname.label[2]) == 1 and int(request.q.qname.label[1].decode(), 16) == ord(request.q.qname.label[2]):
            with open(LOGFILENAME, 'a') as fOut:
                print(f'BYTE_EQUAL {request.q.qname.label[1]} {request.q.qname.label[2]}', file=fOut)
            qname = request.q.qname
            answer = '. 60 IN A 127.0.0.1'
            for rr in dnslib.RR.fromZone(answer):
                a = copy.copy(rr)
                a.rname = qname
                reply.add_answer(a)
            return False, None
        else:
            with open(LOGFILENAME, 'a') as fOut:
                print(f'BYTE_DIFFERENT {request.q.qname.label[1]} {request.q.qname.label[2]}', file=fOut)
    return True, None

Start it as follows: dnsresolver.py -s bod-dnsresolver-test.py type=resolve,label=bytes,function=BoDTest

And make sure your DNS glue records (e.g., for mydomain.com) point to your server.

Then you can do a small test: nslookup bytes.3D.=.mydomain.com.

This will return 127.0.0.1 when the request arrives unaltered, and NXDOMAIN when it is altered. The BoDTest function will also log the results in text file bod-dnsresolver-test.log.

Then, on your workstation, you can run the following script to test all bytes values in the DNS request via the API of your OS:

#!/usr/bin/env python3

import socket
import sys

DOMAIN = '.mydomain.com.'

def DNSResolveA(char: int):
    hostname_ascii = 'bytes.%02x.%s' % (char, chr(char)) + DOMAIN
    hostname_ascii = hostname_ascii.replace('\\', '\\\\')
    print(hostname_ascii)
    try:
        results = socket.getaddrinfo(hostname_ascii, None, family=socket.AF_INET, type=0, proto=0, flags=socket.AI_CANONNAME)
    except socket.gaierror as e:
        print(f"Resolution failed: {e}")
        return 1
    except UnicodeError as e:
        print(f"Resolution failed: {e}")
        return 1

    if not results:
        print("No results returned by getaddrinfo.")
        return 0

    # Collect canonical name (may be empty) and addresses
    canon_names = set()
    addresses = []
    for res in results:
        family, socktype, proto, canonname, sockaddr = res
        if canonname:
            canon_names.add(canonname)
        # sockaddr is a tuple; for IPv4 it's (addr, port), for IPv6 it's (addr, port, flowinfo, scopeid)
        ip = sockaddr[0]
        addresses.append((family, ip))

    if canon_names:
        print("Canonical name(s):")
        for cn in sorted(canon_names):
            print("  -", cn)
        print()

    # Deduplicate and group by family
    unique_ips = {}
    for fam, ip in addresses:
        fam_name = "IPv4" if fam == socket.AF_INET else ("IPv6" if fam == socket.AF_INET6 else str(fam))
        unique_ips.setdefault(fam_name, set()).add(ip)

    for fam_name in sorted(unique_ips.keys()):
        print(f"{fam_name} addresses ({len(unique_ips[fam_name])}):")
        for ip in sorted(unique_ips[fam_name]):
            print("  -", ip)
    print()

    # Optionally, try reverse DNS for each IP (may be slow / not always available)
    print("Reverse DNS (PTR) lookups:")
    for fam_name, ips in unique_ips.items():
        for ip in sorted(ips):
            try:
                host, aliases, _ = socket.gethostbyaddr(ip)
                print(f"  {ip} -> {host}")
            except Exception as e:
                print(f"  {ip} -> (no PTR)  [{e}]")

    return 0

if __name__ == "__main__":
    for char in range(256):
        DNSResolveA(char)

Use this script to perform the tests via the dnspython/dns.resolver Python module:

import dns.resolver

resolver = dns.resolver.Resolver()
DOMAIN = b'.mydomain.com.'

#resolver.nameservers = ['127.0.0.1']
#resolver.nameservers = ['1.1.1.1']
resolver.nameservers = ['8.8.8.8']

for i in range(256):
    if i == 0x2E:
        continue
    if i == 0x5C:
        byte = b'\\\\'
    else:
        byte = bytes([i])
    try:
        answer = resolver.resolve(((b'bytes.%02x.%s' + DOMAIN) % (i, byte)).decode('latin'), "A")
        for rdata in answer:
            print(i, rdata.to_text())
    except (dns.name.LabelTooLong, dns.resolver.NXDOMAIN) as e:
        print(i, e)

And use this script to perform the tests by crafting your own DNS packets:

import socket

DOMAIN = b'mydomain.com.'
DNS = '1.1.1.1'
DNS = '8.8.8.8'

def send_udp_payload(data: bytes, target_ip: str, port: int = 53) -> None:
    """
    Send raw binary data via UDP to a target IP and port (default 53).
    
    :param data: The binary payload to send (must be bytes).
    :param target_ip: The destination IP address (string).
    :param port: Destination UDP port (default = 53).
    """
    # Create UDP socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        sock.sendto(data, (target_ip, port))
        print(f"Sent {len(data)} bytes to {target_ip}:{port}")
    except Exception as e:
        print(f"Error sending data: {e}")
    finally:
        sock.close()

def DNSEncodeDomain(domain):
    labels = domain.split(b'.')
    if labels[-1] != b'':
        labels.append(b'')
    data = bytearray()
    for label in labels:
        data += bytes([len(label)])
        data += label
    return data

data = bytearray([0x88, 0xea, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x02, 0x32, 0x65, 0x01, 0x2e]) + DNSEncodeDomain(DOMAIN) + bytearray([0x00, 0x01, 0x00, 0x01])

for i in range(256):
    data[1] = i
    data[22] = i
    hexvalue = b'%02x' % i
    data[19:21] = hexvalue
    print(data)
    send_udp_payload(data, DNS)

« Previous PageNext Page »

Blog at WordPress.com.