https://alpacahack.com/daily/challenges/vending-machine-revised-version
import os
FLAG = os.getenv("flag", "Alpaca{dummy}")
class VendingMachine:
def __init__(self):
self.stock = 'a'*30 + 'b'*60 + 'c'*20 + 'd'*50 + 'e'*40 + 'f' # 'aaa...eeef'
self.item_names = {
'a': 'apple juice',
'b': 'banana juice',
'c': 'coke',
'd': 'draft beer',
'e': 'energy drink',
'f': 'flag'
}
def buy(self, mark:str):
if 'abcde'.find(mark) < 0: # No 'f'? Hmm...
print("Invalid choice.")
return
loc = self.stock.find(mark)
#...
stock_list = list(self.stock)
item = stock_list.pop(loc)
self.stock = ''.join(stock_list)
name = self.item_names[item]
print(f"You bought {name}.")
if item == 'f':
print(f"Flag: {FLAG}")
else:
print("Thank you!")
def main():
vm = VendingMachine()
vm.print_menu()
while True:
mark = input("your choice> ").lower()
# ...
vm.buy(mark)
if __name__ == '__main__':
main()
item に f が入ればよい。ところでこの変数は
if 'abcde'.find(mark) < 0: # No 'f'? Hmm...
print("Invalid choice.")
return
# ...
loc = self.stock.find(mark)
# ...
item = stock_list.pop(loc)
の通り代入されているので、ここを見ればよく
'abcde'.find(mark) >= 0 を満たすloc に何らかの配列内のインデックスが入ることが期待される。というわけで、この条件の満たし方を考えてみる。
はじめに str.find(mark) は mark が空文字列だと 0 を返す仕様である(後述するが、mark の0文字目は部分文字列に空文字列をもつため)。
self.stock.find(mark) も同様に、mark が空文字列だと 0 を返す仕様であるので、結局 loc = 0 なので先頭が pop() の対象になるから、
stock_list の先頭が item になる。
このことから、
のいずれの方法でも Flag を入手することができる。 入力の方法には pwntools を使うなりなんでもよいので、今回は pwntools を使った。
import sys
from pathlib import Path
from pwn import args, process, remote
if not args.FILE and not (args.IP or args.PORT):
print("You must specify either (IP and PORT) or FILE")
exit(1)
IP = args.IP or "127.0.0.1"
PORT = int(args.PORT or 1337)
FILE = Path(args.FILE) if args.FILE else None
STOCK = "a" * 30 + "b" * 60 + "c" * 20 + "d" * 50 + "e" * 40
def connect():
file = FILE
if file and not file.is_absolute():
file = Path(__file__).parent / file
return process([sys.executable, "-u", str(file)]) if file else remote(IP, PORT)
def solve() -> None:
io = connect()
try:
print(io.recvuntil(b"your choice> ").decode(), end="")
for mark in STOCK:
# io.sendline(b"") # 空文字列でもいい
io.sendline(mark.encode()) # あるいは在庫を律儀に1つずつ入力してもいい
print(io.recvuntil(b"your choice> ").decode(), end="")
io.sendline(b"")
print(io.recvall(timeout=2).decode(), end="")
finally:
io.close()
if __name__ == "__main__":
solve()
0 を返す理由grep.app で cpython/cpython の実装を見てみると、この挙動はドキュメントが保証しているものではなく実装由来だとわかる。