Monday, 8. May 2006, 13:49:38
Patching code using gdb is no fun at all, if you need to do anything more complicated than just nop'ing a few instructions the standard procedure is to write the code, assemble it, objdump it then use ht or biew or similar to do the patching. You can patch code within gdb, but it's so long winded I'm sure few people bother unless they just need to modify one or two opcodes.Someone mentioned that gentoo's skel
.gdbinit file contained a handy macro
`assemble` that would print opcodes to stdout, I normally use gdb vanilla so hadnt tried it before, but after checking it out, it's a great idea.
gdb> nasm_assemble
Hit Ctrl-D to start, type code to assemble, hit Ctrl-D when done.
It is recommended to start with
BITS 32
Note that this command uses NASM (Intel syntax) to assemble.
BITS 32
xor eax, eax
rdtsc
xor eax, edx
rol eax, 0x4
31 c0 0f 31 31 d0 c1 c0 04
Unfortunately it has a number of problems:
- It's buggy, you have to hit ctrl-d before starting, then again to finish, this is just confusing.
- You have to enter "BITS 32" before entering any code, if you forget, you have to start again.
- You can't see which instructions correspond to which bytes in the output.
- It's not very gdb like, which normally allows you to finish by entering 'end' and uses readline.
- You still have to patch the code manually using the bytes it outputs.
I rewrote the macro to fix all these issues. Here's an example of it in use.
$ gdb -q
(gdb) file true
Using host libthread_db library "/lib/tls/libthread_db.so.1".
(gdb) tb main
Breakpoint 1 at 0x80489b9: file true.c, line 62.
(gdb) r
main (argc=1, argv=0xbfffea64) at true.c:62
(gdb) x/6i $pc
0x80489b9 <main+21>: mov eax,DWORD PTR [ebx]
0x80489bb <main+23>: mov DWORD PTR [esp],0x6
0x80489c2 <main+30>: mov DWORD PTR [esp+4],0x8049e43
0x80489ca <main+38>: mov ds:0x804b884,eax
0x80489cf <main+43>: call 0x80486e0 <setlocale@plt>
0x80489d4 <main+48>: mov DWORD PTR [esp],0x8048a79
(gdb) help assemble
Assemble instructions using nasm.
Type a line containing "end" to indicate the end.
If an address is specified, insert instructions at that address.
If no address is specified, assembled instructions are printed to stdout.
(gdb) assemble
Instructions will be written to stdout.
Type instructions, one per line.
End with a line saying just "end".
>xor eax, eax
>inc eax
>end
00000000 31C0 xor eax,eax
00000002 40 inc eax
(gdb) assemble $pc
Instructions will be written to 0x80489b9.
Type instructions, one per line.
End with a line saying just "end".
>xor eax, eax
>inc eax
>mov ebx, 42
>int 0x80
>end
(gdb) x/6i $pc
0x80489b9 <main+21>: xor eax,eax
0x80489bb <main+23>: inc eax
0x80489bc <main+24>: mov ebx,0x2a
0x80489c1 <main+29>: int 0x80
0x80489c3 <main+31>: inc esp
0x80489c4 <main+32>: and al,0x4
(gdb) c
Program exited with code 052.
This is really great for patching routines on the fly, if a hostile binary is using the classic
ptrace(PTRACE_TRACEME, ...) trick to detect debuggers using
ptrace(), patching it out is now as simple as:
$ cat test.c
#include <unistd.h>
#include <sys/ptrace.h>
#include <stdio.h>
int main(int argc, char **argv)
{
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
fprintf(stdout, "debugger present.\n");
return 1;
}
fprintf(stdout, "no debugger present.\n");
return 0;
}
$ gdb -q
(gdb) file ptrace
Using host libthread_db library "/lib/tls/libthread_db.so.1".
(gdb) r
debugger present.
Program exited with code 01.
(gdb) set write
(gdb) file ptrace
(gdb) info functions ptrace
0x080482f4 ptrace@plt
(gdb) assemble 0x080482f4
Instructions will be written to 0x80482f4.
Type instructions, one per line.
End with a line saying just "end".
>xor eax, eax
>ret
>end
(gdb) set write off
(gdb) file ptrace
(gdb) r
no debugger present.
Program exited normally.
(gdb) q
You can use convenience variables (eg registers), symbol names or actual addresses as an argument, and the address is checked for validity before prompting for input.
$ gdb -q
(gdb) assemble 0x12345
Cannot access memory at address 0x12345
(gdb) q
The prompt is supposed to be identical to the multiline prompt gdb uses, it also uses readline so you can use standard readline bindings, and you no longer have to use
BITS 32. I'm really pleased with how it worked out, hopefuly somebody else will find it useful. Here's the full code of the macro:
define assemble
# dont enter routine again if user hits enter
dont-repeat
if ($argc)
if (*$arg0 = *$arg0)
# check if we have a valid address by dereferencing it,
# if we havnt, this will cause the routine to exit.
end
printf "Instructions will be written to %#x.\n", $arg0
else
printf "Instructions will be written to stdout.\n"
end
printf "Type instructions, one per line.\n"
printf "End with a line saying just \"end\".\n"
if ($argc)
# argument specified, assemble instructions into memory
# at address specified.
shell nasm -f bin -o /dev/stdout /dev/stdin \
<<< "$( echo "BITS 32"; while read -ep '>' r && test "$r" != end; \
do echo -E "$r"; done )" | hexdump -ve \
'1/1 "set *((unsigned char *) $arg0 + %#2_ax) = %#02x\n"' \
> ~/.gdbassemble
# load the file containing set instructions
source ~/.gdbassemble
# all done.
shell rm -f ~/.gdbassemble
else
# no argument, assemble instructions to stdout
shell nasm -f bin -o /dev/stdout /dev/stdin \
<<< "$( echo "BITS 32"; while read -ep '>' r && test "$r" != end; \
do echo -E "$r"; done )" | ndisasm -i -b32 /dev/stdin
end
end
document assemble
Assemble instructions using nasm.
Type a line containing "end" to indicate the end.
If an address is specified, insert instructions at that address.
If no address is specified, assembled instructions are printed to stdout.
Use the pseudo instruction "org ADDR" to set the base address.
end