美文网首页
BUUCTF刷题-1

BUUCTF刷题-1

作者: SamiraG | 来源:发表于2020-04-01 21:19 被阅读0次

crackRTF

是一个资源题,以前没见过,记录一下。
反编译一下:

int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  DWORD v3; // eax
  DWORD v4; // eax
  CHAR String; // [esp+4Ch] [ebp-310h]
  int v7; // [esp+150h] [ebp-20Ch]
  CHAR String1; // [esp+154h] [ebp-208h]
  BYTE pbData; // [esp+258h] [ebp-104h]

  memset(&pbData, 0, 0x104u);
  memset(&String1, 0, 0x104u);
  v7 = 0;
  printf("pls input the first passwd(1): ");
  scanf("%s", &pbData);
  if ( strlen((const char *)&pbData) != 6 )
  {
    printf("Must be 6 characters!\n");
    ExitProcess(0);
  }
  v7 = atoi((const char *)&pbData);
  if ( v7 < 100000 ) //输入的password可以转化为小于100000的数字
    ExitProcess(0);
  strcat((char *)&pbData, "@DBApp");
  v3 = strlen((const char *)&pbData);
  sub_40100A(&pbData, v3, &String1);  //求哈希值
  if ( !_strcmpi(&String1, "6E32D0943418C2C33385BC35A1470250DD8923A9") )
  {
    printf("continue...\n\n");
    printf("pls input the first passwd(2): ");
    memset(&String, 0, 0x104u);
    scanf("%s", &String);
    if ( strlen(&String) != 6 )
    {
      printf("Must be 6 characters!\n");
      ExitProcess(0);
    }
    strcat(&String, (const char *)&pbData);
    memset(&String1, 0, 0x104u);
    v4 = strlen(&String);
    sub_401019((BYTE *)&String, v4, &String1);
    if ( !_strcmpi("27019e688a4e62a649fd99cadaafdb4e", &String1) )
    {
      if ( !sub_40100F(&String) )
      {
        printf("Error!!\n");
        ExitProcess(0);
      }
      printf("bye ~~\n");
    }
  }
  return 0;
}

第一个密码是一个小于100000的数字,数字后面加上@DBApp后求哈希值,求哈希值的函数如下:

int __cdecl sub_401230(BYTE *pbData, DWORD dwDataLen, LPSTR lpString1)
{
  int result; // eax
  DWORD i; // [esp+4Ch] [ebp-28h]
  CHAR String2; // [esp+50h] [ebp-24h]
  char v6[20]; // [esp+54h] [ebp-20h]
  DWORD pdwDataLen; // [esp+68h] [ebp-Ch]
  HCRYPTHASH phHash; // [esp+6Ch] [ebp-8h]
  HCRYPTPROV phProv; // [esp+70h] [ebp-4h]

  if ( !CryptAcquireContextA(&phProv, 0, 0, 1u, 0xF0000000) )
    return 0;
  if ( CryptCreateHash(phProv, 0x8004u, 0, 0, &phHash) )
  {
    if ( CryptHashData(phHash, pbData, dwDataLen, 0) )
    {
      CryptGetHashParam(phHash, 2u, (BYTE *)v6, &pdwDataLen, 0);
      *lpString1 = 0;
      for ( i = 0; i < pdwDataLen; ++i )
      {
        wsprintfA(&String2, "%02X", (unsigned __int8)v6[i]);
        lstrcatA(lpString1, &String2);
      }
      CryptDestroyHash(phHash);
      CryptReleaseContext(phProv, 0);
      result = 1;
    }
    else
    {
      CryptDestroyHash(phHash);
      CryptReleaseContext(phProv, 0);
      result = 0;
    }
  }
  else
  {
    CryptReleaseContext(phProv, 0);
    result = 0;
  }
  return result;
}

CryptCreateHash函数的第二个参数用来确定哈希函数的类型
0x8004 -> SHA1, 0x800c -> SHA256, 0x8003 -> MD5
所以第一个密码是爆破SHA1,爆破范围是0-100000,爆破结果是123321
第二个输入是6字节的字符串,然后求MD5,我一开始的想法是爆破后来才知道超过4字节爆破是在想peach
继续往下走,在sub_40100F函数中调用了sub_4014D0

char __cdecl sub_4014D0(LPCSTR lpString)
{
  LPCVOID lpBuffer; // [esp+50h] [ebp-1Ch]
  DWORD NumberOfBytesWritten; // [esp+58h] [ebp-14h]
  DWORD nNumberOfBytesToWrite; // [esp+5Ch] [ebp-10h]
  HGLOBAL hResData; // [esp+60h] [ebp-Ch]
  HRSRC hResInfo; // [esp+64h] [ebp-8h]
  HANDLE hFile; // [esp+68h] [ebp-4h]

  hFile = 0;
  hResData = 0;
  nNumberOfBytesToWrite = 0;
  NumberOfBytesWritten = 0;
  hResInfo = FindResourceA(0, (LPCSTR)0x65, "AAA");
  if ( !hResInfo )
    return 0;
  nNumberOfBytesToWrite = SizeofResource(0, hResInfo);
  hResData = LoadResource(0, hResInfo);
  if ( !hResData )
    return 0;
  lpBuffer = LockResource(hResData);
  sub_401005(lpString, (int)lpBuffer, nNumberOfBytesToWrite);
  hFile = CreateFileA("dbapp.rtf", 0x10000000u, 0, 0, 2u, 0x80u, 0);
  if ( hFile == (HANDLE)-1 )
    return 0;
  if ( !WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0) )
    return 0;
  CloseHandle(hFile);
  return 1;
}

FindResourceA(0, (LPCSTR)0x65, "AAA")函数用于寻找名称为AAA的资源文件,下面是关于windows中资源文件的的一些资料.
VC使用自定义资源,FindResource,LoadResource,UnLockResource
Windows MFC工程起步
使用ResourceHacker可以查找文件中的resource:

看到资源AAA中是一系列的字符串
sub_401005中将AAA中的字节与输入的6字节字符串进行异或,最终将结果写入dbapp.rtf中,用写字板随便写一个文件然后查看RTF文件格式


取前6个与AAA中的字符串异或就能得到passwd2

babyre

是SCTF2019的一道RE题,当时没做出来看了师傅的WP后明白了,后来看到Ex大佬的WP感觉大佬的做法好特别,记录一下。
首先是调试:因为前面有花指令无法F5只有看汇编+调试.文件是动态加载的, 无法确定断点位置, 我之前没调过这种, 查资料发现了attach的方法. 先运行可执行文件, 然后使用ps -aux | grep filename的方法查看进程号, 使用sudo gdb filename的方法运行gdb, 然后使用attach PID附加进程. 如果程序停留在库函数中可以使用finish命令快速跳过.
还有一些实用的指令:

1. 反向调试: 要求平台支持记录回放
reverse-step <--- 反向运行程序到上一次被执行的源代码行。
reverse-stepi <--- 反向运行程序到上一条机器指令
watch a <--- 给变量a设置检查点,每次执行一条机器指令会打印出a在指令执行前后的值
record <--- 启动进程回放
reverse-next <--- 让程序倒退回到上一步的状态

2. 在启动gdb的时候附加的选项
-symbols <file> -s <file> 从指定文件中读取符号表。
-se file 从指定文件中读取符号表信息,并把他用在可执行文件中。
core <file> -c <file> 调试时core dump的core文件。

3.打印
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十六进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
c 按字符格式显示变量。
f 按浮点数格式显示变量。
(gdb) p i
$21 = 101
(gdb) p/a i
$22 = 0x65
(gdb) p/c i
$23 = 101 'e'

查看文件中某变量的值:
file::variable
function::variable
可以通过这种形式指定你所想查看的变量,是哪个文件中的或是哪个函数中的。例如,查看文件f2.c中的全局变量x的值:
(gdb) p 'f2.c'::x
查看数组的值, 比如数组的一段,或是动态分配的数据的大小。可以使用GDB的“@”操作符,“@”的左边是第一个内存的地址的值,“@”的右边是想查看内存的长度。
例如,程序中有这样的语句:
int *array = (int *) malloc (len * sizeof (int));
于是,在GDB调试过程中,可以以如下命令显示出这个动态数组的取值:
p *array@len
二维数组打印 ----> p **array@len
如果是静态数组的话,可以直接用print数组名,就可以显示数组中所有数据的内容了。
http://www.cppblog.com/chaosuper85/archive/2009/08/04/92123.html

4.断点 
gdb断点分类:
以设置断点的命令分类:
break 可以根据行号、函数、条件生成断点。
watch 监测变量或者表达式的值发生变化时产生断点。
catch 监测信号的产生。例如c++的throw,或者加载库的时候。
可以借助catch命令来反反调试:
`catch syscall ptrace`会在发生ptrace调用的时候停下,因此在第二次停住的时候`set $rax=0`,从而绕过程序中`ptrace(PTRACE_TRACEME, 0, 0, 0) ==-1`的判断

gdb中的变量从1开始标号,不同的断点采用变量标号同一管理,可以 用enable、disable等命令管理,同时支持断点范围的操作,比如有些命令接受断点范围作为参数。
例如:disable 5-8

break,tbreak ----> 可以根据行号、函数、条件生成断点。tbreak设置方法与break相同,只不过tbreak只在断点停一次,过后会自动将断点删除,break需要手动控制断点的删除和使能。

多文件设置断点:
在进入指定函数时停住:
C++中可以使用class::function或function(type,type)格式来指定函数名。如果有名称空间,可以使用namespace::class::function或者function(type,type)格式来指定函数名。
break filename:linenum ---> 在源文件filename的linenum行处停住 
break filename:function ---> 在源文件filename的function函数的入口处停住
break class::function或function(type,type)  (个人感觉这个比较方便,b 类名::函数名,执行后会提示如:
>>b GamePerson::update
Breakpoint 1 at 0x46b89e: file GamePerson.cpp, line 14.
在类class的function函数的入口处停住
break namespace::class::function ---> 在名称空间为namespace的类class的function函数的入口处停住

until
until line-number  继续运行直到到达指定行号,或者函数,地址等。
until line-number if condition
 
info break <--- 查看断点信息
(gdb) bt <--- 查看函数堆栈。backtrace 打印当前的函数调用栈的所有信息。
#0  func (n=250) at tst.c:5
#1  0x080484e4 in main () at tst.c:24
#2  0x400409ed in __libc_start_main () from /lib/libc.so.6

一共有三关, 第一关是一个三维迷宫
第二关输入经过特定表表转换之后,不断左移累加,最终的转换结果需要与字符串“sctf_9102”相等
调试发现是一个4字节到3字节的转换关系, 进行爆破.
一般的爆破流程:

data = [0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000003E, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000003F, 0x00000034, 0x00000035, 0x00000036, 0x00000037, 0x00000038, 0x00000039, 0x0000003A, 0x0000003B, 0x0000003C, 0x0000003D, 0x0000007F, 0x0000007F, 0x0000007F, 0x00000040, 0x0000007F, 0x0000007F, 0x0000007F, 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E, 0x0000000F, 0x00000010, 0x00000011, 0x00000012, 0x00000013, 0x00000014, 0x00000015, 0x00000016, 0x00000017, 0x00000018, 0x00000019, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000001A, 0x0000001B, 0x0000001C, 0x0000001D, 0x0000001E, 0x0000001F, 0x00000020, 0x00000021, 0x00000022, 0x00000023, 0x00000024, 0x00000025, 0x00000026, 0x00000027, 0x00000028, 0x00000029, 0x0000002A, 0x0000002B, 0x0000002C, 0x0000002D, 0x0000002E, 0x0000002F, 0x00000030, 0x00000031, 0x00000032, 0x00000033, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F]
# Python>'sctf_9102'.encode('hex')
# 736374665f39313032
pass2 = ''
dest = [0x736374,0x665f39,0x313032]
for d in range(3):  #三个输入,每个输入有四字节
    for i in range(0x20,0x80):
        for j in range(0x20,0x80):
            for k in range(0x20,0x80):
                 for l in range(0x20,0x80):
                    h = (((((data[i]<<6)|data[j])<<6)|data[k])<<6)|data[l]
                    if h == dest[d]:
                        print chr(i) + chr(j) + chr(k) + chr(l)
                        pass2 += chr(i) + chr(j) + chr(k) + chr(l)
print pass2

Ex大佬直接利用源程序里的流程进行爆破, 我觉得这样可以并不清楚具体实现细节,出错的概率也更小.具体的映射细节在loc_C22中:

// gcc -fPIC -O3 -shared hook.c -c -o hook.o
// ld -shared -ldl hook.o -o hook.so
#include <stdio.h>
#include <dlfcn.h>

typedef void (*FUNC)(char *, char *);

void _init()
{
    FUNC func;
    char *printable = "_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    #define LENGTH 63
    char in[8], out[8];
    char *image_base = *(void **)dlopen(NULL, 1);
    printf("Image base: %p\n", image_base);
    func = image_base + 0xC22;
    *(size_t *)in = 0;
    for (int i = 0; i < LENGTH; i++)
    {
        in[0] = printable[i];
        for (int ii = 0; ii < LENGTH; ii++)
        {
            in[1] = printable[ii];
            for (int iii = 0; iii < LENGTH; iii++)
            {
                in[2] = printable[iii];
                for (int iiii = 0; iiii < LENGTH; iiii++)
                {
                    in[3] = printable[iiii];
                    *(size_t *)out = 0;
                    func(in, out);
                    switch(*(size_t *)out)
                    {
                    case 0x746373:
                        fprintf(stderr, "%s -> %s\n",in, out);
                        break;
                    case 0x395F66:
                        fprintf(stderr, "%s -> %s\n",in, out);
                        break;
                    case 0x323031:
                        fprintf(stderr, "%s -> %s\n",in, out);
                        break;
                    }
                }
            }
        }
    }
    printf("over\n");
    exit(0);
}

运行:

$ LD_PRELOAD=./hook.so ./babygame
Image base: 0x559ccb697000
c2N0 -> sct
MTAy -> 102
Zl85 -> f_9
over

主要关键在于LD_PRELOAD, 找到一篇介绍文章如下:
https://www.cnblogs.com/net66/p/5609026.html
说明在babygame最开始调用_init函数的时候其实会调用hook.c中的_init函数, _init是一个爆破的函数,调用func时会调用loc_C22
还有关于dlopen()函数的问题
https://www.cnblogs.com/youxin/p/5109520.html

第三问是一个可逆函数。

array[30]
array[x+4] = array[x+0] ^ f(array[x+1] ^ array[x+2] ^ array[x+3])

输入分别分位4个32bit的整数,赋值给array[0]-array[3],然后根据异或变换和f(x)转换得到array[30],最后四个字节分别为0xD8BF92EF,0x9FCC401F,0xC5AF7647,0xBE040680
f(x)函数依赖于x。所以直接将公式倒置即可。

// gcc -fPIC -O3 -shared hook2.c -c -o hook2.o
// ld -shared -ldl hook2.o -o hook2.so
#include <stdio.h>
#include <dlfcn.h>

typedef int (*FUNC)(int);

void _init()
{
    FUNC func;
    int array[0x100] = {0};
    array[0] = 0xD8BF92EF;
    array[1] = 0x9FCC401F;
    array[2] = 0xC5AF7647;
    array[3] = 0xBE040680;

    char *image_base = *(void **)dlopen(NULL, 1);
    printf("Image base: %p\n", image_base);
    func = image_base + 0x1464;
    for (int i = 0; i < 26; i++)
    {
        array[i + 4] = array[i] ^ func(array[i+1] ^ array[i+2] ^ array[i+3]);
    }

    for(int i=3;i>=0;i--)
    {
        printf("%08X\n", array[26 + i]);
        printf("%c%c%c%c\n", ((char *)&array[26 + i])[0], ((char *)&array[26 + i])[1], ((char *)&array[26 + i])[2], ((char *)&array[26 + i])[3]);
    }
    printf("over\n");
    exit(0);
}

运行实例:

$ LD_PRELOAD=./hook2.so ./babygame
Image base: 0x562b0a9e9000
67346C66
fl4g
5F73695F
_is_
755F3073
s0_u
21793167
g1y!
over

creakme

在main函数中,首先进行了一些预处理。sub_402320添加了SEH
添加SEH的基本步骤

在上图中1步骤之前和步骤2之后的SEH链分别如下:

安装完SEH后通过call ds:DebugBreak调用DebugBreak函数进入SEH, 在调试过程中发生中断会优先交给调试器处理,需要在SEH函数开始下断点后用Shift + F9运行才可以在SEH函数处暂停.
SEH函数如下:

最终会调用0x4023EF函数,这个函数首先使用call ds:CheckRemoteDebuggerPresentcall ds:IsDebuggerPresent来检查程序是否被调试, 如果没有调试则进入sub_402450函数,这个函数会将0x404000后的0x200个字节与sycloversyclover进行异或

import idc
addr = 0x404000
sy = "sycloversyclover"
length = len(sy)
for i in range(0x200):
    b = int(idc.Byte(addr + i))
    idc.PatchByte(addr+i, ~(b ^ ord(sy[i % length])))

sub_4024A0首先使用__readfsdword(0x30u)检查程序是否被调试,然后调用404000函数,SMC后404000成了一个函数,用于修改最后的对比字符串。
最后在sub_4020D0中,对输入进行AES_CBC_128加密。密钥为’sycloversyclover’,IV为’sctfsctfsctfsctf’。这里的AES流程并不是正常的算法流程,这里使用了查表法,对比查表参数可以看出来是AES.
关于AES查表法的识别有下面的文章可以参考
逆向分析及识别恶意代码中的AES算法

BJDCTF_easy

下面代码中v2中用来存储v14转化为二进制的数值,v2[0]存储v14的最高位

while ( SHIDWORD(v14) > 0 || v14 >= 0 && v14 )
{
      v2[v16++] = ((SHIDWORD(v14) >> 31) ^ (((SHIDWORD(v14) >> 31) ^ v14) - (SHIDWORD(v14) >> 31)) & 1)
                - (SHIDWORD(v14) >> 31);
      v14 /= 2LL;
}

#define SHIDWORD(x) (*((int32*)&(x)+1))
取变量X地址的下一个字节的地址并且解引用,最后得到的是地址中的值,通俗点讲就是X所在内存中的邻居

相关文章

  • BUUCTF刷题-1

    crackRTF 是一个资源题,以前没见过,记录一下。反编译一下: 第一个密码是一个小于100000的数字,数字后...

  • 强网杯2019 随便注

    buuctf第二题。根据题目提示可知是sql注入题先进入页面,发现一个输入框且有缺省值1,先在1后加上单引号,出现...

  • buuctf-upload-labs

    刷了下buuctf搭建的upload-labs,记录一下。 地址:https://buuoj.cn/ Pass 0...

  • 教你怎么做一只考试锦鲤

    考试前14天疯狂刷题,各个平台疯狂刷题,刷题就对了。 刷的越多,大脑记得越多,也许你刷10道题,能记住的只有1道题...

  • ubuntu19.04 pwn做题环境布置

    在buuctf上练题的时候,发现自己缺少19.04的做题环境,这里记录下最后整合为了docker镜像,用来完成基础...

  • 刷题说(1)

    所谓高效刷题,应该是根据结构化思维所呈现出的不足,而进行的针对性提高。 如果按我最近说的框架学习法,那就是去填充框架。

  • leetcode刷题-1

    小A 和 小B 在玩猜数字。小B 每次从 1, 2, 3 中随机选择一个,小A 每次也从 1, 2, 3 中选择一...

  • 刷题纠错-1

    5.21卷二 1、留置权的概念与构成要件

  • 刷题总结(1)

    题目描述 给出一个数n,求1到n中,有多少个数不是2 5 11 13的倍数。 输入描述 本题有多组输入每行一个数n...

  • 刷题感觉1

    刷了一阵子题了,觉得对锻炼思考能力确实有帮助,想要继续下去,却发现接连遇到两个很难的题,加上最近情绪不稳定,实在提...

网友评论

      本文标题:BUUCTF刷题-1

      本文链接:https://www.haomeiwen.com/subject/zmrzuhtx.html