Post

gift-card

author: deomkicer

In this challenge we will get this attachment, here is the full main.py.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import random
import signal

FLAG = open("../flag.txt").read()


class GiftShop:
    # TODO: design better giftcard generator and validator

    def __init__(self, name: str) -> None:
        self.mod = lambda x: x % 256
        self.Hm = self.mod(sum(name.encode()))

    def generate_giftcard(self) -> str:
        r = random.randbytes(15)
        s = int.to_bytes(self.mod(self.Hm - self.mod(sum(r))))
        signature = r + s
        return signature.hex()

    def validate_giftcard(self, giftcard: str) -> bool:
        signature = bytes.fromhex(giftcard)
        return len(signature) == 16 and self.mod(sum(signature)) == self.Hm


class Challenge:
    def __init__(self, name):
        self.name = name
        self.balance = 100
        self.items = {
            "bread": {"price": 33, "callable": self.__get_bread},
            "giftcard": {"price": 50, "callable": self.__get_giftcard},
            "flag": {"price": 420, "callable": self.__get_flag},
        }
        self.redeemed_giftcard = set()
        self.gift_shop = GiftShop(self.name)

    def greet(self):
        msg = ""
        msg += f"\nWelcome, {self.name}!"
        msg += f"\nOption:"
        msg += f"\n  [1] Check available items"
        msg += f"\n  [2] Buy item"
        msg += f"\n  [3] Redeem giftcard"
        msg += f"\n  [9] Input admin code"
        print(msg)

    def __get_bread(self):
        return chr(0x1F35E)

    def __get_giftcard(self):
        return self.gift_shop.generate_giftcard()

    def __get_flag(self):
        return FLAG

    def check_available(self):
        msg = ""
        msg += f"\nAvailable items:"
        for item_name, item_info in self.items.items():
            item_price = item_info["price"]
            msg += f"\n  [+] {item_name} (${item_price})"
        return msg

    def buy_item(self, item):
        if item not in self.items.keys():
            return f"Item {item} is not available"

        if self.balance < self.items[item]["price"]:
            return f"Insufficient balance"

        self.balance -= self.items[item]["price"]
        return self.items[item]["callable"]()

    def redeem_giftcard(self, giftcard):
        if giftcard in self.redeemed_giftcard:
            return "Your giftcard is already used"

        if not self.gift_shop.validate_giftcard(giftcard):
            return "Your giftcard is invalid"

        value = random.randint(30, 60)
        self.balance += value
        self.redeemed_giftcard.add(giftcard)
        return f"You got ${value} from the giftcard"

    def admin_code(self, code):
        if code != FLAG:
            return "Incorrect code"

        self.balance += self.items["flag"]["price"]
        return "Correct code"


def user_input(s):
    inp = input(s).strip()
    assert len(inp) < 256
    return inp


def main():
    name = user_input("Name: ")
    challenge = Challenge(name)
    challenge.greet()

    while True:
        print(f"\nCurrent balance: ${challenge.balance}")
        match int(user_input("> ")):
            case 1:
                print(challenge.check_available())
            case 2:
                item = user_input("Item: ")
                print(challenge.buy_item(item))
            case 3:
                giftcard = user_input("Giftcard: ")
                print(challenge.redeem_giftcard(giftcard))
            case 9:
                code = user_input("Code: ")
                print(challenge.admin_code(code))
            case _:
                print("Invalid option")
                break


if __name__ == "__main__":
    signal.alarm(60)
    main()

First we can take a look in this section

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class GiftShop:
    # TODO: design better giftcard generator and validator

    def __init__(self, name: str) -> None:
        self.mod = lambda x: x % 256
        self.Hm = self.mod(sum(name.encode()))

    def generate_giftcard(self) -> str:
        r = random.randbytes(15)
        s = int.to_bytes(self.mod(self.Hm - self.mod(sum(r))))
        signature = r + s
        return signature.hex()

    def validate_giftcard(self, giftcard: str) -> bool:
        signature = bytes.fromhex(giftcard)
        return len(signature) == 16 and self.mod(sum(signature)) == self.Hm

We can see that the giftcard will be generated by 15 bytes random and 1 bytes from this expression int.to_bytes(self.mod(self.Hm - self.mod(sum(r)))). Then to check the giftcard is valid, it will check the length (must be 16) and modulus of sum signature equal to modulus of sum name.

Ok, our goal is to buy the flag and it cost 420. The challenge is we only have 100. We can buy a giftcard, it cost 50. If we reedem the giftcard we can get random value 30 - 60. So, now the question is how do we could get as many as possible the giftcard? Since we can retry the program, my idea is to generate as many as possible the giftcard without reedeming it. After we have a lot of giftcard we can reedem it to buy the flag.

Here is the solver :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwn import *
context.log_level = 'error'

def buy(item):
    p.sendlineafter(b"> ", b"2")
    p.sendlineafter(b"Item: ", item)
    return p.recvline()

code = []
for i in range(10):
    p = remote(f"127.0.0.1", 21000)
    p.sendlineafter(b"Name: ", b"0")
    for i in range(2):
        val = buy(b"giftcard")
        code.append(val)
    p.close()

p = remote(f"127.0.0.1", 21000)
p.sendlineafter(b"Name: ", b"0")
for i in code:
    p.sendlineafter(b"> ", b"3")
    p.sendlineafter(b"Giftcard: ", i.strip())

flag = buy(b"flag").strip().decode()
print("[+] The flag is: ", flag)

Output :

1
2
❯ python3 solve.py
[+] The flag is:  PLACEHOLDER
This post is licensed under CC BY 4.0 by the author.