pwn college 刷题记录:debugging refresher

本文最后更新于:2024年5月20日 下午

GDB 非常强大!

https://pwn.college/fundamentals/debugging-refresher

总结

  • 熟悉一些gdb命令
    • 一些常用的指令
      • layout asm
      • layout regs
      • display /10i
        • undisplay num 取消
      •  start 启动程序,断点设置在 main 
      •  run 启动程序,不设置断点
      • p/x $rax 以十六进制打印出rax的值
      • x 查看内存的值
        • x/20g $rbp-0x18
      • set 修改内存的值
        • set *(int*)($rbp-0x1c) = 100
      • call (void)win()  调用函数
  • 熟悉如何写gdb脚本与程序进行交互
    • gdb启动时候的脚本:.gdbinit
    • 与程序交互的脚本:xxx.gdb。将命令写入某个文件,例如x.gdb,然后使用标志-x <PATH_TO_SCRIPT>启动gdb
    • 调整汇编风格:set disassembly-flavor intel

Lectures and Reading

1

1
2
3
4
5
6
7
8
9
GDB是一个非常强大的动态分析工具,您可以使用它来了解程序在执行过程中的状态。在本模块中,您将熟悉一些GDB的功能。

您正在运行GDB!程序当前处于暂停状态。这是因为它在这里设置了断点。

您可以使用命令 `start` 启动程序,断点设置在 `main` 上。您可以使用命令 `starti` 启动程序,断点设置在 `_start` 上。您可以使用命令 `run` 启动程序,不设置断点。您可以使用命令 `attach <PID>` 连接到其他已经运行的程序。您可以使用命令 `core <PATH>` 分析已经运行的程序的核心转储。

在启动或运行程序时,您可以几乎以与在shell中相同的方式指定参数。例如,您可以使用 `start <ARGV1> <ARGV2> <ARGVN> < <STDIN_PATH>`。

使用命令 `continue` 或 `c` 继续执行程序。

直接run,程序执行完毕时吐出flag

2

打印出寄存器的值

  • info registers
  • p/x $rax
    • 以十六进制打印出rax的值

flag存在r12寄存器中

3

1
2
3
4
5
6
7
8
9
10
11
12

GDB是一个非常强大的动态分析工具,您可以使用它来了解程序在执行过程中的状态。在本模块中,您将熟悉一些GDB的功能。

您可以使用 `x/<n><u><f> <address>` 命令来检查内存的内容。在这个格式中,`<u>` 是要显示的单元大小,`<f>` 是要显示的格式,`<n>` 是要显示的元素数量。有效的单元大小有 `b`(1字节)、`h`(2字节)、`w`(4字节)和 `g`(8字节)。有效的格式有 `d`(十进制)、`x`(十六进制)、`s`(字符串)和 `i`(指令)。地址可以使用寄存器名、符号名或绝对地址来指定。此外,在指定地址时,您还可以使用数学表达式。

例如,`x/8i $rip` 将打印当前指令指针后面的8条指令。`x/16i main` 将打印 main 函数的前16条指令。您还可以使用 `disassemble main` 或 `disas main`(简写)来打印 main 函数的所有指令。另外,`x/16gx $rsp` 将打印堆栈上的前16个值。`x/gx $rbp-0x32` 将打印存储在堆栈上的该位置的局部变量。

您可能希望使用正确的汇编语法查看指令。您可以使用命令 `set disassembly-flavor intel` 来实现。

为了解决这个关卡,您必须找出堆栈上的随机值(从 `/dev/urandom` 读取的值)。思考一下 read 系统调用的参数是什么。

程序接收到信号 SIGTRAP,跟踪/断点陷阱。
  • layout asm
  • layout regs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1. `open`函数:

`#include <fcntl.h> int open(const char *pathname, int flags, mode_t mode);`

`open`函数用于打开一个文件,并返回文件描述符(file descriptor)。它接受三个参数:

- `pathname`:要打开的文件的路径名。
- `flags`:打开文件的标志,例如`O_RDONLY`(只读)、`O_WRONLY`(只写)、`O_RDWR`(读写)等。
- `mode`:指定新创建文件的权限,通常使用八进制表示,例如`0644`。

`open`函数返回一个非负整数的文件描述符,如果出现错误,则返回-1

2. `read`函数:

`#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);`

`read`函数用于从文件中读取数据。它接受三个参数:

- `fd`:要读取的文件的文件描述符。
- `buf`:用于存储读取数据的缓冲区的指针。
- `count`:要读取的最大字节数。

`read`函数返回实际读取的字节数,如果返回值为0表示已到达文件末尾,如果返回-1表示出现错误。

看一下对应的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:0000000000001C2C loc_1C2C:                               ; CODE XREF: .text:0000000000001CDD↓j
.text:0000000000001C2C mov esi, 0
.text:0000000000001C31 lea rdi, aDevUrandom ; "/dev/urandom"
.text:0000000000001C38 mov eax, 0
.text:0000000000001C3D call _open
.text:0000000000001C42 mov ecx, eax
.text:0000000000001C44 lea rax, [rbp-18h]
.text:0000000000001C48 mov edx, 8
.text:0000000000001C4D mov rsi, rax
.text:0000000000001C50 mov edi, ecx
.text:0000000000001C52 call _read
.text:0000000000001C57 lea rdi, aTheRandomValue ; "The random value has been set!\n"
.text:0000000000001C5E call _puts
.text:0000000000001C63 int 3 ; Trap to Debugger

这里打开random文件,然后read到缓冲区中,获取缓冲区的内容,作为下一个scanf函数的输入,即可获得flag

x/20g $rbp-0x18即可读取

4

1
2
3
4
5
6
7
动态分析的一个关键部分是将程序运行到您感兴趣的状态。到目前为止,这些挑战已经为您自动设置了断点,以便在您可能感兴趣的状态下暂停执行。自己能够做到这一点非常重要。

在程序的执行过程中,有多种方式可以向前移动。您可以使用`stepi <n>`命令或`si <n>`命令(简写)向前单步执行一条指令。您可以使用`nexti <n>`命令或`ni <n>`命令(简写)向前单步执行一条指令,同时跳过任何函数调用。`<n>`参数是可选的,但可以让您一次执行多个步骤。您可以使用`finish`命令来完成当前正在执行的函数。您可以使用`break *<address>`参数化命令在指定的地址设置断点。您已经使用了`continue`命令,它将继续执行,直到程序遇到断点。

在逐步执行程序时,您可能会发现始终显示一些值对您很有用。有多种方法可以做到这一点。最简单的方法是使用`display/<n><u><f>`参数化命令,其格式与`x/<n><u><f>`参数化命令完全相同。例如,`display/8i $rip`将始终显示接下来的8条指令。另一方面,`display/4gx $rsp`将始终显示堆栈上的前4个值。另一个选项是使用`layout regs`命令。这将将GDB切换到TUI模式,并显示所有寄存器的内容,以及附近的指令。

为了解决这个级别的问题,您必须找出一系列将放置在堆栈上的随机值。强烈建议您尝试使用`stepi`、`nexti`、`break`、`continue`和`finish`的组合,以确保您对这些命令有良好的内部理解。这些命令在导航程序的执行过程中是绝对关键的。

看一下汇编源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
.text:0000000000001AA6 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0000000000001AA6 public main
.text:0000000000001AA6 main proc near ; DATA XREF: _start+21↑o
.text:0000000000001AA6
.text:0000000000001AA6 var_38 = qword ptr -38h
.text:0000000000001AA6 var_30 = qword ptr -30h
.text:0000000000001AA6 var_24 = dword ptr -24h
.text:0000000000001AA6 var_8 = qword ptr -8
.text:0000000000001AA6
.text:0000000000001AA6 ; __unwind {
.text:0000000000001AA6 endbr64
.text:0000000000001AAA push rbp
.text:0000000000001AAB mov rbp, rsp
.text:0000000000001AAE sub rsp, 40h
.text:0000000000001AB2 mov [rbp+var_24], edi
.text:0000000000001AB5 mov [rbp+var_30], rsi
.text:0000000000001AB9 mov [rbp+var_38], rdx
.text:0000000000001ABD mov rax, fs:28h
.text:0000000000001AC6 mov [rbp+var_8], rax
.text:0000000000001ACA xor eax, eax
.text:0000000000001ACC cmp [rbp+var_24], 0
.text:0000000000001AD0 jg short loc_1AF1
.text:0000000000001AD2 lea rcx, __PRETTY_FUNCTION___5345 ; "main"
.text:0000000000001AD9 mov edx, 51h ; 'Q' ; line
.text:0000000000001ADE lea rsi, file ; "<stdin>"
.text:0000000000001AE5 lea rdi, aArgc0 ; "argc > 0"
.text:0000000000001AEC call ___assert_fail
.text:0000000000001AF1 ; ---------------------------------------------------------------------------
.text:0000000000001AF1
.text:0000000000001AF1 loc_1AF1: ; CODE XREF: main+2A↑j
.text:0000000000001AF1 lea rdi, asc_21C5 ; "###"
.text:0000000000001AF8 call _puts
.text:0000000000001AFD mov rax, [rbp+var_30]
.text:0000000000001B01 mov rax, [rax]
.text:0000000000001B04 mov rsi, rax
.text:0000000000001B07 lea rdi, aWelcomeToS ; "### Welcome to %s!\n"
.text:0000000000001B0E mov eax, 0
.text:0000000000001B13 call _printf
.text:0000000000001B18 lea rdi, asc_21C5 ; "###"
.text:0000000000001B1F call _puts
.text:0000000000001B24 mov edi, 0Ah ; c
.text:0000000000001B29 call _putchar
.text:0000000000001B2E mov rax, cs:stdin@@GLIBC_2_2_5
.text:0000000000001B35 mov ecx, 0 ; n
.text:0000000000001B3A mov edx, 2 ; modes
.text:0000000000001B3F mov esi, 0 ; buf
.text:0000000000001B44 mov rdi, rax ; stream
.text:0000000000001B47 call _setvbuf
.text:0000000000001B4C mov rax, cs:__bss_start
.text:0000000000001B53 mov ecx, 1 ; n
.text:0000000000001B58 mov edx, 2 ; modes
.text:0000000000001B5D mov esi, 0 ; buf
.text:0000000000001B62 mov rdi, rax ; stream
.text:0000000000001B65 call _setvbuf
.text:0000000000001B6A lea rdi, aGdbIsAVeryPowe ; "GDB is a very powerful dynamic analysis"...
.text:0000000000001B71 call _puts
.text:0000000000001B76 lea rdi, aItsExecutionYo ; "its execution. You will become familiar"...
.text:0000000000001B7D call _puts
.text:0000000000001B82 lea rdi, aACriticalPartO ; "A critical part of dynamic analysis is "...
.text:0000000000001B89 call _puts
.text:0000000000001B8E lea rdi, aChallengesHave ; "challenges have automatically set break"...
.text:0000000000001B95 call _puts
.text:0000000000001B9A lea rdi, aItIsImportantT ; "It is important to be able to do this y"...
.text:0000000000001BA1 call _puts
.text:0000000000001BA6 lea rdi, aThereAreANumbe ; "There are a number of ways to move forw"...
.text:0000000000001BAD call _puts
.text:0000000000001BB2 lea rdi, aForShortInOrde ; "for short, in order to step forward one"...
.text:0000000000001BB9 call _puts
.text:0000000000001BBE lea rdi, aOrderToStepFor ; "order to step forward one instruction, "...
.text:0000000000001BC5 call _puts
.text:0000000000001BCA lea rdi, aAllowsYouToPer ; "allows you to perform multiple steps at"...
.text:0000000000001BD1 call _puts
.text:0000000000001BD6 lea rdi, aExecutingFunct ; "executing function. You can use the `br"...
.text:0000000000001BDD call _puts
.text:0000000000001BE2 lea rdi, aSpecifiedAddre ; "specified-address. You have already use"...
.text:0000000000001BE9 call _puts
.text:0000000000001BEE lea rdi, aBreakpoint ; "breakpoint.\n"
.text:0000000000001BF5 call _puts
.text:0000000000001BFA lea rdi, aWhileSteppingT ; "While stepping through a program, you m"...
.text:0000000000001C01 call _puts
.text:0000000000001C06 lea rdi, aMultipleWaysTo ; "multiple ways to do this. The simplest "...
.text:0000000000001C0D call _puts
.text:0000000000001C12 lea rdi, aExactlyTheSame ; "exactly the same format as the `x/<n><u"...
.text:0000000000001C19 call _puts
.text:0000000000001C1E lea rdi, aTheNext8Instru ; "the next 8 instructions. On the other h"...
.text:0000000000001C25 call _puts
.text:0000000000001C2A lea rdi, aAnotherOptionI ; "Another option is to use the `layout re"...
.text:0000000000001C31 call _puts
.text:0000000000001C36 lea rdi, aOfTheRegisters ; "of the registers, as well as nearby ins"...
.text:0000000000001C3D call _puts
.text:0000000000001C42 lea rdi, aInOrderToSolve ; "In order to solve this level, you must "...
.text:0000000000001C49 call _puts
.text:0000000000001C4E lea rdi, aHighlyEncourag ; "highly encouraged to try using combinat"...
.text:0000000000001C55 call _puts
.text:0000000000001C5A lea rdi, aAGoodInternalU ; "a good internal understanding of these "...
.text:0000000000001C61 call _puts
.text:0000000000001C66 lea rdi, aExecution ; "execution.\n"
.text:0000000000001C6D call _puts
.text:0000000000001C72 int 3 ; Trap to Debugger
.text:0000000000001C72 main endp
.text:0000000000001C72
.text:0000000000001C72 ; ---------------------------------------------------------------------------
.text:0000000000001C73 align 4
.text:0000000000001C74 mov dword ptr [rbp-1Ch], 0
.text:0000000000001C7B jmp loc_1D2B
.text:0000000000001C80 ; ---------------------------------------------------------------------------
.text:0000000000001C80
.text:0000000000001C80 loc_1C80: ; CODE XREF: .text:0000000000001D2F↓j
.text:0000000000001C80 mov esi, 0
.text:0000000000001C85 lea rdi, aDevUrandom ; "/dev/urandom"
.text:0000000000001C8C mov eax, 0
.text:0000000000001C91 call _open
.text:0000000000001C96 mov ecx, eax
.text:0000000000001C98 lea rax, [rbp-18h]
.text:0000000000001C9C mov edx, 8
.text:0000000000001CA1 mov rsi, rax
.text:0000000000001CA4 mov edi, ecx
.text:0000000000001CA6 call _read
.text:0000000000001CAB lea rdi, aTheRandomValue ; "The random value has been set!\n"
.text:0000000000001CB2 call _puts
.text:0000000000001CB7 lea rdi, aRandomValue ; "Random value: "
.text:0000000000001CBE mov eax, 0
.text:0000000000001CC3 call _printf
.text:0000000000001CC8 lea rax, [rbp-10h]
.text:0000000000001CCC mov rsi, rax
.text:0000000000001CCF lea rdi, aLlx ; "%llx"
.text:0000000000001CD6 mov eax, 0
.text:0000000000001CDB call ___isoc99_scanf
.text:0000000000001CE0 mov rax, [rbp-10h]
.text:0000000000001CE4 mov rsi, rax
.text:0000000000001CE7 lea rdi, aYouInputLlx ; "You input: %llx\n"
.text:0000000000001CEE mov eax, 0
.text:0000000000001CF3 call _printf
.text:0000000000001CF8 mov rax, [rbp-18h]
.text:0000000000001CFC mov rsi, rax
.text:0000000000001CFF lea rdi, aTheCorrectAnsw ; "The correct answer is: %llx\n"
.text:0000000000001D06 mov eax, 0
.text:0000000000001D0B call _printf
.text:0000000000001D10 mov rdx, [rbp-10h]
.text:0000000000001D14 mov rax, [rbp-18h]
.text:0000000000001D18 cmp rdx, rax
.text:0000000000001D1B jz short loc_1D27
.text:0000000000001D1D mov edi, 1
.text:0000000000001D22 call _exit
.text:0000000000001D27 ; ---------------------------------------------------------------------------
.text:0000000000001D27
.text:0000000000001D27 loc_1D27: ; CODE XREF: .text:0000000000001D1B↑j
.text:0000000000001D27 add dword ptr [rbp-1Ch], 1
.text:0000000000001D2B
.text:0000000000001D2B loc_1D2B: ; CODE XREF: .text:0000000000001C7B↑j
.text:0000000000001D2B cmp dword ptr [rbp-1Ch], 3
.text:0000000000001D2F jle loc_1C80
.text:0000000000001D35 mov eax, 0
.text:0000000000001D3A call win

有一个跳转,会和3比较,小于等于3则跳回去,重复验证输入是不是正确

也就是for(i=0;i<=3;i++)

三次都对即可(相当于三次level 3 )

5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
虽然像我们在过去的级别中所做的那样交互地使用gdb非常强大,但另一个强大的工具是gdb脚本。通过编写gdb脚本,您可以快速创建一个定制的程序分析工具。如果您知道如何与gdb进行交互,那么您已经知道如何编写gdb脚本——语法完全相同。您可以将命令写入某个文件,例如`x.gdb`,然后使用标志`-x <PATH_TO_SCRIPT>`启动gdb。这个文件将在gdb启动后执行所有的gdb命令。或者,您可以使用`-ex '<COMMAND>'`执行单个命令。您可以使用多个`-ex`参数传递多个命令。最后,您可以将一些命令放在`~/.gdbinit`中,以便在任何gdb会话中始终执行。您可能希望在其中放入`set disassembly-flavor intel`。

在gdb脚本中,一个非常强大的结构是断点命令。考虑以下gdb脚本:

start
break *main+42
commands
x/gx $rbp-0x32
continue
end
continue

在这种情况下,每当我们到达`main+42`处的指令时,我们将输出一个特定的局部变量,然后继续执行。

现在考虑一个类似但稍微更高级的脚本,其中使用了一些您尚未见过的命令:

start
break *main+42
commands
silent
set �������������=∗(����������������∗)(localv​ariable=∗(unsignedlonglong∗)(rbp-0x32)
printf "当前值:%llx\n", $local_variable
continue
end
continue

在这种情况下,`silent`表示我们不希望gdb报告我们已经触发了一个断点,以使输出更清晰。然后我们使用`set`命令在gdb会话中定义一个变量,其值为我们的局部变量。最后,我们使用格式化字符串输出当前值。

使用gdb脚本来帮助您收集随机值。

open 打开/dev/urandom的文件描述符,返回描述符

read读取描述符所在位置的值写到缓冲区中,缓冲区位置在$rbp-0x18

scanf两个参数,第一个是 “llx”,第二个是$rbp-0x10

在main+752处打断点

display用法:

  • display/10i $rip
    • 每次gdb停止都会打印出接下来的十条指令
  • info display
    • 查看目前有多少个display
  • undisplay

layout asm和layout regs的窗口绘制有问题,所以不如用display指令

题干给的gdb脚本,一直没搞懂怎么在二进制中自动调用的gdb窗口中使用和交互,这类还是用最基本的方法,让程序使用display自动输出值,复制粘贴作为scanf的输入

  • b *main+752
  • run
  • (gdb) display /10i $rip
  • (gdb) display/xg $rbp-0x18
    • x/gx $rbp-0x18
  • 输入值
  • c

这一次和上一次相比,循环次数变成了7,因此如果每一次都是手动打命令,非常容易出错。display可以帮助我们

6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GDB是一个非常强大的动态分析工具,可以帮助你了解程序在执行过程中的状态。在本模块中,你将熟悉一些gdb的功能。

事实证明,gdb对目标进程具有完全的控制权。你不仅可以分析程序的状态,还可以修改它。尽管gdb可能不是长期维护程序的最佳工具,但有时候为了更容易分析目标进程,快速修改其行为可能会很有用。

你可以使用`set`命令来修改目标程序的状态。例如,你可以使用`set $rdi = 0`将 `$rdi` 寄存器置为0。你可以使用 `set *((uint64_t *) $rsp) = 0x1234` 将栈上的第一个值设置为0x1234。你可以使用 `set *((uint16_t *) 0x31337000) = 0x1337` 将地址为0x31337000的两个字节设置为0x1337

假设你的目标是一个网络应用程序,它从文件描述符42上读取数据。也许为了分析方便,你希望目标改为从标准输入(stdin)读取。你可以使用以下gdb脚本实现这样的功能:

start
catch syscall read
commands
silent
if ($rdi == 42)
set $rdi = 0
end
continue
end
continue

这个示例gdb脚本演示了如何自动在系统调用上设置断点,并如何在命令中使用条件来有条件地执行gdb命令。

在上一级中,你的gdb脚本解决方案可能仍然需要你复制和粘贴解决方案。这一次,尝试编写一个脚本,不需要与程序进行交互,而是通过正确修改寄存器和内存来自动解决每个挑战。

一直在如何和gdb交互,跳过scanf函数、达到和scanf相同的效果上搞事情了,结果越想越复杂

但是其实如果只是想要flag,可以直接修改内存的值,也即是说,将和0x3F对比的那个循环条件的内存的值修改为一个较大的值

set *(int*)($rbp-0x1c) = 100

7

1
2
3
4
5
6
7
GDB是一个非常强大的动态分析工具,您可以使用它来了解程序在执行过程中的状态。在本模块中,您将熟悉一些GDB的功能。

正如我们在前一个级别中演示的那样,GDB对目标进程具有完全控制权。在正常情况下,作为普通用户运行的GDB无法附加到特权进程。这就是为什么GDB不是一个巨大的安全问题,不会让您立即解决所有级别的原因。尽管如此,GDB仍然是一个非常强大的工具。

在这个提升的GDB实例中运行,您将对整个系统拥有更高的控制权。为了清楚地证明这一点,试试运行命令`call (void)win()`会发生什么。事实证明,这个模块中的所有级别都可以用这种方式解决。

GDB非常强大!

直接调用win

call (void)win()

8

1
2
3
4
5
6
7
8
9
10
11
GDB是一个非常强大的动态分析工具,可以帮助你了解程序在执行过程中的状态。在本模块中,你将熟悉一些gdb的功能。

正如我们在前一个级别中演示的那样,gdb对目标进程具有完全控制权。在正常情况下,以你常规用户身份运行的gdb无法附加到特权进程。这就是为什么gdb不是一个允许你立即解决所有级别的严重安全问题。尽管如此,gdb仍然是一个非常强大的工具。

在这个提升的gdb实例中运行,你可以对整个系统拥有更高的控制权。为了清楚地证明这一点,看看当你运行命令 `call (void)win()` 时会发生什么。

请注意,这不会给你带来标志(似乎我们破坏了win函数!),所以你需要更加努力才能获得这个标志!

事实证明,本模块的所有其他级别都可以用这种方式解决。

GDB非常强大!

看一下win函数,把0赋值给rax,然后取0地址指向的值,这造成指针引用错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:0000000000001951 win             proc near
.text:0000000000001951
.text:0000000000001951 var_8 = qword ptr -8
.text:0000000000001951
.text:0000000000001951 ; __unwind {
.text:0000000000001951 endbr64
.text:0000000000001955 push rbp
.text:0000000000001956 mov rbp, rsp
.text:0000000000001959 sub rsp, 10h
.text:000000000000195D mov [rbp+var_8], 0
.text:0000000000001965 mov rax, [rbp+var_8]
.text:0000000000001969 mov eax, [rax]
.text:000000000000196B lea edx, [rax+1]
.text:000000000000196E mov rax, [rbp+var_8]
.text:0000000000001972 mov [rax], edx
.text:0000000000001974 lea rdi, aYouWinHereIsYo ; "You win! Here is your flag:"
.text:000000000000197B call _puts

解决方法:直接调用win函数,然后单步执行,如果遇到对0解引用就修改使其不为0


pwn college 刷题记录:debugging refresher
http://gls.show/p/a2494a57/
作者
郭佳明
发布于
2024年3月27日
许可协议