BUUCTF刷题记录 [Reverse]

BUUCTF刷题记录[Reverse]

SimpleRev

64位无壳程序,重点函数为Decry函数。

0x01 小端序存储

*(_QWORD *)src = 0x534C43444ELL;
v9 = 0x776F646168LL;

IDA直接转字符串的结果是

*(_QWORD *)src = 'SLCDN';
v9 = 'wodah';

Intel的CPU是小端序存储,因此程序载入内存后,字符串顺序应该反过来,也就是

*(_QWORD *)src = 'NDCLS';
v9 = 'hadow';

0x02 大写字母转小写字母

v3 = 0;
getchar();
v5 = strlen(key);
for ( i = 0; i < v5; ++i )
{
  if ( key[v3 % v5] > 64 && key[v3 % v5] <= 90 )
    key[i] = key[v3 % v5] + 32;
  ++v3;
}

大写字母和小写字母的ASCII码相差32,所以该段程序是将key中的大写字母转成小写字母。

0x03 混淆算法

while ( 1 )
{
  v1 = getchar();
  if ( v1 == 10 )
    break;
  if ( v1 == 32 )
  {
    ++v2;
  }
  else
  {
    if ( v1 <= 96 || v1 > 122 )
    {
      if ( v1 > 64 && v1 <= 90 )
        str2[v2] = (v1 - 39 - key[v3++ % v5] + 97) % 26 + 97;
    }
    else
    {
      str2[v2] = (v1 - 39 - key[v3++ % v5] + 97) % 26 + 97;
    }
    if ( !(v3 % v5) )
      putchar(32);
    ++v2;
  }
}
if ( !strcmp(text, str2) )
  puts("Congratulation!\n");
else
  puts("Try again!\n");

如果对ASCII码熟悉的话,很容易可以看出几个判断条件的目的是确保输入的v1是26的英文字母之一,大小写皆可,因此遍历26个英文字母,依次输出符合条件的字符即可。

0x04 EXP

src = "ADSFKNDCLS"
v3 = 0
v5 = len(src)
key = ""
for i in range(v5):
    if (src[i] > "@" and src[i] <= "Z"):
        key += (chr(ord(src[i]) + 32))
    else:
        key + (src[i])
#print("%s" %key)

text = "killshadow"
flag = ""
while (v3 < 10):
    for i in range(65, 91):
        if (ord(text[v3]) == ((i - 39 - ord(key[v3]) + 97) % 26 + 97)):
            flag += chr(i)
            v3 += 1
            break
        else:
            if (i == 90):
                for j in range(97, 122):
                    if (ord(text[v3]) == ((j - 39 - ord(key[v3]) + 97) % 26 + 97)):
                        flag += chr(j)
                        v3 += 1
                        break
print("flag{%s}" %flag) 

flag{KLDQCUDFZO}

0x05 反思

  1. 大小端存储知识点不熟悉,变量赋值和载入内存时的字符串顺序转换不够熟悉。
  2. ASCII码不熟悉,不清楚函数的意图。

luck_guy

EXP

f1 = "GXY{do_not_"
s = [0x7F, 0x66, 0x6F, 0x60, 0x67, 0x75, 0x63, 0x69][::-1]
f2 = ""
for i in range(8):
    if (i % 2 == 1):
        f2 += chr(int(s[i]) - 2)
    else:
        f2 += chr(int(s[i]) - 1)
flag = f1 + f2
print("%s" %flag)
print("flag%s" %flag[3:])

flag{do_not_hate_me}

[BJDCTF2020]JustRE

比较直接,需要用户点击19999次弹出的框后,打印flag。

INT_PTR __stdcall DialogFunc(HWND hWnd, UINT a2, WPARAM a3, LPARAM a4)
{
  CHAR String; // [esp+0h] [ebp-64h]

  if ( a2 != 272 )
  {
    if ( a2 != 273 )
      return 0;
    if ( (_WORD)a3 != 1 && (_WORD)a3 != 2 )
    {
      sprintf(&String, Format, ++dword_4099F0);
      if ( dword_4099F0 == 19999 )
      {
        sprintf(&String, aBjdDD2069a4579, 19999, 0);// 打印flag
        SetWindowTextA(hWnd, &String);
        return 0;
      }
      SetWindowTextA(hWnd, &String);
      return 0;
    }
    EndDialog(hWnd, (unsigned __int16)a3);
  }
  return 1;
}

直接使用ipython将19999和0填入进去即可。

flag{1999902069a45792d233ac}

刮开有奖

0x01 总体分析

32位无壳,main函数比较简单,它的参数DialogFunc比较有问题。

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
  DialogBoxParamA(hInstance, (LPCSTR)0x67, 0, DialogFunc, 0);
  return 0;
}

DialogBoxParamA函数(winuser.h)

从对话框模板资源创建模式对话框。 在显示对话框之前,函数会将应用程序定义的值作为WM_INITDIALOG消息的 lParam 参数传递给对话框过程。 应用程序可以使用此值初始化对话框控件。

https://learn.microsoft.com/zh-CN/windows/win32/api/winuser/nf-winuser-dialogboxparama

DialogFunc函数中重点代码如下:

  if ( (_WORD)a3 == 1001 )
  {
    memset(&String, 0, 0xFFFFu);
    GetDlgItemTextA(hDlg, 1000, &String, 0xFFFF);
    if ( strlen(&String) == 8 )
    {
      v7 = 'Z';
      v8 = 'J';
      v9 = 'S';
      v10 = 'E';
      v11 = 'C';
      v12 = 'a';
      v13 = 'N';
      v14 = 'H';
      v15 = '3';
      v16 = 'n';
      v17 = 'g';
      sub_4010F0((int)&v7, 0, 10);
      memset(&v26, 0, 0xFFFFu);
      v26 = v23;
      v28 = v25;
      v27 = v24;
      v4 = sub_401000((int)&v26, strlen(&v26));
      memset(&v26, 0, 0xFFFFu);
      v27 = v21;
      v26 = v20;
      v28 = v22;
      v5 = sub_401000((int)&v26, strlen(&v26));
      if ( String == v7 + 34
        && v19 == v11
        && 4 * v20 - 141 == 3 * v9
        && v21 / 4 == 2 * (v14 / 9)
        && !strcmp(v4, "ak1w")
        && !strcmp(v5, "V1Ax") )
      {
        MessageBoxA(hDlg, "U g3t 1T!", "@_@", 0);
      }
    }
    return 0;
  }
  1. String的长度为8,因此flag为8位。
  2. 查看内存布局后发现,v7~v17是一段连续的内存空间,因此(int)&v7可以看做一段字符串头指针。
  3. String、v19~v25是一段连续的内存空间,长度为8,因此该段存储的flag。

0x02 sub_4010F0

int __cdecl sub_4010F0(int a1, int a2, int a3)
{
  int result; // eax
  int i; // esi
  int v5; // ecx
  int v6; // edx

  result = a3;
  for ( i = a2; i <= a3; a2 = i )
  {
    v5 = 4 * i;
    v6 = *(_DWORD *)(4 * i + a1);
    if ( a2 < result && i < result )
    {
      do
      {
        if ( v6 > *(_DWORD *)(a1 + 4 * result) )
        {
          if ( i >= result )
            break;
          ++i;
          *(_DWORD *)(v5 + a1) = *(_DWORD *)(a1 + 4 * result);
          if ( i >= result )
            break;
          while ( *(_DWORD *)(a1 + 4 * i) <= v6 )
          {
            if ( ++i >= result )
              goto LABEL_13;
          }
          if ( i >= result )
            break;
          v5 = 4 * i;
          *(_DWORD *)(a1 + 4 * result) = *(_DWORD *)(4 * i + a1);
        }
        --result;
      }
      while ( i < result );
    }
LABEL_13:
    *(_DWORD *)(a1 + 4 * result) = v6;
    sub_4010F0(a1, a2, i - 1);
    result = a3;
    ++i;
  }
  return result;
}

直接用C语言按照伪代码写脚本即可,此过程遇到两个问题。

  1. *(_DWORD *)是强制类型转化,然后在提领指针。

    dword是指注册表的键值,每个word为2个字节,dword双字即为4个字节。

    伪代码中的*(_DWORD *)(4 * i + a1)其实就是a1[i]*(_DWORD *)(a1 + 4 * result)a1[result]

  2. char *tmp = "aaaaaaaa"类似的指针定义方式,是固定类型的字符串,不能改变,通过指针对字符串进行修改会报错。

    char tmp[]="aaaaaaaa"类似的定义方式,可以对字符串进行修改。

加密脚本如下,ZJSECaNH3ng加密后得到的结果是3CEHJNSZagn

#include <stdio.h>

int sub_4010F0(char *a1, int a2, int a3)
{
    int result;
    int i;
    int v5;
    int v6;

    result = a3;
    for (i = a2; i <= a3; a2 = i)
    {
        v5 = i;
        v6 = a1[i];
        if (a2 < result && i < result)
        {
            do
            {
                if (v6 > a1[result])
                {
                    if (i >= result)
                        break;
                    ++i;
                    a1[v5] = a1[result];
                    if (i >= result)
                        break;
                    while(a1[i] <= v6)
                    {
                        if (++i >= result)
                            goto LABEL_13;
                    }
                    if (i >= result)
                        break;
                    v5 = i;
                    a1[result] = a1[i];
                }
                --result;
            }
            while (i < result);
        }
LABEL_13:
        a1[result] = v6;
        sub_4010F0(a1, a2, i-1);
        result = a3;
        ++i;
    }
    return result;
}

int main()
{
    char v7[] = "ZJSECaNH3ng";
    sub_4010F0(v7, 0, 10);
    printf("%s", v7);
    return 0;
}

0x03 sub_401000

推测是base64编码,理由如下:

  1. 该函数两个参数分别是字符串头指针和字符串长度,函数开头定义了v4,赋值为字符串长度的4/3倍。
  v2 = a2 / 3;
  v3 = 0;
  if ( a2 % 3 > 0 )
    ++v2;
  v4 = 4 * v2 + 1;
  1. 函数使用到了字符串byte_407830,该字符串存储了base64映射表中的元素。
.rdata:00407830 ; char byte_407830[]
.rdata:00407830 byte_407830     db 41h                  ; DATA XREF: sub_401000+C0↑r
.rdata:00407831 aBcdefghijklmno db 'BCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',0

0x04 EXP

memset(&v26, 0, 0xFFFFu);
v26 = v23;
v28 = v25;
v27 = v24;
v4 = sub_401000((int)&v26, strlen(&v26));
memset(&v26, 0, 0xFFFFu);
v27 = v21;
v26 = v20;
v28 = v22;
v5 = sub_401000((int)&v26, strlen(&v26));
if ( String == v7 + 34
  && v19 == v11
  && 4 * v20 - 141 == 3 * v9
  && v21 / 4 == 2 * (v14 / 9)
  && !strcmp(v4, "ak1w")
  && !strcmp(v5, "V1Ax") )
{
  MessageBoxA(hDlg, "U g3t 1T!", "@_@", 0);
}

前文提到过,flag[0]、flag[1]–flag[7]分别对应String、v19–v25。

String,即flag[0] = v7 + 34。

v9,即flag[1] = v11。

sub_4010F0函数加密后,v7~v17一段内存中存储的字符串是3CEHJNSZagn

v4是flag[5]、flag[6]、flag[7]组成的字符串base64编码后的结果。

v5是flag[2]、flag[3]、flag[4]组成的字符串base64编码后的结果。

import base64

v7 = '3'
v11 = 'J'
v4 = "ak1w"
v5 = 'V1Ax'
v5 = base64.b64decode(v5)
v4 = base64.b64decode(v4)

flag = ""
flag += chr(ord(v7) + 34)
flag += v11
flag += str(v5, encoding = "utf-8")
flag += str(v4, encoding = "utf-8")
print("flag{%s}" %flag)

flag{UJWP1jMp}

[ACTF新生赛2020]easyre

0x01 分析

32位,upx壳。

v4~v15一共12位,for循环也是12位,可推测flag有12位。

从if语句中可以看出flag的一部分是ACTF{},内容是v22,v23,v24。

从栈中查看,上述三个变量都是int型变量,分别可以存储4个字符型数据,也验证了flag有12位的推测。

从v4到v15分别与__data_start__比较,索引值+1即为flag中对应位置的字符。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  /*   Define variables   */

  __main();
  v4 = 42;
  v5 = 70;
  v6 = 39;
  v7 = 34;
  v8 = 78;
  v9 = 44;
  v10 = 34;
  v11 = 40;
  v12 = 73;
  v13 = 63;
  v14 = 43;
  v15 = 64;
  printf("Please input:");
  scanf("%s", v19);
  if ( v19[0] != 'A' || v19[1] != 'C' || v19[2] != 'T' || v20 != 'F' || v21 != '{' || v25 != '}' )
    return 0;
  v16 = v22;
  v17 = v23;
  v18 = v24;
  for ( i = 0; i <= 11; ++i )
  {
    if ( *(&v4 + i) != _data_start__[*((char *)&v16 + i) - 1] )
      return 0;
  }
  printf("You are correct!");
  return 0;
}

0x02 EXP

.data:00402000                 public __data_start__
.data:00402000 ; char _data_start__[]
.data:00402000 __data_start__  db 7Eh                  ; DATA XREF: _main+EC↑r
.data:00402001 aZyxwvutsrqponm db '}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>='
.data:00402001                 db '<;:9876543210/.-,+*)(',27h,'&%$# !"',0
.data:00402060                 align 40h
  1. 不要忘记__data_start__的第一个字符,也就是地址0x00402000的db 7Eh

  2. 不要忘记27h,对应的是转义符\

  3. 为防止字符被转义,Python可以使用r/R来表明非转义的原始字符串。

    python中 r’‘, b’‘, u’‘, f’’ 的含义

data_start = r'~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)(' + chr(0x27) + r'&%$# !"'
flag = ''
v4 = [42,70,39,34,78,44,34,40,73,63,43,64]

for i in v4:
    flag += chr(data_start.find(chr(i)) + 1)

print("flag{%s}" %flag)

flag{U9X_1S_W6@T?}

[ACTF新生赛2020]rome

32位无壳,重点代码很短,其实就是一段凯撒加密

result = [81, 115, 119, 51, 115, 106, 95, 108, 122, 52, 95, 85, 106, 119, 64, 108]
flag = ""
for re in result:
    if re > 64 and re <= 90:
        for i in range(65, 91):
            if ((i - 51) % 26 + 65) == re:
                flag += chr(i)
                break
    elif re > 96 and re <= 122:
        for j in range(97, 123):
            if ((j - 79) % 26 + 97) == re:
                flag += chr(j)
                break
    else:
        flag += chr(re)

print("flag{%s}" %flag)

flag{Cae3ar_th4_Gre@t}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值