Tuplanolla Sampsa Kiiskinen

2015

This was inspired by The Design of Everyday Things.

A Simple Use Case

Making a small change to a running program sounds simple and should definitely be one of the easiest use cases imaginable. Let us find out how simple it really is.

The following session was captured verbatim.

$ # We need a simple executable to analyze.
$ # Core utilities work well for that purpose.
$ cp `which date` .
$ # What is the time?
$ ./date
Fri May  9 02:40:08 EEST 2014
$ # This clock needs to be set to academic time, which is 15 minutes late.
$ # Where to start?
$ ltrace ./date 3>&1 1>&2 2>&3 | grep time
clock_gettime(0, 0x7fff6a4eb020, 0x2520440, 0, 0)                       = 0
localtime(0x7fff6a4eaf90)                                               = 0x7fc4b6218e80
strftime("", 140482835952155, NULL, 0x00006972)                         = 4
strftime("", 140482835952236, NULL, 0x00007961)                         = 4
Fri May  9 02:40:22 EEST 2014
$ # Let us try them in order.
$ gdb -q date
Reading symbols from ./date...(no debugging symbols found)...done.
(gdb) break clock_gettime
Breakpoint 1 at 0x401a20
(gdb) run
Starting program: ./date
warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7ffff7ffa000
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, clock_gettime () at clock-compat.c:50
50      clock-compat.c: No such file or directory.
(gdb) backtrace
#0  clock_gettime () at clock-compat.c:50
#1  0x00007ffff7de8d9b in elf_ifunc_invoke (addr=<optimized out>) at ../sysdeps/x86_64/dl-irel.h:32
#2  _dl_fixup (l=<optimized out>, reloc_arg=<optimized out>) at ../elf/dl-runtime.c:144
#3  0x00007ffff7def725 in _dl_runtime_resolve () at ../sysdeps/x86_64/dl-trampoline.S:45
#4  0x0000000000404812 in ?? ()
#5  0x0000000000402205 in ?? ()
#6  0x00007ffff782aea5 in __libc_start_main (main=0x401b90, argc=1, ubp_av=0x7fffffffe558, init=<optimized out>,
    fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe548) at libc-start.c:260
#7  0x0000000000402285 in ?? ()
(gdb) # This stack frame is a mess.
(gdb) # Best try another one.
(gdb) disable 1
(gdb) break localtime
Breakpoint 2 at 0x7ffff78bace0: file localtime.c, line 42.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: ./date
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 2, __GI_localtime (t=0x7fffffffe310) at localtime.c:42
42      localtime.c: No such file or directory.
(gdb) backtrace
#0  __GI_localtime (t=0x7fffffffe310) at localtime.c:42
#1  0x000000000040237d in ?? ()
#2  0x0000000000402083 in ?? ()
#3  0x00007ffff782aea5 in __libc_start_main (main=0x401b90, argc=1, ubp_av=0x7fffffffe558, init=<optimized out>,
    fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe548) at libc-start.c:260
#4  0x0000000000402285 in ?? ()
(gdb) # This stack frame is much better.
(gdb) finish
Run till exit from #0  __GI_localtime (t=0x7fffffffe310) at localtime.c:42
0x000000000040237d in ?? ()
Value returned is $1 = (struct tm *) 0x7ffff7bd0e80 <_tmbuf>
(gdb) x/i $pc - 5
   0x402378:    callq  0x401760 <localtime@plt>
(gdb) # We are too far ahead though.
(gdb) disable 2
(gdb) # Before we continue we should locate an appropriate region to inject code.
(gdb) ^Z
[1]+  Stopped                 gdb -q date
$ # We should also switch to a sane syntax.
$ # Let us dump the last stack frame.
$ objdump -M intel -d --start-address=0x402083 --stop-address=0x40237d date

date:     file format elf64-x86-64


Disassembly of section .text:

0000000000402083 <.text+0x4f3>:
  402083:       41 89 c5                mov    r13d,eax
  402086:       41 21 dd                and    r13d,ebx
  402089:       e9 eb fe ff ff          jmp    401f79 <__sprintf_chk@plt+0x3f9>
  40208e:       48 8b 7c c3 08          mov    rdi,QWORD PTR [rbx+rax*8+0x8]
  402093:       e8 18 68 00 00          call   4088b0 <__sprintf_chk@plt+0x6d30>
  402098:       ba 05 00 00 00          mov    edx,0x5
  40209d:       48 89 c3                mov    rbx,rax
  4020a0:       be 87 99 40 00          mov    esi,0x409987
  4020a5:       31 ff                   xor    edi,edi
  4020a7:       e8 64 f7 ff ff          call   401810 <dcgettext@plt>
  4020ac:       48 89 d9                mov    rcx,rbx
  4020af:       48 89 c2                mov    rdx,rax
  4020b2:       31 f6                   xor    esi,esi
  4020b4:       31 ff                   xor    edi,edi
  4020b6:       31 c0                   xor    eax,eax
  4020b8:       e8 e3 f9 ff ff          call   401aa0 <error@plt>
  4020bd:       e9 b4 fb ff ff          jmp    401c76 <__sprintf_chk@plt+0xf6>
  4020c2:       45 84 c0                test   r8b,r8b
  4020c5:       0f 84 71 01 00 00       je     40223c <__sprintf_chk@plt+0x6bc>
  4020cb:       48 8b 74 24 18          mov    rsi,QWORD PTR [rsp+0x18]
  4020d0:       48 8d 54 24 50          lea    rdx,[rsp+0x50]
  4020d5:       bf 01 00 00 00          mov    edi,0x1
  4020da:       e8 f1 f8 ff ff          call   4019d0 <__xstat@plt>
  4020df:       85 c0                   test   eax,eax
  4020e1:       74 1d                   je     402100 <__sprintf_chk@plt+0x580>
  4020e3:       e8 98 f6 ff ff          call   401780 <__errno_location@plt>
  4020e8:       48 8b 4c 24 18          mov    rcx,QWORD PTR [rsp+0x18]
  4020ed:       8b 30                   mov    esi,DWORD PTR [rax]
  4020ef:       ba 1c b1 40 00          mov    edx,0x40b11c
  4020f4:       bf 01 00 00 00          mov    edi,0x1
  4020f9:       31 c0                   xor    eax,eax
  4020fb:       e8 a0 f9 ff ff          call   401aa0 <error@plt>
  402100:       48 8b 84 24 a8 00 00    mov    rax,QWORD PTR [rsp+0xa8]
  402107:       00
  402108:       48 89 44 24 40          mov    QWORD PTR [rsp+0x40],rax
  40210d:       48 8b 84 24 b0 00 00    mov    rax,QWORD PTR [rsp+0xb0]
  402114:       00
  402115:       48 89 44 24 48          mov    QWORD PTR [rsp+0x48],rax
  40211a:       e9 42 ff ff ff          jmp    402061 <__sprintf_chk@plt+0x4e1>
  40211f:       4c 89 f7                mov    rdi,r14
  402122:       e8 89 67 00 00          call   4088b0 <__sprintf_chk@plt+0x6d30>
  402127:       48 89 c3                mov    rbx,rax
  40212a:       e8 51 f6 ff ff          call   401780 <__errno_location@plt>
  40212f:       8b 30                   mov    esi,DWORD PTR [rax]
  402131:       48 89 d9                mov    rcx,rbx
  402134:       ba 1c b1 40 00          mov    edx,0x40b11c
  402139:       bf 01 00 00 00          mov    edi,0x1
  40213e:       31 c0                   xor    eax,eax
  402140:       e8 5b f9 ff ff          call   401aa0 <error@plt>
  402145:       e9 25 fe ff ff          jmp    401f6f <__sprintf_chk@plt+0x3ef>
  40214a:       4d 85 e4                test   r12,r12
  40214d:       74 3c                   je     40218b <__sprintf_chk@plt+0x60b>
  40214f:       ba 05 00 00 00          mov    edx,0x5
  402154:       be 08 ac 40 00          mov    esi,0x40ac08
  402159:       31 ff                   xor    edi,edi
  40215b:       88 4c 24 10             mov    BYTE PTR [rsp+0x10],cl
  40215f:       44 88 44 24 08          mov    BYTE PTR [rsp+0x8],r8b
  402164:       e8 a7 f6 ff ff          call   401810 <dcgettext@plt>
  402169:       31 f6                   xor    esi,esi
  40216b:       48 89 c2                mov    rdx,rax
  40216e:       bf 01 00 00 00          mov    edi,0x1
  402173:       31 c0                   xor    eax,eax
  402175:       e8 26 f9 ff ff          call   401aa0 <error@plt>
  40217a:       44 0f b6 44 24 08       movzx  r8d,BYTE PTR [rsp+0x8]
  402180:       0f b6 4c 24 10          movzx  ecx,BYTE PTR [rsp+0x10]
  402185:       8b 05 95 d1 20 00       mov    eax,DWORD PTR [rip+0x20d195]        # 60f320 <optind>
  40218b:       48 63 d0                movsxd rdx,eax
  40218e:       83 c0 01                add    eax,0x1
  402191:       4c 8b 24 d3             mov    r12,QWORD PTR [rbx+rdx*8]
  402195:       89 05 85 d1 20 00       mov    DWORD PTR [rip+0x20d185],eax        # 60f320 <optind>
  40219b:       49 83 c4 01             add    r12,0x1
  40219f:       e9 cb fc ff ff          jmp    401e6f <__sprintf_chk@plt+0x2ef>
  4021a4:       48 8d 7c 24 40          lea    rdi,[rsp+0x40]
  4021a9:       e8 12 67 00 00          call   4088c0 <__sprintf_chk@plt+0x6d40>
  4021ae:       85 c0                   test   eax,eax
  4021b0:       0f 84 b6 fe ff ff       je     40206c <__sprintf_chk@plt+0x4ec>
  4021b6:       ba 05 00 00 00          mov    edx,0x5
  4021bb:       be b7 99 40 00          mov    esi,0x4099b7
  4021c0:       31 ff                   xor    edi,edi
  4021c2:       e8 49 f6 ff ff          call   401810 <dcgettext@plt>
  4021c7:       48 89 c3                mov    rbx,rax
  4021ca:       e8 b1 f5 ff ff          call   401780 <__errno_location@plt>
  4021cf:       8b 30                   mov    esi,DWORD PTR [rax]
  4021d1:       48 89 da                mov    rdx,rbx
  4021d4:       31 ff                   xor    edi,edi
  4021d6:       31 c0                   xor    eax,eax
  4021d8:       31 db                   xor    ebx,ebx
  4021da:       e8 c1 f8 ff ff          call   401aa0 <error@plt>
  4021df:       e9 8d fe ff ff          jmp    402071 <__sprintf_chk@plt+0x4f1>
  4021e4:       e8 c7 66 00 00          call   4088b0 <__sprintf_chk@plt+0x6d30>
  4021e9:       ba 05 00 00 00          mov    edx,0x5
  4021ee:       48 89 c3                mov    rbx,rax
  4021f1:       be b8 ac 40 00          mov    esi,0x40acb8
  4021f6:       e9 aa fe ff ff          jmp    4020a5 <__sprintf_chk@plt+0x525>
  4021fb:       48 8d 7c 24 40          lea    rdi,[rsp+0x40]
  402200:       e8 fb 25 00 00          call   404800 <__sprintf_chk@plt+0x2c80>
  402205:       e9 62 fe ff ff          jmp    40206c <__sprintf_chk@plt+0x4ec>
  40220a:       bf 6c 00 02 00          mov    edi,0x2006c
  40220f:       88 4c 24 10             mov    BYTE PTR [rsp+0x10],cl
  402213:       44 88 44 24 08          mov    BYTE PTR [rsp+0x8],r8b
  402218:       e8 e3 f7 ff ff          call   401a00 <nl_langinfo@plt>
  40221d:       80 38 00                cmp    BYTE PTR [rax],0x0
  402220:       49 89 c4                mov    r12,rax
  402223:       b8 2f 99 40 00          mov    eax,0x40992f
  402228:       44 0f b6 44 24 08       movzx  r8d,BYTE PTR [rsp+0x8]
  40222e:       0f b6 4c 24 10          movzx  ecx,BYTE PTR [rsp+0x10]
  402233:       4c 0f 44 e0             cmove  r12,rax
  402237:       e9 3c fc ff ff          jmp    401e78 <__sprintf_chk@plt+0x2f8>
  40223c:       48 83 7c 24 28 00       cmp    QWORD PTR [rsp+0x28],0x0
  402242:       48 8d 7c 24 40          lea    rdi,[rsp+0x40]
  402247:       4c 0f 45 7c 24 28       cmovne r15,QWORD PTR [rsp+0x28]
  40224d:       31 d2                   xor    edx,edx
  40224f:       4c 89 fe                mov    rsi,r15
  402252:       e8 09 40 00 00          call   406260 <__sprintf_chk@plt+0x46e0>
  402257:       e9 d1 fd ff ff          jmp    40202d <__sprintf_chk@plt+0x4ad>
  40225c:       31 ed                   xor    ebp,ebp
  40225e:       49 89 d1                mov    r9,rdx
  402261:       5e                      pop    rsi
  402262:       48 89 e2                mov    rdx,rsp
  402265:       48 83 e4 f0             and    rsp,0xfffffffffffffff0
  402269:       50                      push   rax
  40226a:       54                      push   rsp
  40226b:       49 c7 c0 80 98 40 00    mov    r8,0x409880
  402272:       48 c7 c1 f0 97 40 00    mov    rcx,0x4097f0
  402279:       48 c7 c7 90 1b 40 00    mov    rdi,0x401b90
  402280:       e8 7b f6 ff ff          call   401900 <__libc_start_main@plt>
  402285:       f4                      hlt
  402286:       66 90                   xchg   ax,ax
  402288:       0f 1f 84 00 00 00 00    nop    DWORD PTR [rax+rax*1+0x0]
  40228f:       00
  402290:       b8 07 f3 60 00          mov    eax,0x60f307
  402295:       55                      push   rbp
  402296:       48 2d 00 f3 60 00       sub    rax,0x60f300
  40229c:       48 83 f8 0e             cmp    rax,0xe
  4022a0:       48 89 e5                mov    rbp,rsp
  4022a3:       77 02                   ja     4022a7 <__sprintf_chk@plt+0x727>
  4022a5:       5d                      pop    rbp
  4022a6:       c3                      ret
  4022a7:       b8 00 00 00 00          mov    eax,0x0
  4022ac:       48 85 c0                test   rax,rax
  4022af:       74 f4                   je     4022a5 <__sprintf_chk@plt+0x725>
  4022b1:       5d                      pop    rbp
  4022b2:       bf 00 f3 60 00          mov    edi,0x60f300
  4022b7:       ff e0                   jmp    rax
  4022b9:       0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]
  4022c0:       b8 00 f3 60 00          mov    eax,0x60f300
  4022c5:       55                      push   rbp
  4022c6:       48 2d 00 f3 60 00       sub    rax,0x60f300
  4022cc:       48 c1 f8 03             sar    rax,0x3
  4022d0:       48 89 e5                mov    rbp,rsp
  4022d3:       48 89 c2                mov    rdx,rax
  4022d6:       48 c1 ea 3f             shr    rdx,0x3f
  4022da:       48 01 d0                add    rax,rdx
  4022dd:       48 89 c6                mov    rsi,rax
  4022e0:       48 d1 fe                sar    rsi,1
  4022e3:       75 02                   jne    4022e7 <__sprintf_chk@plt+0x767>
  4022e5:       5d                      pop    rbp
  4022e6:       c3                      ret
  4022e7:       ba 00 00 00 00          mov    edx,0x0
  4022ec:       48 85 d2                test   rdx,rdx
  4022ef:       74 f4                   je     4022e5 <__sprintf_chk@plt+0x765>
  4022f1:       5d                      pop    rbp
  4022f2:       bf 00 f3 60 00          mov    edi,0x60f300
  4022f7:       ff e2                   jmp    rdx
  4022f9:       0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]
  402300:       80 3d 39 d0 20 00 00    cmp    BYTE PTR [rip+0x20d039],0x0        # 60f340 <stderr+0x8>
  402307:       75 11                   jne    40231a <__sprintf_chk@plt+0x79a>
  402309:       55                      push   rbp
  40230a:       48 89 e5                mov    rbp,rsp
  40230d:       e8 7e ff ff ff          call   402290 <__sprintf_chk@plt+0x710>
  402312:       5d                      pop    rbp
  402313:       c6 05 26 d0 20 00 01    mov    BYTE PTR [rip+0x20d026],0x1        # 60f340 <stderr+0x8>
  40231a:       f3 c3                   repz ret
  40231c:       0f 1f 40 00             nop    DWORD PTR [rax+0x0]
  402320:       48 83 3d e0 ca 20 00    cmp    QWORD PTR [rip+0x20cae0],0x0        # 60ee08 <__sprintf_chk@plt+0x20d2
88>
  402327:       00
  402328:       74 1b                   je     402345 <__sprintf_chk@plt+0x7c5>
  40232a:       b8 00 00 00 00          mov    eax,0x0
  40232f:       48 85 c0                test   rax,rax
  402332:       74 11                   je     402345 <__sprintf_chk@plt+0x7c5>
  402334:       55                      push   rbp
  402335:       bf 08 ee 60 00          mov    edi,0x60ee08
  40233a:       48 89 e5                mov    rbp,rsp
  40233d:       ff d0                   call   rax
  40233f:       5d                      pop    rbp
  402340:       e9 7b ff ff ff          jmp    4022c0 <__sprintf_chk@plt+0x740>
  402345:       e9 76 ff ff ff          jmp    4022c0 <__sprintf_chk@plt+0x740>
  40234a:       66 0f 1f 44 00 00       nop    WORD PTR [rax+rax*1+0x0]
  402350:       55                      push   rbp
  402351:       53                      push   rbx
  402352:       48 89 fb                mov    rbx,rdi
  402355:       48 83 ec 48             sub    rsp,0x48
  402359:       48 8d 7c 24 10          lea    rdi,[rsp+0x10]
  40235e:       48 89 54 24 18          mov    QWORD PTR [rsp+0x18],rdx
  402363:       48 89 74 24 10          mov    QWORD PTR [rsp+0x10],rsi
  402368:       64 48 8b 04 25 28 00    mov    rax,QWORD PTR fs:0x28
  40236f:       00 00
  402371:       48 89 44 24 38          mov    QWORD PTR [rsp+0x38],rax
  402376:       31 c0                   xor    eax,eax
  402378:       e8 e3 f3 ff ff          call   401760 <localtime@plt>
$ # The region from 0x40234a to 0x402378 is safe to relocate.
$ # However relocating the region from 0x40235e to 0x402378 is enough.
$ fg
gdb -q date
set $date = 0x40235e
(gdb) set $datesize = 31
(gdb) break *$date
Breakpoint 3 at 0x40235e
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: ./date
warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7ffff7ffa000
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 3, 0x000000000040235e in ?? ()
(gdb) x/i $pc
=> 0x40235e:    mov    %rdx,0x18(%rsp)
(gdb) # A sane syntax helps here as well.
(gdb) set disassembly-flavor intel
(gdb) x/i $pc
=> 0x40235e:    mov    QWORD PTR [rsp+0x18],rdx
(gdb) # It is easier to write C than assembly, so we do that.
(gdb) ^Z
[1]+  Stopped                 gdb -q date
$ vim f.c
$ cat f.c
#include <time.h>

struct tm* f(time_t const* timep) {
        time_t atimep = *timep;
        atimep += 60 * 15;
        return ((struct tm* (*)(time_t const*))0xdead)(&atimep);
}
$ # However we can violate the contract a little to get a shorter program.
$ vim f.c
$ cat f.c
#include <time.h>

struct tm* f(time_t* timep) {
        *timep += 60 * 15;
        return ((struct tm* (*)(time_t const*))0xdead)(timep);
}
$ # Correctness is extremely important here, so optimizations are disabled.
$ clang -O0 -Weverything -Wno-missing-prototypes -c -o f.o f.c
$ objdump -M intel -d f.o

f.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <f>:
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   4:   48 83 ec 10             sub    rsp,0x10
   8:   48 b8 ad de 00 00 00    movabs rax,0xdead
   f:   00 00 00
  12:   48 89 7d f8             mov    QWORD PTR [rbp-0x8],rdi
  16:   48 8b 7d f8             mov    rdi,QWORD PTR [rbp-0x8]
  1a:   48 8b 0f                mov    rcx,QWORD PTR [rdi]
  1d:   48 81 e9 84 03 00 00    sub    rcx,0x384
  24:   48 89 0f                mov    QWORD PTR [rdi],rcx
  27:   48 8b 7d f8             mov    rdi,QWORD PTR [rbp-0x8]
  2b:   ff d0                   call   rax
  2d:   48 83 c4 10             add    rsp,0x10
  31:   5d                      pop    rbp
  32:   c3                      ret
$ # The 0xdead pointer has to be set later.
$ fg
gdb -q date
set $fptroff = 0x8 + 2
(gdb) # Let us load the object file next.
(gdb) # The literal 2 is O_RDWR.
(gdb) set $fd = open("f.o", 2)
(gdb) # More information is needed for mmap.
(gdb) ^Z
[1]+  Stopped                 gdb -q date
$ stat f.o
  File: ‘f.o’
  Size: 1120            Blocks: 8          IO Block: 4096   regular file
Device: 813h/2067d      Inode: 54657088    Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/tuplanolla)   Gid: ( 1000/tuplanolla)
Access: 2014-05-09 02:47:08.611809937 +0300
Modify: 2014-05-09 02:46:54.471810256 +0300
Change: 2014-05-09 02:46:54.471810256 +0300
 Birth: -
$ readelf -S f.o
There are 10 section headers, starting at offset 0x100:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       0000000000000033  0000000000000000  AX       0     0     16
  [ 2] .data             PROGBITS         0000000000000000  00000074
       0000000000000000  0000000000000000  WA       0     0     4
  [ 3] .bss              NOBITS           0000000000000000  00000074
       0000000000000000  0000000000000000  WA       0     0     4
  [ 4] .note.GNU-stack   PROGBITS         0000000000000000  00000074
       0000000000000000  0000000000000000           0     0     1
  [ 5] .eh_frame         PROGBITS         0000000000000000  00000078
       0000000000000038  0000000000000000   A       0     0     8
  [ 6] .rela.eh_frame    RELA             0000000000000000  00000448
       0000000000000018  0000000000000018           8     5     8
  [ 7] .shstrtab         STRTAB           0000000000000000  000000b0
       000000000000004b  0000000000000000           0     0     1
  [ 8] .symtab           SYMTAB           0000000000000000  00000380
       00000000000000c0  0000000000000018           9     7     8
  [ 9] .strtab           STRTAB           0000000000000000  00000440
       0000000000000007  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
$ fg
gdb -q date
set $objsize = 1120
(gdb) set $textoff = 0x40
(gdb) # The literal 1 | 2 | 4 is PROT_READ | PROT_WRITE | PROT_EXEC.
(gdb) set $map = mmap(0, $objsize, 1 | 2 | 4, 2, $fd, 0)
(gdb) # Using malloc would also work, but involves mprotecting pages manually.
(gdb) # One more thing to check is the offset of the mmapped segment.
(gdb) info proc mappings
process 1764
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
            0x400000           0x40f000     0xf000        0x0 ./date
            0x60e000           0x60f000     0x1000     0xe000 ./date
            0x60f000           0x610000     0x1000     0xf000 ./date
            0x610000           0x631000    0x21000        0x0 [heap]
      0x7ffff6dda000     0x7ffff75ec000   0x812000        0x0 /usr/lib/locale/locale-archive
      0x7ffff75ec000     0x7ffff7604000    0x18000        0x0 /lib/x86_64-linux-gnu/libpthread-2.17.so
      0x7ffff7604000     0x7ffff7803000   0x1ff000    0x18000 /lib/x86_64-linux-gnu/libpthread-2.17.so
      0x7ffff7803000     0x7ffff7804000     0x1000    0x17000 /lib/x86_64-linux-gnu/libpthread-2.17.so
      0x7ffff7804000     0x7ffff7805000     0x1000    0x18000 /lib/x86_64-linux-gnu/libpthread-2.17.so
      0x7ffff7805000     0x7ffff7809000     0x4000        0x0
      0x7ffff7809000     0x7ffff79c8000   0x1bf000        0x0 /lib/x86_64-linux-gnu/libc-2.17.so
      0x7ffff79c8000     0x7ffff7bc7000   0x1ff000   0x1bf000 /lib/x86_64-linux-gnu/libc-2.17.so
      0x7ffff7bc7000     0x7ffff7bcb000     0x4000   0x1be000 /lib/x86_64-linux-gnu/libc-2.17.so
      0x7ffff7bcb000     0x7ffff7bcd000     0x2000   0x1c2000 /lib/x86_64-linux-gnu/libc-2.17.so
      0x7ffff7bcd000     0x7ffff7bd2000     0x5000        0x0
      0x7ffff7bd2000     0x7ffff7bd9000     0x7000        0x0 /lib/x86_64-linux-gnu/librt-2.17.so
      0x7ffff7bd9000     0x7ffff7dd8000   0x1ff000     0x7000 /lib/x86_64-linux-gnu/librt-2.17.so
      0x7ffff7dd8000     0x7ffff7dd9000     0x1000     0x6000 /lib/x86_64-linux-gnu/librt-2.17.so
      0x7ffff7dd9000     0x7ffff7dda000     0x1000     0x7000 /lib/x86_64-linux-gnu/librt-2.17.so
      0x7ffff7dda000     0x7ffff7dfd000    0x23000        0x0 /lib/x86_64-linux-gnu/ld-2.17.so
      0x7ffff7fd4000     0x7ffff7fd8000     0x4000        0x0
      0x7ffff7ff7000     0x7ffff7ff8000     0x1000        0x0 ./f.o
      0x7ffff7ff8000     0x7ffff7ffa000     0x2000        0x0
      0x7ffff7ffa000     0x7ffff7ffc000     0x2000        0x0 [vdso]
      0x7ffff7ffc000     0x7ffff7ffd000     0x1000    0x22000 /lib/x86_64-linux-gnu/ld-2.17.so
      0x7ffff7ffd000     0x7ffff7fff000     0x2000    0x23000 /lib/x86_64-linux-gnu/ld-2.17.so
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]
(gdb) set $mapoff = 0xffff800000000000
(gdb) # We specifically need f, so its properties are extracted next.
(gdb) ^Z
[1]+  Stopped                 gdb -q date
$ readelf -s f.o

Symbol table '.symtab' contains 8 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS f.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    4
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     7: 0000000000000000    51 FUNC    GLOBAL DEFAULT    1 f
$ fg
gdb -q date
set $foff = 0
(gdb) set $fsize = 51
(gdb) set $f = $map - $mapoff + $textoff + $foff
(gdb) # Let us see if we go that right.
(gdb) disassemble $f, +$fsize
Dump of assembler code from 0x7ffff7ff7040 to 0x7ffff7ff7073:
   0x00007ffff7ff7040:  push   rbp
   0x00007ffff7ff7041:  mov    rbp,rsp
   0x00007ffff7ff7044:  sub    rsp,0x10
   0x00007ffff7ff7048:  movabs rax,0xdead
   0x00007ffff7ff7052:  mov    QWORD PTR [rbp-0x8],rdi
   0x00007ffff7ff7056:  mov    rdi,QWORD PTR [rbp-0x8]
   0x00007ffff7ff705a:  mov    rcx,QWORD PTR [rdi]
   0x00007ffff7ff705d:  sub    rcx,0x384
   0x00007ffff7ff7064:  mov    QWORD PTR [rdi],rcx
   0x00007ffff7ff7067:  mov    rdi,QWORD PTR [rbp-0x8]
   0x00007ffff7ff706b:  call   rax
   0x00007ffff7ff706d:  add    rsp,0x10
   0x00007ffff7ff7071:  pop    rbp
   0x00007ffff7ff7072:  ret
End of assembler dump.
(gdb) # Yes.
(gdb) set $fptr = $f + $fptroff
(gdb) x/gx $fptr
0x7ffff7ff704a: 0x000000000000dead
(gdb) x/i $date + $datesize - 5
   0x402378:    callq  0x401760 <localtime@plt>
(gdb) set $localtime = 0x401760
(gdb) set *(uint64_t* )$fptr = $localtime
(gdb) # The 0xdead pointer should now point to the real localtime.
(gdb) x/gx $fptr
0x7ffff7ff704a: 0x0000000000401760
(gdb) # The original reference to localtime can be finally erased.
(gdb) # Alas C will not save us now.
(gdb) ^Z
[1]+  Stopped                 gdb -q date
$ vim from.text to.text
2 files to edit
$ cat from.text
40235e
  48 89 54 24 18              mov   QWORD PTR [rsp + 0x18], rdx
  48 89 74 24 10              mov   QWORD PTR [rsp + 0x10], rsi
  64 48 8b 04 25 28 00 00 00  mov   rax, QWORD PTR fs : 0x28
  48 89 44 24 38              mov   QWORD PTR [rsp + 0x38], rax
  31 c0                       xor   eax, eax
  e8 e3 f3 ff ff              call  401760 <localtime@plt>
$ # Long jumps are needed since the mmapped region is so far away.
$ cat to.text
40235e
  48 89 54 24 18                 mov     QWORD PTR [rsp + 0x18], rdx
  48 89 74 24 10                 mov     QWORD PTR [rsp + 0x10], rsi
  90 90 90 90 90 90 90           7 nop
  50                             push    rax
  48 b8 ad de 00 00 00 00 00 00  movabs  rax, 0xdead
  ff e0                          jmp     rax  --.
  58                             pop     rax  <-+---------------------.
7ffff7ff7005                                    |                     |
  58                             pop     rax  <-'                     |
  64 48 8b 04 25 28 00 00 00     mov     rax, QWORD PTR fs : 0x28     |
  48 89 44 24 38                 mov     QWORD PTR [rsp + 0x38], rax  |
  31 c0                          xor     eax, eax                     |
  e8 0d 00 00 00                 call    +0xd  -------.               |
  50                             push    rax  <-------+-.             |
  48 b8 ad de 00 00 00 00 00 00  movabs  rax, 0xdead  | |             |
  ff e0                          jmp     rax  --------+-+-------------'
7ffff7ff7040                                          | |
                                 <--------------------' |
                                 -----------------------'
$ # Both parts have one 0xdead pointer.
$ fg
gdb -q date
# There are 0x40 unused bytes before f that can be used for relocation.
(gdb) set $relsize = 35
(gdb) # We patch the instructions manually.
(gdb) set *(uint8_t(*)[35] )($f - $relsize) = {0x58, 0x64, 0x48, 0x8b, 0x04, 0x25, 0x28, 0x00, 0x00, 0x00, 0x48, 0x89
, 0x44, 0x24, 0x38, 0x31, 0xc0, 0xe8, 0x0d, 0x00, 0x00, 0x00, 0x50, 0x48, 0xb8, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, 0
x00, 0x00, 0xff, 0xe0}
(gdb) print $datesize
$2 = 31
(gdb) set *(uint8_t(*)[31] )$date = {0x48, 0x89, 0x54, 0x24, 0x18, 0x48, 0x89, 0x74, 0x24, 0x10, 0x90, 0x90, 0x90, 0x
90, 0x90, 0x90, 0x90, 0x50, 0x48, 0xb8, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xe0, 0x58}
(gdb) # Is everything in order?
(gdb) disassemble $date, +$datesize
Dump of assembler code from 0x40235e to 0x40237d:
=> 0x000000000040235e:  mov    QWORD PTR [rsp+0x18],rdx
   0x0000000000402363:  mov    QWORD PTR [rsp+0x10],rsi
   0x0000000000402368:  nop
   0x0000000000402369:  nop
   0x000000000040236a:  nop
   0x000000000040236b:  nop
   0x000000000040236c:  nop
   0x000000000040236d:  nop
   0x000000000040236e:  nop
   0x000000000040236f:  push   rax
   0x0000000000402370:  movabs rax,0xdead
   0x000000000040237a:  jmp    rax
   0x000000000040237c:  pop    rax
End of assembler dump.
(gdb) disassemble $f - $relsize, $f + $fsize
Dump of assembler code from 0x7ffff7ff701d to 0x7ffff7ff7073:
   0x00007ffff7ff701d:  pop    rax
   0x00007ffff7ff701e:  mov    rax,QWORD PTR fs:0x28
   0x00007ffff7ff7027:  mov    QWORD PTR [rsp+0x38],rax
   0x00007ffff7ff702c:  xor    eax,eax
   0x00007ffff7ff702e:  call   0x7ffff7ff7040
   0x00007ffff7ff7033:  push   rax
   0x00007ffff7ff7034:  movabs rax,0xdead
   0x00007ffff7ff703e:  jmp    rax
   0x00007ffff7ff7040:  push   rbp
   0x00007ffff7ff7041:  mov    rbp,rsp
   0x00007ffff7ff7044:  sub    rsp,0x10
   0x00007ffff7ff7048:  movabs rax,0x401760
   0x00007ffff7ff7052:  mov    QWORD PTR [rbp-0x8],rdi
   0x00007ffff7ff7056:  mov    rdi,QWORD PTR [rbp-0x8]
   0x00007ffff7ff705a:  mov    rcx,QWORD PTR [rdi]
   0x00007ffff7ff705d:  sub    rcx,0x384
   0x00007ffff7ff7064:  mov    QWORD PTR [rdi],rcx
   0x00007ffff7ff7067:  mov    rdi,QWORD PTR [rbp-0x8]
   0x00007ffff7ff706b:  call   rax
   0x00007ffff7ff706d:  add    rsp,0x10
   0x00007ffff7ff7071:  pop    rbp
   0x00007ffff7ff7072:  ret
End of assembler dump.
(gdb) # Yes!
(gdb) # The last thing we must to do is fix the 0xdead pointers.
(gdb) # We need the literal 1 to pop the address we pushed for the call instruction.
(gdb) set *(uint64_t* )(0x7ffff7ff7034 + 2) = $date + $datesize - 1
(gdb) disassemble $f - $relsize, $f + $fsize
Dump of assembler code from 0x7ffff7ff701d to 0x7ffff7ff7073:
   0x00007ffff7ff701d:  pop    rax
   0x00007ffff7ff701e:  mov    rax,QWORD PTR fs:0x28
   0x00007ffff7ff7027:  mov    QWORD PTR [rsp+0x38],rax
   0x00007ffff7ff702c:  xor    eax,eax
   0x00007ffff7ff702e:  call   0x7ffff7ff7040
   0x00007ffff7ff7033:  push   rax
   0x00007ffff7ff7034:  movabs rax,0x40237c
   0x00007ffff7ff703e:  jmp    rax
   0x00007ffff7ff7040:  push   rbp
   0x00007ffff7ff7041:  mov    rbp,rsp
   0x00007ffff7ff7044:  sub    rsp,0x10
   0x00007ffff7ff7048:  movabs rax,0x401760
   0x00007ffff7ff7052:  mov    QWORD PTR [rbp-0x8],rdi
   0x00007ffff7ff7056:  mov    rdi,QWORD PTR [rbp-0x8]
   0x00007ffff7ff705a:  mov    rcx,QWORD PTR [rdi]
   0x00007ffff7ff705d:  sub    rcx,0x384
   0x00007ffff7ff7064:  mov    QWORD PTR [rdi],rcx
   0x00007ffff7ff7067:  mov    rdi,QWORD PTR [rbp-0x8]
   0x00007ffff7ff706b:  call   rax
   0x00007ffff7ff706d:  add    rsp,0x10
   0x00007ffff7ff7071:  pop    rbp
   0x00007ffff7ff7072:  ret
End of assembler dump.
(gdb) set *(uint64_t* )(0x402370 + 2) = $f - $relsize
(gdb) # One more check does not hurt.
(gdb) disassemble $date, +$datesize
Dump of assembler code from 0x40235e to 0x40237d:
=> 0x000000000040235e:  mov    QWORD PTR [rsp+0x18],rdx
   0x0000000000402363:  mov    QWORD PTR [rsp+0x10],rsi
   0x0000000000402368:  nop
   0x0000000000402369:  nop
   0x000000000040236a:  nop
   0x000000000040236b:  nop
   0x000000000040236c:  nop
   0x000000000040236d:  nop
   0x000000000040236e:  nop
   0x000000000040236f:  push   rax
   0x0000000000402370:  movabs rax,0x7ffff7ff701d
   0x000000000040237a:  jmp    rax
   0x000000000040237c:  pop    rax
End of assembler dump.
(gdb) disassemble $f - $relsize, $f + $fsize
Dump of assembler code from 0x7ffff7ff701d to 0x7ffff7ff7073:
   0x00007ffff7ff701d:  pop    rax
   0x00007ffff7ff701e:  mov    rax,QWORD PTR fs:0x28
   0x00007ffff7ff7027:  mov    QWORD PTR [rsp+0x38],rax
   0x00007ffff7ff702c:  xor    eax,eax
   0x00007ffff7ff702e:  call   0x7ffff7ff7040
   0x00007ffff7ff7033:  push   rax
   0x00007ffff7ff7034:  movabs rax,0x40237c
   0x00007ffff7ff703e:  jmp    rax
   0x00007ffff7ff7040:  push   rbp
   0x00007ffff7ff7041:  mov    rbp,rsp
   0x00007ffff7ff7044:  sub    rsp,0x10
   0x00007ffff7ff7048:  movabs rax,0x401760
   0x00007ffff7ff7052:  mov    QWORD PTR [rbp-0x8],rdi
   0x00007ffff7ff7056:  mov    rdi,QWORD PTR [rbp-0x8]
   0x00007ffff7ff705a:  mov    rcx,QWORD PTR [rdi]
   0x00007ffff7ff705d:  sub    rcx,0x384
   0x00007ffff7ff7064:  mov    QWORD PTR [rdi],rcx
   0x00007ffff7ff7067:  mov    rdi,QWORD PTR [rbp-0x8]
   0x00007ffff7ff706b:  call   rax
   0x00007ffff7ff706d:  add    rsp,0x10
   0x00007ffff7ff7071:  pop    rbp
   0x00007ffff7ff7072:  ret
End of assembler dump.
(gdb) # Let us unleash our hack.
(gdb) continue
Continuing.
Fri May  9 02:29:24 EEST 2014
[Inferior 1 (process 1764) exited normally]
(gdb) # Success!
(gdb) quit
$ # Time to get depressed, fifteen minutes late.