← Back to Writeups
CUSTOMEasyReversing

ПлюсМинус (Task 50)

XESXOR9/2/20264 min read
#reversing#custom#easy#rodata-analysis#static_binary_analysis#xor-subtraction-cipher#linux

ПлюсМинус (Task 50)

Platform: duckerz | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-01-09 | Status: Solved Techniques: rodata-analysis, static_binary_analysis, xor-subtraction-cipher

Summary

Task: 64-bit ELF binary with encrypted flag. Solution: Static analysis revealed subtraction cipher (each byte minus 10) in .rodata section.

ПлюсМинус — duckerz CTF

Description

Category: Reverse Engineering
Difficulty: Easy
Points: 100

The task is a 64-bit ELF executable for Linux called plusminus. Participants need to recover the original flag by analyzing the binary and understanding the decryption algorithm.


Binary Analysis

Basic Information

Initial analysis of the binary revealed the following characteristics:

bash
$ file plusminus
plusminus: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

$ ls -la plusminus
-rwxr-xr-x 1 user user 16K ... plusminus

The binary is a 64-bit executable for Linux.

String Analysis

Using the strings utility, strings were extracted from the binary:

bash
$ strings plusminus | head -30
/lib64/ld-linux-x86-64.so.2
libc.so.6
fgets
printf
puts
...
Enter your answer: 
DUCKERZ{
Correct!
Wrong!

The binary interacts with the user through the console and expects input. The presence of Correct! and Wrong! strings indicates validation of the entered value. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Section Structure

Analysis of the binary's sections shows standard ELF sections:

bash
$ readelf -S plusminus
There are 27 section headers, starting at offset 0x...
Section Headers:
  [Nr] Name              Type            Addr     Off    Size
  [ 0]                   NULL            00000000 000000 000000
  [ 1] .text             PROGBITS        00001000 001000 000bba
  [ 2] .rodata           PROGBITS        00002000 002000 00003c
  [ 3] .data             PROGBITS        00003000 003000 000004
  ...

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The .rodata section (address 0x2000) is of particular interest, as it typically contains constant data, including encrypted flags.


Reverse Engineering

Disassembling the main Function

Using radare2, the main function was analyzed:

bash
$ r2 -AA plusminus
[0x00001090]> pdf @ main

Key points from the disassembled code:

asm
; main function
0x00001090      push rbp
0x00001091      mov rbp, rsp
...
; Read user input (up to 100 chars)
0x0000110a      mov edi, 0x64          ; size = 100
0x0000110f      lea rsi, [rbp - 0x70]  ; buffer
0x00001113      call fgets             ; Read input
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

; Call decrypt function
0x0000113f      lea rdi, [rbp - 0x70]  ; user input
0x00001143      call decrypt_flag      ; Decrypt and check

The decrypt_flag Function

Next, the decrypt_flag function was analyzed, which performs the decryption:

bash
[0x00001000]> pdf @ decrypt_flag
asm
; decrypt_flag function
0x00001000      push rbp
0x00001001      mov rbp, rsp
0x00001004      mov QWORD PTR [rbp-0x8], rdi
...
; Decryption loop
0x0000101c      movzx eax, BYTE PTR [rbx]
0x0000101f      sub eax, 0xa           ; SUBTRACT 10 from each byte!
0x00001022      mov BYTE PTR [rbx], al
0x00001024      add rbx, 1
0x00001027      cmp BYTE PTR [rbx], 0  ; Check for null terminator
0x0000102a      jne 0x101c             ; Loop if not end
...

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key discovery: The decryption algorithm subtracts 10 (0xA) from each byte of the encrypted data!

Extracting Encrypted Data

The encrypted flag data is located in the .rodata section at address 0x2010:

bash
$ xxd -s 0x2010 -l 64 plusminus
00002010: 4e5f 4d55 4f5c 6485 5076 6b71 695a 3b7f  N_MOU\d.PvkqiZ;.
00002020: 5d69 3b3a 87                        ]i;:

Decrypted bytes (each byte minus 10):

EncryptedDecryptedCharacter
0x4E0x44 (68)D
0x5F0x55 (85)U
0x4D0x43 (67)C
0x550x4B (75)K
0x4F0x45 (69)E
0x5C0x52 (82)R
0x640x5A (90)Z
0x850x7B (123){
0x500x46 (70)F
0x760x6C (108)l
0x6B0x61 (97)a
0x710x67 (103)g
0x690x5F (95)_
0x5A0x50 (80)P
0x3B0x31 (49)1
0x7F0x6C (108)l
0x5D0x75 (117)u
0x690x53 (83)S
0x3B0x31 (49)1
0x3A0x30 (48)0
0x870x7D (125)}
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Exploitation

Python Decryption Script

The following script was used to automate the decryption:

python
#!/usr/bin/env python3
"""
Decrypts the flag from plusminus binary.
The encryption is a simple subtraction of 10 from each byte.
"""

# Encrypted flag bytes from .rodata section at 0x2010
encrypted_bytes = bytes([
    0x4e, 0x5f, 0x4d, 0x55, 0x4f, 0x5c, 0x64, 0x85,
    0x50, 0x76, 0x6b, 0x71, 0x69, 0x5a, 0x3b, 0x7f,
    0x5d, 0x69, 0x3b, 0x3a, 0x87
])

# Decrypt by adding 10 (subtract -10 means add 10)
decrypted = bytes([b - 10 for b in encrypted_bytes])
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

print("Encrypted bytes:", encrypted_bytes.hex())
print("Decrypted:", decrypted.decode())
print("Flag:", decrypted.decode())

Execution Result

bash
$ python3 solve.py
Encrypted bytes: 4e5f4d554f5c648550766b71695a3b7f5d693b3a87
Decrypted: DUCKERZ{REDACTED}
Flag: DUCKERZ{REDACTED}

Solution Verification

Running the binary with the correct answer:

bash
$ ./plusminus
Enter your answer: DUCKERZ{REDACTED}
Correct!

Tags

#reverse-engineering #elf #simple-decryption #static-analysis ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍