You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardexpand all lines: content/blog/ctfcup2024-olymp/index.md
+14-12
Original file line number
Diff line number
Diff line change
@@ -6,7 +6,7 @@ params:
6
6
links:
7
7
- name: channel
8
8
link: https://t.me/theinkyvoid
9
-
title: "ctfcup 2024 - olymp"
9
+
title: "CTFCup 2024 - olymp"
10
10
tldr: "unsolved crypto pwn task from a ctfcup organized by us"
11
11
date: "2024-10-31"
12
12
tags: [pwn, crypto]
@@ -16,15 +16,16 @@ summary: |
16
16
17
17
# olymp (ctfup 2024)
18
18
19
-
I was running out of time organizing ctfcup 2024, especially conserning pwn challenges. But then I thought what about an algorithmic pwn challenge and thus olymp was born. Sadly no one was able to solve it during the ctf, thus I am publishing this writeup.
19
+
While organizing CTFCup 2024, I was running out of time, especially concerning pwn challenges. Then I had an idea - what about an algorithmic pwn challenge? And thus olymp was born. Sadly, no one was able to solve it during the CTF, so I am publishing this writeup.
20
20
21
21
## Quick summary
22
22
23
-
The task is basicly solving a problem, where given a string `s`answer queries of the form `compare s[a:b] s[c:d]`. To this end it first inputs the number of test cases, then for each test casereads a string from `std::cin` into a `std::string` variable on `bss`, builds the prefix hash array, reads the number of queries from `std::cin` (which will be important later), then for each queryreads four numbers, compares appropriate string computed hashes and if those are equal compares the substrings themselves.
23
+
The task involves solving a problem where, given a string `s`, we need to answer queries of the form `compare s[a:b] s[c:d]`. The program first inputs the number of test cases. For each test case, it reads a string from `std::cin` into a `std::string` variable in the BSS segment and builds a prefix hash array. It then reads the number of queries from `std::cin` (which becomes important later). For each query, it reads four numbers representing the substring bounds, compares the computed hashes of the substrings, and if the hashes match, performs a direct comparison of the substrings themselves.
24
24
25
25
## The vuln
26
26
27
-
The vuln is quite easy to spot when reading the source code (which was provided). There is no control over how much prefix hashes we build, meaning we can overflow into `s`, where conveniatly the first field is the data pointer, allowing us to get arbitrary write (PIE was disabled).
27
+
The vulnerability is quite easy to spot when reading the source code (which was provided). There is no bound checking on how many prefix hashes we build, meaning we can overflow into the string `s`, where conveniently the first field is the data pointer, allowing us to get arbitrary write (PIE was disabled).
28
+
28
29
```c++
29
30
#defineMAX_LENGTH 200
30
31
@@ -44,10 +45,10 @@ void build_prefix_hashes() {
44
45
}
45
46
```
46
47
47
-
48
48
## Forging a hash
49
49
50
-
Before we can exploit the overflow we have to be able to create such a string, that its polymial hash is equal to arbitrary value `target`. Lets refolmulate the problem in terms of lattices, the polymial hash of string `s` is equal to `P(s) = p ^ (len(s) - 1) s[0] + p ^ (len(s) - 2) s[1] + ... + p ^ 0 s[len(s) - 1] mod P`, pick the middle of the alphabet `ord('n')`, we want to find the minimal vector satisfying `P(midle * len(v)) - P(v) = 0`, for which the corresponding lattice is
50
+
Before we can exploit the overflow, we have to be able to create a string whose polynomial hash equals an arbitrary value `target`. Let's reformulate the problem in terms of lattices. The polynomial hash of string `s` is equal to `P(s) = p^(len(s)-1) * s[0] + p^(len(s)-2) * s[1] + ... + p^0 * s[len(s)-1] mod P`. If we pick the middle of the alphabet `ord('n')`, we want to find the minimal vector satisfying `P(middle * len(v)) - P(v) = 0`. The corresponding lattice is
51
+
51
52
```python
52
53
L = IntegerLattice(
53
54
[
@@ -58,24 +59,25 @@ L = IntegerLattice(
58
59
+ [[W * P] + [0] *len(known)]
59
60
)
60
61
```
62
+
61
63
Then we just use `L.approximate_closest_vector` from sage and obtain our string.
62
64
63
65
## Leaking libstdc++
64
66
65
-
The easiest thing we can do is overwrite `std::istream::operator>>(int &)`to`puts`. Then it will be called with `std::cin` as the first argument and thus we can leak libstdc++. I thought that was enough and libstdc++ would always allocate after libc, but during final testing that turned out not to be case.
67
+
The easiest thing we can do is overwrite `std::istream::operator>>(int &)`with`puts`. When called with `std::cin` as the first argument, this allows us to leak libstdc++. I initially thought this would be sufficient since libstdc++ typically allocates after libc, but during final testing this turned out not to be the case.
66
68
67
69
## Leaking libc
68
70
69
-
Leaking libc on the other hand is a bit more tricky. First we have to notice that comparing two substrings actually uses `memcmp`
71
+
Leaking libc is a bit more tricky. First, we need to notice that comparing two substrings actually uses `memcmp`:
70
72
71
-

73
+

72
74
73
-
We can overwrite `memcmp` with `puts` and the rest of the got with the resolvers. Then call the comparison on the same indexes (such that the first index corresponds to `memcmp`got) two times (which intern will call puts): the first to resolve puts, the second to leak it.
75
+
We can overwrite `memcmp` with `puts` and the rest of the GOT with resolvers. Then we call the comparison on the same indexes (such that the first index corresponds to the `memcmp`GOT entry) twice: the first call resolves puts, and the second call leaks it.
74
76
75
77
## Running system
76
78
77
-
Finally we can use the same `memcmp` trick: overwrite `memcmp` with `system`, `setvbuf`got with `"sh\x00"`, the run the comparison on `0 1`, intern calling `system("sh\x00")`. You can check the full sploit[here](sploit.py).
79
+
Finally, we can use the same `memcmp` trick: overwrite `memcmp` with `system`, overwrite the `setvbuf`GOT entry with `"sh\x00"`, then run the comparison on indexes `0 1`, which internally calls `system("sh\x00")`. You can check the full exploit[here](sploit.py).
78
80
79
81
## Conclusion
80
82
81
-
Overall I thought the challenge was quite easy, but it turned to be quick tricky even after the forging of the hash was fully hinted.
83
+
Overall, while I initially thought the challenge was quite easy, it turned out to be quite tricky even after the hash forging technique was fully hinted at.
0 commit comments