Skip to content

Commit b9dfb4e

Browse files
committed
post: nops ctf writeup
1 parent 04ed24e commit b9dfb4e

File tree

9 files changed

+205
-0
lines changed

9 files changed

+205
-0
lines changed
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
---
2+
layout: post
3+
title: "[CTF] N0PS CTF writeups"
4+
categories: ctf re forensics
5+
---
6+
7+
## Reverse Me
8+
9+
```sh
10+
> file img.jpg
11+
img.jpg: JPEG image data
12+
```
13+
14+
The file looks like a JPEG, but it does not show anything in an image viewer, let's have a closer look.
15+
```sh
16+
> xxd img.jpg
17+
00000000: ffd8 ffe0 0000 0000 0000 0000 0000 0000 ................
18+
00000010: 0000 0000 0000 0001 0000 0000 0000 0000 ................
19+
00000020: 0000 0000 0000 010a 0000 0000 0000 303b ..............0;
20+
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
21+
00000040: 0000 0003 0000 0001 0000 0000 0000 0001 ................
22+
00000050: 0000 0000 0000 0001 0000 0000 0000 0000 ................
23+
00000060: 0000 0000 0000 002b 0000 0000 0000 3010 .......+......0.
24+
00000070: 0000 0000 0000 0000 0000 0000 0000 0030 ...............0
25+
...
26+
```
27+
The file starts with `ffd8 ffe0`, which is a signature for [Raw JPEG](https://en.wikipedia.org/wiki/List_of_file_signatures), so what could be wrong?
28+
![raw jpeg](/assets/images/nopsctf/jpeg.png)
29+
30+
```sh
31+
...
32+
00003800: 0000 0000 0000 0318 0000 0000 0000 0318 ................
33+
00003810: 0000 0004 0000 0003 0000 0000 0000 0008 ................
34+
00003820: 0000 0000 0000 02d8 0000 0000 0000 02d8 ................
35+
00003830: 0000 0000 0000 0040 0000 0000 0000 0040 .......@.......@
36+
00003840: 0000 0000 0000 0040 0000 0004 0000 0006 .......@........
37+
00003850: 001c 001d 0040 000d 0038 0040 0000 0000 .....@...8.@....
38+
00003860: 0000 0000 0000 3148 0000 0000 0000 0040 ......1H.......@
39+
00003870: 0000 0000 0000 1310 0000 0001 003e 0003 .............>..
40+
00003880: 0000 0000 0000 0000 0001 0102 464c 457f ............FLE.
41+
```
42+
Scrolling to the end of the file, there is something odd. That's the ELF binary signature in reverse. Let's reverse the file back. And voila!
43+
44+
```python
45+
input_file = "img.jpg"
46+
output_file = "img.elf"
47+
48+
with open(input_file, 'rb') as f:
49+
data = f.read()
50+
51+
reversed_data = data[::-1]
52+
53+
with open(output_file, 'wb') as f:
54+
f.write(reversed_data)
55+
```
56+
57+
```
58+
> python reverse.py
59+
60+
> file img.elf
61+
img.elf: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ae64a94832a94702644e170ebf1177740605cb34, for GNU/Linux 3.2.0, stripped
62+
```
63+
64+
Now, let's open the file in IDA.
65+
![ida](/assets/images/nopsctf/ida.png)
66+
67+
The program reads 4 arguments and calls `sub_1460` to do some check on them. The check is quite simple.
68+
```c
69+
__int64 __fastcall sub_1460(int a1, int a2, int a3, int a4)
70+
{
71+
unsigned int v4; // r8d
72+
73+
v4 = 0;
74+
if ( 3 * a4 + a3 + 4 * a2 - 10 * a1 != 28 )
75+
return 0LL;
76+
if ( 9 * a2 - 8 * a1 + 6 * a3 - 2 * a4 == 72 && a4 + -3 * a2 - 2 * a1 - 8 * a3 == 29 )
77+
LOBYTE(v4) = a3 + 5 * a1 + 7 * a2 - 6 * a4 == 88;
78+
return v4;
79+
}
80+
```
81+
82+
Let's find the 4 numbers that satisfy the check with z3.
83+
```python
84+
import z3
85+
from z3 import Solver, Real
86+
87+
def solve_range_equation():
88+
# Create a solver
89+
solver = Solver()
90+
91+
# Define variables
92+
a1 = Real('a1')
93+
a2 = Real('a2')
94+
a3 = Real('a3')
95+
a4 = Real('a4')
96+
97+
# Add equation to the solver
98+
solver.add(3 * a4 + a3 + 4 * a2 - 10 * a1 == 28)
99+
solver.add(9 * a2 - 8 * a1 + 6 * a3 - 2 * a4 == 72)
100+
solver.add(a4 + -3 * a2 - 2 * a1 - 8 * a3 == 29)
101+
solver.add(a3 + 5 * a1 + 7 * a2 - 6 * a4 == 88)
102+
103+
# Check satisfiability and get the solution
104+
if solver.check() == z3.sat:
105+
model = solver.model()
106+
print("Solution:", model[a1], model[a2], model[a3], model[a4])
107+
else:
108+
print("No solution found.")
109+
110+
if __name__ == "__main__":
111+
solve_range_equation()
112+
```
113+
114+
```
115+
> python solve.py
116+
Solution: -3 8 -7 -9
117+
```
118+
119+
And finally solve the challenge:
120+
```
121+
> chmod +x img.elf
122+
> ./img.elf -3 8 -7 -9
123+
N0PS{r1CKUNr0111N6}
124+
```
125+
126+
## HID
127+
128+
The challenge provides a pcapng file (Packet capture). Let's open it in Wireshark.
129+
![wireshark](/assets/images/nopsctf/wireshark.png)
130+
131+
Since there are packets that are irrelevant to HID, let's filter them out with `usbhid.data`
132+
133+
Now we're left with all the HID packets. After scrolling up and down, we can see clearly that there are two HID devices: a keyboard, and a pointing device. The thing is, there is only one packet from the keyboard, all other packets are from the pointing device (supposedly a mouse). Let's analyze them.
134+
135+
![keyboard](/assets/images/nopsctf/keyboard.png)
136+
137+
![mouse](/assets/images/nopsctf/mouse.png)
138+
139+
At this point, we can forget the keyboard and focus on the mouse, since the mouse has information about its movements (x axis and y axis). Let's filter out the keyboard and export the result to another file for further processing.
140+
141+
![filtered](/assets/images/nopsctf/filtered.png)
142+
143+
Since we know the offsets in Wireshark, we will use Python to extract all the movements, and visualize it.
144+
145+
```python
146+
import matplotlib.pyplot as plt
147+
from scapy.all import *
148+
149+
def draw_plot(movements):
150+
# Initialize coordinates with the root point
151+
x_coords = [0]
152+
y_coords = [0]
153+
154+
# Accumulate movements to generate coordinates
155+
current_x = 0
156+
current_y = 0
157+
for movement in movements:
158+
current_x += movement[0]
159+
current_y += movement[1]
160+
x_coords.append(current_x)
161+
y_coords.append(current_y)
162+
163+
# Plot the points
164+
plt.plot(x_coords, y_coords, marker='o')
165+
166+
# Connect all points with lines
167+
plt.plot(x_coords, y_coords, linestyle='-', color='b')
168+
169+
# Add labels and title
170+
plt.xlabel('X Coordinate')
171+
plt.ylabel('Y Coordinate')
172+
plt.title('Plot of Movements')
173+
174+
# Show the plot
175+
plt.grid(True)
176+
plt.show()
177+
178+
if __name__ == "__main__":
179+
# Set the input pcap file path
180+
input_file = "filtered.pcapng"
181+
packets = rdpcap(input_file)
182+
183+
movements = []
184+
for p in packets:
185+
xb = p.load[66:68]
186+
yb = p.load[68:70]
187+
188+
print(xb, yb)
189+
190+
x = struct.unpack('<h', xb)[0]
191+
y = struct.unpack('<h', yb)[0]
192+
193+
movements.append((x,y))
194+
195+
# Draw mouse movement
196+
draw_plot(movements)
197+
198+
```
199+
![movements](/assets/images/nopsctf/movements.png)
200+
201+
It doesn't look quite right, does it? Because in computer graphics, the 2D Cartesian Coordinate System is horizontally flipped, so we have to flip the image upside down. And here's the result.
202+
203+
![flipped](/assets/images/nopsctf/flipped.png)
204+
205+
It took me a while to guess the flag due to the ugly mousewriting. `N0PS{m0Us3_dR4w1Ng}`

assets/images/nopsctf/filtered.png

229 KB
Loading

assets/images/nopsctf/flipped.png

103 KB
Loading

assets/images/nopsctf/ida.png

35.9 KB
Loading

assets/images/nopsctf/jpeg.png

50.3 KB
Loading

assets/images/nopsctf/keyboard.png

69.3 KB
Loading

assets/images/nopsctf/mouse.png

26.6 KB
Loading

assets/images/nopsctf/movements.png

131 KB
Loading

assets/images/nopsctf/wireshark.png

274 KB
Loading

0 commit comments

Comments
 (0)