Игра с ptrace, часть I

Привет всем! Данная статья является вольным переводом статьи Pradeep Padala. Статья была написана для 32-bit системы Linux. Если взять исходные тексты из статьи и попытаться их использовать на 64-х битной системе , то большая часть будет работать неправильно. А примеры из II части неправильно работают даже на 32-bit системе. Все дело конечно в том, что с момента написания статьи прошло слишком много времени (многие изменилось). Помните , что все исходные коды , приведенные в этой статье для 64-bit платформы ( в оригинальной статье для 32-bit платформы). Так что , это не просто перевод , а переосмысленная статья, которая конечно же базируется на первоначальном интеллектуальном труде Pradeep Padala.

-------------------------------------------------------------------------------------------------------------------
Задумывались ли вы, как системные вызовы могут быть перехвачены? Вы когда-нибудь пытались обмануть ядро, изменив аргументы системного вызова? Задумывались ли вы, как отладчики останавливают запущенный процесс и позволяют вам контролировать процесс?

Если вы думаете об использовании сложного программирования ядра для выполнения задачи, то вы ошибаетесь. Linux предоставляет элегантный механизм для достижения всех этих целей: системный вызов ptrace (Process Trace). Ptrace предоставляет механизм, с помощью которого родительский процесс может наблюдать и контролировать выполнение другого процесса. Он может проверять и изменять свой основной образ и регистры и используется главным образом для реализации отладки точек останова и отслеживания системных вызовов.

В этой статье мы узнаем, как перехватить системный вызов и изменить его аргументы. Во второй части статьи мы будем изучать передовые методы - установка точек останова и внедрение кода в работающую программу. Мы заглянем в регистры дочернего процесса и сегмент данных и изменим их содержимое. Мы также опишем способ внедрения кода, чтобы процесс можно было остановить и выполнить произвольные инструкции.

Основы

Операционные системы предлагают услуги через стандартный механизм, называемый системными вызовами. Они предоставляют стандартный API для доступа к базовому оборудованию и низкоуровневым службам, таким как файловые системы. Когда процесс хочет вызвать системный вызов, он помещает аргументы системных вызовов в регистры и вызывает прерывание 0x80 (для i386) и syscall (для x86-64). Это прерывание похоже на вход в режим ядра, и ядро выполнит системный вызов после изучения аргументов.
В архитектуре i386, номер системного вызова заносится в регистр %eax. Аргументы этого системного вызова заносятся в регистры %ebx, %ecx, %edx, %esi и %edi в указанном порядке. Например, вызов:

Write(2, "Hello", 5)

ориентировочно можно было бы перевести в:

movl   $4, %eax
movl   $2, %ebx
movl   $hello,%ecx
movl   $5, %edx
Int    $0x80

где $hello указывает на строковый литерал "Hello".

В архитектуре 64-bit , номер системного вызова заносится в регистр RAX , а аргументы заносятся в регистры: %rdi, %rsi, %rdx, %rcx, %r8 и %r9 для пользовательского уровня. А для интерфейса ядра %rdi, %rsi, %rdx, %r10, %r8 и %r9 . А системный вызов осуществляется с помощью инструкции syscall. Примерно это выглядит так:

mov   $1, %rax
mov   $2, %rdi
mov   $hello,%rsi
mov   $5, %rdx
syscall


Итак, где же появляется ptrace? Перед выполнением системного вызова ядро проверяет, отслеживается ли процесс. Если это так, ядро останавливает процесс и дает контроль над процессом отслеживания, чтобы оно могло просматривать и изменять регистры отслеживаемого процесса.

Давайте поясним это на примере того, как этот процесс работает. Но прежде я выкладываю заголовочный файл, который необходим для всех примеров ниже (в том числе для второй части статьи):

ptrace.bi:

#INCLUDE "crt.bi"
#INCLUDE "crt/linux/unistd.bi"
#INCLUDE "crt/linux/fcntl.bi"

'/* Type of the REQUEST argument to `ptrace.'  */

Enum ptrace_request

   '/* 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_TRACEME = 0,
   #DEFINE PT_TRACE_ME PTRACE_TRACEME

   '/* Return the word in the process's text space at address ADDR.  */
   PTRACE_PEEKTEXT = 1,
   #DEFINE PT_READ_I PTRACE_PEEKTEXT

   '/* Return the word in the process's data space at address ADDR.  */
   PTRACE_PEEKDATA = 2,
   #DEFINE PT_READ_D PTRACE_PEEKDATA

   '/* Return the word in the process's user area at offset ADDR.  */
   PTRACE_PEEKUSER = 3,
   #DEFINE PT_READ_U PTRACE_PEEKUSER

   '/* Write the word DATA into the process's text space at address ADDR.  */
   PTRACE_POKETEXT = 4,
   #DEFINE PT_WRITE_I PTRACE_POKETEXT

   '/* Write the word DATA into the process's data space at address ADDR.  */
   PTRACE_POKEDATA = 5,
   #DEFINE PT_WRITE_D PTRACE_POKEDATA

   '/* Write the word DATA into the process's user area at offset ADDR.  */
   PTRACE_POKEUSER = 6,
   #DEFINE PT_WRITE_U PTRACE_POKEUSER

   '/* Continue the process.  */
   PTRACE_CONT = 7,
   #DEFINE PT_CONTINUE PTRACE_CONT

   '/* Kill the process.  */
   PTRACE_KILL = 8,
   #DEFINE PT_KILL PTRACE_KILL

   '/* Single step the process.
   'This is not supported on all machines.  */
   PTRACE_SINGLESTEP = 9,
   #DEFINE PT_STEP PTRACE_SINGLESTEP

   '/* Get all general purpose registers used by a processes.
   'This is not supported on all machines.  */
   PTRACE_GETREGS = 12,
   #DEFINE PT_GETREGS PTRACE_GETREGS

   '/* Set all general purpose registers used by a processes.
   'This is not supported on all machines.  */
   PTRACE_SETREGS = 13,
   #DEFINE PT_SETREGS PTRACE_SETREGS

   '/* Get all floating point registers used by a processes.
   'This is not supported on all machines.  */
   PTRACE_GETFPREGS = 14,
   #DEFINE PT_GETFPREGS PTRACE_GETFPREGS

   '/* Set all floating point registers used by a processes.
   'This is not supported on all machines.  */
   PTRACE_SETFPREGS = 15,
   #DEFINE PT_SETFPREGS PTRACE_SETFPREGS

   '/* Attach to a process that is already running. */
   PTRACE_ATTACH = 16,
   #DEFINE PT_ATTACH PTRACE_ATTACH

   '/* Detach from a process attached to with PTRACE_ATTACH.  */
   PTRACE_DETACH = 17,
   #DEFINE PT_DETACH PTRACE_DETACH

   '/* Get all extended floating point registers used by a processes.
   'This is not supported on all machines.  */
   PTRACE_GETFPXREGS = 18,
   #DEFINE PT_GETFPXREGS PTRACE_GETFPXREGS

   '/* Set all extended floating point registers used by a processes.
   'This is not supported on all machines.  */
   PTRACE_SETFPXREGS = 19,
   #DEFINE PT_SETFPXREGS PTRACE_SETFPXREGS

   '/* Continue and stop at the next (return from) syscall.  */
   PTRACE_SYSCALL = 24,
   #DEFINE PT_SYSCALL PTRACE_SYSCALL

   '/* Set ptrace filter options.  */
   PTRACE_SETOPTIONS = &h4200,
   #DEFINE PT_SETOPTIONS PTRACE_SETOPTIONS

   '/* Get last ptrace message.  */
   PTRACE_GETEVENTMSG = &h4201,
   #DEFINE PT_GETEVENTMSG PTRACE_GETEVENTMSG

   '/* Get siginfo for process.  */
   PTRACE_GETSIGINFO = &h4202,
   #DEFINE PT_GETSIGINFO PTRACE_GETSIGINFO

   '/* Set new siginfo for process.  */
   PTRACE_SETSIGINFO = &h4203
   #DEFINE PT_SETSIGINFO PTRACE_SETSIGINFO

End Enum

Type user_regs_struct

   As Uinteger r15

   As Uinteger r14

   As Uinteger r13

   As Uinteger r12

   As Uinteger rbp

   As Uinteger rbx

   As Uinteger r11

   As Uinteger r10

   As Uinteger r9

   As Uinteger r8

   As Uinteger rax

   As Uinteger rcx

   As Uinteger rdx

   As Uinteger rsi

   As Uinteger rdi

   As Uinteger orig_rax

   As Uinteger rip

   As Uinteger cs

   As Uinteger eflags

   As Uinteger rsp

   As Uinteger ss

   As Uinteger fs_base

   As Uinteger gs_base

   As Uinteger ds

   As Uinteger es

   As Uinteger fs

   As Uinteger gs

End Type

Extern "C"

Declare Function wait_ Alias "wait" (wiStatus As Integer Ptr) As pid_t

Declare Function ptrace(request As ptrace_request, pid As  pid_t, addr As Any Ptr, uData As Any Ptr) As Integer

End Extern

Const ORIG_RAX =15

Const RAX   =10

Const RBX   =5

Const RCX   =11

Const RDX   =12

Const RSI   =13

Const RDI   =14

Const SYS_write =1

И так, вот пример:

#INCLUDE "ptrace.bi"

Dim As pid_t pidChild

Dim As Long lOrigRAX

pidChild = fork()

If pidChild = 0 Then

   ptrace(PTRACE_TRACEME, 0, NULL, NULL)

   execl("/bin/ls", "ls", NULL)

Else

   wait_(NULL)

   lOrigRAX = ptrace(PTRACE_PEEKUSER, pidChild , Cast(Any Ptr,Cint(8 * ORIG_RAX)), NULL)

   printf(!"The child made a " _
_
   !"system call %ld\n", lOrigRAX)

   ptrace(PTRACE_CONT, pidChild, NULL, NULL)

Endif

Sleep

При запуске эта программа печатает:

The child made a system call 59

наряду с выводом ls. Системный вызов номер 59 - execve, это первый системный вызов, выполняемый дочерним процессом. Для справки номера системных вызовов можно найти в /usr/include/x86_64-linux-gnu/asm/unistd_64.h Как видно из примера, процесс разветвляется с помощью fork() , и дочерний процесс -это именно тот процесс, который мы хотим отследить. Перед запуском exec дочерний процесс вызывает ptrace с первым аргументом, равным PTRACE_TRACEME. Это сообщает ядру, что процесс отслеживается, и когда дочерний процесс выполняет системный вызов execve, он передает управление своему родителю. Родитель ожидает уведомления от ядра с помощью вызова wait (). Затем родитель может проверить аргументы системного вызова или выполнить другие действия, такие как просмотр регистров.Когда происходит системный вызов, ядро сохраняет исходное содержимое регистра rax, который содержит номер системного вызова. Мы можем прочитать это значение из дочернего сегмента USER, вызвав ptrace с первым аргументом PTRACE_PEEKUSER, как показано выше. После того как мы закончили проверку системного вызова, дочерний процесс может продолжить вызов ptrace с первым аргументом PTRACE_CONT, который позволяет продолжить системный вызов.

Параметры ptrace

ptrace вызывается с четырьмя аргументами:

Long ptrace (Enum __ptrace_request request,
            pid_t pid,
            void * addr,
            void * Data);

Первый аргумент определяет поведение ptrace и то, как используются другие аргументы. Значение запроса должно быть одним из: PTRACE_TRACEME, PTRACE_PEEKTEXT, PTRACE_PEEKDATA, PTRACE_PEEKUSER, PTRACE_POKETEXT, PTRACE_POKEDATA, PTRACE_POKEUSER, PTRACE_GETREGS, PTRACE_GETFPREGS, PTRACE_SETREGS, PTRACE_SETFPREGS, PTRACE_CONT, PTRACE_SYSCALL, PTRACE_SINGLESTEP, PTRACE_DETACH. Значение каждого из этих запросов будет объяснено в оставшейся части статьи.

Чтение параметров системного вызова

Вызывая ptrace с PTRACE_PEEKUSER в качестве первого аргумента, мы можем проверить содержимое области USER, где хранится содержимое регистра и другая информация. Ядро хранит содержимое регистров в этой области, чтобы родительский процесс исследовал их через ptrace.
Давайте покажем это на примере:

#INCLUDE "ptrace.bi"

Dim As pid_t pidChild

Dim As Long lOrigRAX, lRax

Dim As Integer lParams(2)

Dim As Integer iStatus

Dim As Integer insyscall

Dim As user_regs_struct regs

pidChild = fork()

If pidChild = 0 Then
    
    ptrace(PTRACE_TRACEME, 0, NULL, NULL)
    
    execl("/bin/ls", "ls", NULL)
    
Else
    
    Do
        
        wait_ (@iStatus)
        
        If (iStatus And &h7f) = 0 Then Exit Do
        
        lOrigRAX = ptrace(PTRACE_PEEKUSER, pidChild, Cast(Any Ptr,Cint(8 * ORIG_RAX)), NULL)
        
        If lOrigRAX = SYS_write  Then
            
            If insyscall = 0 Then
                
                '/* Syscall entry */
                insyscall = 1
                
                lParams(0) = ptrace(PTRACE_PEEKUSER , pidChild, Cast(Any Ptr, 8 * RDI), NULL)
                
                lParams(1) = ptrace(PTRACE_PEEKUSER, pidChild, Cast(Any Ptr, 8 * RSI), NULL)
                
                lParams(2) = ptrace(PTRACE_PEEKUSER, pidChild, Cast(Any Ptr, 8 * RDX), NULL)
                
                printf(!"Write called with  %ld, %ld, %ld\n", lParams(0), lParams(1), lParams(2))
                
            Else
                
                '{ /* Syscall exit */
                lRax = ptrace(PTRACE_PEEKUSER, pidChild, Cast(Any Ptr,Cint(8 * RAX)), NULL)
                
                printf(!"Write returned with %ld\n", lRax)
                
                insyscall = 0
                
            Endif
            
        Endif
        
        ptrace(PTRACE_SYSCALL, pidChild, NULL, NULL)
        
    Loop
    
Endif

Sleep

Эта программа должна напечатать вывод, подобный следующему:

ppadala@linux:~/ptrace > ls
a.out        dummy.s      ptrace.txt
libgpm.html  registers.c  syscallparams.c
dummy        ptrace.html  simple.c
ppadala@linux:~/ptrace > ./a.out
Write called with 1, 1075154944, 48
a.out        dummy.s      ptrace.txt
Write returned with 48
Write called with 1, 1075154944, 59
libgpm.html  registers.c  syscallparams.c
Write returned with 59
Write called with 1, 1075154944, 30
dummy        ptrace.html  simple.c
Write returned with 30

Здесь мы отслеживаем системные вызовы write. В данном случае ls выполнила три системных вызова write. Вызов ptrace с первым аргументом PTRACE_SYSCALL заставляет ядро останавливать дочерний процесс всякий раз, когда выполняется вход или выход из системного вызова. Это эквивалентно выполнению PTRACE_CONT и остановке при следующем входе / выходе из системного вызова. В предыдущем примере мы использовали PTRACE_PEEKUSER для просмотра аргументов системного вызова write. Когда происходит возврат системного вызова, возвращаемое значение помещается в %rax, и его можно прочитать, как показано в этом примере. Переменная iStatus в вызове wait используется для проверки выхода ребенка. Это типичный способ проверить, был ли ребенок остановлен ptrace или смог выйти.

Чтение значений регистра

Если вы хотите прочитать значения регистров во время входа или выхода из системного вызова, процедура, показанная выше, может быть громоздкой. Вызов ptrace с первым аргументом PTRACE_GETREGS поместит все регистры в один вызов. Код для получения значений регистра выглядит так:

#INCLUDE "ptrace.bi"

Dim As pid_t pidChild

Dim As Long lOrigRAX, lRax

Dim As Integer iStatus

Dim As Integer insyscall

Dim As user_regs_struct regs

pidChild = fork()

If pidChild = 0 Then
    
    ptrace(PTRACE_TRACEME, 0, NULL, NULL)
    
    execl("/bin/ls", "ls", NULL)
    
Else 
    
    Do
        
        wait_ (@iStatus)
        
        If (iStatus And &h7f) = 0 Then Exit Do
        
        lOrigRAX = ptrace(PTRACE_PEEKUSER, pidChild, Cast(Any Ptr,Cint(8 * ORIG_RAX)), NULL)
        
        If lOrigRAX = SYS_write  Then
            
            If insyscall = 0 Then
                
                '/* Syscall entry */
                insyscall = 1
                
                ptrace(PTRACE_GETREGS, pidChild, NULL, @regs)
                
                printf(!"Write called with %ld, %ld, %ld\n", regs.rdi, regs.rsi, regs.rdx)
                
            Else 
                
                '{ /* Syscall exit */
                lRax = ptrace(PTRACE_PEEKUSER, pidChild, Cast(Any Ptr,Cint(8 * RAX)), NULL)
                
                printf(!"Write returned "_
               _ 
                !"with %ld\n", lRax)
                
                insyscall = 0
                
            Endif
            
        Endif
        
        ptrace(PTRACE_SYSCALL, pidChild, NULL, NULL)
        
    Loop
    
Endif

Sleep

Этот код аналогичен предыдущему примеру, за исключением вызова ptrace с параметром PTRACE_GETREGS. Здесь мы использовали структуру user_regs_struct, определенную в /usr/include/x86_64-linux-gnu/sys/user.h , чтобы прочитать значения регистра.

Веселимся

Теперь пришло время повеселиться. В следующем примере мы перевернем строку, переданную системному вызову write:

#INCLUDE "ptrace.bi"

Sub reversestring(s As Zstring Ptr)
    
    Dim sTemp  As String = *s
    
    For i As Long = 0 To Len(sTemp) -1
        
        (*s)[i] = sTemp[(Len(sTemp) -1)-i]
        
    Next 
    
End Sub

Sub getdata( pidChild As pid_t, iAddr As  Integer, szBuf As Zstring Ptr, iLen As Integer)
    
    Dim As  Zstring Ptr szTempAddr
    
    Dim As Integer i, j
    
    Union u 
        
        Dim As Long iValue
        
        Dim As Byte bBytes(4)
        
    End Union
    
    Dim uData As u
    
    i = 0
    
    j = iLen / 4
    
    szTempAddr = szBuf
    
    Do
        
        uData.iValue = ptrace(PTRACE_PEEKDATA, pidChild, Cast(Any Ptr,iAddr + i * 4), NULL)
        
        memcpy(szTempAddr, @(uData.bBytes(0)), 4)
        
        i+=1
        
        szTempAddr += 4
        
        If i>=j Then Exit Do
        
    Loop
    
    j = iLen Mod 4
    
    If j <> 0 Then
        
        uData.iValue = ptrace(PTRACE_PEEKDATA, pidChild, Cast(Any Ptr,iAddr + i * 4), NULL)
        
        memcpy(szTempAddr, @(uData.bBytes(0)), j)
        
    Endif
    
    szBuf[iLen] = !"\0"
    
End Sub

Sub putdata(pidChild As pid_t , iAddr As Integer,szBuf As Zstring Ptr,iLen As Integer)
    
    Dim As Zstring Ptr szTempAddr
    
    Dim As Integer i, j
    
    Union u 
        
        Dim As Long iValue
        
        Dim As Byte bBytes(4)
        
    End Union
    
    Dim uData As u
    
    i = 0
    
    j = iLen / 4
    
    szTempAddr = szBuf
    
    While i < j 
        
        memcpy(@(uData.bBytes(0)), szTempAddr, 4)
        
        ptrace(PTRACE_POKEDATA, pidChild, Cast(Any Ptr,iAddr + i * 4), Cast(Any Ptr,Cint(uData.iValue)))
        
        i+=1
        
        szTempAddr += 4
        
    Wend
    
    j = iLen Mod 4
    
    If j <> 0  Then
        
        memcpy(@(uData.bBytes(0)), szTempAddr, j)
        
        ptrace(PTRACE_POKEDATA, pidChild, Cast(Any Ptr,iAddr + i * 4), Cast(Any Ptr,Cint(uData.iValue)))
        
    Endif
    
End Sub

Dim As pid_t pidChild

pidChild = fork()

If pidChild = 0 Then
    
    ptrace(PTRACE_TRACEME, 0, NULL, NULL)
    
    execl("/bin/ls", "ls", NULL)
    
Else 
    
    Dim As Long lOrigRAX
    
    Dim As Integer iParams(3)
    
    Dim As Integer iStatus
    
    Dim As Zstring Ptr szBuf, szTempAddr
    
    Dim As Integer iToogle = 0
    
    Do
        
        wait_(@iStatus)
        
        If (iStatus And &h7f) = 0 Then Exit Do
        
        lOrigRAX = ptrace(PTRACE_PEEKUSER, pidChild, Cast(Any Ptr,8 * ORIG_RAX), NULL)
        
        If lOrigRAX = SYS_write Then
            
            If iToogle = 0  Then
                
                iToogle = 1
                
                iParams(0) = ptrace(PTRACE_PEEKUSER, pidChild, Cast(Any Ptr,8 * RDI), NULL)
                
                iParams(1) = ptrace(PTRACE_PEEKUSER, pidChild, Cast(Any Ptr,8 * RSI), NULL)
                
                iParams(2) = ptrace(PTRACE_PEEKUSER, pidChild, Cast(Any Ptr,8 * RDX), NULL)
                
                szBuf = calloc((iParams(2)+10) , 1)
                
                getdata(pidChild, iParams(1), szBuf, iParams(2))
                
                reversestring(szBuf)
                
                putdata(pidChild, iParams(1), szBuf, iParams(2))
                
            Else 
                
                iToogle = 0
                
            Endif
            
        Endif
        
        ptrace(PTRACE_SYSCALL, pidChild, NULL, NULL)
        
    Loop
    
Endif 

Sleep

Вывод выглядит примерно так:

ppadala@linux: ~ / ptrace> ls
a.out dummy.s ptrace.txt
libgpm.html registers.c syscallparams.c
пустышка ptrace.html simple.c
ppadala@linux: ~ / ptrace> ./a.out
txt.ecartp s.ymmud tuo.a
c.sretsiger lmth.mpgbil c.llacys_egnahc
c.elpmis lmth.ecartp ymmud

В этом примере используются все ранее обсужденные концепции, а также некоторые другие. В нем мы используем вызовы ptrace с PTRACE_POKEDATA для изменения значений данных. Он работает точно так же, как PTRACE_PEEKDATA, за исключением того, что он читает и записывает данные, которые потомок передает в аргументах системному вызову, тогда как PEEKDATA только читает данные.

Пошаговый

ptrace предоставляет функции для пошагового выполнения кода дочернего элемента. Вызов ptrace (PTRACE_SINGLESTEP, ..) сообщает ядру, что нужно останавливать дочерний элемент при каждой инструкции и позволить родительскому элементу получить контроль. В следующем примере показан способ чтения инструкции, выполняемой при выполнении системного вызова. Я создал небольшой фиктивный исполняемый файл, чтобы вы могли понять, что происходит, вместо того, чтобы беспокоиться о вызовах, сделанных libc.
Вот код для dummy1.s. Он написан на ассемблере. Для компиляции используйте gcc -o dummy1 dummy1.s :

.data
hello:
    .string "hello world\n"
.globl  main
main:
    
mov $1, %rax    /* syscall number */
    mov $1, %rdi    /* stdout */
    mov $hello, %rsi  /* buffer */
    mov $12, %rdx  /* Len */
    syscall

    /* Exit */
    mov $60, %rax   /* syscall number */
    mov $0, %rdi    /* Exit status */
    syscall

Программа-пример, которая пошагово выполняет приведенный выше код:

#INCLUDE "ptrace.bi"

Dim As pid_t pidChild

Dim As Long lOrigRAX

pidChild = fork()

If pidChild = 0 Then
    
    ptrace(PTRACE_TRACEME, 0, NULL, NULL)
    
    execl("./dummy1", "dummy1", NULL)
    
Else 
    
    Dim As Integer iStatus
    
    Union u 
        
        Dim As Long iValue
        
        Dim As Byte bBytes(4)
        
    End Union
    
    Dim uData As u
    
    Dim As user_regs_struct regs
    
    Dim As Integer iStart = 0
    
    Dim As Long lins
    
    Do
        
        wait_(@iStatus)
        
        If (iStatus And &h7f) = 0 Then Exit Do
        
        ptrace(PTRACE_GETREGS, pidChild, NULL, @regs)
        
        If iStart = 1 Then
            
            lins = ptrace(PTRACE_PEEKTEXT, pidChild, Cast(Any Ptr , regs.rip), NULL)
            
            printf(!"EIP: %lx Instruction executed: %lx\n", regs.rip, lins)
            
        Endif
        
        If regs.orig_rax = SYS_write Then
            
            iStart = 1
            
            ptrace(PTRACE_SINGLESTEP, pidChild, NULL, NULL)
            
        Else
            
            ptrace(PTRACE_SYSCALL, pidChild, NULL, NULL)
            
        Endif   
        
    Loop
    
Endif

Sleep

Возможно, вам придется взглянуть на руководство Intel, чтобы разобраться в том, что напечатает эта программа. Использование пошагового выполнения кода для более сложных процессов, таких как установка точек останова (брекпоинтов), требует тщательного проектирования и более сложного кода. Во второй части мы увидим, как можно вставлять точки останова и вводить код в работающую программу.

Оригинал статьи , написанный Pradeep Padala: https://www.linuxjournal.com/article/6100?page=0,3