This post will explaining about how the TV-Box are able to achive peak level of the use case, I built an exploit development homelabs from cheap TV-Box and its called Pwnadol.

Avatar

Zuhri

  ( 16 min read )

Historical Intros #

Trondol begins #

Its start from curiousity about “What the peak level of this machine?”, and then landed into tinkering how to make this TV-Box becoming a Linux server.

Trondol

After becoming a Linux server, I named it "Trondol". Then I've thought "how to make this TV-Box (Trondol) are able to work with other TV-Box?".

Trondol Cluster

Then this things evolved, I call it "Trondol Cluster", after its work together in Cluster system. What if work together but in security areas?.

Infosec Trondol

Its evolved for 2nd times, The OG "Infosec Trondol". Aren't done yet, After this things are able to work in security areas.

Pwnadol

What if get some low level workspace? like binary layer? Then it's evolved, proudly present, the peak level of TV-Box. Pwnadol Homelabs, with no cluster.

What is Pwnadol #

Pwnadol is a low cost and highly efficient exploit development homelabs that I built with formerly Android TV-Box device.

Why dont we just use pi’s? what make Pwnadol difference from any kind of pi’s? It’s ARM64 right?

Yes its ARM64 device, but a TV-Box are much more cheaper than comparable any pi’s. Just it, lets build our homelabs, btw here is the CPU details.

[ root@labs: ~ ]># lscpu | head -n10
Architecture:                            aarch64
CPU op-mode(s):                          32-bit, 64-bit
Byte Order:                              Little Endian
CPU(s):                                  4
On-line CPU(s) list:                     0-3
Vendor ID:                               ARM
Model name:                              Cortex-A53
Model:                                   4
Thread(s) per core:                      1
Core(s) per cluster:                     4
[ root@labs: ~ ]>#

What is Radare2 #

Free Reversing Toolkit

“A free/libre toolchain for easing several low level tasks like forensics, software reverse engineering, exploiting, debugging, …

It is composed by a bunch of libraries (which are extended with plugins) and programs that can be automated with almost any programming language.”

~ rada.re

Radare2 Installation #

First thing todo, you need to install some development dependency to build Radare2 binary from source.

sudo apt install git build-essential

According to radare2 installation, you just need todo a simple paste command on the terminal.

git clone https://github.com/radareorg/radare2
cd radare2 ; sys/install.sh

If you face some build issue then try this command, if not just skip this.

sudo make purge
rm -rf shlr/capstone
git clean -xdf
git reset --hard @~50
sys/install.sh
[ root@labs: radare2 ]># radare2 -v
radare2 6.1.9 +36803 abi:127 @ linux-arm_64
birth: git.6.1.8-342-g9104dd7ff4 2026-07-24__21:57:49
commit: 9104dd7ff4311780fe268a529524c394b4147b9f
options: gpl -O? cs:5 cl:2 make

If we were done, lets go to walkthrough. Maybe I will demonstrate for bit for radare2 usecase.

Walkthrough #

lets we create a simple C-lang hello-world.c code.

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}   
[ root@labs: walkthrough ]># gcc hello-world.c -o hello
[ root@labs: walkthrough ]># ./hello
Hello, World!
[ root@labs: walkthrough ]># file hello
hello: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=1666d7a861d5c78f6a7f2bc982042241fd013c40, for GNU/Linux 3.7.0, not stripped
[ root@labs: walkthrough ]># uname -m
aarch64

As you can see, from the execution up there. We have some information about the binary itself. Its print Hello, World! ofcourse and the binary are compiled by using ARM aarch64 architecture GCC compiler. Because this TV-Box is ARM based CPU.

[ root@labs: walkthrough ]># file `which gcc`
/usr/bin/gcc: symbolic link to gcc-14
[ root@labs: walkthrough ]># file `which gcc-14`
/usr/bin/gcc-14: symbolic link to aarch64-linux-gnu-gcc-14
[ root@labs: walkthrough ]># file `which aarch64-linux-gnu-gcc-14`
/usr/bin/aarch64-linux-gnu-gcc-14: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=99ea0b90dea6c913445097ead845ec23d4fb4499, for GNU/Linux 3.7.0, stripped

And then let’s try our Reversing toolkit framework (Radare2).

[ root@labs: walkthrough ]># file hello
hello: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=1666d7a861d5c78f6a7f2bc982042241fd013c40, for GNU/Linux 3.7.0, not stripped
[ root@labs: walkthrough ]># r2 hello
 -- Control the height of the terminal on serial consoles with e scr.height
[0x00000680]>

After we type r2 hello or radare2 hello, it will pops up a new shell environment with set of weird number.

[0x00000680]>

Lets talk about this number in another section, lets being objective first.

After we pops a new shell, we need to do a recon or find what we looking for inside of the hello binary file.

[0x00000680]> i
fd       3
file     hello
size     0x11390
humansz  68.9K
mode     r-x
format   elf64
iorw     true
block    0x100
type     DYN (Shared object file)
arch     arm
baddr    0x0
binsz    68620
bintype  elf
bits     64
canary   false
injprot  false
class    ELF64
compiler GCC: (Debian 14.2.0-19) 14.2.0
crypto   false
endian   little
havecode true
intrp    /lib/ld-linux-aarch64.so.1
laddr    0x0
lang     c
linenum  false
lsyms    true
machine  ARM aarch64
nx       true
os       linux
pic      true
relocs   true
relro    partial
rpath    NONE
sanitize false
static   false
stripped false
uncaps   true
subsys   linux
va       true
[0x00000680]>

By typing i inside of shell, we got lot of information to better understanding what this file binary is. Those info much better than file command that we use previously.

And then lets start to looking for what we want. Let trying to patch this binary without re-compiled the binary.

[0x00000680]> aaa
INFO: Analyze all flags starting with sym. and entry0 (aa)
INFO: Analyze imports (af@@@i)
INFO: Analyze entrypoint (af@ entry0)
INFO: Analyze symbols (af@@@s)
INFO: Running plugin pre-analysis hooks
INFO: Analyze all functions arguments/locals (afva@@F)
INFO: Analyze function calls (aac)
INFO: Analyze len bytes of instructions for references (aar)
INFO: Finding and parsing C++ vtables (avrr)
INFO: Analyzing methods (af @@ method.*)
INFO: Finding function preludes (aap)
INFO: Emulate functions to find computed references (aaef)
INFO: Recovering local variables (afva@@@F)
INFO: Type matching analysis for all functions (aaft)
INFO: Propagate noreturn information (aanr)
INFO: Use -AA or aaaa to perform additional experimental analysis
INFO: Finding xrefs in noncode sections (e anal.in=io.maps.x; aav)
WARN: Skipping aav because base address is zero. Use -B 0x800000 or aav0
[0x00000680]>

By typing aaa on the shell, it suppose to analysing whole hello binary. We will try to seek Hello, World! string, and then patch them in disassembly language, then re-run the binary and see the result.

[0x00000680]> iz
nth paddr      vaddr      len size section type  string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――
0   0x000007e8 0x000007e8 13  14   .rodata ascii Hello, World!
[0x00000680]>

By typing iz radare2 will seeking the strings type inside whole binary, and print the offset or the address of string are in placed.

Then, what next? After we find the address of Hello, World! string, We need to seek into the offset of string which in case 0x000007e8 are the correspond for Hello, World!.

[0x00000680]> s 0x000007e8
[0x000007e8]>

Here you can see, can you spot the different? Yes, the weird number is called with “offset”. The offset is change or switch from 0x00000680 into 0x000007e8. By typing s <OFFSET> you will change the address, from current into another place.

Then what next? After we move into another offset, we need to show the disassembly code. How to show the disassembly code of the offset string Hello, World!?

By typing pd command inside the shell, you will facing front gate of hell. pd command are correspond for print the disassembly code.

[0x000007e8]> pd
            ;-- str.Hello__World_:
            ;-- hit10_0:
            ; STRN XREF from main @ 0x7b4(r)
            0x000007e8     .string "Hello, World!" ; len=14
            0x000007f6      0000           unaligned
            ;-- section..eh_frame_hdr:
            ;-- segment.GNU_EH_FRAME:
            ;-- __GNU_EH_FRAME_HDR:
            0x000007f8      011b033b       invalid                     ; [15] -r-- section size 60 named .eh_frame_hdr
			; ---SNIP---
            0x00000834      00000000       invalid
            ;-- section..eh_frame:
            0x00000838      10000000       invalid                     ; [16] -r-- section size 180 named .eh_frame
			; ---SNIP---
            0x0000089c      0e209d04       invalid
            0x000008a0      9e034293       sbfiz x30, x28, 0x3e, 1
			; ---SNIP---
            0x000008d4      20000000       invalid
            0x000008d8      00410e10       adr x0, 0x1d0f8
            0x000008dc      9d029e01       invalid
            0x000008e0      46dedd0e       invalid
            0x000008e4      00000000       invalid
            ;-- __FRAME_END__:
            0x000008e8      00000000       invalid
            ;-- section..note.ABI_tag:
            ;-- segment.NOTE_1:
            ;-- __abi_tag:
            0x000008ec      04000000       invalid                     ; [17] -r-- section size 32 named .note.ABI-tag
[0x000007e8]> p

See? Its HELL, I can’t read my brain goes Dyslexic, but I’ve high-lighting the Hello, World! section. The comment it said, “; STRN XREF from main @ 0x7b4(r)”, its a XREF or cross reference into main. What these mean? Its mean the Hello, World! string are came from main function.

Lets find main function, how to find main function? to find main function you need to type afl which stand for “Analysing Function List” maybe, or whatever.

[0x000007e8]> afl
0x00000610    1     16 sym.imp.__libc_start_main
0x00000620    1     32 sym.imp.__cxa_finalize
0x00000640    1     16 sym.imp.abort
0x00000650    1     20 sym.imp.puts
0x00000680    1     48 entry0
0x000006b4    3     20 sym.call_weak_fn
0x000006e0    4     48 sym.deregister_tm_clones
0x00000710    4     60 sym.register_tm_clones
0x0000074c    5     80 entry.fini0
0x000007a0    1      8 entry.init0
0x000007c8    1     24 sym._fini
0x000007a8    1     32 main
0x000005d0    1     28 sym._init
0x000005f0    1     32 fcn.000005f0
[0x000007e8]>

After command afl radare spit out bunch of list, those list is a functions, as you can see part of those list the main function are included.

What next? We need to change the address offset into main function, then show the disassembly code again.

[0x000007e8]> s 0x000007a8
[0x000007a8]>

Spot the different? Yes, e turn into a. Its hard to spot, and the next step is do with command pd to show disassembly code again, but instead of pd we gonna make the code more clean by typing pdf.

[0x000007a8]> pdf
            ;-- pc:
            ; DATA XREF from entry0 @ 0x6a0(r)
┌ 32: int main (int argc, char **argv, char **envp);
│ afv: vars(2:sp[0x8..0x10])
│           0x000007a8      fd7bbfa9       stp x29, x30, [var_10h]!
│           0x000007ac      fd030091       mov x29, sp
│           0x000007b0      00000090       adrp x0, 0
│           0x000007b4      00a01f91       add x0, x0, str.Hello__World_ ; 0x7e8 ; "Hello, World!" ; const char *s
│           0x000007b8      a6ffff97       bl sym.imp.puts             ; int puts(const char *s)
│           0x000007bc      00008052       mov w0, 0
│           0x000007c0      fd7bc1a8       ldp x29, x30, [sp], 0x10
└           0x000007c4      c0035fd6       ret
[0x000007a8]>

Lets try to analysing the highlighted code, cuz I just understanding these single piece of line.

  • After we go from 0x000007e8 offset.
  • We landed into 0x000007a8 which is the main function.
  • In the main function, at the offset 0x000007b4 it said;
add x0, x0, str.Hello__World_ ; 0x7e8 ; "Hello, World!" ; const char *s

Add the string str.Hello__World_ into x0, which previously the str.Hello__World_ came from 0x7e8 or 0x000007e8. Radare2 automatically set the comment with ; "Hello, World!" to represent this add x0, x0, str.Hello__World_ disassembly instruction, maybe.

Or to able have a better view we can have a pseudo Decompiled code by typing pdc.

[0x000007a8]> pdc
// callconv: x0 arm64 (x0, x1, x2, x3, x4, x5, x6, x7, stack);
int main (int argc, char **argv, char **envp) {
        // DATA XREF from entry0 @ 0x6a0(r)
        [var_10h]! = (x29, 2)
        x29 = sp
        x0 = 0
        x0 += str.Hello__World_ // 0x7e8 // "Hello, World!" // const char *s // hit10_0
        sym.imp.puts () // int puts("Hello, World!")
        w0 = 0
        (x29, 2) = 3
}

[0x000007a8]>

Also you can compare the decompiled code, with its offset side by side using pdco command.

[0x000007a8]> pdco
 0x000007a8 |                                | // callconv: x0 arm64 (x0, x1, x2, x3, x4, x5, x6, x7, stack);
 0x000007a8 |                                | int main (int argc, char **argv, char **envp) {
 0x000007a8 |         // DATA XREF from entry0 @ 0x6a0(r)
 0x000007a8 |         [var_10h]! = (x29, 2)
 0x000007ac |         x29 = sp
 0x000007b0 |         x0 = 0
 0x000007b4 |         x0 += str.Hello__World_ // 0x7e8 // "Hello, World!" // const char *s // hit10_0
 0x000007b8 |         sym.imp.puts () // int puts("Hello, World!")
 0x000007bc |         w0 = 0
 0x000007c0 |         (x29, 2) = 3
 0x000007c0 | }

[0x000007a8]>

Or even with the disassembly code side by side with set of instruction and the offset by pdca command.

[0x000007a8]> pdca
 0x000007a8 |                                | // callconv: x0 arm64 (x0, x1, x2, x3, x4, x5, x6, x7, stack);
 0x000007a8 |                                | int main (int argc, char **argv, char **envp) {
 0x000007a8 | stp x29, x30, [var_10h]!       |         // DATA XREF from entry0 @ 0x6a0(r)
 0x000007a8 | stp x29, x30, [var_10h]!       |         [var_10h]! = (x29, 2)
 0x000007ac | mov x29, sp                    |         x29 = sp
 0x000007b0 | adrp x0, 0                     |         x0 = 0
 0x000007b4 | add x0, x0, str.Hello__World_  |         x0 += str.Hello__World_ // 0x7e8 // "Hello, World!" // const char *s // hit10_0
 0x000007b8 | bl sym.imp.puts                |         sym.imp.puts () // int puts("Hello, World!")
 0x000007bc | mov w0, 0                      |         w0 = 0
 0x000007c0 | ldp x29, x30, [sp], 0x10       |         (x29, 2) = 3
 0x000007a8 |                                | }

[0x000007a8]>

Lets move forward, how to patch the binary without re-compile it? First we need to reopen the binary file with write access mode, by typing oo+ radare2 will automatically reopen the binary file with writable access.

[0x000007a8]> oo+
[0x000007a8]> pdf
            ;-- pc:
            ; DATA XREF from entry0 @ 0x6a0(r)
┌ 32: int main (int argc, char **argv, char **envp);
│ afv: vars(2:sp[0x8..0x10])
│           0x000007a8      fd7bbfa9       stp x29, x30, [var_10h]!
│           0x000007ac      fd030091       mov x29, sp
│           0x000007b0      00000090       adrp x0, 0
│           0x000007b4      00a01f91       add x0, x0, str.Hello__World_ ; 0x7e8 ; "Hello, World!" ; const char *s
│           0x000007b8      a6ffff97       bl sym.imp.puts             ; int puts(const char *s)
│           0x000007bc      00008052       mov w0, 0
│           0x000007c0      fd7bc1a8       ldp x29, x30, [sp], 0x10
└           0x000007c4      c0035fd6       ret
[0x000007a8]>

If there is no output after oo+, this mean radare2 was in writable mode. You can verify with o command.

[0x000007a8]> o
 3 * r-x 0x00011390 hello
 4 - rw- 0x00000008 null://8
[0x000007a8]> oo+
[0x000007a8]> o
 3 * rwx 0x00011390 hello
 4 - rw- 0x00000008 null://8
[0x000007a8]>

As you can see it was rwx, which mean read-write-exec permission access (full access).

After we have a write access, lets rewrite Hello, World! value with Hello, Pwnadol!. How?

First we need to find where is the Hello, World! offset really are. By the main references comment it said 0x000007e8. Let seeking into it, within s 0x000007e8 command.

[0x000007a8]> pdf
            ;-- pc:
            ; DATA XREF from entry0 @ 0x6a0(r)
┌ 32: int main (int argc, char **argv, char **envp);
│ afv: vars(2:sp[0x8..0x10])
│           0x000007a8      fd7bbfa9       stp x29, x30, [var_10h]!
│           0x000007ac      fd030091       mov x29, sp
│           0x000007b0      00000090       adrp x0, 0
│           ; NULL XREF from main @ 0x7a8(r)
│           0x000007b4      00a01f91       add x0, x0, str.Hello__World_ ; 0x7e8 ; "Hello, World!" ; const char *s
│           0x000007b8      a6ffff97       bl sym.imp.puts             ; int puts(const char *s)
│           0x000007bc      00008052       mov w0, 0
│           0x000007c0      fd7bc1a8       ldp x29, x30, [sp], 0x10
└           0x000007c4      c0035fd6       ret
[0x000007a8]> s 0x000007e8
[0x000007e8]>

And then type pd to show content of the offset, instead of pd lets add pd <Nunmber>, for example pd 1. This mean the pd command will show the disassembly code by just one line on top.

[0x000007e8]> pd 1
            ;-- str.Hello__World_:
            ;-- hit8_0:
            ; STRN XREF from main @ 0x7b4(r)
            0x000007e8     .string "Hello, World!" ; len=14
[0x000007e8]>

Then verified with ps command.

[0x000007e8]> pd 1
            ;-- str.Hello__World_:
            ;-- hit8_0:
            ; STRN XREF from main @ 0x7b4(r)
            0x000007e8     .string "Hello, World!" ; len=14
[0x000007e8]> ps
Hello, World!
[0x000007e8]>

Then replace Hello, World! with Hello, Pwnadol!, by typing wz <String> for example wz Hello, Pwnadol!. Then just q for quit, that will automatically save.

Note: If the radare2 option is bin.cache=true the patch code will goes into RAM instead, rather goes into Disk. So, make sure to make it bin.cache=false, if true the patch that you make will not applied.

[0x000007e8]> ps
Hello, World!
[0x000007e8]> wz Hello, Pwnadol!
[0x000007e8]> ps
Hello, Pwnadol!
[0x000007e8]> q

After we quit, lets rerun the hello binary again, and see the output.

[ root@labs: walkthrough ]># ./hello
Hello, Pwnadol!

We Pwnadol it.

But, what if set instruction are x86(-64)? we just have ARM64 machine, how we do x86(-64) executable in ARM64? it can’t, need Proof of Concept? here it is.

[ root@labs: walkthrough ]># x86_64-linux-gnu-gcc hello-world.c -o hello-x86_64
[ root@labs: walkthrough ]># ./hello-x86_64
-bash: ./hello-x86_64: cannot execute binary file: Exec format error
[ root@labs: walkthrough ]># file hello-x86_64 hello
hello-x86_64: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=c03c4d533ef0a7dc698fa0f64c6c1dae611e94f9, for GNU/Linux 3.2.0, not stripped
hello:        ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=1666d7a861d5c78f6a7f2bc982042241fd013c40, for GNU/Linux 3.7.0, not stripped
[ root@labs: walkthrough ]>#

There is so many antidot out there. But I’ve find the most simple, fast and efficient way to make it worth, with low overhead resource.

What is FEX-Emu #

FEX: Emulation x86 Programs on ARM64

“FEX allows you to run x86 applications on ARM64 Linux devices, similar to qemu-user and box64. It offers broad compatibility with both 32-bit and 64-bit binaries, and it can be used alongside Wine/Proton to play Windows games.”

~ FEX-Emu

FEX-Emu Installation #

There is QT Graphical dependency are required by FEX-Emu to make it run smooth like butter.

sudo apt install qtbase5-dev qtdeclarative5-dev qttools5-dev-tools libqt5svg5-dev fuse3

Below is the most important part of installation for FEX-Emu dependencies, to make FEX-Emu not screaming and yelling at you about minimum prerequisite dependency. Install those all.

apt install -y \
	git \
	cmake \
	ninja-build \
	pkgconf \
	ccache  \
	clang \
	llvm \
	lld  \
	binfmt-support  \
	libssl-dev \
	python3-setuptools  \
	g++-x86-64-linux-gnu  \
	libgcc-14-dev-i386-cross  \
	libgcc-14-dev-amd64-cross  \
	nasm  \
	python3-clang  \
	libstdc++-14-dev-i386-cross  \
	libstdc++-14-dev-amd64-cross  \
	libstdc++-14-dev-arm64-cross  \
	squashfs-tools  \
	squashfuse  \
	libc-bin \
	libc6-dev-i386-amd64-cross  \
	lib32stdc++-14-dev-amd64-cross  

Then build or compile the binary from FEX-Emu git repository source, with this FEX-Emu installation reference.

 git clone --recurse-submodules https://github.com/FEX-Emu/FEX.git
 cd FEX
 mkdir Build
 cd Build
 CC=clang CXX=clang++ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DUSE_LINKER=lld -DENABLE_LTO=True -DBUILD_TESTING=False -DENABLE_ASSERTIONS=False -G Ninja ..
 ninja

After the compilation is done, lets run the FEX-Emu with FEXBash, that will make a new pops up shell. Which will “for every x86-64 binary inside of new environment, are able to run with ARM64 set instruction”.

[ root@labs: walkthrough ]># uname -m
aarch64
[ root@labs: walkthrough ]># FEXBash
FEXBash root@labs:~/.development/walkthrough> uname -m
x86_64
FEXBash root@labs:~/.development/walkthrough> file hello-x86_64
hello-x86_64: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=c03c4d533ef0a7dc698fa0f64c6c1dae611e94f9, for GNU/Linux 3.2.0, not stripped
FEXBash root@labs:~/.development/walkthrough> ./hello-x86_64
Hello, World!
FEXBash root@labs:~/.development/walkthrough>

Then we made it, Let try to Pwnadol it too, with ARM64 radare2 executable binary.

[ root@labs: walkthrough ]># r2 -w hello-x86_64
WARN: Relocs has not been applied. Please use `-e bin.relocs.apply=true` or `-e bin.cache=true` next time
 -- Use rarun2 to launch your programs with a predefined environment.
[0x00001050]> o
 3 * rwx 0x00003ea8 hello-x86_64
 4 - rw- 0x00000008 null://8
[0x00001050]> i
fd       3
file     hello-x86_64
size     0x3ea8
humansz  15.7K
mode     rwx
format   elf64
iorw     true
block    0x100
type     DYN (Shared object file)
arch     x86
baddr    0x0
binsz    13985
bintype  elf
bits     64
canary   false
injprot  false
class    ELF64
compiler GCC: (Debian 14.2.0-19) 14.2.0
crypto   false
endian   little
havecode true
intrp    /lib64/ld-linux-x86-64.so.2
laddr    0x0
lang     c
linenum  false
lsyms    true
machine  AMD x86-64 architecture
nx       true
os       linux
pic      true
relocs   true
relro    partial
rpath    NONE
sanitize false
static   false
stripped false
uncaps   true
subsys   linux
va       true
[0x00001050]>

See the highlighted, those is a proof for binary data that was x86-64 bit set instruction code.

Lets rewrite the Hello, World! like before.

[0x00001050]> aaa
INFO: Analyze all flags starting with sym. and entry0 (aa)
INFO: Analyze imports (af@@@i)
INFO: Analyze entrypoint (af@ entry0)
INFO: Analyze symbols (af@@@s)
INFO: Running plugin pre-analysis hooks
INFO: Analyze all functions arguments/locals (afva@@F)
INFO: Analyze function calls (aac)
INFO: Analyze len bytes of instructions for references (aar)
INFO: Finding and parsing C++ vtables (avrr)
INFO: Analyzing methods (af @@ method.*)
INFO: Recovering local variables (afva@@@F)
INFO: Type matching analysis for all functions (aaft)
INFO: Propagate noreturn information (aanr)
INFO: Use -AA or aaaa to perform additional experimental analysis
[0x00001050]> iz
nth paddr      vaddr      len size section type  string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――
0   0x00002004 0x00002004 13  14   .rodata ascii Hello, World!
[0x00001050]> s 0x00002004
[0x00002004]> ps
Hello, World!
[0x00002004]> wz Hello, Pwnadol!
[0x00002004]> ps
Hello, Pwnadol!
[0x00002004]>q

Let me extends the history line, still merge into prev history line.

[0x00001050]> iz
nth paddr      vaddr      len size section type  string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――
0   0x00002004 0x00002004 13  14   .rodata ascii Hello, World!
[0x00001050]> s 0x00002004
[0x00002004]> ps
Hello, World!
[0x00002004]> wz Hello, Pwnadol!
[0x00002004]> ps
Hello, Pwnadol!
[0x00002004]> q
[ root@labs: walkthrough ]># FEXBash
FEXBash root@labs:~/.development/walkthrough> ./hello-x86_64
Hello, Pwnadol!
FEXBash root@labs:~/.development/walkthrough>

DAMMMNNNN! We just succesfully built extensible Pwnadol Exploit Development Homelabs, by just using a $10 TV-Box with Radare2 and FEX-Emu.