So, I was working with a typical project of mine. For that I wrote the following program:
/***************************demo.c*************************/
#include <stdio.h>
#include <unistd.h>
void foo(int x)
{
getpid();
open();
getpid();
}
int main()
{
int x;
foo(10);
}
and I statically compiled it as follows:
$gcc -g -static demo.c -o demo
I tried to stack trace using gdb:
(gdb) list
1 /***************************demo.c*************************/
2 #include <stdio.h>
3 #include <unistd.h>
4 void foo(int x)
5 {
6 getpid();
7 open();
8 getpid();
9
10 }
(gdb)
11 int main()
12 {
13 int x;
14 foo(10);
15 }
(gdb) start
Temporary breakpoint 1 at 0x401773: file demo.c, line 14.
Starting program: /home/abhishek/Documents/Security_Project/bin/backend_v2/demo
Temporary breakpoint 1, main () at demo.c:14
14 foo(10);
(gdb) s
foo (x=10) at demo.c:6
6 getpid();
(gdb) backtrace
#0 foo (x=10) at demo.c:6
#1 0x000000000040177d in main () at demo.c:14
(gdb)
But to my astonishment, why isn't something like the following shown?
# 0x0000000000401759 in foo (x=10) at demo.c:6
I tried to use ptrace(PTRACE_PEEKDATA,...
and ptrace(PTRACE_GETREGS,...
to get hold of rip, and rbp and then peeking into memory of attached child process as follows:
long return_addr=ptrace(PTRACE_PEEKDATA, pid,regs.rbp+8,0);
long next_rbp=ptrace(PTRACE_PEEKDATA, pid, regs.rbp,0);
while(next_rbp>0){
// printf("rbp = 0x%lx", next_rbp);
// getchar();
printf("0x%lx\t", return_addr);
addr_on_stk.insert(return_addr);
return_addr=ptrace(PTRACE_PEEKDATA, pid, next_rbp+8,0);
next_rbp=ptrace(PTRACE_PEEKDATA, pid, next_rbp,0);
}
To which I get the following output during the first iteration stopping at the first system call, (ignoring the initial 14
syscall made to setup the program, I mean stopping it at getpid syscall)
Trace back of the addresses on stack is as follows:
0x44673b
0x40177d
0x401bba
apparently what I see is :
Dump of assembler code for function getpid:
0x0000000000446730 <+0>: endbr64
0x0000000000446734 <+4>: mov $0x27,%eax
0x0000000000446739 <+9>: syscall
0x000000000044673b <+11>: ret <----------------------- first address
End of assembler dump.
Dump of assembler code for function main:
0x000000000040176b <+0>: endbr64
0x000000000040176f <+4>: push %rbp
0x0000000000401770 <+5>: mov %rsp,%rbp
0x0000000000401773 <+8>: mov $0xa,%edi
0x0000000000401778 <+13>: call 0x401745 <foo>
0x000000000040177d <+18>: mov $0x0,%eax <-------------- second address
0x0000000000401782 <+23>: pop %rbp
0x0000000000401783 <+24>: ret
End of assembler dump.
0x401bba <__libc_start_call_main+106>: mov %eax,%edi
(gdb) disassemble __libc_start_call_main
Dump of assembler code for function __libc_start_call_main:
0x0000000000401b50 <+0>: push %rax
0x0000000000401b51 <+1>: pop %rax
0x0000000000401b52 <+2>: sub $0x98,%rsp
0x0000000000401b59 <+9>: mov %rdi,0x8(%rsp)
0x0000000000401b5e <+14>: lea 0x20(%rsp),%rdi
0x0000000000401b63 <+19>: mov %esi,0x14(%rsp)
0x0000000000401b67 <+23>: mov %rdx,0x18(%rsp)
0x0000000000401b6c <+28>: call 0x409ed0 <_setjmp>
0x0000000000401b71 <+33>: endbr64
0x0000000000401b75 <+37>: test %eax,%eax
0x0000000000401b77 <+39>: jne 0x401bc1 <__libc_start_call_main+113>
0x0000000000401b79 <+41>: mov %fs:0x300,%rax
0x0000000000401b82 <+50>: mov %rax,0x68(%rsp)
0x0000000000401b87 <+55>: mov %fs:0x2f8,%rax
0x0000000000401b90 <+64>: mov %rax,0x70(%rsp)
0x0000000000401b95 <+69>: lea 0x20(%rsp),%rax
0x0000000000401b9a <+74>: mov %rax,%fs:0x300
0x0000000000401ba3 <+83>: mov 0xca686(%rip),%rdx # 0x4cc230 <environ>
0x0000000000401baa <+90>: mov 0x14(%rsp),%edi
0x0000000000401bae <+94>: mov 0x18(%rsp),%rsi
0x0000000000401bb3 <+99>: mov 0x8(%rsp),%rax
0x0000000000401bb8 <+104>: call *%rax
0x0000000000401bba <+106>: mov %eax,%edi <------------------------third address
0x0000000000401bbc <+108>: call 0x40aa80 <exit>
0x0000000000401bc1 <+113>: call 0x414520 <__nptl_deallocate_tsd>
0x0000000000401bc6 <+118>: lock decl 0xc3b33(%rip) # 0x4c5700 <__nptl_nthreads>
0x0000000000401bcd <+125>: sete %al
0x0000000000401bd0 <+128>: test %al,%al
0x0000000000401bd2 <+130>: jne 0x401be8 <__libc_start_call_main+152>
0x0000000000401bd4 <+132>: mov $0x3c,%edx
0x0000000000401bd9 <+137>: nopl 0x0(%rax)
0x0000000000401be0 <+144>: xor %edi,%edi
0x0000000000401be2 <+146>: mov %edx,%eax
0x0000000000401be4 <+148>: syscall
0x0000000000401be6 <+150>: jmp 0x401be0 <__libc_start_call_main+144>
0x0000000000401be8 <+152>: xor %edi,%edi
0x0000000000401bea <+154>: jmp 0x401bbc <__libc_start_call_main+108>
End of assembler dump.
(gdb)
All the return addresses foo
is found.
My question is when the getpid syscall is intercepted, it is down in the glibc wrapper of getpid as shown. But why isn't the function foo
's frame formally placed on the stack?
[I mean, by the time is syscall is intercepted by PTRACE, push rbp
instruction should have been performed and rbp should point to the old rbp on stack and just below it (i.e. rbp+8), shall have the return address of getpid for foo()
.]