> 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-no-shift-please.md).

# \[DreamHack] No shift please

#### 문제 링크

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

[ No shift please!AES에서 ShiftRows 기능을 없애봤어요! 이 암호는 과연 안전할까요? 주어진 AES.py는 블록암호: AES에 제시된 코드와 동일합니다. Exploit Tech: AES without ShiftRows에서 함께 실습하는 문제입니다.dreamhack.io](https://dreamhack.io/wargame/challenges/1121)

#### 문제

<figure><img src="https://blog.kakaocdn.net/dna/bIrIRr/btsNotO7ZKd/AAAAAAAAAAAAAAAAAAAAAFGMC4wlB6LV5umycPEWP3_9u5SkVyR5eoiUxfJeJF0D/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&#x26;expires=1782831599&#x26;allow_ip=&#x26;allow_referer=&#x26;signature=ArhMMu1yjEYM7awTASPTGKIfR8M%3D" alt="" height="345" width="477"><figcaption></figcaption></figure>

***

#### WriteUp

```
from AES import AES_implemented
import os

# For real AES without modification, this challenge is unsolvable with modern technology.
# But let's remove a step.
ret = lambda x: None
AES_implemented._shift_rows = ret
AES_implemented._shift_rows_inv = ret
# Will it make a difference?

secret = os.urandom(16)
key = os.urandom(16)

flag = open("flag.txt", "r").read()

cipher = AES_implemented(key)

secret_enc = cipher.encrypt(secret)
assert cipher.decrypt(secret_enc) == secret
print(f"enc(secret) = {bytes.hex(secret_enc)}")

while True:
    option = int(input("[1] encrypt, [2] decrypt: "))

    if option == 1: # Encryption
        plaintext = bytes.fromhex(input("Input plaintext to encrypt in hex: "))
        assert len(plaintext) == 16

        ciphertext = cipher.encrypt(plaintext)
        print(f"enc(plaintext) = {bytes.hex(ciphertext)}")

        if plaintext == secret:
            print(flag)
            exit()

    elif option == 2: # Decryption
        ciphertext = bytes.fromhex(input("Input ciphertext to decrypt in hex: "))
        assert len(ciphertext) == 16
        
        if ciphertext == secret_enc:
            print("No way!")
            continue
            
        plaintext = cipher.decrypt(ciphertext)
        print(f"dec(ciphertext) = {bytes.hex(plaintext)}")
```

-> chall.py

-> AES 암호에서 ShiftRows 과정에 해당하는 \_shift\_rows 함수를 제거하였고, 복호화 과정에서 SubBytes의 역연산인 \_shift\_rows\_inv 함수 또한 제거함.

임의의 16바이트 key를 이용해 secret을 암호화한 암호문이 주어지고, 다음의 두 가지 기능이 주어짐.

\- 임의의 16바이트 평문 암호화

\- secret의 암호문을 제외한 임의의 16바이트 암호문 복호화

이때, secret을 알아내는 것이 목적임

\- ShiftRows가 담당하는 확산(Diffusion)이 필요한 만큼 충족되지 않으면 평문의 작은 변화로 인한 변화가 암호문에 고르게 전파되는 것이 아니라 특정 부분에만 나타남.

확산이란? 입력 데이터(평문)의 각 비트가 출력(암호문) 전체에 널리 퍼지도록 하는 성질로 공격자가 평문과 암호문 간의 패턴을 추론하지 못 함.

&#x20;

#### Exploit

한 비트의 확산

코드를 분석해보기에 앞서, 몇 가지 입력을 통해서 특징을 파악할 수 있음

(아래는 한 비트만이 차이나는 평문쌍을 각각 암호화한 결과)

```
[1] encrypt, [2] decrypt: 1
Input plaintext to encrypt in hex: 00000000000000000000000000000000
enc(plaintext) = 25de8dcf447352d4661c7008e4794783
[1] encrypt, [2] decrypt: 1
Input plaintext to encrypt in hex: 00000000000000000000000000000001
enc(plaintext) = 25de8dcf447352d4661c70080192da49
```

두 결과에서는 마지막 8글자(4바이트)만 다른 결과값을 가지고, 앞 24글자(12바이트)는 키의 값과 상관없이 항상 같은 결과를 가짐

다른 비트에 대해서 실험해보면,

```
[1] encrypt, [2] decrypt: 1
Input plaintext to encrypt in hex: 00000000000000000000000000000000
enc(plaintext) = 25de8dcf447352d4661c7008e4794783
[1] encrypt, [2] decrypt: 1
Input plaintext to encrypt in hex: 00000000000000000000000000010000
enc(plaintext) = 25de8dcf447352d4661c7008deb0c328
[1] encrypt, [2] decrypt: 1
Input plaintext to encrypt in hex: 000000000000000000000000deadbeef
enc(plaintext) = 25de8dcf447352d4661c7008ab3c996a
```

\- 평문의 마지막 4바이트의 값에 상관없이, 암호문의 앞 12바이트는 항상 일정함.

\=> 평문의 마지막 4바이트는 암호문의 마지막 4바이트에만 영향을 준다는 것을 유추함.

\* 같은 방법으로 추가적인 실험을 통해 4개의 4바이트 단위로 나뉘어 각각 독립적인 연산이 이루어지는 것을 확인할 수 있음

```
[1] encrypt, [2] decrypt: 1
Input plaintext to encrypt in hex: 00000000000000000000000000000000
enc(plaintext) = 25de8dcf447352d4661c7008e4794783
[1] encrypt, [2] decrypt: 1
Input plaintext to encrypt in hex: 12345678000000000000000000000000
enc(plaintext) = 8e9ae77a447352d4661c7008e4794783
[1] encrypt, [2] decrypt: 1
Input plaintext to encrypt in hex: 00000000876543210000000000000000
enc(plaintext) = 25de8dcffe831023661c7008e4794783
[1] encrypt, [2] decrypt: 1
Input plaintext to encrypt in hex: 0000000000000000cafebabe00000000
enc(plaintext) = 25de8dcf447352d457c59639e4794783
```

<figure><img src="https://blog.kakaocdn.net/dna/AQ5ok/btsNr7jejvC/AAAAAAAAAAAAAAAAAAAAAO7JPf0BaWlSx9FxgQwvzxJw9-Cn2LqAKjdnTJrdPB3D/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&#x26;expires=1782831599&#x26;allow_ip=&#x26;allow_referer=&#x26;signature=VzKFpxTuIXqqqqqHw8ONk2gOHgU%3D" alt="" height="332" width="740"><figcaption></figcaption></figure>

&#x20;

#### ShiftRows의 역할

<figure><img src="https://blog.kakaocdn.net/dna/lg79C/btsNsb7NtAp/AAAAAAAAAAAAAAAAAAAAAPHehtgrYq3g_sRd8q_KKWsPpbg8fdQnbPBm5OEKctk-/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&#x26;expires=1782831599&#x26;allow_ip=&#x26;allow_referer=&#x26;signature=rZo2zl53R4%2B21TiG1kbTp6JTx3c%3D" alt="" height="212" width="598"><figcaption></figcaption></figure>

\=> ShiftRows가 사라짐으로서 암호화과정에 무슨 변화가 일어나는지 살펴보자면,

SubBytes 과정은 각 바이트 단위, MixColumn 과정은 각 열 내에서만 연산이 이루어지기에, 서로 다른 열을 섞어주는 과정은 ShiftRows가 담당함. 하지만 ShiftRows 과정이 없을 시, 각 열들은 독립적인 연산이 진행됨

예를 들어, 위 그림에서의 0열(a0, 0, a1, 0, a2, 0, a3, 0)에 어떤 변화가 생겨도 1열(a0, 1, a1, 1, a2, 1, a3, 1)의 값은 전혀 바뀌지 않고, 이 성질이 모든 라운드에서 적용됨

AES.py의 구현을 살펴보면 Transpose 과정에 따라 각 열이 4개의 연속한 4바이트에 대응됨.

&#x20;

#### Exploit

Secret의 복호화

16바이트 중 4개의 4바이트 블록들은 암호화가 독립적으로 진행되기 때문에 복호화 또한 독립적으로 진행됨

사용 가능한 복호화 기능은 정확히 16바이트가 모두 secret\_enc와 일치할 경우에만 필터링 함 -> 각 4바이트 블록들을 순차적으로 복호화할 암호문에 넣고 나머지 12바이트는 무의미한 데이터로 채워 복호화하여 올바른 블록만을 추출하면 문제를 해결할 수 있다.

<figure><img src="https://blog.kakaocdn.net/dna/bi9wLJ/btsNroz6y5m/AAAAAAAAAAAAAAAAAAAAAJ1RDK1UP4pPjouvaxeVdPl2sYkR1syya4vVBPMmXcgH/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&#x26;expires=1782831599&#x26;allow_ip=&#x26;allow_referer=&#x26;signature=X9vCC5YHblssLe%2FJFbgShil698o%3D" alt="" height="386" width="748"><figcaption></figcaption></figure>

-> 출력된 평문에서 올바른 블록들을 이어붙이면 올바른 secret의 값을 얻을 수 있고, 위의 예시에서는 1fd9373e 67d67586 c2c3b193 6e7ab147

```
enc(secret) = a9e3b389df3f1899437e7916b81867df
[1] encrypt, [2] decrypt: 2
Input ciphertext to decrypt in hex: a9e3b389000000000000000000000000
dec(ciphertext) = 1fd9373e321069774ad8fc9f46670893
[1] encrypt, [2] decrypt: 2
Input ciphertext to decrypt in hex: 00000000df3f18990000000000000000
dec(ciphertext) = 08c59eee67d675864ad8fc9f46670893
[1] encrypt, [2] decrypt: 2
Input ciphertext to decrypt in hex: 0000000000000000437e791600000000
dec(ciphertext) = 08c59eee32106977c2c3b19346670893
[1] encrypt, [2] decrypt: 2
Input ciphertext to decrypt in hex: 000000000000000000000000b81867df
dec(ciphertext) = 08c59eee321069774ad8fc9f6e7ab147
[1] encrypt, [2] decrypt: 1
Input plaintext to encrypt in hex: 1fd9373e67d67586c2c3b1936e7ab147
enc(plaintext) = a9e3b389df3f1899437e7916b81867df
DH{}
```

&#x20;

#### Solve.py

```
from pwn import *
import os

io = process(["python3", "chall.py"])

xor = lambda a, b: bytes([i ^ j for i, j in zip(a, b)])

io.recvuntil(b"= ")
secret_enc = bytes.fromhex(io.recvline().decode())

def encrypt(plaintext):
    io.sendline(b"1")
    io.sendline(bytes.hex(plaintext).encode())
    io.recvuntil(b"= ")
    ciphertext = bytes.fromhex(io.recvline().decode())
    return ciphertext

def decrypt(ciphertext):
    io.sendline(b"2")
    io.sendline(bytes.hex(ciphertext).encode())
    io.recvuntil(b"= ")
    plaintext = bytes.fromhex(io.recvline().decode())
    return plaintext

secret = [0] * 16

for i in range(4):
    c = [0] * 16
    for j in range(4):
        c[4 * i + j] = secret_enc[4 * i + j]
    c = bytes(c)

    p = decrypt(c)
    for j in range(4):
        secret[4 * i + j] = p[4 * i + j]

secret = bytes(secret)

encrypt(secret)

io.interactive()
```

&#x20;

\+ ShiftRows : AES 대칭키 암호에서 state의 각 열을 섞어 확산(Diffusion)을 담당하는 기능

<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-no-shift-please.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.
