Changing Program Flow

ขณะที่เรา dynamic debugging บน GDB เนี่ย นอกจากแค่ทำตาม flow program ปกติแล้ว เรายังสามารถเปลี่ยน flow ของโปรแกรมได้ด้วย เช่น การแก้ข้อมูลต่างๆ, เปลี่ยน 1 เป็น 0, กระโดดไปมา, สั่ง function เพิ่ม เป็นต้น

อันนี้มีประโยชน์เมื่อผู้ใช้งานต้องการทดสอบอะไรบางอย่างบน GDB โดยที่ไม่ต้องเสียเวลาไปแก้โค้ด compile ใหม่หลายๆครั้งครับ ดู advance นิดหน่อย ในมหาลัยคงไม่จำเป็นต้องใช้ อ่านผ่านๆให้รู้ว่ามันมีนะ พอจะใช้จริงค่อยหาตาม search engine ดูก็ได้ครับ

สำหรับนัก reverse engineering ทั้งหลาย รวมทั้งนักเล่น CTF ต่างๆ หัวข้อนี้มีประโยชน์มหาศาลครับ บางข้อนี่ง่ายงงโจทย์เลย

Edit variables

แก้ variables ง่ายมากครับ เช่น

(gdb) r
Starting program: /home/bankde/Desktop/tmp/example.o
Put string to split: helloworld

Breakpoint 1, split (input=0x7fffffffdf40 "helloworld\n") at example.c:7
7      char** result = malloc(10*sizeof(char*));
(gdb) p input
$4 = 0x7fffffffdf40 "helloworld\n"
(gdb) set var input = "hello world"
(gdb) p input
$5 = 0x7ffff7fe1f20 "hello world"
(gdb) c
Continuing.
First str: Hello
Second str: World

จะเห็นว่า input ที่เราใส่เข้าไป helloworld แล้วปกติมันจะทำให้โปรแกรม crash ถูกเปลี่ยนเป็น hello world จนสามารถรันโปรแกรมได้ตามปกติได้ (ถ้า string อาจจะมีข้อจำกัดเรื่อง memory space นะครับ)

นอกจากนี้ยังสามารถแก้ registers ต่างๆได้ด้วย (ถ้าไม่ได้ลง asm มากๆ ข้ามส่วนนี้ไปได้ครับ) เช่น

(gdb) b *0x00005555555548a2
Breakpoint 2 at 0x5555555548a2: file example.c, line 11.
(gdb) cond 2 i==5
(gdb) r
Starting program: /home/bankde/Desktop/tmp/example.o
Put string to split: helloaworld

Breakpoint 2, 0x00005555555548a2 in split (input=0x7fffffffdf40 "helloaworld\n")
    at example.c:11
11        if (input[i] == ' ') {
(gdb) info reg
rax            0x61    97
rbx            0x5    5
rcx            0x0    0
rdx            0x5    5
rsi            0x0    0
rdi            0x7fffffffdf40    140737488346944
rbp            0x7fffffffdf20    0x7fffffffdf20
rsp            0x7fffffffdef0    0x7fffffffdef0
r8             0x55555575667c    93824994338428
r9             0x0    0
r10            0x555555756010    93824994336784
r11            0x0    0
r12            0x555555554750    93824992233296
r13            0x7fffffffe0a0    140737488347296
r14            0x0    0
r15            0x0    0
rip            0x5555555548a2    0x5555555548a2 <split+72>
eflags         0x206    [ PF IF ]
cs             0x33    51
ss             0x2b    43
ds             0x0    0
es             0x0    0
fs             0x0    0
---Type <return> to continue, or q <return> to quit---
gs             0x0    0
(gdb) p i
$2 = 5
(gdb) p input[i]
$3 = 97 'a'
(gdb) i r eflags
eflags         0x206    [ PF IF ]
(gdb) set $ZF = 6
(gdb) set $eflags |= (1 << $ZF)
(gdb) i r eflags
eflags         0x246    [ PF ZF IF ]
(gdb) c
Continuing.
First str: Hello
Second str: World

ตัวอย่างแอบยากนิดนึงเพราะเป็น ZeroFlag ซึ่งไม่ได้มี register ของตัวเอง จึงต้องแก้ eflags แต่ถ้าในกรณี eax, rax ก็แค่ set $eax = -1 แบบนี้ได้เลย เท่านี้ส่วนของ main ก็จะได้รับ return เป็น -1 แล้วจบโปรแกรมได้ เช่น

(gdb) b *0x00005555555549fb
Breakpoint 3 at 0x5555555549fb: file example.c, line 33.
(gdb) r
Starting program: /home/bankde/Desktop/tmp/example.o
Put string to split: hello world
First str: Hello
Second str: World

Breakpoint 3, getInputAndRun () at example.c:33
33    }
(gdb) set $eax = -1
(gdb) c
Continuing.
[Inferior 1 (process 20460) exited normally]

Jump to any instruction

กระโดดได้เลยครับ ข้ามไปเลยยยยย จะเห็นว่าผมข้ามโค้ดส่วนที่เป็น toupper ไปทั้งก้อนเลย ต้องระวังให้ดีนะครับ เพราะการ jump เนี่ยไม่ได้มีการดัก scope อะไรให้เรา พังได้ง่ายๆ ปกติผมใช้ jump เพื่อทดสอบอะไรสั้นๆ เช่น ข้าม function ที่มีปัญหาไปก่อน เป็นต้น

(gdb) break example.c:27
Breakpoint 1 at 0x976: file example.c, line 27.
(gdb) r
Starting program: /home/bankde/Desktop/tmp/example.o
Put string to split: hello world

Breakpoint 1, getInputAndRun () at example.c:27
27        *result[0] = toupper(result[0][0]);
(gdb) jump example.c:29
Continuing at 0x5555555549bc.
First str: hello
Second str: world

Run function

เราสามารถสั่ง function อะไรก็ได้ที่เราต้องการได้ระหว่าง GDB อยู่ครับ (แต่ function ต้องถูกอ่าน define มาก่อนแล้ว) อย่างตัวอย่างผมอยู่ที่ main ยังไม่ทันรับ input จริงๆ ผมก็สั่ง split เพื่อดูผลลัพธ์ได้เลย

Breakpoint 1, main () at example.c:37
37        if (getInputAndRun() == -1) break;
(gdb) call split("test bank")
$1 = (char **) 0x555555756260
(gdb) p ((char**)0x555555756260)[0]
$2 = 0x7ffff7fe1f20 "test"
(gdb) p ((char**)0x555555756260)[1]
$3 = 0x7ffff7fe1f25 "bank"

หรือใช้ p แทน call ก็ได้ครับ แล้วเปลี่ยน memory address เป็นเลขตัวแปรแทนก็ได้เช่นกัน

(gdb) p split("hello bonk")
$4 = (char **) 0x5555557562c0
(gdb) p ((char**)$4)[0]
$7 = 0x7ffff7fe1f30 "hello"
(gdb) p ((char**)$4)[1]
$8 = 0x7ffff7fe1f36 "bonk"

Last updated

Was this helpful?