Lets assume that i wrote program in .c and that end user is starting .exe file. During program's execution there is variable called CHECK that gets dynamically assigned in middle of program's execution using some pseudo random algorithms. In one point, if the variable matches some criteria (lets say CHECK == 1580 or some static predifined number) the program does something on output. My question is, can a person that has controll over the system running this program modify memory in such way that he modifies address space of variable CHECK and match it to number '1580' before IF condition is set and trigger IF function even if the algorithm didnt set '1580' in the first place?
Yes, it's easy using a debugger, e.g. gdb. Set a breakpoint right before the if, run the program until the breakpoint triggers, set the variable to any desired value, remove the breakpoint, and continue. You could even have the debugger skip the condition check altogether, jumping directly into the if-block. You could also replace the check in the binary code by a nop. This is basically what "cracks" for pirating software do.
Without the source code and debugging symbols this becomes somewhat harder as you have to figure out the addresses, but it just delays the inevitable. With complete access to the computer, you can manipulate any program any way you want. Various protection schemes exist (mainly obfuscation), but they just make it harder, not impossible.
To further prove my point, here is a very quick example:
Given the following C code:
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main () {
srand (time (NULL));
while (1) {
if (rand () == 1580) {
puts ("You got me!");
break;
}
}
}
Compile it with optimizations and without symbols to make it a little harder, assuming an x86_64 linux system:
gcc -O3 -flto -ffunction-sections -fdata-sections -Wl,--gc-sections -s test.c -o test
Ordinarily, this program would run for a few seconds before it quits. We want to make it quit immediately. Start it via the gdb debugger:
$ gdb ./test
(gdb) starti
Starting program: /tmp/test
Program stopped.
0x00007ffff7dd6090 in _start () from /lib64/ld-linux-x86-64.so.2
Obtain information about memory ranges. We are interested in the start address of the .text section:
(gdb) info files
Symbols from "/tmp/test".
Native process:
Using the running image of child process 12745.
While running this, GDB does not access memory from...
Local exec file:
`/tmp/test', file type elf64-x86-64.
Entry point: 0x555555554650
...
0x0000555555554610 - 0x00005555555547b2 is .text
...
So the actual code starts at 0x0000555555554610 in memory. Let's disassemble some of it:
(gdb) disas 0x0000555555554610,0x0000555555554700
Dump of assembler code from 0x555555554610 to 0x555555554700:
0x0000555555554610: xor %edi,%edi
0x0000555555554612: sub $0x8,%rsp
0x0000555555554616: callq 0x5555555545e0 <time#plt>
0x000055555555461b: mov %eax,%edi
0x000055555555461d: callq 0x5555555545d0 <srand#plt>
0x0000555555554622: nopl 0x0(%rax)
0x0000555555554626: nopw %cs:0x0(%rax,%rax,1)
0x0000555555554630: callq 0x5555555545f0 <rand#plt>
0x0000555555554635: cmp $0x62c,%eax
0x000055555555463a: jne 0x555555554630
0x000055555555463c: lea 0x17a(%rip),%rdi # 0x5555555547bd
0x0000555555554643: callq 0x5555555545c0 <puts#plt>
0x0000555555554648: xor %eax,%eax
0x000055555555464a: add $0x8,%rsp
0x000055555555464e: retq
...
That's the whole program. The cmp instruction is the interesting part; set a breakpoint there and let the program run:
(gdb) break *(0x0000555555554635)
Breakpoint 1 at 0x555555554635
(gdb) c
Continuing.
Breakpoint 1, 0x0000555555554635 in ?? ()
From the above assembly output you can see that 0x62c (i.e. 1580) is the magic number. Write it into the register, overwriting rand()s return value, and continue the program:
(gdb) set $eax = 1580
(gdb) c
Continuing.
You got me!
[Inferior 1 (process 12745) exited normally]
(gdb)
The program will immediately print the message and quit. Had we used some kind of password-entry function instead of just rand(), we could have done exactly the same thing to circumvent the password check. Instead of setting the value in the register, we could also have typed jump *0x000055555555463c to just jump into the if-block; that way, we don't even have to find the "magic" number.
Related
I have the following assembly file test which I want to debug,
How can I do that?
Note I am working with x86-64 and att syntax, plus I don't have access to c code.
I want to stop after each line and being able to see the registers in a table (I remember there is such an option).
I tried:
gdb test
r
but I get:
Starting program:
No executable file specified.
Use the "file" or "exec-file" command.
After running GDB on the executable1:
Use start or starti to set a breakpoint in main or _start respectively and run the program.
Or set breakpoints yourself with b 12 to set a breakpoint on source line 12 (if you built with enough debug info for this to work), or b *0x00401007 to set a breakpoint on an address you copy/pasted from disas output.
layout asm / layout reg puts GDB into text-UI mode with "windows" in your terminal for disassembly and registers. (This can be a bit flaky, you sometimes need control-L to redraw the screen, and sometimes GDB crashes when your process exits although I'm not sure if that's specifically from TUI.)
Otherwise without TUI mode, info reg and disas can be useful.
See the bottom of https://stackoverflow.com/tags/x86/info for more asm debugging tips.
Especially strace ./test is highly useful to see the system calls your program makes, decoded into C style. In toy programs you're playing with for your own experimentation, this basically works as an alternative to checking for error return values.
Footnote 1: You're not doing that part correctly:
No executable file specified.
That means no file called test existed in the directory where you ran gdb test.
You have to assemble + link test.S into an executable called test before you can run GDB on that file. If ls -l test shows it, then gdb test can debug it. (And ./test can run it.)
Often gcc -no-pie foo.S is a good choice to make debugging easier: addresses will be fixed at link time, so objdump -drwC -Mintel test output will match the addresses you see at run-time. And the addresses will be numerically smaller, so it's easier to visually spot a code (.text) address vs. .rodata (modern ld puts it in a separate page so it can avoid exec permission) vs. .data / .bss.
Either way, stack addresses are still easy to distinguish from code either way, 0x555... or 0x0000...XXXXXX is in the executable, 0x7fffff... is in the stack, other addresses from mmap are randomized. (But libc also gets mapped to a high address near the stack, with or without PIE.)
(Or if you're writing _start instead of main, gcc -nostdlib -static foo.S implies -no-pie)
I'm getting a weird error message when trying to assemble and run a .s file using AT&T Intel Syntax. Not sure if I'm even using the correct architecture to begin with, or if I'm having syntax errors, if I'm not using the correct commands to assemble and link, etc. Completely lost and I do not know where to begin.
So basically, I have a file called yea.s , which contains some simple assembler instructions. I then try to compile it using the command as yea.s -o yea.o and then link is using ld yea.o -o yea. When running ld, I get this weird message:ld: warning: cannot find entry symbol _start; defaulting to 000000440000.
This is the program im trying to run, very simple and doesn't really do anything.
resMsg: .asciz "xxxxxxxx"
.text
.global main
main:
pushq $0
ret
I just cannot figure out what's going on. Obviously, this is for school homework. I'm not looking for the answer to the homework, obviously, but this is the starting point to where I can actually start the coding. And I just cant figure out how to simple run the program, which it doesn't say in the assignment. Anyway, thanks in advance guys!
Linux executables require an entry point to be specified. The entry point is the address of the first instruction to be executed in your program. If not specified otherwise, the link editor looks for a symbol named _start to use as an entry point. Your program does not contain such a symbol, thus the linker complains and picks the beginning of the .text section as the entry point. To fix this problem, rename main to _start.
Note further that unlike on DOS, there is nothing to return to from _start. So your attempt to return is going to cause a crash. Instead, call the system call sys_exit to exit the program:
mov $0, %edi # exit status
mov $60, %eax # system call number
syscall # perform exit call
Alternatively, if you want to use the C runtime environment and call functions from the C library, leave your program as is and instead assemble and link using the C compiler driver cc:
cc -o yea yea.s
If you do so, the C runtime environment provides the entry point for you and eventually tries to call a function main which is where your code comes in. This approach is required if you want to call functions from the C library. If you do it this way, make sure that main follows the SysV ABI (calling convention).
Note that even then your code is incorrect. The return value of a function is given in the eax (resp. rax) register and not pushed on the stack. To return zero from main, write
mov $0, %eax # exit status
ret # return from function
In all currently supported versions of Ubuntu open the terminal and type:
sudo apt install as31 nasm
as31: Intel 8031/8051 assembler
This is a fast, simple, easy to use Intel 8031/8051 assembler.
nasm: General-purpose x86 assembler
Netwide Assembler. NASM will currently output flat-form binary files, a.out, COFF and ELF Unix object files, and Microsoft 16-bit DOS and Win32 object files.
If you are using NASM in Ubuntu 18.04, the commands to compile and run an .asm file named example.asm are:
nasm -f elf64 example.asm # assemble the program
ld -s -o example example.o # link the object file nasm produced into an executable file
./example # example is an executable file
I try to use printf from my assembler code, this is a minimal example which should just print hello to stdout:
.section .rodata
hello:
.ascii "hello\n\0"
.section .text
.globl _start
_start:
movq $hello, %rdi # first parameter
xorl %eax, %eax # 0 - number of used vector registers
call printf
#exit
movq $60, %rax
movq $0, %rdi
syscall
I build it with
gcc -nostdlib try_printf.s -o try_printf -lc
and when I run it, it seems to work: the string hello is printed out and the exit status is 0:
XXX$ ./try_printf
hello
XXX$ echo $?
0
XXX$
But when I try to capture the text, it is obvious, that something is not working properly:
XXX$ output=$(./try_printf)
XXX$ echo $output
XXX$
The variable output should have the value hello, but is empty.
What is wrong with my usage of printf?
Use call exit instead of a raw _exit syscall after using stdio functions like printf. This flushes stdio buffers (write system call) before making an exit_group system call).
(Or if your program defines a main instead of _start, returning from main is equivalent to calling exit. You can't ret from _start.) Calling fflush(NULL) should also work.
As Michael explained, it is OK to link the C-library dynamically. This is also how it is introduced in the "Programming bottom up" book (see chapter 8).
However it is important to call exit from the C-library in order to end the program and not to bypass it, which was what I wrongly did by calling exit-syscall. As hinted by Michael, exit does a lot of clean up like flushing streams.
That is what happened: As explained here, the C-library buffers the the standard streams as follows:
No buffering for standard error.
If standard out/in is a terminal, it is line-buffered.
If standard out/in is a not a terminal, it is fully-buffered and thus flush is needed before a raw exit system call.
Which case applies is decided when printf is called for the first time for a stream.
So if printf_try is called directly in the terminal, the output of the program can be seen because hello has \n at the end (which triggers the flush in the line-buffered mode) and it is a terminal, also the 2. case.
Calling printf_try via $(./printf_try) means that the stdout is no longer a terminal (actually I don't know whether is is a temp file or a memory file) and thus the 3. case is in effect - there is need for an explicit flush i.e. call to C-exit.
The C standard library often contains initialization code for the standard I/O streams — initialization code that you're bypassing by defining your own entry point. Try defining main instead of _start:
.globl main
main:
# _start code here.
and then build with gcc try_printf.s -o try_printf (i.e., without -nostdlib).
I try to use printf from my assembler code, this is a minimal example which should just print hello to stdout:
.section .rodata
hello:
.ascii "hello\n\0"
.section .text
.globl _start
_start:
movq $hello, %rdi # first parameter
xorl %eax, %eax # 0 - number of used vector registers
call printf
#exit
movq $60, %rax
movq $0, %rdi
syscall
I build it with
gcc -nostdlib try_printf.s -o try_printf -lc
and when I run it, it seems to work: the string hello is printed out and the exit status is 0:
XXX$ ./try_printf
hello
XXX$ echo $?
0
XXX$
But when I try to capture the text, it is obvious, that something is not working properly:
XXX$ output=$(./try_printf)
XXX$ echo $output
XXX$
The variable output should have the value hello, but is empty.
What is wrong with my usage of printf?
Use call exit instead of a raw _exit syscall after using stdio functions like printf. This flushes stdio buffers (write system call) before making an exit_group system call).
(Or if your program defines a main instead of _start, returning from main is equivalent to calling exit. You can't ret from _start.) Calling fflush(NULL) should also work.
As Michael explained, it is OK to link the C-library dynamically. This is also how it is introduced in the "Programming bottom up" book (see chapter 8).
However it is important to call exit from the C-library in order to end the program and not to bypass it, which was what I wrongly did by calling exit-syscall. As hinted by Michael, exit does a lot of clean up like flushing streams.
That is what happened: As explained here, the C-library buffers the the standard streams as follows:
No buffering for standard error.
If standard out/in is a terminal, it is line-buffered.
If standard out/in is a not a terminal, it is fully-buffered and thus flush is needed before a raw exit system call.
Which case applies is decided when printf is called for the first time for a stream.
So if printf_try is called directly in the terminal, the output of the program can be seen because hello has \n at the end (which triggers the flush in the line-buffered mode) and it is a terminal, also the 2. case.
Calling printf_try via $(./printf_try) means that the stdout is no longer a terminal (actually I don't know whether is is a temp file or a memory file) and thus the 3. case is in effect - there is need for an explicit flush i.e. call to C-exit.
The C standard library often contains initialization code for the standard I/O streams — initialization code that you're bypassing by defining your own entry point. Try defining main instead of _start:
.globl main
main:
# _start code here.
and then build with gcc try_printf.s -o try_printf (i.e., without -nostdlib).
I was searching other threads without luck.
My problem is perhaps simple but frustrating.
I'm compiling two files on 64-bit Ubuntu 11.04:
nasm -f elf64 -g file64.asm
gcc -g -o file file.c file64.o
Then I debug the resulting executables with gdb.
With C, everything is OK.
However, when debugging assembly, the source code is "not visible" to the debugger. I'm getting the following output:
(gdb) step
Single stepping until exit from function line,
which has no line number information.
0x0000000000400962 in convert ()
A quick investigation with:
objdump --source file64.o
shows that the assembly source code (and line information) is contained in the file.
Why can't I see it in a debug session? What am I doing wrong?
These problems arose after moving to 64-bit Ubuntu. In the 32-bit Linux it worked (as it should).
With NASM, I've had much better experience in gdb when using the dwarf debugging format. gdb then treats the assembly source as if it were any other language (i.e., no disassemble commands necessary)
nasm -f elf64 -g -F dwarf file64.asm
(Versions 2.03.01 and later automatically enable -g if -F is specified.)
I'm using NASM version 2.10.07. I'm not sure if that makes a difference or not.
GDB is a source-level (or symbolic) debugger, which means that it's supposed to work with 'high-level programming languages' ... which is not you're case!
But wait a second, because, from a debugger's point of view, debugging ASM programs is way easier than higher level languages: there's almost nothing to do! The program binary always contains the assembly instruction, there're just written in their machine format, instead of ascii format.
And GDB has the ability to convert it for you. Instead of executing list to see the code, use disassemble to see a function code:
(gdb) disassemble <your symbol>
Dump of assembler code for function <your symbol>:
0x000000000040051e <+0>: push %rbp
0x000000000040051f <+1>: mov %rsp,%rbp
=> 0x0000000000400522 <+4>: mov 0x20042f(%rip),%rax
0x0000000000400529 <+11>: mov %rax,%rdx
0x000000000040052c <+14>: mov $0x400678,%eax
0x0000000000400531 <+19>: mov %rdx,%rcx
or x/5i $pc to see 5 i nstruction after your $pc
(gdb) x/5i $pc
=> 0x400522 <main+4>: mov 0x20042f(%rip),%rax
0x400529 <main+11>: mov %rax,%rdx
0x40052c <main+14>: mov $0x400678,%eax
0x400531 <main+19>: mov %rdx,%rcx
0x400534 <main+22>: mov $0xc,%edx
then use stepi (si) instread of step and nexti (ni) instead of next.
display $pc could also be useful to print the current pc whenever the inferior stops (ie, after each nexti/stepi.
For anyone else stuck with the broken things on NASM (the bug is not fixed so far): just download the NASM git repository and switch to version 2.7, which is probably the last version that works fine, i.e. supports gdb. Building from source this outdated version is only a workaround (you don't have support for the last ISA for example), but it's sufficient for most students.
GDB might not know where to search for your source files. Try to explicitly tell it with directory.