> For the complete documentation index, see [llms.txt](https://docs.cooku222.kr/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.cooku222.kr/security/crypto/dreamhack/dreamhack-padding-miracle-attack.md).

# \[DreamHack] Padding Miracle Attack

#### 문제 링크

<https://dreamhack.io/wargame/challenges/1122>

[ Padding Miracle Attackunpad는 정말 까다로운 친구에요... Padding oracle attack을 공부해봅시다! Exploit Tech: Padding oracle attack에서 함께 실습하는 문제입니다.dreamhack.io](https://dreamhack.io/wargame/challenges/1122)

&#x20;

#### 문제

<figure><img src="https://blog.kakaocdn.net/dna/H1qB7/btsNrvS3Hvl/AAAAAAAAAAAAAAAAAAAAAEXUVcLWXo4ETcL2euJtLkkjSpzVrc-Dnql2jM6Q-AET/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&#x26;expires=1782831599&#x26;allow_ip=&#x26;allow_referer=&#x26;signature=BNoQrzaVQxTlLqAfT9lCLVay4OU%3D" alt="" height="251" width="477"><figcaption></figcaption></figure>

#### WriteUp

```
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import os

flag = open("flag.txt", "r").read()
secret, key, iv = [os.urandom(16) for _ in range(3)]

while True:
    mode = int(input("[1] Encrypt [2] Decrypt [3] Submit: "))

    if mode == 1:
        pt = input("Input plaintext: ")
        if pt == "secret":
            pt = secret
        else:
            pt = bytes.fromhex(pt)
        pt = pad(pt, 16)
        cipher = AES.new(key, AES.MODE_CBC, iv)
        ct = bytes.hex(cipher.encrypt(pt))
        print("pt ==Encryption=>", ct)
        
    elif mode == 2:
        ct = bytes.fromhex(input("Input ciphertext: "))
        try:
            cipher = AES.new(key, AES.MODE_CBC, iv)
            pt = unpad(cipher.decrypt(ct), 16)
            pt = "Don't steal my secret!"
        except:
            pt = "Invalid ciphertext."
        print("ct ==Decryption=>", pt)

    elif mode == 3:
        if bytes.fromhex(input("Enter secret: ")) == secret:
            print("You are a human decryptor!!", flag)
            exit()
        else:
            print("Try again.. T.T")
    else:
        exit()
```

-> chall.py

-> 암/복호화 기능이 주어져 있고, secret의 암호화 결과를 알 수 있음

암호문으로부터 secret의 값을 알아내면 플래그를 획득할 수 있다.

복호화과정에선, try문을 사용하여 복호화 과정에서 에러가 발생시 -> "Invalid ciphertext."가 암호문에 저장됨

-> "Don't steal my secret!"이 저장되어서 사용자는 에러 발생 여부, 즉 패딩 성공 여부만을 알 수 있다.

&#x20;

#### \* 익스플로잇 설계시

Secret 복구

-> Padding Oracle Attack을 사용하면 임의의 16바이트 암호문을 복호화할 수 있다.

-> Padding Oracle Attack의 구현은 다음 단계에서 설명하고 여기서는 복호화가 가능하다고 가정할 때 secret을 복구하는 방법을 보면,

```
from pwn import *
from tqdm import trange
from Crypto.Util.Padding import unpad

# io = process(["python3", "chall.py"])
io = remote("host3.dreamhack.games", 23839)

def encrypt(msg):
    if msg != "secret":
        msg = bytes.hex(msg)
    io.sendline(b"1")
    io.sendline(msg.encode())
    io.recvuntil(b"=> ")
    return bytes.fromhex(io.recvline().decode())

def decrypt_oracle(msg):
    msg = bytes.hex(msg)
    io.sendline(b"2")
    io.sendline(msg.encode())
    io.recvuntil(b"=> ")
    return io.recvline() == b"Don't steal my secret!\n"

def submit(msg):
    msg = bytes.hex(msg)
    io.sendline(b"3")
    io.sendline(msg.encode())
    io.recvuntil(b"secret: ")

def decrypt_block(msg):
    # TODO 1, Padding Oracle Attack


zeroblock = bytes(16)
enc_secret = encrypt("secret")[:16]

# TODO 2, Recovering Secret

submit(secret)

io.interactive()
```

-> 시스템과의 통신을 담당하는 함수와 기본적인 틀을 작성하면,

\#TODO 2, Recovering Secret에 해당하는 부분을 보면,

먼저 enc\_secret에 저장되어 있는 값은

<figure><img src="https://blog.kakaocdn.net/dna/c5afrx/btsNrtNyD5D/AAAAAAAAAAAAAAAAAAAAALN6Pzs_XpZmIDMWiDvw9y_DXIe1I86zllM2gPae3xmU/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&#x26;expires=1782831599&#x26;allow_ip=&#x26;allow_referer=&#x26;signature=eW0MwCQsfW9Whz8etUV2kZQKFLw%3D" alt="" height="42" width="183"><figcaption></figcaption></figure>

\=> enc\_secret을 복호화하더라도 초기화 벡터를 알아야 문제를 해결할 수 있다.

0블록을 암호화한 결과의 첫 번재 블록은 다음과 같다.

<figure><img src="https://blog.kakaocdn.net/dna/GEqZL/btsNsRNPD1I/AAAAAAAAAAAAAAAAAAAAACJdMVuBgcCTww7UDoytLE6r9McDQO6IbL3505IFLEbk/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&#x26;expires=1782831599&#x26;allow_ip=&#x26;allow_referer=&#x26;signature=OoKneM5Uzxu7t5IdzYn15YRqCyk%3D" alt="" height="53" width="243"><figcaption></figcaption></figure>

이 블록을 복호화하면 초기화벡터의 값을 얻을 수 있습니다.

&#x20;

\=> 아래와 같이 코드로 작성할 수 있는데, decrypt\_block 함수는 2회 사용됨

```
# TODO 2, Recovering Secret
enc_iv = encrypt(zeroblock)[:16]
iv = decrypt_block(enc_iv)
secret = xor(decrypt_block(enc_secret), iv)
```

&#x20;

#### Padding Oracle Attack

\- decrypt\_oracle, 즉 unpad 성공 여부를 T/F로 반환해주는 함수의 결과를 이용하여 한 블록을 복호화하는 함수 decrypt\_block을 작성하면,

```
def decrypt_oracle(msg):
    msg = bytes.hex(msg)
    io.sendline(b"2")
    io.sendline(msg.encode())
    io.recvuntil(b"=> ")
    return io.recvline() == b"Don't steal my secret!\n"

...

def decrypt_block(msg):
    # TODO 1, Padding Oracle Attack
```

```
def decrypt_block(msg):
    # TODO 1, Padding Oracle Attack
    pt = [0] * 16
    for i in trange(16):
        p = pt[:]
        for j in range(i):
            p[15 - j] ^= (i + 1)

        for b in range(256):
            p[15 - i] = b
            if decrypt_oracle(bytes(p) + msg):
                break

        pt[15 - i] = b ^ (i + 1)
    return bytes(pt)
```

-> 두번째 거는 간단한 구현코드

위 복호화 함수는 높은 확률로 올바르게 동작하지만, 1/256정도의 낮은 확률로 첫 단계에서의 마지막 바이트가 b'\x01'이 아닌 예외가 발생할 수 있음

강의에서 설명한 것과 갈이 첫 단계에서 뒤에서 두 번째 바이트가 다른 경우에도 unpad가 성공하는지 확인함으로서 모든 예외를 처리할 수 있음

```
def decrypt_block(msg):
    # TODO 1, Padding Oracle Attack
    pt = [0] * 16
    for i in trange(16):
        p = pt[:]
        for j in range(i):
            p[15 - j] ^= (i + 1)

        for b in range(256):
            p[15 - i] = b
            if decrypt_oracle(bytes(p) + msg):
                # i = 0, the first step
                if i == 0:
                    # Second last byte is fixed to 0 for 256 iterations
                    # But setting it 1 here removes multiple(>1) byte padding success.
                    p[14] = 1
                    if decrypt_oracle(bytes(p) + msg):
                        break
                    else:
                        continue
                break

        pt[15 - i] = b ^ (i + 1)
    return bytes(pt)
```

&#x20;

#### solve.py

```
from pwn import *
from tqdm import trange
from Crypto.Util.Padding import unpad

# io = process(["python3", "chall.py"])
io = remote("host3.dreamhack.games", 23839)

def encrypt(msg):
    if msg != "secret":
        msg = bytes.hex(msg)
    io.sendline(b"1")
    io.sendline(msg.encode())
    io.recvuntil(b"=> ")
    return bytes.fromhex(io.recvline().decode())

def decrypt_oracle(msg):
    msg = bytes.hex(msg)
    io.sendline(b"2")
    io.sendline(msg.encode())
    io.recvuntil(b"=> ")
    return io.recvline() == b"Don't steal my secret!\n"

def submit(msg):
    msg = bytes.hex(msg)
    io.sendline(b"3")
    io.sendline(msg.encode())
    io.recvuntil(b"secret: ")

def decrypt_block(msg):
    # TODO 1, Padding Oracle Attack
    pt = [0] * 16
    for i in trange(16):
        p = pt[:]
        for j in range(i):
            p[15 - j] ^= (i + 1)

        for b in range(256):
            p[15 - i] = b
            if decrypt_oracle(bytes(p) + msg):
                # i = 0, the first step
                if i == 0:
                    # Second last byte is fixed to 0 for 256 iterations
                    # But setting it 1 here removes multiple(>1) byte padding success.
                    p[14] = 1
                    if decrypt_oracle(bytes(p) + msg):
                        break
                    else:
                        continue
                break

        pt[15 - i] = b ^ (i + 1)
    return bytes(pt)


zeroblock = bytes(16)
enc_secret = encrypt("secret")[:16]

# TODO 2, Recovering Secret
enc_iv = encrypt(zeroblock)[:16]
iv = decrypt_block(enc_iv)
secret = xor(decrypt_block(enc_secret), iv)

submit(secret)

io.interactive()
```

```
[+] Opening connection to host3.dreamhack.games on port 23839: Done
100%|███████████████████████████████████████████| 16/16 [03:12<00:00, 12.01s/it]
100%|███████████████████████████████████████████| 16/16 [03:34<00:00, 13.44s/it]
[*] Switching to interactive mode
You are a human decryptor!! DH{}
```

\=> tqdm 모듈의 trange 를 사용 -> 원격 환경에서 한 블록을 복호화하는데 3분 이상의 시간이 걸림을 확인

<br>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.cooku222.kr/security/crypto/dreamhack/dreamhack-padding-miracle-attack.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
