Skip to content

Commit dcd6aab

Browse files
committed
fix
1 parent 08e95ef commit dcd6aab

File tree

3 files changed

+14
-12
lines changed

3 files changed

+14
-12
lines changed
-27.7 KB
Binary file not shown.
Binary file not shown.

content/blog/ctfcup2024-olymp/index.md

+14-12
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ params:
66
links:
77
- name: channel
88
link: https://t.me/theinkyvoid
9-
title: "ctfcup 2024 - olymp"
9+
title: "CTFCup 2024 - olymp"
1010
tldr: "unsolved crypto pwn task from a ctfcup organized by us"
1111
date: "2024-10-31"
1212
tags: [pwn, crypto]
@@ -16,15 +16,16 @@ summary: |
1616

1717
# olymp (ctfup 2024)
1818

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.
2020

2121
## Quick summary
2222

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 case reads 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 query reads 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.
2424

2525
## The vuln
2626

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+
2829
```c++
2930
#define MAX_LENGTH 200
3031

@@ -44,10 +45,10 @@ void build_prefix_hashes() {
4445
}
4546
```
4647

47-
4848
## Forging a hash
4949

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+
5152
```python
5253
L = IntegerLattice(
5354
[
@@ -58,24 +59,25 @@ L = IntegerLattice(
5859
+ [[W * P] + [0] * len(known)]
5960
)
6061
```
62+
6163
Then we just use `L.approximate_closest_vector` from sage and obtain our string.
6264

6365
## Leaking libstdc++
6466

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.
6668

6769
## Leaking libc
6870

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`:
7072

71-
![comparing uses memcmp](compare_memcmp.png)
73+
![comparing uses memcmp](compare_memcmp.webp)
7274

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.
7476

7577
## Running system
7678

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).
7880

7981
## Conclusion
8082

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

Comments
 (0)