# SekaiCTF
## Algorthim multitools
The challenge give us `libc.so.6`, a `.cpp` file and a binary
This program can do some tasks

Basically we will create some tasks, after that we choose `resume` to execute the algorthim

This program uses coroutine in c++20 which is a function that can be stopped and be resumed
* A function will be a coroutine when there're one of these in that function: `co_await`, `co_return`, `co_yield`
Example:
```cpp=
auto slow_algo_factory(SlowAlgo* algo)
{
algo->get_algo_params();
auto h = [algo_l = algo]() -> Task
{
co_await run_algo_async(algo_l);
std::cout << "Result: " << algo_l->get_result() << std::endl;
co_return;
};
return h;
}
```
* When calling `slow_algo_factory` it will run until meeting the `run_algo_async` function, it stops and will continue when it is resumed
Um i don't understand how it works, everything in gg are confusing :face_with_head_bandage: so i use `ida` and `gdb` to debug myself
### Decompile program
Since the program is kinda big, i will only explain about `slow_algo_factory`, but the `fast` is similar tho
#### The coroutine
Here the function in .cpp file
```cpp=
if(choice == 1) {
AES_CBC *cbc = new AES_CBC();
temp = new SavedTask(slow_algo_factory(cbc)(), cbc, cbc->get_name());
temp->call_count = 0;
}
```
See it in `ida`
```cpp=
case 1:
v3 = (AES_CBC *)operator new(0xB8uLL);
AES_CBC::AES_CBC(v3);
v63 = v3;
obj = slow_algo_factory((__int64)v3);
hdl = slow_algo_factory(SlowAlgo *)::{lambda(void)#1}::operator()(&obj);
Algo::get_name[abi:cxx11](name, v63);
v4 = operator new(0x38uLL);
SavedTask::SavedTask(v4, hdl, v63, name);
v52 = v4;
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(name);
*(_QWORD *)(v52 + 48) = 0LL;
goto LABEL_20;
```
* First it just call constructor of `AES_CBC` class
* Next, we will se the first `slow_algo_factory`
```cpp=
__int64 __fastcall slow_algo_factory(__int64 a1)
{
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 16LL))(a1);
return a1;
}
```
* We pass the `AES_CBC` object to it before then it call `*(*(obj) + 16)(obj)`
* It's the calling of `get_algo_params` function through vtable.

* The next `slow_algo_factory(SlowAlgo *)::{lambda(void)#1}::operator()(&obj)`
```cpp=
__int64 __fastcall slow_algo_factory(SlowAlgo *)::{lambda(void)#1}::operator()(__int64 obj_pointer)
{
__int64 return_object; // rbx
__int64 v3; // [rsp+18h] [rbp-18h]
v3 = operator new(0x40uLL);
*(_BYTE *)(v3 + 42) = 1;
*(_QWORD *)v3 = slow_algo_factory(SlowAlgo *)::{lambda(void)#1}::operator();
*(_QWORD *)(v3 + 8) = slow_algo_factory(SlowAlgo *)::{lambda(void)#1}::operator();
*(_QWORD *)(v3 + 32) = obj_pointer;
return_object = promise::get_return_object((#3059 *)(v3 + 16));
*(_WORD *)(v3 + 40) = 0;
slow_algo_factory(SlowAlgo *)::{lambda(void)#1}::operator()(v3);
return return_object;
}
```
* Here is how the coroutine work
* First it will create an handler object which will handle the `resume` and `destroy` of a coroutine
* We see that it sets the first `2` field to `2` functions, they have the same name but are different, i will call them `lambda1` and `lambda2`
* Next it stores the address of object we pass to it which is a stack value
* The `promise::get_return_object((#3059 *)(v3 + 16))` will return `v3`, nothing special
* It stores some value such as `1`, `0` in object, they're will be used to determine the state of coroutine
* Then it calls ` slow_algo_factory(SlowAlgo *)::{lambda(void)#1}::operator()(v3)`, it's `lambda1`
* Before we go into `lambda1` i show you what the `handler` object looks like

* Pls don't notice to some garbage values which are in heap before :cry:
```
|lambda1 |lambda2
| |hdl
|objaddr |0x0000000000010000 (state of coroutine)
| |
```
`lambda1` function
```cpp=
unsigned __int64 __fastcall slow_algo_factory(SlowAlgo *)::{lambda(void)#1}::operator()(_BYTE *a1)
{
int v1; // eax
int v2; // eax
__int64 v3; // rax
__int64 v4; // rsi
__int64 v5; // rbx
__int64 v6; // rax
__int64 v7; // rax
_BYTE v9[40]; // [rsp+20h] [rbp-40h] BYREF
unsigned __int64 v10; // [rsp+48h] [rbp-18h]
v10 = __readfsqword(0x28u);
if ( (*((_WORD *)a1 + 20) & 1) != 0 )
{
v1 = *((unsigned __int16 *)a1 + 20);
if ( v1 != 7
&& (*((unsigned __int16 *)a1 + 20) > 7u || v1 != 5 && (*((unsigned __int16 *)a1 + 20) > 5u || v1 != 1 && v1 != 3)) )
{
BUG();
}
goto LABEL_28;
}
v2 = *((unsigned __int16 *)a1 + 20);
if ( v2 != 6 )
{
if ( *((unsigned __int16 *)a1 + 20) > 6u )
goto LABEL_18;
if ( v2 != 4 )
{
if ( *((unsigned __int16 *)a1 + 20) > 4u )
goto LABEL_18;
if ( *((_WORD *)a1 + 20) )
{
if ( v2 != 2 )
LABEL_18:
BUG();
}
else
{ // go to here if 0
// this just setup someting
*((_QWORD *)a1 + 3) = std::__n4861::coroutine_handle<promise>::from_address(a1);
a1[43] = 0;
promise::initial_suspend((#3059 *)(a1 + 16));
if ( (unsigned __int8)std::__n4861::suspend_always::await_ready((#3048 *)(a1 + 44)) != 1 )
{
*((_WORD *)a1 + 20) = 2; // set the state
v3 = std::__n4861::coroutine_handle<promise>::operator std::__n4861::coroutine_handle<void>(a1 + 24);
std::__n4861::suspend_always::await_suspend(a1 + 44, v3);
return v10 - __readfsqword(0x28u);
}
}
a1[43] = 1; // go to here if 2
std::__n4861::suspend_always::await_resume((#3048 *)(a1 + 44));
*((_QWORD *)a1 + 6) = run_algo_async(**((_QWORD **)a1 + 4));// set obj here, this will use the object stored in objaddress to call do_algo
if ( (unsigned __int8)run_algo_async(Algo *)::awaitable::await_ready(a1 + 48) != 1 )
{
*((_WORD *)a1 + 20) = 4;
v4 = std::__n4861::coroutine_handle<promise>::operator std::__n4861::coroutine_handle<void>(a1 + 24);
run_algo_async(Algo *)::awaitable::await_suspend(a1 + 48, v4);// call do_algo
return v10 - __readfsqword(0x28u);
}
} // the second resume will go into here
// it just print out the result and it still use the object stored in objaddr
run_algo_async(Algo *)::awaitable::await_resume(a1 + 48);
v5 = std::operator<<<std::char_traits<char>>(&std::cout, "Result: ");
Algo::get_result[abi:cxx11](v9, **((_QWORD **)a1 + 4));
v6 = std::operator<<<char,std::char_traits<char>,std::allocator<char>>(v5, v9);
std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v9);
promise::return_void((#3059 *)(a1 + 16));
*(_QWORD *)a1 = 0LL;
promise::final_suspend((#3059 *)(a1 + 16));
if ( (unsigned __int8)std::__n4861::suspend_always::await_ready((#3048 *)(a1 + 56)) != 1 )
{
*((_WORD *)a1 + 20) = 6;
v7 = std::__n4861::coroutine_handle<promise>::operator std::__n4861::coroutine_handle<void>(a1 + 24);
std::__n4861::suspend_always::await_suspend(a1 + 56, v7);
return v10 - __readfsqword(0x28u);
}
}
std::__n4861::suspend_always::await_resume((#3048 *)(a1 + 56));
LABEL_28: //
// go to here when user calls destroy
if ( a1[42] )
operator delete(a1);
return v10 - __readfsqword(0x28u);
}
```
* To simplify it, i won't go detail into this code
* The function will have `3` times called to be doned
1. First it will be called by the function `slow_algo_factory`. It just set up something in object
2. Second call when user resumes it will call `do_algo` by using the object stored in `objaddr`
3. Third call when user resumes again. It will call `get_result` (still uses the object in `objaddr`) and prints it
4. The `lambda2` sets up the `state` then call `lambda1` so that it will `delete` the `handler` object
```cpp=
__int64 __fastcall slow_algo_factory(SlowAlgo *)::{lambda(void)#1}::operator()(__int64 a1)
{
*(_WORD *)(a1 + 40) |= 1u;
return slow_algo_factory(SlowAlgo *)::{lambda(void)#1}::operator()(a1);
}
```
* Now back to `main`
```cpp=
Algo::get_name[abi:cxx11](name, v63);
v4 = operator new(0x38uLL);
SavedTask::SavedTask(v4, hdl, v63, name);
v52 = v4;
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(name);
*(_QWORD *)(v52 + 48) = 0LL;
goto LABEL_20;
```
* It creates `SavedTask` object will `handler`, `AES_CBC`(a `Algo` object), and a `name`
Here the constructor of `SavedTask`
```cpp=
__int64 __fastcall SavedTask::SavedTask(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
Task::Task((#3064 *)a1);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(a1 + 16);
*(_QWORD *)a1 = a2;
*(_QWORD *)(a1 + 8) = a3;
return std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator=(a1 + 16, a4);
}
```
* The object will look like
```
|hdl |obj
|nameobj |
| |
|0(count) |
```
* Last it will store the object in a vector
```cpp=
tasks.push_back(temp);
```
#### The bug
* The bug here is that every `slow_Algo` object (or `fast_Algo`) will be stored in the same address before passed to `slow_algo_factory` to create a `handler`
```cpp=
v3 = (AES_CBC *)operator new(0xB8uLL);
AES_CBC::AES_CBC(v3);
v63 = v3;
obj = slow_algo_factory((__int64)v3);
```
* The `obj` here you see that it's a value in stack. So the `objaddr` field in `handler` will be the same for all handlers of `slow_Algo`. When we `destroy` a task
```cpp=
v43 = (_QWORD **)std::vector<SavedTask *,std::allocator<SavedTask *>>::operator[](v64, index);
std::__n4861::coroutine_handle<promise>::destroy(*v43);// call lambda2 to delete hdl
v44 = (std::stop_source **)std::vector<SavedTask *,std::allocator<SavedTask *>>::operator[](v64, index);
v45 = *v44;
if ( *v44 )
{
SavedTask::~SavedTask(*v44); // call destructor obj
operator delete(v45, 0x38uLL);
}
i_1 = index;
v53 = std::vector<SavedTask *,std::allocator<SavedTask *>>::begin(v64);
obj = __gnu_cxx::__normal_iterator<SavedTask **,std::vector<SavedTask *,std::allocator<SavedTask *>>>::operator+(
&v53,
i_1); // [1]
__gnu_cxx::__normal_iterator<SavedTask * const*,std::vector<SavedTask *,std::allocator<SavedTask *>>>::__normal_iterator<SavedTask **>(
&hdl,
&obj);
std::vector<SavedTask *,std::allocator<SavedTask *>>::erase(v64, hdl);
v47 = std::operator<<<std::char_traits<char>>(&std::cout, "Done!");
```
* It calls some `destructor` but look at `[1]`. The `obj` here is changed to a `SavedTask` object. It's supposed to be a `Algo` object. That's the first bug
* The `obj` is set to `vector[i]` then the program erases the object in index `i`. What if we `destroy` the last object ?
```
Before erasing the n object which is the last
vector[0] = savedtask
vector[1] = savedtask
.
.
.
vecotr[n] = savetask <- obj
After erasing
vector[0] = savedtask
vector[1] = savedtask
.
.
.
vecotr[n] = savedtask <- obj
It's the same but savedtask[n] is now freed so that's use-after-free bug
```
* More interesting, if we create so many `SavedTask` so the old chunk vector points to won't have enough space so it will `free` the old chunk and `malloc` a new chunk but the `objaddr` still points to the old chunk
### Exploit
#### Leak libc and heap
* String object
```
0 8 0x10
pointer to string |len of string |string if len < 0x10
```
* The pointer will point to next `0x10` bytes if string's length <= `0x10` (include `null` byte) else it will point to an allocated chunk
* When we use `cout << string_object` it will read the length of string then print that number of bytes.
* To leak libc, i will fake the `len` field
Here's how it works
* First i create a `AES_CBC` object with a large string so the program will `malloc` a big chunk. This string will be freed somehow
* Next i create many tasks so `vector` will allocate a new big chunk, it will use my previous chunk

* The `get_result` will use `string` object in `Algo + 0x48` so the `size` field here is `0x4b4b4b4b4b4b4b4b`. Here i `destroy(0)` so the `objaddr` is `0x5649ebdd040a0`
* Abuse the `size` to a big numbers to print `libc` and `heap`


* I get libc since there's some unsorted bin in heap
#### Fake vtable and one gadget
* Use the previous method, i make `objaddr` points to a freed chunk
* Now to craft this chunk, i will create a fast algo task to not change the value at `objaddr`. Calculate the right `size` to trigger the right chunk
```python=
linear(0x20*2, list_)
```
* So to trigger code execution i craft a vtable to call `one_gadget`
* The first `resume` will call `do_algo`. It's offset in vtable

* When i write `one_gadget` to `do_algo` offset the constraints isn't met. Then i found a useful gadget
```
0x000000000008ab82 : xor edx, edx ; mov rdi, rbp ; call qword ptr [rax + 0x80]
```
* Since the `rax` points to our crafted chunk i can use it
* So here's the crafted chunk

Then i get a shell

But since the server is closed, i don't if it works in server :crying_cat_face: