gdb catch syscall condition and string comparisson - linux

I would like to catch a system call (more specifically access) and set a condition on it based on string comparison (obviously for arguments that are strings).
Specific example: when debugging ls I would like to catch access syscalls for specific pathnames (the 1st argument)
int access(const char *pathname, int mode);
So far, I have succeeded in manually inspecting the pathname argument of access (see [1]).
I tried to use this blog post:
catch syscall access
condition 1 strcmp((char*)($rdi), "/etc/ld.so.preload") == 0
but failed (see [2]), as gdb informed me of a segfault and that Evaluation of the expression containing the function (strcmp#plt) will be abandoned.. However gdb suggested set unwindonsignal on.
Which I tried:
set unwindonsignal on
catch syscall access
condition 1 strcmp((char*)($rdi), "/etc/ld.so.preload") == 0
but failed again (see [3]) with a similar error and the suggestion set unwindonsignal off...
I searched for the The program being debugged was signaled while in a function called from GDB. error message, but (I think) I didn't find something relevant.
Any help or ideas?
[1]
$ gdb ls
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
...
Reading symbols from ls...(no debugging symbols found)...done.
(gdb) catch syscall access
Catchpoint 1 (syscall 'access' [21])
(gdb) r
Starting program: /bin/ls
Catchpoint 1 (call to syscall access), 0x00007ffff7df3537 in access () at ../sysdeps/unix/syscall-template.S:81
81 ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) x /s $rdi
0x7ffff7df6911: "/etc/ld.so.nohwcap"
(gdb) c
Continuing.
Catchpoint 1 (returned from syscall access), 0x00007ffff7df3537 in access () at ../sysdeps/unix/syscall-template.S:81
81 in ../sysdeps/unix/syscall-template.S
(gdb) x /s $rdi
0x7ffff7df6911: "/etc/ld.so.nohwcap"
(gdb) c
Continuing.
Catchpoint 1 (call to syscall access), 0x00007ffff7df3537 in access () at ../sysdeps/unix/syscall-template.S:81
81 in ../sysdeps/unix/syscall-template.S
(gdb) x /s $rdi
0x7ffff7df9420 <preload_file.9747>: "/etc/ld.so.preload"
[2]
$ gdb ls
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
...
Reading symbols from ls...(no debugging symbols found)...done.
(gdb) catch syscall access
Catchpoint 1 (syscall 'access' [21])
(gdb) condition 1 strcmp((char*)($rdi), "/etc/ld.so.preload") == 0
(gdb) info breakpoints
Num Type Disp Enb Address What
1 catchpoint keep y syscall "access"
stop only if strcmp((char*)($rdi), "/etc/ld.so.preload") == 0
(gdb) r
Starting program: /bin/ls
Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
Error in testing breakpoint condition:
The program being debugged was signaled while in a function called from GDB.
GDB remains in the frame where the signal was received.
To change this behavior use "set unwindonsignal on".
Evaluation of the expression containing the function
(strcmp#plt) will be abandoned.
When the function is done executing, GDB will silently stop.
Catchpoint 1 (returned from syscall munmap), 0x0000000000000000 in ?? ()
[3]
$ gdb ls
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
...
Reading symbols from ls...(no debugging symbols found)...done.
(gdb) set unwindonsignal on
(gdb) catch syscall access
Catchpoint 1 (syscall 'access' [21])
(gdb) condition 1 strcmp((char*)($rdi), "/etc/ld.so.preload") == 0
(gdb) r
Starting program: /bin/ls
Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
Error in testing breakpoint condition:
The program being debugged was signaled while in a function called from GDB.
GDB has restored the context to what it was before the call.
To change this behavior use "set unwindonsignal off".
Evaluation of the expression containing the function
(strcmp#plt) will be abandoned.
Catchpoint 1 (returned from syscall munmap), 0x00007ffff7df3537 in access () at ../sysdeps/unix/syscall-template.S:81
81 ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) x /s $rdi
0x7ffff7df6911: "/etc/ld.so.nohwcap"

You can use the gdb internal function $_streq like this:
(gdb) catch syscall access
Catchpoint 1 (syscall 'access' [21])
(gdb) condition 1 $_streq((char *)$rdi, "/etc/ld.so.preload")
(gdb) ru
Starting program: /bin/ls
Catchpoint 1 (call to syscall access), 0x00007ffff7df3537 in access ()
at ../sysdeps/unix/syscall-template.S:81
81 ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) p (char *)$rdi
$1 = 0x7ffff7df9420 <preload_file> "/etc/ld.so.preload"

Related

how does my program terminate when i trap SIGSEGV

I wrote a program that run some code on the stack without having execution permission on the stack. And before I run the program execute: trap '' SIGSEGV to ensure the program will ignore the signal.
lqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqk
x0x80486be <_IO_stdin_used+2> add (%eax),%al x
x0x80486c0 mov $0x12d687,%eax x
x0x80486c5 mov $0x12d687,%eax x
x0x80486ca mov $0x12d687,%eax x
x0x80486cf mov $0x12d687,%eax x
x0x80486d4 add %ah,0x6c(%esi) x
x0x80486d7 popa x
x0x80486d8 addr16 je,pn 0x8048754 x
x0x80486dc je 0x80486de x
>x0x80486de outsl %ds:(%esi),(%dx) x
x0x80486df jo 0x8048746 x
x0x80486e1 outsb %ds:(%esi),(%dx) x
x0x80486e2 and %ah,0x69(%esi) x
x0x80486e5 insb (%dx),%es:(%edi) x
x0x80486e6 add %dh,%gs:0x65(%edx) x
x0x80486ea popa x
x0x80486eb and %ah,%fs:0x69(%esi) x
x0x80486ef insb (%dx),%es:(%edi) x
mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj
native process 225742 In: L?? PC: 0x80486de
(gdb) info register eax
eax 0x12d687 1234567
(gdb) si
0x080486d7 in ?? ()
0x080486d8 in ?? ()
0x080486dc in ?? ()
0x080486de in ?? ()
Program received signal SIGSEGV, Segmentation fault.
0x080486de in ?? ()
(gdb)
I know that outsl required higher privileged level and that's why the process terminate.
what I don't understand is why the code terminate by SIGSEGV and not another way? because if it will terminate this way it will suppose to ignore the SIGSEGV.
In addition I don't understand how the code indeed run on the stack. I thought it will just ignore the signal and skip each instruction in the assembly but the value of eax indeed changed.
checksec child.o
[*] '/tmp/nadav_tiny_hard/child.o'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
// code.c
#include <sys/syscall.h>
#include <sys/user.h>
#include <sys/reg.h>
#include <fcntl.h>
// execute mov eax, 1234567 four times. (not return or finish after that, continue to execute bullshit bits)
unsigned char *shellcode = "\xB8\x87\xD6\x12\x00\xB8\x87\xD6\x12\x00\xB8\x87\xD6\x12\x00\xB8\x87\xD6\x12\x00";
int main()
{
setvbuf(stdout, 0, 2, 0);
int (*ret)() = (int(*)())shellcode;
ret();
return 0;
}
EDIT:
$ ./code
Segmentation fault (core dumped)
don't know how to check which instruction makes the abort, and I would like to know how to survive sigsegv without terminating immediately, for the purpose of using ptrace.
EDIT2:
according to Hyde isn't it strange that my program called just one time strace and not endless number of time?
$ strace ./code
--- SIGSEGV {si_signo=SIGSEGV, si_code=SI_KERNEL, si_addr=0} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)
...

GDB error in eclipse: Invalid thread id: 1

I try to debug an out-file with gdb in eclipse Version: 2021-12 (4.22.0).
With using gdb in a command shell I could start the gdb and set breakpoints without any errors. In the output I could see that several threads are created:
[New Thread 8200.0x2188]
[New Thread 8200.0x4274]
[New Thread 8200.0x276c]
[New Thread 8200.0x2a4c
But if I try to start the debugger in eclipse I always got the error message msg="Invalid thread id: 1". I also see in the output that the gdbinit file could not be loaded:
955,742 &".gdbinit: No such file or directory.\n"
955,742 12^error,msg=".gdbinit: No such file or directory."
And there is no further information, that some threads are created.
Could you please help me?
Thanks
I added the complete log:
720,985 2-gdb-version
720,989 ~"GNU gdb (GDB) 8.1\n"
720,989 ~"Copyright (C) 2018 Free Software Foundation, Inc.\n"
720,989 ~"License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html\nThis is fre\
e software: you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitt\
ed by law. Type "show copying"\nand "show warranty" for details.\n"
720,990 ~"This GDB was configured as "x86_64-w64-mingw32".\nType "show configuration" for config\
uration details."
720,990 ~"\nFor bug reporting instructions, please see:\n"
720,990 ~"http://www.gnu.org/software/gdb/bugs/.\n"
720,990 ~"Find the GDB manual and other documentation resources online at:\n<http://www.gnu.org/soft\
ware/gdb/documentation/>.\n"
720,990 ~"For help, type "help".\n"
720,990 ~"Type "apropos word" to search for commands related to "word".\n"
720,990 2^done
720,990 (gdb)
720,991 3-environment-cd "C:/Projects/xy/03_SourceCode"
721,004 3^done
721,005 (gdb)
721,005 4-gdb-set breakpoint pending on
721,019 4^done
721,019 (gdb)
721,020 5-enable-pretty-printing
721,035 5^done
721,035 (gdb)
721,036 6-gdb-set python print-stack none
721,055 6^done
721,056 (gdb)
721,056 7-gdb-set print object on
721,066 7^done
721,066 (gdb)
721,066 8-gdb-set print sevenbit-strings on
721,082 8^done
721,082 (gdb)
721,082 9-gdb-set charset ISO-8859-1
721,098 9^done
721,098 (gdb)
721,098 10-gdb-set dprintf-style gdb
721,113 10^done
721,113 (gdb)
721,113 11source .gdbinit
721,129 &"source .gdbinit\n"
721,129 &".gdbinit: No such file or directory.\n"
721,130 11^error,msg=".gdbinit: No such file or directory."
721,130 (gdb)
721,130 12-gdb-set target-async off
721,144 12^done
721,144 (gdb)
721,144 13-gdb-set auto-solib-add on
721,160 13^done
721,160 (gdb)
721,162 14-file-exec-file C:\Temp\test_Generic.out
721,176 14^done
721,176 (gdb)
721,176 15-gdb-show --thread-group i1 language
721,191 15^done,value="auto"
721,191 (gdb)
721,192 16-gdb-set --thread-group i1 language c
721,207 16^done
721,207 (gdb)
721,207 17-interpreter-exec --thread-group i1 console "p/x (char)-1"
721,223 ~"$1 = 0xff\n"
721,223 17^done
721,224 (gdb)
721,224 18-data-evaluate-expression --thread-group i1 "sizeof (void*)"
721,239 18^done,value="8"
721,239 (gdb)
721,240 19-gdb-set --thread-group i1 language auto
721,254 19^done
721,254 (gdb)
721,255 20-interpreter-exec --thread-group i1 console "show endian"
721,271 ~"The target endianness is set automatically (currently little endian)\n"
721,271 20^done
721,271 (gdb)
721,272 21-break-list
721,273 22-stack-info-depth --thread 1
721,273 23-list-thread-groups
721,285 21^done,BreakpointTable={nr_rows="0",nr_cols="6",hdr=[{width="7",alignment="-1",col_name="nu\
mber",colhdr="Num"},{width="14",alignment="-1",col_name="type",colhdr="Type"},{width="4",alignment="\
-1",col_name="disp",colhdr="Disp"},{width="3",alignment="-1",col_name="enabled",colhdr="Enb"},{width\
="10",alignment="-1",col_name="addr",colhdr="Address"},{width="40",alignment="2",col_name="what",col\
hdr="What"}],body=[]}
721,285 (gdb)
721,285 22^error,msg="Invalid thread id: 1"
721,285 (gdb)
721,286 23^done,groups=[{id="i1",type="process",executable="C:\Temp\test_Generic.out"}]
721,286 (gdb)
721,286 24-stack-list-frames --thread 1
721,300 24^error,msg="Invalid thread id: 1"
721,300 (gdb)

How does gdb start an assembly compiled program and step one line at a time?

Valgrind says the following on their documentation page
Your program is then run on a synthetic CPU provided by the Valgrind core
However GDB doesn't seem to do that. It seems to launch a separate process which executes independently. There's also no c library from what I can tell. Here's what I did
Compile using clang or gcc gcc -g tiny.s -nostdlib (-g seems to be required)
gdb ./a.out
Write starti
Press s a bunch of times
You'll see it'll print out "Test1\n" without printing test2. You can also kill the process without terminating gdb. GDB will say "Program received signal SIGTERM, Terminated." and won't ever write Test2
How does gdb start the process and have it execute only one line at a time?
.text
.intel_syntax noprefix
.globl _start
.p2align 4, 0x90
.type _start,#function
_start:
lea rsi, [rip + .s1]
mov edi, 1
mov edx, 6
mov eax, 1
syscall
lea rsi, [rip + .s2]
mov edi, 1
mov edx, 6
mov eax, 1
syscall
mov eax, 60
xor edi, edi
syscall
.s1:
.ascii "Test1\n"
.s2:
.ascii "Test2\n"
starti implementation
As usual for a process that wants to start another process, it does a fork/exec, like a shell does. But in the new process, GDB doesn't just make an execve system call right away.
Instead, it calls ptrace(PTRACE_TRACEME) to wait for the parent process to attach to it, so GDB (the parent) is already attached before the child process makes an execve() system call to make this process start executing the specified executable file.
Also note in the execve(2) man page:
If the current program is being ptraced, a SIGTRAP signal is sent
to it after a successful execve().
So that's how the kernel debugging API supports stopping before the first user-space instruction is executed in a newly-execed process. i.e. exactly what starti wants. This doesn't depend on setting a breakpoint; that can't happen until after execve anyway, and with ASLR the correct address isn't even known until after execve picks a base address. (GDB by default disables ASLR, but it still works if you tell it not to disable ASLR.)
This is also what GDB use if you set breakpoints before run, manually, or by using start to set a one-time breakpoint on main. Before the starti command existed, a hack to emulate that functionality was to set an invalid breakpoint before run, so GDB would stop on that error, giving you control at that point.
If you strace -f -o gdb.trace gdb ./foo or something, you'll see some of what GDB does. (Nested tracing apparently doesn't work, so running GDB under strace means GDB's ptrace system call fails, but we can see what it does leading up to that.)
...
231566 execve("/usr/bin/gdb", ["gdb", "./foo"], 0x7ffca2416e18 /* 57 vars */) = 0
# the initial GDB process is PID 231566.
... whole bunch of stuff
231566 write(1, "Starting program: /tmp/foo \n", 28) = 28
231566 personality(0xffffffff) = 0 (PER_LINUX)
231566 personality(PER_LINUX|ADDR_NO_RANDOMIZE) = 0 (PER_LINUX)
231566 personality(0xffffffff) = 0x40000 (PER_LINUX|ADDR_NO_RANDOMIZE)
231566 vfork( <unfinished ...>
# 231584 is the new PID created by vfork that would go on to execve the new PID
231584 openat(AT_FDCWD, "/proc/self/fd", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 13
231584 newfstatat(13, "", {st_mode=S_IFDIR|0500, st_size=0, ...}, AT_EMPTY_PATH) = 0
231584 getdents64(13, 0x558403e20360 /* 16 entries */, 32768) = 384
231584 close(3) = 0
... all these FDs
231584 close(12) = 0
231584 getdents64(13, 0x558403e20360 /* 0 entries */, 32768) = 0
231584 close(13) = 0
231584 getpid() = 231584
231584 getpid() = 231584
231584 setpgid(231584, 231584) = 0
231584 ptrace(PTRACE_TRACEME) = -1 EPERM (Operation not permitted)
231584 write(2, "warning: ", 9) = 9
231584 write(2, "Could not trace the inferior pro"..., 37) = 37
231584 write(2, "\n", 1) = 1
231584 write(2, "warning: ", 9) = 9
231584 write(2, "ptrace", 6) = 6
231584 write(2, ": ", 2) = 2
231584 write(2, "Operation not permitted", 23) = 23
231584 write(2, "\n", 1) = 1
# gotta love unbuffered stderr
231584 exit_group(127) = ?
231566 <... vfork resumed>) = 231584 # in the parent
231584 +++ exited with 127 +++
# then the parent is running again
231566 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=231584, si_uid=1000, si_status=127, si_utime=0, si_stime=0} ---
231566 rt_sigreturn({mask=[]}) = 231584
... then I typed "quit" and hit return
There some earlier clone system calls to create more threads in the main GDB process, but those didn't exit until after the vforked PID that attempted ptrace(PTRACE_TRACEME). They were all just threads since they used clone with CLONE_VM. There was one earlier vfork / execve of /usr/bin/iconv.
Annoyingly, modern Linux has moved to PIDs wider than 16-bit so the numbers get inconveniently large for human minds.
step implementation:
Unlike stepi which would use PTRACE_SINGLESTEP on ISAs that support it (e.g. x86 where the kernel can use the TF trap flag, but interestingly not ARM), step is based on source-level line number <-> address debug info. That's usually pointless for asm, unless you want to step past macro expansions or something.
But for step, GDB will use ptrace(PTRACE_POKETEXT) to write an int3 debug-break opcode over the first byte of an instruction, then ptrace(PTRACE_CONT) to let execution run in the child process until it hits a breakpoint or other signal. (Then put back the original opcode byte when this instruction needs to execute). The place at which it puts that breakpoint is something it finds by looking for the next address of a line-number in the DWARF or STABS debug info (metadata) in the executable. That's why only stepi (aka si) works when you don't have debug info.
Or possibly it would use PTRACE_SINGLESTEP one or two times as an optimization if it saw it was close.
(I normally only use si or ni for debugging asm, not s or n. layout reg is also nice, when GDB doesn't crash. See the bottom of the x86 tag wiki for more GDB asm debugging tips.)
If you meant to ask how the x86 ISA supports debugging, rather than the Linux kernel API which exposes those features via a target-independent API, see the related Q&As:
How is PTRACE_SINGLESTEP implemented?
Why Single Stepping Instruction on X86?
How to tell length of an x86-64 instruction opcode using CPU itself?
Also How does a debugger work? has some Windowsy answers.

C program stores function parameters from $rbp+4 in memory? My check failed

I was trying to learn how to use rbp/ebp to visit function parameters and local variables on ubuntu1604, 64bit. I've got a simply c file:
#include<stdio.h>
int main(int argc,char*argv[])
{
printf("hello\n");
return argc;
}
I compiled it with:
gcc -g my.c
Then debug it with argument parameters:
gdb --args my 01 02
Here I know the "argc" should be 3, so I tried to check:
(gdb) b main
Breakpoint 1 at 0x400535: file ret.c, line 5.
(gdb) r
Starting program: /home/a/cpp/my 01 02
Breakpoint 1, main (argc=3, argv=0x7fffffffde98) at ret.c:5
5 printf("hello\n");
(gdb) x $rbp+4
0x7fffffffddb4: 0x00000000
(gdb) x $rbp+8
0x7fffffffddb8: 0xf7a2e830
(gdb) x/1xw $rbp+8
0x7fffffffddb8: 0xf7a2e830
(gdb) x/1xw $rbp+4
0x7fffffffddb4: 0x00000000
(gdb) x/1xw $rbp
0x7fffffffddb0: 0x00400550
I don't find any clue that a dword of "3" is saved in any of bytes in $rbp+xBytes. Did I get anything wrong in my understanding or commands?
Thanks!
I was trying to learn how to use rbp/ebp to visit function parameters and local variables
The x86_64 ABI does not use stack to pass parameters; they are passed in registers. Because of that, you wouldn't find them at any offset off $rbp (this is different from ix86 calling convention).
To find the parameters, you'll need to look at the $rdi and $rsi regusters:
Breakpoint 1, main (argc=3, argv=0x7fffffffe3a8) at my.c:4
4 printf("hello\n");
(gdb) p/x $rdi
$1 = 0x3 # matches argc
(gdb) p/x $rsi
$2 = 0x7fffffffe3a8 # matches argv
x $rbp+4
You almost certainly wouldn't find anything useful at $rbp+4, because it is usually incremented or decremented by 8, in order to store the entire 64-bit value.

How to find the main function's entry point of elf executable file without any symbolic information?

I developed a small cpp program on platform of Ubuntu-Linux 11.10.
Now I want to reverse engineer it. I am beginner. I use such tools: GDB 7.0, hte editor, hexeditor.
For the first time I made it pretty easy. With help of symbolic information I founded the address of main function and made everything I needed.
Then I striped (--strip-all) executable elf-file and I have some problems.
I know that main function starts from 0x8960 in this program.
But I haven't any idea how should I find this point without this knowledge.
I tried debug my program step by step with gdb but it goes into __libc_start_main
then into the ld-linux.so.3 (so, it finds and loads the shared libraries needed by a program). I debugged it about 10 minutes. Of course, may be in 20 minutes I can reach the main function's entry point, but, it seems, that more easy way has to exist.
What should I do to find the main function's entry point without any symbolic info?
Could you advise me some good books/sites/other_sources from reverse engineering of elf-files with help of gdb?
Any help would be appreciated.
Locating main() in a stripped Linux ELF binary is straightforward. No symbol information is required.
The prototype for __libc_start_main is
int __libc_start_main(int (*main) (int, char**, char**),
int argc,
char *__unbounded *__unbounded ubp_av,
void (*init) (void),
void (*fini) (void),
void (*rtld_fini) (void),
void (*__unbounded stack_end));
The runtime memory address of main() is the argument corresponding to the first parameter, int (*main) (int, char**, char**). This means that the last memory address saved on the runtime stack prior to calling __libc_start_main is the memory address of main(), since arguments are pushed onto the runtime stack in the reverse order of their corresponding parameters in the function definition.
One can enter main() in gdb in 4 steps:
Find the program entry point
Find where __libc_start_main is called
Set a break point to the address last saved on stack prior to the call to _libc_start_main
Let program execution continue until the break point for main() is hit
The process is the same for both 32-bit and 64-bit ELF binaries.
Entering main() in an example stripped 32-bit ELF binary called "test_32":
$ gdb -q -nh test_32
Reading symbols from test_32...(no debugging symbols found)...done.
(gdb) info file #step 1
Symbols from "/home/c/test_32".
Local exec file:
`/home/c/test_32', file type elf32-i386.
Entry point: 0x8048310
< output snipped >
(gdb) break *0x8048310
Breakpoint 1 at 0x8048310
(gdb) run
Starting program: /home/c/test_32
Breakpoint 1, 0x08048310 in ?? ()
(gdb) x/13i $eip #step 2
=> 0x8048310: xor %ebp,%ebp
0x8048312: pop %esi
0x8048313: mov %esp,%ecx
0x8048315: and $0xfffffff0,%esp
0x8048318: push %eax
0x8048319: push %esp
0x804831a: push %edx
0x804831b: push $0x80484a0
0x8048320: push $0x8048440
0x8048325: push %ecx
0x8048326: push %esi
0x8048327: push $0x804840b # address of main()
0x804832c: call 0x80482f0 <__libc_start_main#plt>
(gdb) break *0x804840b # step 3
Breakpoint 2 at 0x804840b
(gdb) continue # step 4
Continuing.
Breakpoint 2, 0x0804840b in ?? () # now in main()
(gdb) x/x $esp+4
0xffffd110: 0x00000001 # argc = 1
(gdb) x/s **(char ***) ($esp+8)
0xffffd35c: "/home/c/test_32" # argv[0]
(gdb)
Entering main() in an example stripped 64-bit ELF binary called "test_64":
$ gdb -q -nh test_64
Reading symbols from test_64...(no debugging symbols found)...done.
(gdb) info file # step 1
Symbols from "/home/c/test_64".
Local exec file:
`/home/c/test_64', file type elf64-x86-64.
Entry point: 0x400430
< output snipped >
(gdb) break *0x400430
Breakpoint 1 at 0x400430
(gdb) run
Starting program: /home/c/test_64
Breakpoint 1, 0x0000000000400430 in ?? ()
(gdb) x/11i $rip # step 2
=> 0x400430: xor %ebp,%ebp
0x400432: mov %rdx,%r9
0x400435: pop %rsi
0x400436: mov %rsp,%rdx
0x400439: and $0xfffffffffffffff0,%rsp
0x40043d: push %rax
0x40043e: push %rsp
0x40043f: mov $0x4005c0,%r8
0x400446: mov $0x400550,%rcx
0x40044d: mov $0x400526,%rdi # address of main()
0x400454: callq 0x400410 <__libc_start_main#plt>
(gdb) break *0x400526 # step 3
Breakpoint 2 at 0x400526
(gdb) continue # step 4
Continuing.
Breakpoint 2, 0x0000000000400526 in ?? () # now in main()
(gdb) print $rdi
$3 = 1 # argc = 1
(gdb) x/s **(char ***) ($rsp+16)
0x7fffffffe35c: "/home/c/test_64" # argv[0]
(gdb)
A detailed treatment of program initialization and what occurs before main() is called and how to get to main() can be found be found in Patrick Horgan's tutorial "Linux x86 Program Start Up
or - How the heck do we get to main()?"
If you have a very stripped version, or even a binary that is packed, as using UPX, you can gdb on it in the tough way as:
$ readelf -h echo | grep Entry
Entry point address: 0x103120
And then you can break at it in GDB as:
$ gdb mybinary
(gdb) break * 0x103120
Breakpoint 1 at 0x103120gdb)
(gdb) r
Starting program: mybinary
Breakpoint 1, 0x0000000000103120 in ?? ()
and then, you can see the entry instructions:
(gdb) x/10i 0x0000000000103120
=> 0x103120: bl 0x103394
0x103124: dcbtst 0,r5
0x103128: mflr r13
0x10312c: cmplwi r7,2
0x103130: bne 0x103214
0x103134: stw r5,0(r6)
0x103138: add r4,r4,r3
0x10313c: lis r0,-32768
0x103140: lis r9,-32768
0x103144: addi r3,r3,-1
I hope it helps
As far as I know, once a program has been stripped, there is no straightforward way to locate the function that the symbol main would have otherwise referenced.
The value of the symbol main is not required for program start-up: in the ELF format, the start of the program is specified by the e_entry field of the ELF executable header. This field normally points to the C library's initialization code, and not directly to main.
While the C library's initialization code does call main() after it has set up the C run time environment, this call is a normal function call that gets fully resolved at link time.
In some cases, implementation-specific heuristics (i.e., the specific knowledge of the internals of the C runtime) could be used to determine the location of main in a stripped executable. However, I am not aware of a portable way to do so.

Resources