Page 1 of 1

[Linux] ImportC example: ptrace()

Posted: Sun Jan 24, 2010 6:30 pm
by mdp
Hi,
the magic of ImportC is able to provide us with functions not present in the API that PB recognizes by default.

ptrace() allows a user to attach to a process in order to peek and poke (read and inject) into it.

The following code attaches to a running process and reads the contents of its data section (entailed from the /proc/ pseudofiles), then displays a chunk of it in a gadget.
Note: the program needs to be fed the PID of the to be debugged process. In the following code, pls set the 'pid' variable accordingly | add a ProgramParameter() accordingly etc.

Code: Select all

pid = 6200        ; !!!! PROCESS TO ATTACH TO !!!!

;{ PTRACE CONSTANTS
#PTRACE_TRACEME = 0        ; Indicate that the process making this request should be traced. All signals received by this process can be intercepted by its parent, And its parent can use the other `ptrace' requests.  
#PTRACE_PEEKTEXT = 1       ; Return the word in the process's text space at address ADDR.  
#PTRACE_PEEKDATA = 2       ; Return the word in the process's data space at address ADDR.  
#PTRACE_PEEKUSER = 3       ; Return the word in the process's user area at offset ADDR.  
#PTRACE_POKETEXT = 4       ; Write the word DATA into the process's text space at address ADDR.  
#PTRACE_POKEDATA = 5       ; Write the word DATA into the process's data space at address ADDR.  
#PTRACE_POKEUSER = 6       ; Write the word DATA into the process's user area at offset ADDR.  
#PTRACE_CONT = 7           ; Continue the process.  
#PTRACE_KILL = 8           ; Kill the process.  
#PTRACE_SINGLESTEP = 9     ; Single step the process. This is not supported on all machines.  
#PTRACE_GETREGS = 12       ; Get all general purpose registers used by a processes. This is not supported on all machines.  
#PTRACE_SETREGS = 13       ; Set all general purpose registers used by a processes. This is not supported on all machines.  
#PTRACE_GETFPREGS = 14     ; Get all floating point registers used by a processes. This is not supported on all machines.  
#PTRACE_SETFPREGS = 15     ; Set all floating point registers used by a processes. This is not supported on all machines.  
#PTRACE_ATTACH = 16        ; Attach to a process that is already running.  
#PTRACE_DETACH = 17        ; Detach from a process attached to with PTRACE_ATTACH.  
#PTRACE_GETFPXREGS = 18    ; Get all extended floating point registers used by a processes. This is not supported on all machines.  
#PTRACE_SETFPXREGS = 19    ; Set all extended floating point registers used by a processes. This is not supported on all machines.  
#PTRACE_SYSCALL = 24       ; Continue and stop at the next (return from) syscall.  
#PTRACE_SETOPTIONS = $4200   ; Set ptrace filter options.  
#PTRACE_GETEVENTMSG = $4201  ; Get last ptrace message.  
#PTRACE_GETSIGINFO = $4202   ; Get siginfo For process.  
#PTRACE_SETSIGINFO = $4203   ; Set new siginfo for process.  
;}

ImportC ""
   ptrace(request, pid, *paddr, *pdata )
EndImport

Procedure Hex2Val( h.s )
   h=UCase(h) : c=0
   b=PeekB(@h) : m=Pow(16,Len(h)-1) : Repeat
      If b>64 : b-7 : EndIf
      b-48
      v=v+(m*b) : m/16
      c+1 : b=PeekB(@h+c)
   Until b=0 
   ProcedureReturn v
EndProcedure

Procedure.s GetShellOutput( cmd.s )
   prg = RunProgram("sh","-c "+#DQUOTE$+cmd+#DQUOTE$,"",#PB_Program_Open|#PB_Program_Read)
   If prg
      While ProgramRunning(prg) ;And AvailableProgramOutput(prg)
         out.s=out+ReadProgramString(prg)+#LF$
      Wend
   EndIf
   ProcedureReturn out
EndProcedure

Procedure GetMemoryMap( pid , *mstart.long , *mend.long )
   s.s = GetShellOutput("ls -l /proc/"+Str(pid)+"/exe") : p = FindString(s," -> ",1)
   exefullpath.s = Trim(Right(s,Len(s)-p-4)) 
   s = GetShellOutput("cat /proc/"+Str(pid)+"/maps | grep rwxp | grep "+exefullpath)
   s = StringField(s,1," ")
   *mstart\l = Hex2Val(StringField(s,1,"-"))
   *mend\l   = Hex2Val(StringField(s,2,"-"))
   ProcedureReturn *mstart\l
EndProcedure


Procedure FillPtraceBuffer( *mem.ascii , pid , mstart , mend )
   err = ptrace( #PTRACE_ATTACH , pid ,0,0) : Delay(10)
   If err=0
      For a=mstart To mend-1
         by.a=ptrace( #PTRACE_PEEKDATA , pid , a , 0 )
         *mem\a=by : *mem+1
      Next
      ptrace( #PTRACE_DETACH , pid ,0,0) : Delay(10)
   Else
      Debug "Can't attach!"
   EndIf
EndProcedure

Procedure UpdateMemoryView( gad , *mem.ascii , startaddr )
   s.s="" : wl=60 : ww=512
   For a=0 To ww-1
      If Len(s)%wl=0  : s=s+"."+RSet(Hex(startaddr,#PB_Long),8,"0")+"  " : startaddr+16 : EndIf 
      s = s+RSet(Hex(*mem\a,#PB_Byte),3) : *mem+1 
      If Len(s)%wl=59 : s=s+#LF$ : EndIf 
   Next
   SetGadgetText(gad,s)
EndProcedure

ms.l : me.l : GetMemoryMap(pid,@ms,@me) : ml = me-mf
ptrc_buf = AllocateMemory(ml) 

If OpenWindow(0,0,0,400,600,"Ptrace Example")
   EditorGadget(0,0,0,400,600)
      LoadFont(0,"Monospace",8) : SetGadgetFont(0,FontID(0))
   AddWindowTimer(0,1,500)
Else : End : EndIf

Repeat
   EvID=WaitWindowEvent()
   Select EvID
      Case #PB_Event_Timer
         FillPtraceBuffer( ptrc_buf , pid , ms , me )
         UpdateMemoryView( 0 , ptrc_buf , ms )
   EndSelect
Until EvID=#PB_Event_CloseWindow
Now for a question: I believe there should be some better way than continuously attaching and detaching. I tried for some time to have it do PTRACE_ATTACH only once, then PTRACE_CONT after the reading sweep, but then PTRACE_PEEKDATA would return errors (after the 'continue' issuance). I cannot find a way to 'break' the process again, without detaching or without actually breaking it the bad way.
Doubtful as an implementation as it is, the above works.