用 IDA 调试 Switch 游戏
2024-03-17 17:33:41

附加进程

确保 Atmosphere 系统在 v1.2.3 及以上。
开启系统 gdbstub 选项。修改sdmc:/atmosphere/config/system_settings.ini文件,确保文件中包含以下设置:

1
2
3
[atmosphere]
enable_htc = u8!0x0
enable_standalone_gdbstub = u8!0x1

接着启动 IDA,依次点击菜单Debugger > Attach > Remote GDB Debugger,出现以下对话框:

Hostname 就填写 Switch 的网络地址。Port 是固定的 22225,且不可更改。
按照图中指示,依次点击Debug options > Set specific options,将架构改为 ARM64。


此时先启动游戏,然后再在 IDA 中附加远程进程:

拉到窗口最下,ID 最大的那个就是刚启动的游戏进程,附加它。

区段识别

此时进程的内存区都载入了,但是分不清堆、栈等区段。此时就需要跑一下网友 Eiffel2018 制作的 IDA 脚本文件。(脚本发布于 【金手指教程1】如何使用 IDA Pro 的 GDB 除错器

其他下载地址:https://p.shipengliang.com/f/11753764-858159738-9a7583
为了避免链接失效,我将脚本直接贴出来,分为32位和64位两种版本。
32位:

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
print('----- Script created by Eiffel2018 -----')

info = send_dbg_command('get info')
infoheader, dummy, infobody = info.partition('\nLayout:\n')
layout, dummy, modules = infobody.partition('\nModules:\n')
regions = ida_idd.meminfo_vec_t()
for region in layout.splitlines():
name, start, end = re.split('[:|-]',region.replace(' ', ''))
if (name=='Alias' or name=='Heap' or name=='Stack'):
print(name, start, hex(int(end,16)+1))
info = ida_idd.memory_info_t()
info.name = name.lower()
info.start_ea = int(start,16)
info.end_ea = int(end,16)+1
info.sclass = 'DATA'
info.sbase = 0
info.bitness = 1
info.perm = 6
regions.push_back(info)
lastend=0
lastbase=0
lastname=''
for region in modules.splitlines():
start, end, name = region.strip().replace(' - ', ' ').split(' ');
name, dummy, ext = name.partition('.');
if (ext=='nss'):
name='main'
if (ext=='nrs.elf'):
name='nro'
if (lastend>0):
info = ida_idd.memory_info_t()
info.name = lastname + '-data'
info.start_ea = lastend
info.end_ea = int(start,16)
info.sclass = 'DATA'
# info.sbase = lastbase
info.sbase = 0
info.bitness = 1
info.perm = 6
regions.push_back(info)
print(lastname + '-data', hex(lastend), start)
lastend=0
if (name=='saltysd_core' or name=='saltysd_core-data'):
continue
if (name=='' or name=='-data'):
continue
# if (name=='nnSdk'):
# continue
print(name, start, hex(int(end,16)+1))
info = ida_idd.memory_info_t()
info.name = name
info.start_ea = int(start,16)
info.end_ea = int(end,16)+1
info.sclass = 'CODE'
info.sbase = 0
if (name=='main'):
info.sbase = int(start[:-1],16)
info.bitness = 1
info.perm = 5
regions.push_back(info)
lastend=info.end_ea
lastbase=info.sbase
lastname=info.name
if (ext=='nrs.elf'):
mapping = send_dbg_command('get mapping '+hex(int(end,16)+1))
start, end, dummy, nextName, dummy = mapping.replace(' - ', ' ').split(' ', 4);
if (nextName=='AliasCode'):
name='nro-static'
print(name, start, hex(int(end,16)+1))
info = ida_idd.memory_info_t()
info.name = name
info.start_ea = int(start,16)
info.end_ea = int(end,16)+1
info.sclass = 'DATA'
info.sbase = 0
info.bitness = 1
info.perm = 4
regions.push_back(info)
lastend=info.end_ea
lastbase=info.sbase
lastname=info.name
mapping = send_dbg_command('get mapping '+hex(int(end,16)+1))
start, end, dummy, nextName, dummy = mapping.replace(' - ', ' ').split(' ', 4);
if (nextName=='AliasCodeData'):
name='nro-data'
mapping = send_dbg_command('get mapping '+hex(int(end,16)+1))
start2, end2, dummy, nextName2, dummy = mapping.replace(' - ', ' ').split(' ', 4);
if (nextName2=='AliasCodeData'):
end = end2
mapping = send_dbg_command('get mapping '+hex(int(end,16)+1))
start2, end2, dummy, nextName2, dummy = mapping.replace(' - ', ' ').split(' ', 4);
if (nextName2=='AliasCodeData'):
end = end2
print(name, start, hex(int(end,16)+1))
info = ida_idd.memory_info_t()
info.name = name
info.start_ea = int(start,16)
info.end_ea = int(end,16)+1
info.sclass = 'DATA'
info.sbase = 0
info.bitness = 1
info.perm = 6
regions.push_back(info)
lastend=info.end_ea
lastbase=info.sbase
lastname=info.name
mapping = send_dbg_command('get mapping '+hex(int(end,16)+1))
start, end, dummy, nextName, dummy = mapping.replace(' - ', ' ').split(' ', 4);
lastend=0
ida_dbg.set_manual_regions(regions)
ida_dbg.enable_manual_regions(0)
ida_dbg.refresh_debugger_memory()
ida_dbg.enable_manual_regions(1)
ida_dbg.refresh_debugger_memory()
ida_dbg.edit_manual_regions()
pc = idaapi.get_reg_val('PC')
ida_kernwin.jumpto(pc)
ida_kernwin.refresh_idaview_anyway()

64位:

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
print('----- Script created by Eiffel2018 -----')

info = send_dbg_command('get info')
infoheader, dummy, infobody = info.partition('\nLayout:\n')
layout, dummy, modules = infobody.partition('\nModules:\n')
regions = ida_idd.meminfo_vec_t()
for region in layout.splitlines():
name, start, end = re.split('[:|-]',region.replace(' ', ''))
if (name=='Alias' or name=='Heap' or name=='Stack'):
print(name, start, hex(int(end,16)+1))
info = ida_idd.memory_info_t()
info.name = name.lower()
info.start_ea = int(start,16)
info.end_ea = int(end,16)+1
info.sclass = 'DATA'
info.sbase = 0
info.bitness = 2
info.perm = 6
regions.push_back(info)
lastend=0
lastbase=0
lastname=''
for region in modules.splitlines():
start, end, name = region.strip().replace(' - ', ' ').split(' ');
name, dummy, ext = name.partition('.');
if (ext=='nss'):
name='main'
if (ext=='nrs.elf'):
name='nro'
if (lastend>0):
info = ida_idd.memory_info_t()
info.name = lastname + '-data'
info.start_ea = lastend
info.end_ea = int(start,16)
info.sclass = 'DATA'
# info.sbase = lastbase
info.sbase = 0
info.bitness = 2
info.perm = 6
regions.push_back(info)
print(lastname + '-data', hex(lastend), start)
lastend=0
if (name=='saltysd_core' or name=='saltysd_core-data'):
continue
if (name=='' or name=='-data'):
continue
# if (name=='nnSdk'):
# continue
print(name, start, hex(int(end,16)+1))
info = ida_idd.memory_info_t()
info.name = name
info.start_ea = int(start,16)
info.end_ea = int(end,16)+1
info.sclass = 'CODE'
info.sbase = 0
if (name=='main'):
info.sbase = int(start[:-1],16)
info.bitness = 2
info.perm = 5
regions.push_back(info)
lastend=info.end_ea
lastbase=info.sbase
lastname=info.name
if (ext=='nrs.elf'):
mapping = send_dbg_command('get mapping '+hex(int(end,16)+1))
start, end, dummy, nextName, dummy = mapping.replace(' - ', ' ').split(' ', 4);
if (nextName=='AliasCode'):
name='nro-static'
print(name, start, hex(int(end,16)+1))
info = ida_idd.memory_info_t()
info.name = name
info.start_ea = int(start,16)
info.end_ea = int(end,16)+1
info.sclass = 'DATA'
info.sbase = 0
info.bitness = 2
info.perm = 4
regions.push_back(info)
lastend=info.end_ea
lastbase=info.sbase
lastname=info.name
mapping = send_dbg_command('get mapping '+hex(int(end,16)+1))
start, end, dummy, nextName, dummy = mapping.replace(' - ', ' ').split(' ', 4);
if (nextName=='AliasCodeData'):
name='nro-data'
mapping = send_dbg_command('get mapping '+hex(int(end,16)+1))
start2, end2, dummy, nextName2, dummy = mapping.replace(' - ', ' ').split(' ', 4);
if (nextName2=='AliasCodeData'):
end = end2
mapping = send_dbg_command('get mapping '+hex(int(end,16)+1))
start2, end2, dummy, nextName2, dummy = mapping.replace(' - ', ' ').split(' ', 4);
if (nextName2=='AliasCodeData'):
end = end2
print(name, start, hex(int(end,16)+1))
info = ida_idd.memory_info_t()
info.name = name
info.start_ea = int(start,16)
info.end_ea = int(end,16)+1
info.sclass = 'DATA'
info.sbase = 0
info.bitness = 2
info.perm = 6
regions.push_back(info)
lastend=info.end_ea
lastbase=info.sbase
lastname=info.name
mapping = send_dbg_command('get mapping '+hex(int(end,16)+1))
start, end, dummy, nextName, dummy = mapping.replace(' - ', ' ').split(' ', 4);
lastend=0
ida_dbg.set_manual_regions(regions)
ida_dbg.enable_manual_regions(0)
ida_dbg.refresh_debugger_memory()
ida_dbg.enable_manual_regions(1)
ida_dbg.refresh_debugger_memory()
ida_dbg.edit_manual_regions()
pc = idaapi.get_reg_val('PC')
ida_kernwin.jumpto(pc)
ida_kernwin.refresh_idaview_anyway()

回到 IDA 中,依次点击菜单File > Script file,或者用快捷键ALT + F7运行脚本。

都识别出来了,没问题。
自己动手搜过数据的都知道,我们主要关心的就是代码区 main 和数据区 heap。

编辑机器码

现在假设我们已经找到了需要修改程序逻辑的地方,如何在 IDA 中方便的编辑呢?答案是 keypatch 插件。
这个插件依赖两个 python 包,确保已经安装了:

1
2
pip install six
pip install keystone-engine

然后将 keypatch.py 文件保存到 IDA 的 plugins 目录中即可。

接着在 main 区段中要编辑的地方点击右键,会有一个 Keypatch 菜单项。

或者直接按Ctrl + Alt + K快捷键启动编辑对话框

输入要修改的语句,按下 Patch 按钮完成编辑。

总结

可能是因为大气层系统 GDBstub 是实验性的问题,动态调式很容易让程序崩溃,所以并不怎么好用。主要还是要靠金手指工具搜索。

相关资料

https://gist.github.com/jam1garner/c9ba6c0cff150f1a2480d0c18ff05e33
Switch 金手指制作教程二:如何使用 IDA Pro 的 GDB 调试器