Skip to content

Commit

Permalink
Merge pull request #2 from buzzer-re/dev
Browse files Browse the repository at this point in the history
Single step command (Set TF on the rflags)
  • Loading branch information
buzzer-re authored Sep 13, 2024
2 parents 488e8bf + 22a49d4 commit 8635b5d
Show file tree
Hide file tree
Showing 18 changed files with 205 additions and 185 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ The currently supported commands are fairly straightforward:
- pause
- "Pauses" the current YorhaDBG thread to trigger the trap handler.
- Note: The system is not entirely frozen, as some network components would not work.
- step
- Perform a single-step execution (execute just one instruction) and return control to the debugger.

- load_kpayload
- Loads a kernel payload and attach the debugger into it
Expand Down Expand Up @@ -159,6 +161,10 @@ With the `break` command, you can set a breakpoint at a specified memory address
To delete a breakpoint, use the `breakdel` command with the address as an argument.


## Single Step

To perform a single step on the paused thread, use the `step` command, which internally issues the context command. You can then resume execution normally with `continue`.

## Load kernel payload

You can load a kernel payload using the `load_kpayload` command with the `--path` argument, specifying the path to the payload on disk. YoRHa will load the payload and stop at its entry point, allowing you to debug it easily:
Expand All @@ -176,11 +182,6 @@ You can modify the current thread's registers using the `setr` command, specifyi

Any unused registers will remain unchanged.

# Future features

The debugger is missing a few commands, such as `single-step`, but these will be implemented in the near future. I developed all the existing commands based on my needs during reverse engineering activities and will continue to add features as required.


# Conclusion

As I wrote before, I did this project to learn more about how the PS4 works and to improve my console hacking skills. There are many cool features to explore and to extract from my research on it that apply to both PS4 and PS5. I would like to thank the following projects and resources that helped me a lot:
Expand Down
2 changes: 1 addition & 1 deletion cli/commands/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
"r13" / construct.Int64ul,
"r14" / construct.Int64ul,
"r15" / construct.Int64ul,
# "error_code" / construct.Int64ul,
"rip" / construct.Int64ul,
"cs" / construct.Int64ul,
"eflags" / construct.Int64ul,
Expand Down Expand Up @@ -60,6 +59,7 @@ class DebuggerCommandsCode:
BREAKPOINT_REMOVE = 8
DBG_MEM_WRITE = 9
DBG_SET_THREAD_CONTEXT = 10
DBG_SNGLE_STEP = 11



Expand Down
13 changes: 13 additions & 0 deletions cli/commands/single_step.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from .commands import *


class SingleStep(Command):
ARGUMENTS = []

def __init__(self):
Command.__init__(self, DebuggerCommandsCode.DBG_SNGLE_STEP)
self.response_struct = dbg_response_header
self.command = dbg_request_header.build({
"cmd_type" : DebuggerCommandsCode.DBG_SNGLE_STEP,
"argument_size": 0
})
85 changes: 73 additions & 12 deletions cli/debugger.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from commands import mem_io
from commands import pause, stop, breakpoint, continue_exec, context, kpayload_load, disas
from commands import pause, stop, breakpoint, continue_exec, context, kpayload_load, disas, single_step
from commands.disassembler import Disassembler
import socket
import time
import argparse
import os
import ast
Expand All @@ -22,7 +23,8 @@ class Registers:
"breakdel" : breakpoint.RemoveBreakpoint,
"unload" : None,
"setr" : context.SetThreadContext,
"load_kpayload" : kpayload_load.KPayloadLoader
"load_kpayload" : kpayload_load.KPayloadLoader,
"step" : single_step.SingleStep
}

def hex2int_from_list(l):
Expand Down Expand Up @@ -123,8 +125,11 @@ def __init__(self, host, port, dbg_port, quiet = False):
"breakdel" : self.__remove_breakpoint,
"unload" : self.disconnect,
"setr" : self.__set_thread_context,
"load_kpayload" : self.__load_kpayload
"load_kpayload" : self.__load_kpayload,
"step" : self.__single_step
}
self.stepping = False
self.old_rip = 0

def connect(self, port) -> int:
sock = socket.socket()
Expand Down Expand Up @@ -197,7 +202,8 @@ def __send_cmd(self, command, wait=True, trap_fame=False, retry=True) -> bool:
def launch_cmd(self, cmd, args):
if cmd in self.dispatcher:
if cmd != "unload":
self.dispatcher[cmd](args)
if not self.dispatcher[cmd](args):
print(f"{cmd}: Failed to execute!")
else:
self.disconnect(unload_dbg=True)

Expand All @@ -208,14 +214,20 @@ def __disassemble(self, args: list) -> bool:
disas_cmd = disas.Disassemble(args.address, args.count)
self.__send_cmd(disas_cmd, wait=True, trap_fame=self.in_dbg_context)

return True


def __continue(self, args):
continue_cmd = continue_exec.Continue()
if self.__send_cmd(continue_cmd, False, True):
self.in_dbg_context = False
self.stepping = False
return True

return False


def __memory_read(self, args) -> False:
def __memory_read(self, args) -> bool:
args = parse_args(args, mem_io.MemRead.ARGUMENTS)
if not args:
return False
Expand All @@ -226,12 +238,17 @@ def __memory_read(self, args) -> False:
memory_read_req = mem_io.MemRead(args.address, args.count, args.output)
self.__send_cmd(memory_read_req, True, trap_fame=self.in_dbg_context)

return True


def __pause_debugger(self, args) -> bool:
pause_cmd = pause.PauseDebugger()
if self.__send_cmd(pause_cmd, False, False):
self.in_dbg_context = True
self.print_context()
return True

return False


def print_context(self, args = []) -> bool:
Expand All @@ -242,9 +259,19 @@ def print_context(self, args = []) -> bool:
print("System is not in a paused state!")
return

break_list = self.list_breakpoints()
rip = ctx_cmd.response.trap_frame.rip
mem = mem_io.MemRead(rip - 1, 0x10, only_read = True)
break_list = self.list_breakpoints()
#
# We need the old rip to fix the disassembly output
#
if not self.stepping:
rip = ctx_cmd.response.trap_frame.rip - 1 # back to the int3
else:
print("aqui")
rip = self.old_rip

print(rip)

mem = mem_io.MemRead(rip, 0x10, only_read = True)
self.__send_cmd(mem, True, trap_fame=self.in_dbg_context)
print("\n\n")
try:
Expand All @@ -253,13 +280,13 @@ def print_context(self, args = []) -> bool:
raw_code_bytes = list(mem.data_read)

for i, code in enumerate(mem.data_read):
addr = rip - 1 + i # Minus 1 because RIP points to the next instruction
addr = rip + i
if addr in break_list.breakpoints_lookup:
raw_code_bytes[i] = break_list.breakpoints_lookup[addr].old_opcode

mem.data_read = bytearray(raw_code_bytes)

insts = self.disas.disas(mem.data_read, ctx_cmd.response.trap_frame.rip - 1)
insts = self.disas.disas(mem.data_read, rip)
print("Disassembly: ")
for inst in insts:
print(f"{hex(inst.address)}:\t{inst.mnemonic}\t{inst.op_str}", end=" ")
Expand All @@ -274,6 +301,9 @@ def print_context(self, args = []) -> bool:
except Exception as e:
print(bytearray(ctx_cmd.response.code))
print(e)
return False

return True

def __breakpoint(self, args):
args = parse_args(args, breakpoint.BreakpointCommand.ARGUMENTS)
Expand All @@ -283,6 +313,8 @@ def __breakpoint(self, args):

dbg_cmd = breakpoint.BreakpointCommand(args.address)
self.__send_cmd(dbg_cmd, wait=False, trap_fame=self.in_dbg_context)

return True

def __remove_breakpoint(self, args):
args = parse_args(args, breakpoint.RemoveBreakpoint.ARGUMENTS)
Expand All @@ -293,6 +325,8 @@ def __remove_breakpoint(self, args):
break_del_cmd = breakpoint.RemoveBreakpoint(args.address)
self.__send_cmd(break_del_cmd, wait=False, trap_fame=self.in_dbg_context)

return True


def __memory_write(self, args):
args = parse_args(args, mem_io.MemWrite.ARGUMENTS)
Expand All @@ -307,6 +341,8 @@ def __memory_write(self, args):
dbg_cmd = mem_io.MemWrite(args.address, args.bytes, args.input)
self.__send_cmd(dbg_cmd, wait=False, trap_fame=self.in_dbg_context)

return True


def __set_thread_context(self, args):
args = parse_args(args, context.SetThreadContext.ARGUMENTS)
Expand All @@ -317,7 +353,7 @@ def __set_thread_context(self, args):

if not self.__send_cmd(ctx_cmd, True, True):
print("System is not in a paused state!")
return
return False

# Copy new register values to the trap_frame struct
for reg in ctx_cmd.response.trap_frame:
Expand All @@ -330,7 +366,8 @@ def __set_thread_context(self, args):

dbg_cmd = context.SetThreadContext(ctx_cmd.response.trap_frame)
self.__send_cmd(dbg_cmd, wait=False, trap_fame=True)


return True

def __load_kpayload(self, args):
args = parse_args(args, kpayload_load.KPayloadLoader.ARGUMENTS)
Expand All @@ -346,6 +383,30 @@ def __load_kpayload(self, args):
kpayload_command_req = kpayload_load.KPayloadLoader(payload_data)
self.__send_cmd(kpayload_command_req, wait=False, trap_fame=False)

return True

def __single_step(self, args) -> bool:
if self.in_dbg_context:
#
# Get current thread state
#
ctx_cmd = context.DebuggerContext(quiet=True)
self.__send_cmd(ctx_cmd, wait=True, trap_fame=True)
#
# Enable TF flag
#
step_cmd = single_step.SingleStep()
self.__send_cmd(step_cmd, wait=False, trap_fame=True)
#
# Save current RIP
#
self.old_rip = ctx_cmd.response.trap_frame.rip
self.stepping = True # To help the capstone disassembler to decode correctly
time.sleep(.1)
self.print_context()
return True

return False

def list_breakpoints(self):
list_bp = breakpoint.ListBreakpoints()
Expand Down
2 changes: 1 addition & 1 deletion cli/yorha.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
CONFIG = f"{homepath}/.yorhadbg.ini"
IP_REGEX = r"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"
VERSION = "0.1"
DEV_BUILD = False
DEV_BUILD = True


class DbgCommandCompleter(Completer):
Expand Down
3 changes: 2 additions & 1 deletion include/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ enum
extern int memcpy(void *dest, const void *src, size_t n);
extern void *memset(void *s, int c, size_t n);
#define UNUSED __attribute__((unused))
#define INT3 0xCC
#define INT3 0xCC
#define TF 0x100
10 changes: 10 additions & 0 deletions include/dbg_commands/single_step.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

#include <sys/stdint.h>
#include "../syscall_wrapper.h"

struct dbg_command_t;
struct trap_frame_t;

int single_step_executor(dbg_command_t*, int);
int single_step_trap_handler(dbg_command_t*, int, trap_frame_t*);
19 changes: 19 additions & 0 deletions include/intrin.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@ static inline __attribute__((always_inline)) uint64_t __readcr0(void) {
return cr0;
}

static inline __attribute__((always_inline)) uint64_t __readdr6(void) {
uint64_t cr0;
__asm__ volatile("movq %%dr6, %0"
: "=r"(cr0)
:
: "memory");
return cr0;
}

static inline __attribute__((always_inline)) uint64_t __writedr6(uint64_t dr6) {
uint64_t cr0;
__asm__ volatile("movq %0, %%dr6"
: "=r"(cr0)
:
: "memory");
return cr0;
}


static inline __attribute__((always_inline)) void __writecr0(uint64_t cr0) {
__asm__ volatile("movq %0, %%cr0"
:
Expand Down
4 changes: 2 additions & 2 deletions include/yorha.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
#include "yorha_dbg_ctrl.h"
#include "common.h"

extern void (*int_breakpoint_handler)();
extern void (*debug_int_handler)();
extern uint64_t __get_rsp();
extern uint64_t __get_rip();

#define LOG(msg, ...) kprintf("YorhaDBG: "msg"\n", __VA_ARGS__)

int yorha_dbg_start();
int init_debug_server();
void overwrite_idt_gate(int interruption_number, uint64_t gate_addr);
uint64_t overwrite_idt_gate(int interruption_number, uint64_t gate_addr);
10 changes: 7 additions & 3 deletions include/yorha_dbg_commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ enum dbg_commands_code {
DBG_LIST_BREAKPOINT,
DBG_REMOVE_BREAKPOINT,
DBG_MEM_WRITE,
DBG_SET_THREAD_CONTEXT
DBG_SET_THREAD_CONTEXT,
DBG_SINGLE_STEP
};

enum DbgStatus
Expand Down Expand Up @@ -62,6 +63,7 @@ typedef int(*command_trap_handler)(dbg_command_t*, int, trap_frame_t*);
#include "dbg_commands/mem_rw.h"
#include "dbg_commands/load_kpayload.h"
#include "dbg_commands/set_context.h"
#include "dbg_commands/single_step.h"


static void* command_executor_handlers[] =
Expand All @@ -75,7 +77,8 @@ static void* command_executor_handlers[] =
list_breakpoint_executor,
remove_breakpoint_executor,
memory_write_executor,
NULL
NULL,
NULL,
};

static void* command_trap_handlers[] =
Expand All @@ -89,7 +92,8 @@ static void* command_trap_handlers[] =
list_breakpoint_trap_handler,
remove_breakpoint_trap_handler,
memory_write_trap_handler,
set_thread_context_trap_handler
set_thread_context_trap_handler,
single_step_trap_handler,
};


Expand Down
1 change: 1 addition & 0 deletions src/breakpoint_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ int remove_breakpoint(uint64_t* addr)
// Remove from linked list
//
SLIST_REMOVE(&head, np, __breakpoint_entry, entries);
kfree(np, KM_TEMP);
num_breakpoints--;

return true;
Expand Down
2 changes: 1 addition & 1 deletion src/dbg_commands/load_kpayload.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ int kpayload_loader_executor(dbg_command_t* command, int conn)
//
// Exec in a separted kproc which will break inside the debugger
//
kproc_create( (void (*)(void *)) exec_code, NULL, NULL, NULL, NULL, "YorhaKLoaderPayload");
kproc_create( (void (*)(void *)) exec_code, NULL, NULL, 0, 0, "YorhaKLoaderPayload");

return YORHA_SUCCESS;
}
Loading

0 comments on commit 8635b5d

Please sign in to comment.