0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】

一、基本情况分析:

  cg_pwn2这道题我做了很久,可以说如果能完全理解其漏洞触发及利用原理,就基本入门pwn了。接下来跟着我一起看一下这道经典的pwn题吧!

  首先对可执行文件进行基本检查:
0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】             图1

  如图1所示,该文件为32位i386的ELF可执行文件,只开启了NX,RELRO(部分readonly)。

NX:类似于windows的DEP,数据区被标为不可执行,所以我们只需要找到system或者execute执行/bin/sh来实现RCE。

RELRO(read only relocation):分为 partial relro 和 full relro。是一种用于加强对 binary 数据段的保护的技术,大概实现由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读,设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。

  IDA逆向分析:
  找到main()函数,进入hello()函数。
0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】
            图2

  如图3所示,s是0x26(38)字节长度的字串,gets()函数获取输入流时没有控制输入字符串长度,因而存在栈溢出。
0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】
            图3

  如图4所示,我们尝试对文件输入较长字符串,发现确实存在溢出。
0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】
            图4

  如图5所示,在IDA逆向分析时,发现可执行程序中有system()函数,
0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】
            图5

  如图6所示,system函数的地址为0x08048420。

0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】
            图6

  如图7所示,使用pwntools的工具也能查看system函数的位置:0x08048420(小端序,0x20打印出来是空格所以第一个字节是空格)。
0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】
            图7

  所以可以调用system()函数来rce,但是由于cg_pwn2文件中没有/bin/sh这样的字符串,所以我们需要自行输入字符串/bin/sh作为system()的参数。

二、构造payload:

  payload = ‘A’*42 + addr(system) + ‘A’*4 + addr(/bin/sh)

  system函数的地址我们知道了,字符串/bin/sh存储的地址是多少呢?如图8所示,fgets将stdin输入流中读取的数据保存到name中,我们输入的/bin/sh字符串就存储在name变量中。
0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】
            图8

  name的地址如图9所示为0x0804A080。
0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】
            图9

  由于是小端序,在内存中应存储为0x80 A0 04 08(从左至右地址由低到高)。pwntools中可以直接利用p32()函数,将数值数据转换为小端标准的存储数据。

汇编中,基于大端序或小端序的数值数据与存储数据的差别请自行百度

为什么payload中间要填充四字节字符串’AAAA’?

  如图10所示,由main函数对子函数的调用情况来看,main函数为所有调用的子函数参数提供栈空间,调用子函数完毕后不用释放栈空间。
0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】            图10

  如图11所示,每个子函数(例如hello())在开始时都会先push ebp(保存母函数ebp),mov ebp,esp,sub esp, xxh(开辟新栈帧)。
0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】
            图11

  图12所示为子函数hello()在退出当前函数,返回母函数前需要执行的指令。

  子函数hello()在退出当前函数时都会先mov esp,ebp(这里使用了add,方法不同目标一致)和pop ebp,恢复母函数的栈空间,其中esp和ebp的变化过程如图13所示。

  pop ebp后,esp指向函数返回地址,最后retn再根据esp指向的返回地址,返回到母函数对应的指令处。
0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】
            图12

  当我们使用payload导致栈溢出,子函数hello()在退出并跳转到system()函数的时,栈空间及指令的变化过程如图13所示。
0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】

图13

  hello()函数在gets(&s)后、执行retn指令之前,先分别执行了mov esp,ebp(hello()函数中分别用了add和两个pop指令达到了同样的效果,令esp=ebp)和pop ebp指令。此时esp指向返回地址,ebp指向母函数的ebp。

  在当前函数执行retn指令后,进入system函数,同时esp+4h。此时esp指向返回地址的下一个dword(即图13中被覆盖为字符串‘aaaa’的参数arg_1)。

  进入system函数后,此时对于system()函数而言,当前的esp应指向调用system()完毕后的返回地址,arg_2位置的数据是system()的参数。也就是说,我们伪造了一个system函数的栈帧,system()函数栈帧的返回地址被’aaaa’覆盖,输入参数为覆盖arg_2的字符串’/bin/sh’。

  综上所述,‘aaaa’是栈溢出调用system()函数时,system()函数所认为的返回地址,只有在payload中addr(system())和addr(/bin/sh)中间放一个四字节字符串’aaaa’,system()函数才能将addr(/bin/sh)作为其参数。

三、EXP:

  构造利用脚本如下:
0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】
  成功回连!然后可以cat flag了!
0x00 cg_pwn2【XCTF攻防世界pwn系列writeup】